#3 protect endpoints via API-Key

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2022-02-09 22:11:32 +01:00
parent b7c1caebd7
commit bb5099d7d4
9 changed files with 67 additions and 2 deletions

View file

@ -1,3 +1,4 @@
NODE_ENV=development
VITE_API_BASEURL=http://localhost:5000/api
DATABASE_URL="file:../data/data.db"
API_KEY=TOP_SECRET

View file

@ -3,6 +3,8 @@ version: '3.7'
services:
wishlist:
image: thisisbenny/wishlist-app:latest
environment:
- API_KEY=TOP_SECRET
ports:
- '5000:5000'
volumes:

View file

@ -5,6 +5,7 @@
# @name createWishlistFirst
POST {{BASE_URL}}/wishlist
Content-Type: application/json
Authorization: API-Key TOP_SECRET
{
"title": "Junior",

36
src/api/config/auth.ts Normal file
View file

@ -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)
}
)
},
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -17,6 +17,9 @@ interface createItemRequest extends FastifyRequest {
export const createList = <RouteOptions>{
method: 'POST',
url: '/',
config: {
protected: true,
},
schema: {
body: wishlistRequestSchema,
response: {
@ -33,6 +36,9 @@ export const createList = <RouteOptions>{
export const createItem = <RouteOptions>{
method: 'POST',
url: '/:wishlistId/item',
config: {
protected: true,
},
schema: {
body: wishlistItemRequestSchema,
params: {

View file

@ -17,6 +17,9 @@ interface deleteItemRequest extends FastifyRequest {
export const deleteList = <RouteOptions>{
method: 'DELETE',
url: '/:wishlistId',
config: {
protected: true,
},
schema: {
params: {
type: 'object',
@ -34,6 +37,9 @@ export const deleteList = <RouteOptions>{
export const deleteItem = <RouteOptions>{
method: 'DELETE',
url: '/:wishlistId/item/:itemId',
config: {
protected: true,
},
schema: {
params: {
type: 'object',

View file

@ -24,6 +24,9 @@ interface updateItemRequest extends FastifyRequest {
export const updateList = <RouteOptions>{
method: 'PUT',
url: '/:wishlistId',
config: {
protected: true,
},
schema: {
body: wishlistRequestSchema,
params: {
@ -49,6 +52,9 @@ export const updateList = <RouteOptions>{
export const updateItem = <RouteOptions>{
method: 'PUT',
url: '/:wishlistId/item/:itemId',
config: {
protected: true,
},
schema: {
body: wishlistItemRequestSchema,
params: {