diff --git a/.env.example b/.env.example index 972e061..1d9102b 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,4 @@ HOST='0.0.0.0' PORT='8000' NODE_ENV='development' API_URL='http://localhost:8000' +API_KEY='apiKeySecret' diff --git a/.gitignore b/.gitignore index 1beca0f..5d67d93 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ npm-debug.log* # misc .DS_Store +/uploads diff --git a/src/services/uploads/guilds/get.ts b/src/services/uploads/guilds/get.ts index ed5e0f0..f94862c 100644 --- a/src/services/uploads/guilds/get.ts +++ b/src/services/uploads/guilds/get.ts @@ -12,7 +12,7 @@ const parameters = Type.Object({ type Parameters = Static export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], + tags: ['guilds'] as string[], params: parameters, response: { 200: { diff --git a/src/services/uploads/guilds/post.ts b/src/services/uploads/guilds/post.ts index cd94ea2..1d8f1c0 100644 --- a/src/services/uploads/guilds/post.ts +++ b/src/services/uploads/guilds/post.ts @@ -8,15 +8,23 @@ import { MAXIMUM_IMAGE_SIZE, SUPPORTED_IMAGE_MIMETYPE } from '../../../tools/configurations/index.js' +import verifyAPIKey from '../../../tools/plugins/verifyAPIKey.js' const postServiceSchema: FastifySchema = { description: 'Uploads guild icon', - tags: ['uploads'] as string[], + tags: ['guilds'] as string[], + security: [ + { + apiKeyAuth: [] + } + ] as Array<{ [key: string]: [] }>, consumes: ['multipart/form-data'] as string[], produces: ['application/json'] as string[], response: { 201: Type.String(), 400: fastifyErrors[400], + 401: fastifyErrors[401], + 403: fastifyErrors[403], 431: fastifyErrors[431], 500: fastifyErrors[500] } @@ -26,12 +34,16 @@ export const postGuildsUploadsIconService: FastifyPluginAsync = async ( fastify ) => { await fastify.register(fastifyMultipart) + await fastify.register(verifyAPIKey) fastify.route({ method: 'POST', url: '/uploads/guilds', schema: postServiceSchema, handler: async (request, reply) => { + if (request.apiKey == null) { + throw fastify.httpErrors.forbidden() + } const file = await uploadFile({ fastify, request, diff --git a/src/services/uploads/messages/get.ts b/src/services/uploads/messages/get.ts index 8df36e9..f9c40a3 100644 --- a/src/services/uploads/messages/get.ts +++ b/src/services/uploads/messages/get.ts @@ -12,7 +12,7 @@ const parameters = Type.Object({ type Parameters = Static export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], + tags: ['messages'] as string[], params: parameters, response: { 200: { diff --git a/src/services/uploads/messages/post.ts b/src/services/uploads/messages/post.ts index fa5abdd..b564822 100644 --- a/src/services/uploads/messages/post.ts +++ b/src/services/uploads/messages/post.ts @@ -5,15 +5,23 @@ import fastifyMultipart from 'fastify-multipart' import { fastifyErrors } from '../../../models/utils.js' import { uploadFile } from '../../../tools/utils/uploadFile.js' import { MAXIMUM_IMAGE_SIZE } from '../../../tools/configurations/index.js' +import verifyAPIKey from '../../../tools/plugins/verifyAPIKey.js' const postServiceSchema: FastifySchema = { description: 'Uploads message file', - tags: ['uploads'] as string[], + tags: ['messages'] as string[], + security: [ + { + apiKeyAuth: [] + } + ] as Array<{ [key: string]: [] }>, consumes: ['multipart/form-data'] as string[], produces: ['application/json'] as string[], response: { 201: Type.String(), 400: fastifyErrors[400], + 401: fastifyErrors[401], + 403: fastifyErrors[403], 431: fastifyErrors[431], 500: fastifyErrors[500] } @@ -23,12 +31,16 @@ export const postMessagesUploadsService: FastifyPluginAsync = async ( fastify ) => { await fastify.register(fastifyMultipart) + await fastify.register(verifyAPIKey) fastify.route({ method: 'POST', url: '/uploads/messages', schema: postServiceSchema, handler: async (request, reply) => { + if (request.apiKey == null) { + throw fastify.httpErrors.forbidden() + } const file = await uploadFile({ fastify, request, diff --git a/src/services/uploads/users/get.ts b/src/services/uploads/users/get.ts index 1f77de8..72509de 100644 --- a/src/services/uploads/users/get.ts +++ b/src/services/uploads/users/get.ts @@ -12,7 +12,7 @@ const parameters = Type.Object({ type Parameters = Static export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], + tags: ['users'] as string[], params: parameters, response: { 200: { diff --git a/src/services/uploads/users/post.ts b/src/services/uploads/users/post.ts index f99c167..2cce079 100644 --- a/src/services/uploads/users/post.ts +++ b/src/services/uploads/users/post.ts @@ -8,15 +8,23 @@ import { MAXIMUM_IMAGE_SIZE, SUPPORTED_IMAGE_MIMETYPE } from '../../../tools/configurations/index.js' +import verifyAPIKey from '../../../tools/plugins/verifyAPIKey.js' const postServiceSchema: FastifySchema = { description: 'Uploads user logo', - tags: ['uploads'] as string[], + tags: ['users'] as string[], + security: [ + { + apiKeyAuth: [] + } + ] as Array<{ [key: string]: [] }>, consumes: ['multipart/form-data'] as string[], produces: ['application/json'] as string[], response: { 201: Type.String(), 400: fastifyErrors[400], + 401: fastifyErrors[401], + 403: fastifyErrors[403], 431: fastifyErrors[431], 500: fastifyErrors[500] } @@ -26,12 +34,16 @@ export const postUsersUploadsLogoService: FastifyPluginAsync = async ( fastify ) => { await fastify.register(fastifyMultipart) + await fastify.register(verifyAPIKey) fastify.route({ method: 'POST', url: '/uploads/users', schema: postServiceSchema, handler: async (request, reply) => { + if (request.apiKey == null) { + throw fastify.httpErrors.forbidden() + } const file = await uploadFile({ fastify, request, diff --git a/src/tools/configurations/index.ts b/src/tools/configurations/index.ts index 2d60887..d394fab 100644 --- a/src/tools/configurations/index.ts +++ b/src/tools/configurations/index.ts @@ -7,6 +7,7 @@ dotenv.config() export const PORT = parseInt(process.env.PORT ?? '8000', 10) export const HOST = process.env.HOST ?? '0.0.0.0' export const API_URL = process.env.API_URL ?? `http://${HOST}:${PORT}` +export const API_KEY = process.env.API_KEY ?? 'apiKeySecret' export const SRC_URL = new URL('../../', import.meta.url) export const ROOT_URL = new URL('../', SRC_URL) diff --git a/src/tools/configurations/swaggerOptions.ts b/src/tools/configurations/swaggerOptions.ts index 36ff358..b351ec0 100644 --- a/src/tools/configurations/swaggerOptions.ts +++ b/src/tools/configurations/swaggerOptions.ts @@ -14,7 +14,16 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = { description: packageJSON.description, version: packageJSON.version }, - tags: [{ name: 'guilds' }, { name: 'messages' }, { name: 'users' }] + tags: [{ name: 'guilds' }, { name: 'messages' }, { name: 'users' }], + components: { + securitySchemes: { + apiKeyAuth: { + type: 'apiKey', + name: 'X-API-KEY', + in: 'header' + } + } + } }, exposeRoute: true, staticCSP: true, diff --git a/src/tools/plugins/verifyAPIKey.ts b/src/tools/plugins/verifyAPIKey.ts new file mode 100644 index 0000000..b14f819 --- /dev/null +++ b/src/tools/plugins/verifyAPIKey.ts @@ -0,0 +1,29 @@ +import fastifyPlugin from 'fastify-plugin' +import httpErrors from 'http-errors' + +import { API_KEY } from '../configurations/index.js' + +const { Unauthorized, Forbidden } = httpErrors + +declare module 'fastify' { + export interface FastifyRequest { + apiKey?: string + } +} + +export default fastifyPlugin( + async (fastify) => { + await fastify.decorateRequest('apiKey', null) + await fastify.addHook('onRequest', async (request) => { + const apiKey = request.headers['x-api-key'] + if (apiKey == null || typeof apiKey !== 'string') { + throw new Unauthorized() + } + if (apiKey !== API_KEY) { + throw new Forbidden() + } + request.apiKey = apiKey + }) + }, + { fastify: '3.x' } +)