Skip to content

Commit

Permalink
feat: Added Gitea Oauth Provider
Browse files Browse the repository at this point in the history
  • Loading branch information
justserdar committed Feb 3, 2025
1 parent 42a2a7a commit 2d478c2
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 1 deletion.
3 changes: 3 additions & 0 deletions playground/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ NUXT_OAUTH_GITHUB_CLIENT_SECRET=
# GitLab OAuth
NUXT_OAUTH_GITLAB_CLIENT_ID=
NUXT_OAUTH_GITLAB_CLIENT_SECRET=
# Gitea OAuth
NUXT_OAUTH_GITEA_CLIENT_ID=
NUXT_OAUTH_GITEA_CLIENT_SECRET=
# Spotify OAuth
NUXT_OAUTH_SPOTIFY_CLIENT_ID=
NUXT_OAUTH_SPOTIFY_CLIENT_SECRET=
Expand Down
1 change: 1 addition & 0 deletions playground/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare module '#auth-utils' {
email?: string
password?: string
spotify?: string
gitea?: string
github?: string
gitlab?: string
google?: string
Expand Down
15 changes: 15 additions & 0 deletions playground/server/routes/auth/gitea.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default defineOAuthGiteaEventHandler({
config: {
emailRequired: true
},
async onSuccess(event, { user }) {
await setUserSession(event, {
user: {
gitea: user.email,
},
loggedInAt: Date.now(),
})
return sendRedirect(event, '/')
},

})
7 changes: 7 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ export default defineNuxtModule<ModuleOptions>({

// OAuth settings
runtimeConfig.oauth = defu(runtimeConfig.oauth, {})
// Gitea OAuth
runtimeConfig.oauth.gitea = defu(runtimeConfig.oauth.gitea, {
clientId: '',
clientSecret: '',
redirectURL: '',
baseURL: '',
})
// GitHub OAuth
runtimeConfig.oauth.github = defu(runtimeConfig.oauth.github, {
clientId: '',
Expand Down
154 changes: 154 additions & 0 deletions src/runtime/server/lib/oauth/gitea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import type { H3Event } from 'h3'
import { eventHandler, getQuery, sendRedirect } from 'h3'
import { withQuery } from 'ufo'
import { defu } from 'defu'
import {
handleMissingConfiguration,
handleAccessTokenErrorResponse,
getOAuthRedirectURL,
requestAccessToken,
} from '../utils'
import { useRuntimeConfig, createError } from '#imports'
import type { OAuthConfig } from '#auth-utils'

export interface OAuthGiteaConfig {
/**
* Gitea OAuth Client ID
* @default process.env.NUXT_OAUTH_GITEA_CLIENT_ID
*/
clientId?: string
/**
* Gitea OAuth Client Secret
* @default process.env.NUXT_OAUTH_GITEA_CLIENT_SECRET
*/
clientSecret?: string
/**
* Gitea OAuth Scope
* @default ['read:user']
* @see https://docs.gitea.io/en-us/oauth2-provider/
* @example ['read:user']
*/
scope?: string[]
/**
* Require email from user, adds the ['email'] scope if not present
* @default false
*/
emailRequired?: boolean

/**
* Gitea OAuth Authorization URL
* @default '/login/oauth/authorize'
*/
authorizationURL?: string

/**
* Gitea OAuth Token URL
* @default '/login/oauth/access_token'
*/
tokenURL?: string

/**
* Extra authorization parameters to provide to the authorization URL
* @see https://docs.gitea.io/en-us/oauth2-provider/
*/
authorizationParams?: Record<string, string>

/**
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
* @default process.env.NUXT_OAUTH_GITEA_REDIRECT_URL
*/
redirectURL?: string

/**
* URL of your Gitea instance
* @default 'http://localhost:3000'
*/
baseURL?: string
}

export function defineOAuthGiteaEventHandler({
config,
onSuccess,
onError
}: OAuthConfig<OAuthGiteaConfig>) {
return eventHandler(async (event: H3Event) => {
const runtimeConfig = useRuntimeConfig(event).oauth?.gitea
const baseURL = config?.baseURL ?? runtimeConfig.baseURL ?? 'http://localhost:3000'
config = defu(config, runtimeConfig, {
authorizationURL: `${baseURL}/login/oauth/authorize`,
tokenURL: `${baseURL}/login/oauth/access_token`,
authorizationParams: {}
}) as OAuthGiteaConfig

const query = getQuery<{ code?: string, error?: string }>(event)

if (query.error) {
const error = createError({
statusCode: 401,
message: `Gitea login failed: ${query.error || 'Unknown error'}`,
data: query
})
if (!onError) throw error
return onError(event, error)
}

if (!config.clientId || !config.clientSecret) {
return handleMissingConfiguration(
event,
'gitea',
['clientId', 'clientSecret'],
onError
)
}

const redirectURL = config.redirectURL || getOAuthRedirectURL(event)

if (!query.code) {
config.scope = config.scope || []
if (!config.scope.length) {
config.scope.push('read:user')
}
if (config.emailRequired && !config.scope.includes('email')) {
config.scope.push('email')
}

return sendRedirect(
event,
withQuery(config.authorizationURL as string, {
response_type: 'code',
client_id: config.clientId,
redirect_uri: redirectURL,
scope: config.scope.join(' '),
...config.authorizationParams
})
)
}

const tokens = await requestAccessToken(config.tokenURL as string, {
body: {
grant_type: 'authorization_code',
client_id: config.clientId,
client_secret: config.clientSecret,
redirect_uri: redirectURL,
code: query.code
}
})

if (tokens.error) {
return handleAccessTokenErrorResponse(event, 'gitea', tokens, onError)
}

const accessToken = tokens.access_token

const user: any = await $fetch(`${baseURL}/api/v1/user`, {
headers: {
Authorization: `token ${accessToken}`
}
})

return onSuccess(event, {
user,
tokens
})
})
}
2 changes: 1 addition & 1 deletion src/runtime/types/oauth-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { H3Event, H3Error } from 'h3'

export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | (string & {})
export type OAuthProvider = 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | (string & {})

export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void

Expand Down

0 comments on commit 2d478c2

Please sign in to comment.