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