mirror of
https://github.com/FJurmanovic/sugarr.git
synced 2026-11-11 15:51:09 +00:00
Add core server Controller base, auth hook, and buildServer
This commit is contained in:
5
src/core/server/Controller.ts
Normal file
5
src/core/server/Controller.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
export abstract class Controller {
|
||||||
|
abstract register(app: FastifyInstance): void;
|
||||||
|
}
|
||||||
19
src/core/server/authHook.ts
Normal file
19
src/core/server/authHook.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
export function registerAuthHook(
|
||||||
|
app: FastifyInstance,
|
||||||
|
secretKey: string,
|
||||||
|
exemptPaths: string[] = ['/health']
|
||||||
|
): void {
|
||||||
|
app.addHook('onRequest', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const path = request.url.split('?')[0];
|
||||||
|
if (!secretKey || exemptPaths.includes(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const header = request.headers.authorization ?? '';
|
||||||
|
const token = header.startsWith('Bearer ') ? header.slice('Bearer '.length) : undefined;
|
||||||
|
if (token !== secretKey) {
|
||||||
|
reply.code(401).send({ error: 'unauthorized' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
17
src/core/server/buildServer.ts
Normal file
17
src/core/server/buildServer.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Fastify, { type FastifyInstance } from 'fastify';
|
||||||
|
import type { Controller } from './Controller';
|
||||||
|
import { registerAuthHook } from './authHook';
|
||||||
|
|
||||||
|
export interface BuildServerOptions {
|
||||||
|
secretKey: string;
|
||||||
|
controllers: Controller[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildServer(options: BuildServerOptions): FastifyInstance {
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
registerAuthHook(app, options.secretKey);
|
||||||
|
for (const controller of options.controllers) {
|
||||||
|
controller.register(app);
|
||||||
|
}
|
||||||
|
return app;
|
||||||
|
}
|
||||||
41
test/core/server/authHook.test.ts
Normal file
41
test/core/server/authHook.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import Fastify from 'fastify';
|
||||||
|
import { registerAuthHook } from '../../../src/core/server/authHook';
|
||||||
|
|
||||||
|
function buildApp(secretKey: string) {
|
||||||
|
const app = Fastify();
|
||||||
|
registerAuthHook(app, secretKey);
|
||||||
|
app.get('/health', async () => ({ status: 'ok' }));
|
||||||
|
app.get('/recommendations', async () => ({ items: [] }));
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('registerAuthHook', () => {
|
||||||
|
it('allows all requests through when secretKey is empty', async () => {
|
||||||
|
const app = buildApp('');
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/recommendations' });
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('always exempts /health even when a secret is configured', async () => {
|
||||||
|
const app = buildApp('s3cret');
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/health' });
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects requests without a matching bearer token', async () => {
|
||||||
|
const app = buildApp('s3cret');
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/recommendations' });
|
||||||
|
expect(res.statusCode).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows requests with the correct bearer token', async () => {
|
||||||
|
const app = buildApp('s3cret');
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/recommendations',
|
||||||
|
headers: { authorization: 'Bearer s3cret' },
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user