feat: add OAuth2 authentication (Google/GitHub/Discord)
This commit is contained in:
parent
9e6bf25c83
commit
91a0e2a76f
@ -22,15 +22,7 @@ exports.serviceGenerator = {
|
||||
type: 'list',
|
||||
name: 'tag',
|
||||
message: 'tag',
|
||||
choices: [
|
||||
'users',
|
||||
'guilds',
|
||||
'channels',
|
||||
'invitations',
|
||||
'messages',
|
||||
'members',
|
||||
'uploads'
|
||||
]
|
||||
choices: ['users', 'guilds', 'channels', 'messages', 'members', 'uploads']
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
|
@ -33,24 +33,25 @@ export const getCurrentUser: FastifyPluginAsync = async (fastify) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { user } = request
|
||||
const settings = await prisma.userSetting.findFirst({
|
||||
where: { userId: request.user.current.id }
|
||||
where: { userId: user.current.id }
|
||||
})
|
||||
const OAuths = await prisma.oAuth.findMany({
|
||||
where: { userId: request.user.current.id }
|
||||
where: { userId: user.current.id }
|
||||
})
|
||||
const strategies = OAuths.map((oauth) => {
|
||||
return oauth.provider
|
||||
})
|
||||
if (request.user.current.password != null) {
|
||||
if (user.current.password != null) {
|
||||
strategies.push('local')
|
||||
}
|
||||
reply.statusCode = 200
|
||||
return {
|
||||
user: {
|
||||
...request.user.current,
|
||||
...user.current,
|
||||
settings,
|
||||
currentStrategy: request.user.currentStrategy,
|
||||
currentStrategy: user.currentStrategy,
|
||||
strategies
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,10 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => {
|
||||
const { redirectURI } = request.params
|
||||
const userValidation = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email }, { name }],
|
||||
OR: [
|
||||
...(email != null ? [{ email }] : [{}]),
|
||||
...(name != null ? [{ name }] : [{}])
|
||||
],
|
||||
AND: [{ id: { not: request.user.current.id } }]
|
||||
}
|
||||
})
|
||||
|
@ -13,6 +13,13 @@ import { putCurrentUser } from './current/put.js'
|
||||
import { putCurrentUserSettings } from './current/settings/put.js'
|
||||
import { getUserById } from './[userId]/get.js'
|
||||
import { putCurrentUserLogo } from './current/logo/put.js'
|
||||
import { getSigninDiscordOAuth2Service } from './oauth2/discord/signin/get.js'
|
||||
import { getCallbackDiscordOAuth2Service } from './oauth2/discord/callback/get.js'
|
||||
import { getSigninGoogleOAuth2Service } from './oauth2/google/signin/get.js'
|
||||
import { getCallbackGoogleOAuth2Service } from './oauth2/google/callback/get.js'
|
||||
import { getSigninGitHubOAuth2Service } from './oauth2/github/signin/get.js'
|
||||
import { getCallbackGitHubOAuth2Service } from './oauth2/github/callback/get.js'
|
||||
import { deleteProviderService } from './oauth2/[provider]/delete.js'
|
||||
|
||||
export const usersService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(postSignupUser)
|
||||
@ -28,4 +35,15 @@ export const usersService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(putCurrentUserSettings)
|
||||
await fastify.register(putCurrentUserLogo)
|
||||
await fastify.register(getUserById)
|
||||
|
||||
await fastify.register(getSigninDiscordOAuth2Service)
|
||||
await fastify.register(getCallbackDiscordOAuth2Service)
|
||||
|
||||
await fastify.register(getSigninGoogleOAuth2Service)
|
||||
await fastify.register(getCallbackGoogleOAuth2Service)
|
||||
|
||||
await fastify.register(getSigninGitHubOAuth2Service)
|
||||
await fastify.register(getCallbackGitHubOAuth2Service)
|
||||
|
||||
await fastify.register(deleteProviderService)
|
||||
}
|
||||
|
75
src/services/users/oauth2/[provider]/delete.ts
Normal file
75
src/services/users/oauth2/[provider]/delete.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import prisma from '../../../../tools/database/prisma.js'
|
||||
import { fastifyErrors } from '../../../../models/utils.js'
|
||||
import authenticateUser from '../../../../tools/plugins/authenticateUser.js'
|
||||
import { oauthSchema } from '../../../../models/OAuth.js'
|
||||
|
||||
const parametersSchema = Type.Object({
|
||||
provider: oauthSchema.provider
|
||||
})
|
||||
|
||||
type Parameters = Static<typeof parametersSchema>
|
||||
|
||||
const deleteServiceSchema: FastifySchema = {
|
||||
description: 'DELETE a provider to authenticate with for a user.',
|
||||
tags: ['users'] as string[],
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
] as Array<{ [key: string]: [] }>,
|
||||
params: parametersSchema,
|
||||
response: {
|
||||
200: Type.Object(oauthSchema),
|
||||
400: fastifyErrors[400],
|
||||
401: fastifyErrors[401],
|
||||
403: fastifyErrors[403],
|
||||
404: fastifyErrors[404],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const deleteProviderService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(authenticateUser)
|
||||
|
||||
fastify.route<{
|
||||
Params: Parameters
|
||||
}>({
|
||||
method: 'DELETE',
|
||||
url: '/users/oauth2/:provider',
|
||||
schema: deleteServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { user } = request
|
||||
const { provider } = request.params
|
||||
const OAuths = await prisma.oAuth.findMany({
|
||||
where: { userId: user.current.id }
|
||||
})
|
||||
const strategies = OAuths.map((oauth) => {
|
||||
return oauth.provider
|
||||
})
|
||||
if (user.current.password != null) {
|
||||
strategies.push('local')
|
||||
}
|
||||
const oauthProvider = OAuths.find((oauth) => oauth.provider === provider)
|
||||
if (oauthProvider == null) {
|
||||
throw fastify.httpErrors.notFound('You are not using this provider')
|
||||
}
|
||||
const hasOthersWayToAuthenticate = strategies.length >= 2
|
||||
if (!hasOthersWayToAuthenticate) {
|
||||
throw fastify.httpErrors.badRequest(
|
||||
"You can't delete your only way to authenticate"
|
||||
)
|
||||
}
|
||||
const oauthProviderDelete = await prisma.oAuth.delete({
|
||||
where: { id: oauthProvider.id }
|
||||
})
|
||||
reply.statusCode = 200
|
||||
return oauthProviderDelete
|
||||
}
|
||||
})
|
||||
}
|
60
src/services/users/oauth2/discord/__utils__/utils.ts
Normal file
60
src/services/users/oauth2/discord/__utils__/utils.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js'
|
||||
|
||||
export const DISCORD_PROVIDER = 'discord'
|
||||
export const DISCORD_BASE_URL = 'https://discord.com/api/v6'
|
||||
export const DISCORD_CLIENT_ID =
|
||||
process.env.DISCORD_CLIENT_ID ?? 'DISCORD_CLIENT_ID'
|
||||
export const DISCORD_CLIENT_SECRET =
|
||||
process.env.DISCORD_CLIENT_SECRET ?? 'DISCORD_CLIENT_SECRET'
|
||||
export const discordStrategy = new OAuthStrategy(DISCORD_PROVIDER)
|
||||
|
||||
export interface DiscordUser {
|
||||
id: string
|
||||
username: string
|
||||
discriminator: string
|
||||
avatar?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface DiscordTokens {
|
||||
access_token: string
|
||||
token_type: string
|
||||
expires_in: number
|
||||
refresh_token: string
|
||||
scope: string
|
||||
}
|
||||
|
||||
export const getDiscordUserData = async (
|
||||
code: string,
|
||||
redirectURI: string
|
||||
): Promise<DiscordUser> => {
|
||||
const { data: tokens } = await axios.post<DiscordTokens>(
|
||||
`${DISCORD_BASE_URL}/oauth2/token`,
|
||||
querystring.stringify({
|
||||
client_id: DISCORD_CLIENT_ID,
|
||||
client_secret: DISCORD_CLIENT_SECRET,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: redirectURI,
|
||||
scope: 'identify'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
)
|
||||
const { data: discordUser } = await axios.get<DiscordUser>(
|
||||
`${DISCORD_BASE_URL}/users/@me`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `${tokens.token_type} ${tokens.access_token}`
|
||||
}
|
||||
}
|
||||
)
|
||||
return discordUser
|
||||
}
|
49
src/services/users/oauth2/discord/callback/get.ts
Normal file
49
src/services/users/oauth2/discord/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 { discordStrategy, getDiscordUserData } 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: 'Discord OAuth2 - callback',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackDiscordOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/discord/callback',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code } = request.query
|
||||
const discordUser = await getDiscordUserData(
|
||||
code,
|
||||
`${request.protocol}://${HOST}:${PORT}/users/oauth2/discord/callback?redirectURI=${redirectURI}`
|
||||
)
|
||||
const responseJWT = await discordStrategy.callbackSignin({
|
||||
name: discordUser.username,
|
||||
id: discordUser.id
|
||||
})
|
||||
return await reply.redirect(buildQueryURL(redirectURI, responseJWT))
|
||||
}
|
||||
})
|
||||
}
|
42
src/services/users/oauth2/discord/signin/get.ts
Normal file
42
src/services/users/oauth2/discord/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 { DISCORD_BASE_URL, DISCORD_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: 'Discord OAuth2 - signin',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getSigninDiscordOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/discord/signin',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${request.protocol}://${HOST}:${PORT}/users/oauth2/discord/callback?redirectURI=${redirectURI}`
|
||||
const url = `${DISCORD_BASE_URL}/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&scope=identify&response_type=code&redirect_uri=${redirectCallback}`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
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
|
||||
}
|
||||
})
|
||||
}
|
59
src/services/users/oauth2/google/__utils__/utils.ts
Normal file
59
src/services/users/oauth2/google/__utils__/utils.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import querystring from 'node:querystring'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js'
|
||||
|
||||
export const GOOGLE_PROVIDER = 'google'
|
||||
export const GOOGLE_BASE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
export const GOOGLE_OAUTH2_TOKEN = 'https://oauth2.googleapis.com/token'
|
||||
export const GOOGLE_USERINFO =
|
||||
'https://www.googleapis.com/oauth2/v1/userinfo?alt=json'
|
||||
export const GOOGLE_CLIENT_ID =
|
||||
process.env.GOOGLE_CLIENT_ID ?? 'GOOGLE_CLIENT_ID'
|
||||
export const GOOGLE_CLIENT_SECRET =
|
||||
process.env.GOOGLE_CLIENT_SECRET ?? 'GOOGLE_CLIENT_SECRET'
|
||||
export const googleStrategy = new OAuthStrategy(GOOGLE_PROVIDER)
|
||||
|
||||
export interface GoogleUser {
|
||||
id: string
|
||||
name: string
|
||||
given_name: string
|
||||
link: string
|
||||
picture: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
export interface GoogleTokens {
|
||||
access_token: string
|
||||
expires_in: number
|
||||
token_type: string
|
||||
scope: string
|
||||
refresh_token?: string
|
||||
}
|
||||
|
||||
export const getGoogleUserData = async (
|
||||
code: string,
|
||||
redirectURI: string
|
||||
): Promise<GoogleUser> => {
|
||||
const { data: token } = await axios.post<GoogleTokens>(
|
||||
GOOGLE_OAUTH2_TOKEN,
|
||||
querystring.stringify({
|
||||
client_id: GOOGLE_CLIENT_ID,
|
||||
client_secret: GOOGLE_CLIENT_SECRET,
|
||||
code,
|
||||
redirect_uri: redirectURI,
|
||||
grant_type: 'authorization_code'
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const { data: googleUser } = await axios.get<GoogleUser>(
|
||||
`${GOOGLE_USERINFO}&access_token=${token.access_token}`
|
||||
)
|
||||
return googleUser
|
||||
}
|
49
src/services/users/oauth2/google/callback/get.ts
Normal file
49
src/services/users/oauth2/google/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 { googleStrategy, getGoogleUserData } 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: 'Google OAuth2 - callback',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackGoogleOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/google/callback',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code } = request.query
|
||||
const googleUser = await getGoogleUserData(
|
||||
code,
|
||||
`${request.protocol}://${HOST}:${PORT}/users/oauth2/google/callback?redirectURI=${redirectURI}`
|
||||
)
|
||||
const responseJWT = await googleStrategy.callbackSignin({
|
||||
name: googleUser.name,
|
||||
id: googleUser.id
|
||||
})
|
||||
return await reply.redirect(buildQueryURL(redirectURI, responseJWT))
|
||||
}
|
||||
})
|
||||
}
|
42
src/services/users/oauth2/google/signin/get.ts
Normal file
42
src/services/users/oauth2/google/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 { GOOGLE_BASE_URL, GOOGLE_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: 'Google OAuth2 - signin',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getSigninGoogleOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/google/signin',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${request.protocol}://${HOST}:${PORT}/users/oauth2/google/callback?redirectURI=${redirectURI}`
|
||||
const url = `${GOOGLE_BASE_URL}?client_id=${GOOGLE_CLIENT_ID}&redirect_uri=${redirectCallback}&response_type=code&scope=profile&access_type=online`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
@ -56,7 +56,7 @@ export const postRefreshTokenUser: FastifyPluginAsync = async (fastify) => {
|
||||
) as UserJWT
|
||||
const accessToken = generateAccessToken({
|
||||
id: userJWT.id,
|
||||
currentStrategy: 'local'
|
||||
currentStrategy: userJWT.currentStrategy
|
||||
})
|
||||
reply.statusCode = 200
|
||||
return {
|
||||
|
@ -19,7 +19,8 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = {
|
||||
{ name: 'guilds' },
|
||||
{ name: 'channels' },
|
||||
{ name: 'messages' },
|
||||
{ name: 'members' }
|
||||
{ name: 'members' },
|
||||
{ name: 'uploads' }
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
|
Reference in New Issue
Block a user