feat: add OAuth2 authentication (Google/GitHub/Discord)

This commit is contained in:
Divlo
2022-03-06 15:41:30 +00:00
parent 9e6bf25c83
commit 91a0e2a76f
16 changed files with 556 additions and 17 deletions

View 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
}

View 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))
}
})
}

View 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
}
})
}