feat: add OAuth2 authentication (Google/GitHub/Discord)
This commit is contained in:
57
src/services/users/oauth2/github/__utils__/utils.ts
Normal file
57
src/services/users/oauth2/github/__utils__/utils.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js'
|
||||
|
||||
export const GITHUB_PROVIDER = 'github'
|
||||
export const GITHUB_BASE_URL = 'https://github.com'
|
||||
export const GITHUB_API_BASE_URL = 'https://api.github.com'
|
||||
export const GITHUB_CLIENT_ID =
|
||||
process.env.GITHUB_CLIENT_ID ?? 'GITHUB_CLIENT_ID'
|
||||
export const GITHUB_CLIENT_SECRET =
|
||||
process.env.GITHUB_CLIENT_SECRET ?? 'GITHUB_CLIENT_SECRET'
|
||||
export const githubStrategy = new OAuthStrategy(GITHUB_PROVIDER)
|
||||
|
||||
export interface GitHubUser {
|
||||
login: string
|
||||
id: number
|
||||
name: string
|
||||
avatar_url: string
|
||||
}
|
||||
|
||||
export interface GitHubTokens {
|
||||
access_token: string
|
||||
scope: string
|
||||
token_type: string
|
||||
}
|
||||
|
||||
export const getGitHubUserData = async (
|
||||
code: string,
|
||||
redirectURI: string
|
||||
): Promise<GitHubUser> => {
|
||||
const { data: token } = await axios.post<GitHubTokens>(
|
||||
`${GITHUB_BASE_URL}/login/oauth/access_token`,
|
||||
querystring.stringify({
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
client_secret: GITHUB_CLIENT_SECRET,
|
||||
code,
|
||||
redirect_uri: redirectURI
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const { data: githubUser } = await axios.get<GitHubUser>(
|
||||
`${GITHUB_API_BASE_URL}/user`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${token.access_token}`
|
||||
}
|
||||
}
|
||||
)
|
||||
return githubUser
|
||||
}
|
49
src/services/users/oauth2/github/callback/get.ts
Normal file
49
src/services/users/oauth2/github/callback/get.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { HOST, PORT } from '../../../../../tools/configurations/index.js'
|
||||
import { fastifyErrors } from '../../../../../models/utils.js'
|
||||
import { githubStrategy, getGitHubUserData } from '../__utils__/utils.js'
|
||||
import { buildQueryURL } from '../../../../../tools/utils/buildQueryURL.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
code: Type.String(),
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'GitHub OAuth2 - callback',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackGitHubOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/github/callback',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code } = request.query
|
||||
const githubUser = await getGitHubUserData(
|
||||
code,
|
||||
`${request.protocol}://${HOST}:${PORT}/users/oauth2/github/callback?redirectURI=${redirectURI}`
|
||||
)
|
||||
const responseJWT = await githubStrategy.callbackSignin({
|
||||
name: githubUser.name,
|
||||
id: githubUser.id
|
||||
})
|
||||
return await reply.redirect(buildQueryURL(redirectURI, responseJWT))
|
||||
}
|
||||
})
|
||||
}
|
42
src/services/users/oauth2/github/signin/get.ts
Normal file
42
src/services/users/oauth2/github/signin/get.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { HOST, PORT } from '../../../../../tools/configurations/index.js'
|
||||
import { fastifyErrors } from '../../../../../models/utils.js'
|
||||
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID } from '../__utils__/utils.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'GitHub OAuth2 - signin',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getSigninGitHubOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/github/signin',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${request.protocol}://${HOST}:${PORT}/users/oauth2/github/callback?redirectURI=${redirectURI}`
|
||||
const url = `${GITHUB_BASE_URL}/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${redirectCallback}`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user