@accounter/server
Version:
Accounter GraphQL server
112 lines • 4.51 kB
JavaScript
import { useExtendContext } from 'graphql-yoga';
import { handleDevBypassAuth } from '../modules/auth/providers/auth-context.provider.js';
import { BUSINESS_SCOPE_HEADER, parseBusinessScopeHeader } from './business-scope-header.js';
/**
* 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 = () => {
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) => {
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');
const gatewayCpTokenHeader = request.headers.get('x-gateway-cp-token');
// Parse the requested read scope once. Attach it only when the header was
// actually sent, so requests without it keep the original rawAuth shape.
const parsedScope = parseBusinessScopeHeader(request.headers.get(BUSINESS_SCOPE_HEADER));
const scopeFields = parsedScope.kind === 'absent' ? {} : { requestedBusinessScope: parsedScope };
// 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.pool;
if (pool) {
const db = {
query: async (queryStatement, values) => {
const result = await pool.query(queryStatement, values);
return {
...result,
rowCount: typeof result.rowCount === 'number'
? result.rowCount
: 0,
};
},
};
const context = await handleDevBypassAuth(db, userId);
if (context) {
return {
rawAuth: {
authType: 'devBypass',
token: userId,
...scopeFields,
},
};
}
}
}
}
// Gateway control-plane service identity (no tenant context)
if (gatewayCpTokenHeader) {
const token = gatewayCpTokenHeader.trim();
if (token.length > 0) {
return {
rawAuth: {
authType: 'gatewayControlPlane',
token,
...scopeFields,
},
};
}
}
// 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,
...scopeFields,
},
};
}
}
// Fallback to API key (e.g., automated tools)
if (apiKeyHeader) {
const token = apiKeyHeader.trim();
if (token.length > 0) {
return {
rawAuth: {
authType: 'apiKey',
token,
...scopeFields,
},
};
}
}
// Unauthenticated or malformed headers
return {
rawAuth: {
authType: null,
token: null,
...scopeFields,
},
};
});
};
//# sourceMappingURL=auth-plugin.js.map