diff --git a/.env.template b/.env.template index cef2a95..8e16ad3 100644 --- a/.env.template +++ b/.env.template @@ -1,3 +1,4 @@ NODE_ENV=development VITE_API_BASEURL=http://localhost:5000/api DATABASE_URL="file:../data/data.db" +API_KEY=TOP_SECRET diff --git a/docker-compose.yml b/docker-compose.yml index d4088e2..fb24a5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ version: '3.7' services: wishlist: image: thisisbenny/wishlist-app:latest + environment: + - API_KEY=TOP_SECRET ports: - '5000:5000' volumes: diff --git a/examples.http b/examples.http index 9ff63c8..4e6f4d3 100644 --- a/examples.http +++ b/examples.http @@ -5,6 +5,7 @@ # @name createWishlistFirst POST {{BASE_URL}}/wishlist Content-Type: application/json +Authorization: API-Key TOP_SECRET { "title": "Junior", diff --git a/src/api/config/auth.ts b/src/api/config/auth.ts new file mode 100644 index 0000000..6c43c95 --- /dev/null +++ b/src/api/config/auth.ts @@ -0,0 +1,36 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify' +import { notAuthorized } from './errors' + +const error = notAuthorized('Unauthorized') + +export default { + init: async (app: FastifyInstance) => { + if (!process.env.API_KEY) { + throw new Error('ENV API_KEY is not set!') + } + app.addHook( + 'onRequest', + (request: FastifyRequest, reply: FastifyReply, done) => { + //@ts-expect-error: custom attribute + if (!reply.context.config.protected) { + return done() + } + if (!request.headers.authorization) { + return done(error) + } + const authHeader = request.headers.authorization.split(' ') + request.log.debug(authHeader) + if ( + authHeader[0] && + authHeader[0].trim().toLowerCase() === 'api-key' && + authHeader[1] + ) { + if (authHeader[1] === process.env.API_KEY) { + return done() + } + } + done(error) + } + ) + }, +} diff --git a/src/api/config/errors/index.ts b/src/api/config/errors/index.ts index 9d91200..54c4b3d 100644 --- a/src/api/config/errors/index.ts +++ b/src/api/config/errors/index.ts @@ -21,6 +21,8 @@ export const defaultErrorHandler = ( ) } else if (errorIs(error, 'P2025')) { reply.callNotFound() + } else if (error instanceof httpError) { + reply.send(error) } else { request.log.error(error) const e = new httpError('unexpected error', 500, '500') @@ -47,10 +49,13 @@ class httpError extends Error { } } -const notFoundError = () => { +export const notFoundError = () => { return new httpError('Not Found', 404, '404') } -const uniqueKeyError = (msg: string, code = '4001') => { +export const uniqueKeyError = (msg: string, code = '4001') => { return new httpError(msg, 422, code) } +export const notAuthorized = (msg: string, code = '401') => { + return new httpError(msg, 401, code) +} diff --git a/src/api/config/initApp.ts b/src/api/config/initApp.ts index 4eb83f2..5328d83 100644 --- a/src/api/config/initApp.ts +++ b/src/api/config/initApp.ts @@ -3,6 +3,7 @@ import Fastify, { FastifyContextConfig } from 'fastify' import compress from 'fastify-compress' import cors from 'fastify-cors' import { fastify as defaultConfig } from './' +import auth from './auth' export default async (opts: FastifyContextConfig = {}) => { const app = Fastify({ @@ -23,6 +24,7 @@ export default async (opts: FastifyContextConfig = {}) => { }) await app.register(compress) + await auth.init(app) return app } diff --git a/src/api/routes/wishlist/create.ts b/src/api/routes/wishlist/create.ts index cf8117b..e7b2125 100644 --- a/src/api/routes/wishlist/create.ts +++ b/src/api/routes/wishlist/create.ts @@ -17,6 +17,9 @@ interface createItemRequest extends FastifyRequest { export const createList = { method: 'POST', url: '/', + config: { + protected: true, + }, schema: { body: wishlistRequestSchema, response: { @@ -33,6 +36,9 @@ export const createList = { export const createItem = { method: 'POST', url: '/:wishlistId/item', + config: { + protected: true, + }, schema: { body: wishlistItemRequestSchema, params: { diff --git a/src/api/routes/wishlist/delete.ts b/src/api/routes/wishlist/delete.ts index ebeb9e9..1637370 100644 --- a/src/api/routes/wishlist/delete.ts +++ b/src/api/routes/wishlist/delete.ts @@ -17,6 +17,9 @@ interface deleteItemRequest extends FastifyRequest { export const deleteList = { method: 'DELETE', url: '/:wishlistId', + config: { + protected: true, + }, schema: { params: { type: 'object', @@ -34,6 +37,9 @@ export const deleteList = { export const deleteItem = { method: 'DELETE', url: '/:wishlistId/item/:itemId', + config: { + protected: true, + }, schema: { params: { type: 'object', diff --git a/src/api/routes/wishlist/update.ts b/src/api/routes/wishlist/update.ts index 2f64817..9eb2efc 100644 --- a/src/api/routes/wishlist/update.ts +++ b/src/api/routes/wishlist/update.ts @@ -24,6 +24,9 @@ interface updateItemRequest extends FastifyRequest { export const updateList = { method: 'PUT', url: '/:wishlistId', + config: { + protected: true, + }, schema: { body: wishlistRequestSchema, params: { @@ -49,6 +52,9 @@ export const updateList = { export const updateItem = { method: 'PUT', url: '/:wishlistId/item/:itemId', + config: { + protected: true, + }, schema: { body: wishlistItemRequestSchema, params: {