fix: file upload and OAuth2 (#10)
This commit is contained in:
@ -1,19 +1,14 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
import fastify from 'fastify'
|
||||
import fastifyCors from 'fastify-cors'
|
||||
import fastifySwagger from 'fastify-swagger'
|
||||
import fastifyUrlData from 'fastify-url-data'
|
||||
import fastifyHelmet from 'fastify-helmet'
|
||||
import fastifyRateLimit from 'fastify-rate-limit'
|
||||
import fastifySensible from 'fastify-sensible'
|
||||
import fastifyStatic from 'fastify-static'
|
||||
|
||||
import { services } from './services/index.js'
|
||||
import { swaggerOptions } from './tools/configurations/swaggerOptions.js'
|
||||
import fastifySocketIo from './tools/plugins/socket-io.js'
|
||||
import { UPLOADS_URL } from './tools/configurations/index.js'
|
||||
|
||||
dotenv.config()
|
||||
export const application = fastify({
|
||||
@ -27,7 +22,6 @@ export const application = fastify({
|
||||
|
||||
await application.register(fastifyCors)
|
||||
await application.register(fastifySensible)
|
||||
await application.register(fastifyUrlData)
|
||||
await application.register(fastifySocketIo, {
|
||||
cors: {
|
||||
origin: '*',
|
||||
@ -41,9 +35,5 @@ await application.register(fastifyRateLimit, {
|
||||
max: 200,
|
||||
timeWindow: '1 minute'
|
||||
})
|
||||
await application.register(fastifyStatic, {
|
||||
root: fileURLToPath(UPLOADS_URL),
|
||||
prefix: '/uploads/'
|
||||
})
|
||||
await application.register(fastifySwagger, swaggerOptions)
|
||||
await application.register(services)
|
||||
|
@ -1,51 +0,0 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import prisma from '../tools/database/prisma.js'
|
||||
import { UPLOADS_URL } from '../tools/configurations/index.js'
|
||||
|
||||
const getPathStoredInDatabaseFromFile = (
|
||||
file: string,
|
||||
folderInUploadsFolder: string
|
||||
): string => {
|
||||
return `/uploads/${folderInUploadsFolder}/${file}`
|
||||
}
|
||||
|
||||
const deleteDeadUploadedFiles = async (
|
||||
folderInUploadsFolder: string,
|
||||
getElementInDatabase: (file: string) => Promise<unknown | null>
|
||||
): Promise<void> => {
|
||||
const UPLOADS_FILES_URL = new URL(`./${folderInUploadsFolder}`, UPLOADS_URL)
|
||||
const files = await fs.promises.readdir(UPLOADS_FILES_URL)
|
||||
for (const file of files) {
|
||||
if (file !== '.gitkeep') {
|
||||
const pathStoredInDatabase = getPathStoredInDatabaseFromFile(
|
||||
file,
|
||||
folderInUploadsFolder
|
||||
)
|
||||
const element = await getElementInDatabase(pathStoredInDatabase)
|
||||
if (element == null) {
|
||||
const fileURL = new URL(
|
||||
`./${folderInUploadsFolder}/${file}`,
|
||||
UPLOADS_URL
|
||||
)
|
||||
await fs.promises.rm(fileURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await deleteDeadUploadedFiles('guilds', async (icon: string) => {
|
||||
return await prisma.guild.findFirst({
|
||||
where: { icon }
|
||||
})
|
||||
})
|
||||
await deleteDeadUploadedFiles('messages', async (value: string) => {
|
||||
return await prisma.message.findFirst({
|
||||
where: { type: 'file', value }
|
||||
})
|
||||
})
|
||||
await deleteDeadUploadedFiles('users', async (logo: string) => {
|
||||
return await prisma.user.findFirst({
|
||||
where: { logo }
|
||||
})
|
||||
})
|
@ -10,7 +10,6 @@ import { memberSchema } from '../../../../../models/Member.js'
|
||||
import { userPublicWithoutSettingsSchema } from '../../../../../models/User.js'
|
||||
import { channelSchema } from '../../../../../models/Channel.js'
|
||||
import { uploadFile } from '../../../../../tools/utils/uploadFile.js'
|
||||
import { MAXIMUM_FILE_SIZE } from '../../../../../tools/configurations/index.js'
|
||||
|
||||
const parametersSchema = Type.Object({
|
||||
channelId: channelSchema.id
|
||||
@ -94,8 +93,7 @@ export const postMessageUploadsByChannelIdService: FastifyPluginAsync = async (
|
||||
const file = await uploadFile({
|
||||
fastify,
|
||||
request,
|
||||
folderInUploadsFolder: 'messages',
|
||||
maximumFileSize: MAXIMUM_FILE_SIZE
|
||||
folderInUploadsFolder: 'messages'
|
||||
})
|
||||
const message = await prisma.message.create({
|
||||
data: {
|
||||
|
@ -7,10 +7,6 @@ import { fastifyErrors } from '../../../../models/utils.js'
|
||||
import prisma from '../../../../tools/database/prisma.js'
|
||||
import { uploadFile } from '../../../../tools/utils/uploadFile.js'
|
||||
import { guildSchema } from '../../../../models/Guild.js'
|
||||
import {
|
||||
MAXIMUM_IMAGE_SIZE,
|
||||
SUPPORTED_IMAGE_MIMETYPE
|
||||
} from '../../../../tools/configurations/index.js'
|
||||
import { channelSchema } from '../../../../models/Channel.js'
|
||||
|
||||
const parametersSchema = Type.Object({
|
||||
@ -67,9 +63,7 @@ export const putGuildIconById: FastifyPluginAsync = async (fastify) => {
|
||||
const file = await uploadFile({
|
||||
fastify,
|
||||
request,
|
||||
folderInUploadsFolder: 'guilds',
|
||||
maximumFileSize: MAXIMUM_IMAGE_SIZE,
|
||||
supportedFileMimetype: SUPPORTED_IMAGE_MIMETYPE
|
||||
folderInUploadsFolder: 'guilds'
|
||||
})
|
||||
await prisma.guild.update({
|
||||
where: { id: guildId },
|
||||
|
@ -2,7 +2,6 @@ import { FastifyPluginAsync } from 'fastify'
|
||||
|
||||
import { usersService } from './users/index.js'
|
||||
import { guildsService } from './guilds/index.js'
|
||||
import { uploadsService } from './uploads/index.js'
|
||||
import { channelsService } from './channels/index.js'
|
||||
import { messagesService } from './messages/index.js'
|
||||
|
||||
@ -10,6 +9,5 @@ export const services: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(channelsService)
|
||||
await fastify.register(guildsService)
|
||||
await fastify.register(messagesService)
|
||||
await fastify.register(uploadsService)
|
||||
await fastify.register(usersService)
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
|
||||
import { fastifyErrors } from '../../../models/utils.js'
|
||||
|
||||
const parameters = Type.Object({
|
||||
file: Type.String()
|
||||
})
|
||||
|
||||
type Parameters = Static<typeof parameters>
|
||||
|
||||
export const getServiceSchema: FastifySchema = {
|
||||
tags: ['uploads'] as string[],
|
||||
params: parameters,
|
||||
response: {
|
||||
200: {
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
},
|
||||
400: fastifyErrors[400],
|
||||
404: fastifyErrors[404],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getGuildsUploadsService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.route<{
|
||||
Params: Parameters
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/uploads/guilds/:file',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { file } = request.params
|
||||
return await reply.sendFile(path.join('guilds', file))
|
||||
}
|
||||
})
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { FastifyPluginAsync } from 'fastify'
|
||||
|
||||
import { getGuildsUploadsService } from './guilds/get.js'
|
||||
import { getMessagesUploadsService } from './messages/get.js'
|
||||
import { getUsersUploadsService } from './users/get.js'
|
||||
|
||||
export const uploadsService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(getGuildsUploadsService)
|
||||
await fastify.register(getMessagesUploadsService)
|
||||
await fastify.register(getUsersUploadsService)
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
|
||||
import { fastifyErrors } from '../../../models/utils.js'
|
||||
import authenticateUser from '../../../tools/plugins/authenticateUser.js'
|
||||
import prisma from '../../../tools/database/prisma.js'
|
||||
|
||||
const parameters = Type.Object({
|
||||
file: Type.String()
|
||||
})
|
||||
|
||||
type Parameters = Static<typeof parameters>
|
||||
|
||||
export const getServiceSchema: FastifySchema = {
|
||||
tags: ['uploads'] as string[],
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
] as Array<{ [key: string]: [] }>,
|
||||
params: parameters,
|
||||
response: {
|
||||
200: {
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
},
|
||||
400: fastifyErrors[400],
|
||||
401: fastifyErrors[401],
|
||||
403: fastifyErrors[403],
|
||||
404: fastifyErrors[404],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getMessagesUploadsService: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.register(authenticateUser)
|
||||
|
||||
fastify.route<{
|
||||
Params: Parameters
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/uploads/messages/:file',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { file } = request.params
|
||||
const message = await prisma.message.findFirst({
|
||||
where: { value: `/uploads/messages/${file}` },
|
||||
include: {
|
||||
member: {
|
||||
select: { guildId: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
if (message == null) {
|
||||
throw fastify.httpErrors.notFound('Message not found')
|
||||
}
|
||||
const member = await prisma.member.findFirst({
|
||||
where: {
|
||||
guildId: message.member?.guildId,
|
||||
userId: request.user.current.id
|
||||
}
|
||||
})
|
||||
if (member == null) {
|
||||
throw fastify.httpErrors.notFound('Member not found')
|
||||
}
|
||||
return await reply.sendFile(path.join('messages', file))
|
||||
}
|
||||
})
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
|
||||
import { fastifyErrors } from '../../../models/utils.js'
|
||||
|
||||
const parameters = Type.Object({
|
||||
file: Type.String()
|
||||
})
|
||||
|
||||
type Parameters = Static<typeof parameters>
|
||||
|
||||
export const getServiceSchema: FastifySchema = {
|
||||
tags: ['uploads'] as string[],
|
||||
params: parameters,
|
||||
response: {
|
||||
200: {
|
||||
type: 'string',
|
||||
format: 'binary'
|
||||
},
|
||||
400: fastifyErrors[400],
|
||||
404: fastifyErrors[404],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getUsersUploadsService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.route<{
|
||||
Params: Parameters
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/uploads/users/:file',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { file } = request.params
|
||||
return await reply.sendFile(path.join('users', file))
|
||||
}
|
||||
})
|
||||
}
|
@ -6,10 +6,6 @@ import authenticateUser from '../../../../tools/plugins/authenticateUser.js'
|
||||
import { fastifyErrors } from '../../../../models/utils.js'
|
||||
import prisma from '../../../../tools/database/prisma.js'
|
||||
import { uploadFile } from '../../../../tools/utils/uploadFile.js'
|
||||
import {
|
||||
MAXIMUM_IMAGE_SIZE,
|
||||
SUPPORTED_IMAGE_MIMETYPE
|
||||
} from '../../../../tools/configurations/index.js'
|
||||
|
||||
const putServiceSchema: FastifySchema = {
|
||||
description: 'Edit the current connected user logo',
|
||||
@ -51,9 +47,7 @@ export const putCurrentUserLogo: FastifyPluginAsync = async (fastify) => {
|
||||
const file = await uploadFile({
|
||||
fastify,
|
||||
request,
|
||||
folderInUploadsFolder: 'users',
|
||||
maximumFileSize: MAXIMUM_IMAGE_SIZE,
|
||||
supportedFileMimetype: SUPPORTED_IMAGE_MIMETYPE
|
||||
folderInUploadsFolder: 'users'
|
||||
})
|
||||
await prisma.user.update({
|
||||
where: { id: request.user.current.id },
|
||||
|
@ -20,6 +20,12 @@ 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'
|
||||
import { getCallbackAddStrategyDiscordOAuth2Service } from './oauth2/discord/callback-add-strategy/get.js'
|
||||
import { getAddStrategyDiscordOAuth2Service } from './oauth2/discord/add-strategy/get.js'
|
||||
import { getAddStrategyGitHubOAuth2Service } from './oauth2/github/add-strategy/get.js'
|
||||
import { getCallbackAddStrategyGitHubOAuth2Service } from './oauth2/github/callback-add-strategy/get.js'
|
||||
import { getCallbackAddStrategyGoogleOAuth2Service } from './oauth2/google/callback-add-strategy/get.js'
|
||||
import { getAddStrategyGoogleOAuth2Service } from './oauth2/google/add-strategy/get.js'
|
||||
|
||||
export const usersService: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(postSignupUser)
|
||||
@ -38,12 +44,18 @@ export const usersService: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
await fastify.register(getSigninDiscordOAuth2Service)
|
||||
await fastify.register(getCallbackDiscordOAuth2Service)
|
||||
await fastify.register(getCallbackAddStrategyDiscordOAuth2Service)
|
||||
await fastify.register(getAddStrategyDiscordOAuth2Service)
|
||||
|
||||
await fastify.register(getSigninGoogleOAuth2Service)
|
||||
await fastify.register(getCallbackGoogleOAuth2Service)
|
||||
await fastify.register(getCallbackAddStrategyGoogleOAuth2Service)
|
||||
await fastify.register(getAddStrategyGoogleOAuth2Service)
|
||||
|
||||
await fastify.register(getSigninGitHubOAuth2Service)
|
||||
await fastify.register(getCallbackGitHubOAuth2Service)
|
||||
await fastify.register(getCallbackAddStrategyGitHubOAuth2Service)
|
||||
await fastify.register(getAddStrategyGitHubOAuth2Service)
|
||||
|
||||
await fastify.register(deleteProviderService)
|
||||
}
|
||||
|
53
src/services/users/oauth2/discord/add-strategy/get.ts
Normal file
53
src/services/users/oauth2/discord/add-strategy/get.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } from '../../../../../tools/configurations/index.js'
|
||||
import { fastifyErrors } from '../../../../../models/utils.js'
|
||||
import { DISCORD_BASE_URL, DISCORD_CLIENT_ID } from '../__utils__/utils.js'
|
||||
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'Discord OAuth2 - add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
] as Array<{ [key: string]: [] }>,
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getAddStrategyDiscordOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.register(authenticateUser)
|
||||
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/discord/add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${API_URL}/users/oauth2/discord/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
const url = `${DISCORD_BASE_URL}/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&scope=identify&response_type=code&state=${request.user.accessToken}&redirect_uri=${redirectCallback}`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } 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'
|
||||
import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
code: Type.String(),
|
||||
state: Type.String(),
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'Discord OAuth2 - callback-add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackAddStrategyDiscordOAuth2Service: FastifyPluginAsync =
|
||||
async (fastify) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/discord/callback-add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code, state: accessToken } = request.query
|
||||
const userRequest = await getUserWithBearerToken(
|
||||
`Bearer ${accessToken}`
|
||||
)
|
||||
const discordUser = await getDiscordUserData(
|
||||
code,
|
||||
`${API_URL}/users/oauth2/discord/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
)
|
||||
const message = await discordStrategy.callbackAddStrategy(
|
||||
{
|
||||
name: discordUser.username,
|
||||
id: discordUser.id
|
||||
},
|
||||
userRequest
|
||||
)
|
||||
return await reply.redirect(buildQueryURL(redirectURI, { message }))
|
||||
}
|
||||
})
|
||||
}
|
53
src/services/users/oauth2/github/add-strategy/get.ts
Normal file
53
src/services/users/oauth2/github/add-strategy/get.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } from '../../../../../tools/configurations/index.js'
|
||||
import { fastifyErrors } from '../../../../../models/utils.js'
|
||||
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID } from '../__utils__/utils.js'
|
||||
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'GitHub OAuth2 - add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
] as Array<{ [key: string]: [] }>,
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getAddStrategyGitHubOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.register(authenticateUser)
|
||||
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/github/add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${API_URL}/users/oauth2/github/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
const url = `${GITHUB_BASE_URL}/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&state=${request.user.accessToken}&redirect_uri=${redirectCallback}`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } 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'
|
||||
import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
code: Type.String(),
|
||||
state: Type.String(),
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'GitHub OAuth2 - callback-add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackAddStrategyGitHubOAuth2Service: FastifyPluginAsync =
|
||||
async (fastify) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/github/callback-add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code, state: accessToken } = request.query
|
||||
const userRequest = await getUserWithBearerToken(
|
||||
`Bearer ${accessToken}`
|
||||
)
|
||||
const githubUser = await getGitHubUserData(
|
||||
code,
|
||||
`${API_URL}/users/oauth2/github/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
)
|
||||
const message = await githubStrategy.callbackAddStrategy(
|
||||
{
|
||||
name: githubUser.name,
|
||||
id: githubUser.id
|
||||
},
|
||||
userRequest
|
||||
)
|
||||
return await reply.redirect(buildQueryURL(redirectURI, { message }))
|
||||
}
|
||||
})
|
||||
}
|
53
src/services/users/oauth2/google/add-strategy/get.ts
Normal file
53
src/services/users/oauth2/google/add-strategy/get.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } from '../../../../../tools/configurations/index.js'
|
||||
import { fastifyErrors } from '../../../../../models/utils.js'
|
||||
import { GOOGLE_BASE_URL, GOOGLE_CLIENT_ID } from '../__utils__/utils.js'
|
||||
import authenticateUser from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'Google OAuth2 - add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
] as Array<{ [key: string]: [] }>,
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getAddStrategyGoogleOAuth2Service: FastifyPluginAsync = async (
|
||||
fastify
|
||||
) => {
|
||||
await fastify.register(authenticateUser)
|
||||
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/google/add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
if (request.user == null) {
|
||||
throw fastify.httpErrors.forbidden()
|
||||
}
|
||||
const { redirectURI } = request.query
|
||||
const redirectCallback = `${API_URL}/users/oauth2/google/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
const url = `${GOOGLE_BASE_URL}?client_id=${GOOGLE_CLIENT_ID}&state=${request.user.accessToken}&redirect_uri=${redirectCallback}&response_type=code&scope=profile&access_type=online`
|
||||
reply.statusCode = 200
|
||||
return url
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { Static, Type } from '@sinclair/typebox'
|
||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||
|
||||
import { API_URL } 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'
|
||||
import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js'
|
||||
|
||||
const querySchema = Type.Object({
|
||||
code: Type.String(),
|
||||
state: Type.String(),
|
||||
redirectURI: Type.String({ format: 'uri-reference' })
|
||||
})
|
||||
|
||||
type QuerySchemaType = Static<typeof querySchema>
|
||||
|
||||
const getServiceSchema: FastifySchema = {
|
||||
description: 'Google OAuth2 - callback-add-strategy',
|
||||
tags: ['users'] as string[],
|
||||
querystring: querySchema,
|
||||
response: {
|
||||
200: Type.String(),
|
||||
400: fastifyErrors[400],
|
||||
500: fastifyErrors[500]
|
||||
}
|
||||
} as const
|
||||
|
||||
export const getCallbackAddStrategyGoogleOAuth2Service: FastifyPluginAsync =
|
||||
async (fastify) => {
|
||||
await fastify.route<{
|
||||
Querystring: QuerySchemaType
|
||||
}>({
|
||||
method: 'GET',
|
||||
url: '/users/oauth2/google/callback-add-strategy',
|
||||
schema: getServiceSchema,
|
||||
handler: async (request, reply) => {
|
||||
const { redirectURI, code, state: accessToken } = request.query
|
||||
const userRequest = await getUserWithBearerToken(
|
||||
`Bearer ${accessToken}`
|
||||
)
|
||||
const googleUser = await getGoogleUserData(
|
||||
code,
|
||||
`${API_URL}/users/oauth2/google/callback-add-strategy?redirectURI=${redirectURI}`
|
||||
)
|
||||
const message = await googleStrategy.callbackAddStrategy(
|
||||
{
|
||||
name: googleUser.name,
|
||||
id: googleUser.id
|
||||
},
|
||||
userRequest
|
||||
)
|
||||
return await reply.redirect(buildQueryURL(redirectURI, { message }))
|
||||
}
|
||||
})
|
||||
}
|
@ -7,6 +7,10 @@ dotenv.config()
|
||||
export const PORT = parseInt(process.env.PORT ?? '8080', 10)
|
||||
export const HOST = process.env.HOST ?? '0.0.0.0'
|
||||
export const API_URL = process.env.API_URL ?? `http://${HOST}:${PORT}`
|
||||
export const FILE_UPLOADS_API_URL =
|
||||
process.env.FILE_UPLOADS_API_URL ?? 'http://localhost:8000'
|
||||
export const FILE_UPLOADS_API_KEY =
|
||||
process.env.FILE_UPLOADS_API_KEY ?? 'apiKeySecret'
|
||||
export const JWT_ACCESS_SECRET =
|
||||
process.env.JWT_ACCESS_SECRET ?? 'accessTokenSecret'
|
||||
export const JWT_REFRESH_SECRET =
|
||||
@ -19,17 +23,3 @@ export const ROOT_URL = new URL('../', SRC_URL)
|
||||
export const EMAIL_URL = new URL('./email/', ROOT_URL)
|
||||
export const EMAIL_TEMPLATE_URL = new URL('./email-template.ejs', EMAIL_URL)
|
||||
export const EMAIL_LOCALES_URL = new URL('./locales/', EMAIL_URL)
|
||||
export const UPLOADS_URL = new URL('./uploads/', ROOT_URL)
|
||||
|
||||
export const SUPPORTED_IMAGE_MIMETYPE = [
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/jpeg',
|
||||
'image/gif'
|
||||
]
|
||||
|
||||
/** in megabytes */
|
||||
export const MAXIMUM_IMAGE_SIZE = 10
|
||||
|
||||
/** in megabytes */
|
||||
export const MAXIMUM_FILE_SIZE = 100
|
||||
|
@ -10,7 +10,7 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = {
|
||||
routePrefix: '/documentation',
|
||||
openapi: {
|
||||
info: {
|
||||
title: 'Thream',
|
||||
title: packageJSON.name,
|
||||
description: packageJSON.description,
|
||||
version: packageJSON.version
|
||||
},
|
||||
|
@ -1,21 +1,29 @@
|
||||
import fs from 'node:fs'
|
||||
import { URL } from 'node:url'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import axios from 'axios'
|
||||
import FormData from 'form-data'
|
||||
import { FastifyInstance, FastifyRequest } from 'fastify'
|
||||
import { Multipart } from 'fastify-multipart'
|
||||
|
||||
import { ROOT_URL } from '../configurations/index.js'
|
||||
import {
|
||||
FILE_UPLOADS_API_KEY,
|
||||
FILE_UPLOADS_API_URL
|
||||
} from '../configurations/index.js'
|
||||
|
||||
export const fileUploadAPI = axios.create({
|
||||
baseURL: FILE_UPLOADS_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
/** in megabytes */
|
||||
export const MAXIMUM_FILE_SIZE = 100
|
||||
|
||||
export interface UploadFileOptions {
|
||||
folderInUploadsFolder: 'guilds' | 'messages' | 'users'
|
||||
request: FastifyRequest
|
||||
fastify: FastifyInstance
|
||||
|
||||
/** in megabytes */
|
||||
maximumFileSize: number
|
||||
|
||||
supportedFileMimetype?: string[]
|
||||
}
|
||||
|
||||
export interface UploadFileResult {
|
||||
@ -26,43 +34,42 @@ export interface UploadFileResult {
|
||||
export const uploadFile = async (
|
||||
options: UploadFileOptions
|
||||
): Promise<UploadFileResult> => {
|
||||
const {
|
||||
fastify,
|
||||
request,
|
||||
folderInUploadsFolder,
|
||||
maximumFileSize,
|
||||
supportedFileMimetype
|
||||
} = options
|
||||
const { fastify, request, folderInUploadsFolder } = options
|
||||
let files: Multipart[] = []
|
||||
try {
|
||||
files = await request.saveRequestFiles({
|
||||
limits: {
|
||||
files: 1,
|
||||
fileSize: maximumFileSize * 1024 * 1024
|
||||
fileSize: MAXIMUM_FILE_SIZE * 1024 * 1024
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
throw fastify.httpErrors.requestHeaderFieldsTooLarge(
|
||||
`File should be less than ${maximumFileSize}mb.`
|
||||
`File should be less than ${MAXIMUM_FILE_SIZE}mb.`
|
||||
)
|
||||
}
|
||||
if (files.length !== 1) {
|
||||
throw fastify.httpErrors.badRequest('You must upload at most one file.')
|
||||
}
|
||||
const file = files[0]
|
||||
if (
|
||||
supportedFileMimetype != null &&
|
||||
!supportedFileMimetype.includes(file.mimetype)
|
||||
) {
|
||||
throw fastify.httpErrors.badRequest(
|
||||
`The file must have a valid type (${supportedFileMimetype.join(', ')}).`
|
||||
const formData = new FormData()
|
||||
formData.append('file', fs.createReadStream(file.filepath))
|
||||
try {
|
||||
const response = await fileUploadAPI.post(
|
||||
`/uploads/${folderInUploadsFolder}`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'X-API-Key': FILE_UPLOADS_API_KEY,
|
||||
...formData.getHeaders()
|
||||
}
|
||||
}
|
||||
)
|
||||
return { pathToStoreInDatabase: response.data, mimetype: file.mimetype }
|
||||
} catch (error: any) {
|
||||
throw fastify.httpErrors.createError(
|
||||
error.response.data.statusCode,
|
||||
error.response.data.message
|
||||
)
|
||||
}
|
||||
const splitedMimetype = file.mimetype.split('/')
|
||||
const fileExtension = splitedMimetype[1]
|
||||
const filePath = `uploads/${folderInUploadsFolder}/${randomUUID()}.${fileExtension}`
|
||||
const fileURL = new URL(filePath, ROOT_URL)
|
||||
const pathToStoreInDatabase = `/${filePath}`
|
||||
await fs.promises.copyFile(file.filepath, fileURL)
|
||||
return { pathToStoreInDatabase, mimetype: file.mimetype }
|
||||
}
|
||||
|
Reference in New Issue
Block a user