Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { type JsonObject, type JsonValue, type ReadonlyDeep } from 'type-fest';
2import { v1 as uuidv1 } from 'uuid';
3
4import { type Dependencies } from '../iocContainer/index.js';
5import {
6 fromCorrelationId,
7 toCorrelationId,
8} from './correlationIds.js';
9import { ErrorType, CoopError } from './errors.js';
10import { type RequestHandlerWithBodies } from './route-helpers.js';
11
12/**
13 * Request type that includes orgId property set by API key middleware
14 */
15export interface RequestWithOrgId {
16 orgId: string;
17}
18
19/**
20 * Middleware that validates API key and sets orgId on the request.
21 * Returns 401 if API key is invalid or missing.
22 */
23export function createApiKeyMiddleware<
24 ReqBody extends JsonObject = JsonObject,
25 ResBody extends ReadonlyDeep<JsonValue> | undefined = ReadonlyDeep<JsonValue> | undefined
26>({
27 ApiKeyService,
28}: Pick<Dependencies, 'ApiKeyService'>): RequestHandlerWithBodies<ReqBody, ResBody> {
29 return async (req, _res, next) => {
30 const providedKey = req.header('x-api-key');
31 let orgId: string | null;
32
33 try {
34 orgId = providedKey && !Array.isArray(providedKey)
35 ? await ApiKeyService.validateApiKey(providedKey)
36 : null;
37 } catch (_error) {
38 // If API key validation throws an error, treat it as invalid
39 orgId = null;
40 }
41
42 if (!orgId) {
43 // Invalid API key is a client-side error, so return a 400.
44 const requestId = toCorrelationId({
45 type: 'api-key-validation',
46 id: uuidv1()
47 });
48
49 return next(new CoopError({
50 status: 401,
51 type: [ErrorType.Unauthorized],
52 title: 'Invalid API Key',
53 detail:
54 'Something went wrong finding or validating your API key. ' +
55 'Make sure the proper key is provided in the x-api-key header.',
56 requestId: fromCorrelationId(requestId),
57 name: 'UnauthorizedError',
58 shouldErrorSpan: true,
59 }));
60 }
61
62 // Store orgId on the request for use by route handlers
63 (req as unknown as RequestWithOrgId).orgId = orgId;
64 next();
65 };
66}
67
68/**
69 * Type guard to check if request has orgId set by the API key middleware
70 */
71export function hasOrgId(req: unknown): req is RequestWithOrgId {
72 return typeof req === 'object' && req !== null && 'orgId' in req && typeof (req as { orgId: unknown }).orgId === 'string';
73}