@accounter/server
Version:
Accounter GraphQL server
110 lines (100 loc) • 3.51 kB
text/typescript
import { useExtendContext, YogaInitialContext } from 'graphql-yoga';
import type { Plugin } from '@envelop/types';
import type { DBProvider } from '../modules/app-providers/db.provider.js';
import { handleDevBypassAuth } from '../modules/auth/providers/auth-context.provider.js';
export interface RawAuth {
authType: 'jwt' | 'apiKey' | 'devBypass' | null;
token: string | null;
}
type QueryablePool = {
query: (text: string, values?: unknown[]) => Promise<unknown>;
};
/**
* AuthPlugin - Extracts authentication credentials from request headers.
*
* This plugin is part of the Auth0 authentication system.
* It ONLY handles credential extraction. Verification and context creation
* are delegated to the AuthContextProvider.
*
* Responsibilities:
* 1. Extract `X-Dev-Auth: <user-id>` (Dev Bypass, gated by ALLOW_DEV_AUTH=1)
* 1. Extract `Authorization: Bearer <token>` (JWT)
* 2. Extract `X-API-Key: <key>` (API Key)
* 3. Add `rawAuth` object to Yoga context
*/
export const authPlugin = (): Plugin<{ rawAuth: RawAuth }> => {
const allowDevAuth = process.env.ALLOW_DEV_AUTH === '1';
if (allowDevAuth) {
// eslint-disable-next-line no-console
console.warn('⚠️ DEV AUTH BYPASS ENABLED');
}
return useExtendContext(
async (yogaContext: YogaInitialContext): Promise<{ rawAuth: RawAuth }> => {
const request = yogaContext.request;
const devAuthHeader = request.headers.get('x-dev-auth');
const authHeader = request.headers.get('authorization');
const apiKeyHeader = request.headers.get('x-api-key');
// Dev bypass takes precedence when explicitly enabled and user is resolvable.
if (allowDevAuth && devAuthHeader) {
const userId = devAuthHeader.trim();
if (userId.length > 0) {
const pool = (yogaContext as YogaInitialContext & { pool?: QueryablePool }).pool;
if (pool) {
const db = {
query: async (queryStatement: string, values?: unknown[]) => {
const result = await pool.query(queryStatement, values);
return {
...(result as object),
rowCount:
typeof (result as { rowCount?: number | null }).rowCount === 'number'
? (result as { rowCount: number }).rowCount
: 0,
};
},
} as Pick<DBProvider, 'query'>;
const context = await handleDevBypassAuth(db, userId);
if (context) {
return {
rawAuth: {
authType: 'devBypass',
token: userId,
},
};
}
}
}
}
// JWT takes precedence (e.g., user operations)
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7).trim();
if (token.length > 0) {
return {
rawAuth: {
authType: 'jwt',
token,
},
};
}
}
// Fallback to API key (e.g., automated tools)
if (apiKeyHeader) {
const token = apiKeyHeader.trim();
if (token.length > 0) {
return {
rawAuth: {
authType: 'apiKey',
token,
},
};
}
}
// Unauthenticated or malformed headers
return {
rawAuth: {
authType: null,
token: null,
},
};
},
);
};