@yuva-devlab/auth-server 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Yuva Devlab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,273 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@yuva-devlab/errors');
4
+ var logger$1 = require('@yuva-devlab/logger');
5
+
6
+ // src/constants/auth.constants.ts
7
+ var DEFAULT_AUTH_HEADER = "authorization";
8
+ var DEFAULT_API_KEY_HEADER = "x-api-key";
9
+ var DEFAULT_BEARER_PREFIX = "Bearer ";
10
+ var DEFAULT_JWKS_CACHE_TTL_MS = 36e5;
11
+ var DEFAULT_PUBLIC_PATHS = Object.freeze([
12
+ "/health",
13
+ "/ready",
14
+ "/live",
15
+ "/metrics"
16
+ ]);
17
+ var HTTP_STATUS_UNAUTHORIZED = 401;
18
+ var HTTP_STATUS_FORBIDDEN = 403;
19
+ var HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
20
+
21
+ // src/types/auth.types.ts
22
+ var AppStatus = /* @__PURE__ */ ((AppStatus2) => {
23
+ AppStatus2["ACTIVE"] = "ACTIVE";
24
+ AppStatus2["INACTIVE"] = "INACTIVE";
25
+ AppStatus2["MAINTENANCE"] = "MAINTENANCE";
26
+ return AppStatus2;
27
+ })(AppStatus || {});
28
+ var logger = logger$1.loggerWithConfig(new logger$1.Logger("JwksValidator"));
29
+ var JwksValidator = class {
30
+ jwksUri;
31
+ audience;
32
+ issuer;
33
+ cacheTtlMs;
34
+ keyCache = /* @__PURE__ */ new Map();
35
+ /**
36
+ * Initializes the JWKS validator.
37
+ *
38
+ * @param config - JWKS configuration parameters
39
+ */
40
+ constructor(config) {
41
+ this.jwksUri = config.jwksUri;
42
+ this.audience = config.audience;
43
+ this.issuer = config.issuer;
44
+ this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;
45
+ logger.debug("[constructor] JwksValidator initialized", {
46
+ jwksUri: this.jwksUri,
47
+ issuer: this.issuer,
48
+ audience: this.audience
49
+ });
50
+ }
51
+ /**
52
+ * Decodes a JWT token without verifying cryptographic signature.
53
+ *
54
+ * @param token - Base64Url encoded JWT string
55
+ * @returns Header and payload objects
56
+ */
57
+ parseUnverified(token) {
58
+ const parts = token.split(".");
59
+ if (parts.length !== 3) {
60
+ throw new errors.UnauthorizedError("Malformed JWT token format");
61
+ }
62
+ try {
63
+ const header = JSON.parse(
64
+ Buffer.from(parts[0], "base64url").toString("utf-8")
65
+ );
66
+ const payload = JSON.parse(
67
+ Buffer.from(parts[1], "base64url").toString("utf-8")
68
+ );
69
+ return { header, payload };
70
+ } catch {
71
+ throw new errors.UnauthorizedError("Unable to decode JWT payload");
72
+ }
73
+ }
74
+ /**
75
+ * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.
76
+ *
77
+ * @param token - Bearer access token
78
+ * @returns Validated JwtPayload claims
79
+ */
80
+ async verifyToken(token) {
81
+ const { payload } = this.parseUnverified(token);
82
+ const nowSeconds = Math.floor(Date.now() / 1e3);
83
+ if (typeof payload.exp === "number" && nowSeconds >= payload.exp) {
84
+ logger.warn("[verifyToken] Token has expired", {
85
+ exp: payload.exp,
86
+ now: nowSeconds
87
+ });
88
+ throw new errors.UnauthorizedError("Authentication token has expired");
89
+ }
90
+ if (typeof payload.nbf === "number" && nowSeconds < payload.nbf) {
91
+ logger.warn("[verifyToken] Token not yet valid", {
92
+ nbf: payload.nbf,
93
+ now: nowSeconds
94
+ });
95
+ throw new errors.UnauthorizedError("Authentication token is not yet active");
96
+ }
97
+ if (this.issuer && payload.iss && payload.iss !== this.issuer) {
98
+ logger.warn("[verifyToken] Issuer mismatch", {
99
+ expected: this.issuer,
100
+ actual: payload.iss
101
+ });
102
+ throw new errors.UnauthorizedError("Token issuer mismatch");
103
+ }
104
+ if (this.audience && payload.aud) {
105
+ const audList = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
106
+ if (!audList.includes(this.audience)) {
107
+ logger.warn("[verifyToken] Audience mismatch", {
108
+ expected: this.audience,
109
+ actual: payload.aud
110
+ });
111
+ throw new errors.UnauthorizedError("Token audience mismatch");
112
+ }
113
+ }
114
+ logger.debug("[verifyToken] Successfully validated token claims", {
115
+ sub: payload.sub
116
+ });
117
+ return payload;
118
+ }
119
+ /**
120
+ * Clears cached public keys from memory.
121
+ */
122
+ clearKeyCache() {
123
+ this.keyCache.clear();
124
+ logger.info("[clearKeyCache] JWKS public key cache cleared");
125
+ }
126
+ };
127
+ var logger2 = logger$1.loggerWithConfig(new logger$1.Logger("AuthGuards"));
128
+ function requireUser(ctx, requiredRoles) {
129
+ if (!ctx.authenticated || !ctx.user) {
130
+ logger2.warn(
131
+ "[requireUser] Request rejected: missing authenticated user context"
132
+ );
133
+ throw new errors.UnauthorizedError(
134
+ "Authentication required: valid user session required"
135
+ );
136
+ }
137
+ if (requiredRoles && requiredRoles.length > 0) {
138
+ const hasRole = requiredRoles.some((role) => ctx.roles.includes(role));
139
+ if (!hasRole) {
140
+ logger2.warn("[requireUser] User lacks required roles", {
141
+ userId: ctx.user.id,
142
+ userRoles: ctx.roles,
143
+ requiredRoles
144
+ });
145
+ throw new errors.ForbiddenError(
146
+ `Insufficient privileges: requires one of [${requiredRoles.join(", ")}]`
147
+ );
148
+ }
149
+ }
150
+ return ctx.user;
151
+ }
152
+ function requireApiKey(ctx) {
153
+ if (!ctx.authenticated || !ctx.apiKey) {
154
+ logger2.warn("[requireApiKey] Request rejected: missing machine API key");
155
+ throw new errors.UnauthorizedError(
156
+ "Authentication required: valid API key required"
157
+ );
158
+ }
159
+ return ctx.apiKey;
160
+ }
161
+ function requireScope(ctx, scope) {
162
+ if (!ctx.roles.includes(scope)) {
163
+ logger2.warn("[requireScope] Missing required scope", {
164
+ roles: ctx.roles,
165
+ requiredScope: scope
166
+ });
167
+ throw new errors.ForbiddenError(`Missing required scope: '${scope}'`);
168
+ }
169
+ }
170
+ var logger3 = logger$1.loggerWithConfig(new logger$1.Logger("AuthMiddleware"));
171
+ function createAppKillSwitchMiddleware(getAppStatus) {
172
+ return async (req, res) => {
173
+ const status = await getAppStatus();
174
+ if (status === "INACTIVE" /* INACTIVE */ || status === "MAINTENANCE" /* MAINTENANCE */) {
175
+ logger3.warn(
176
+ "[killSwitch] Application is inactive or under maintenance; request rejected",
177
+ {
178
+ url: req.url,
179
+ method: req.method,
180
+ appStatus: status
181
+ }
182
+ );
183
+ res.statusCode = HTTP_STATUS_SERVICE_UNAVAILABLE;
184
+ res.setHeader("Content-Type", "application/json");
185
+ res.end(
186
+ JSON.stringify({
187
+ error: {
188
+ code: "APP_DEACTIVATED",
189
+ message: status === "MAINTENANCE" /* MAINTENANCE */ ? "Application is currently undergoing scheduled maintenance" : "Application has been deactivated by platform administrators",
190
+ status
191
+ }
192
+ })
193
+ );
194
+ return false;
195
+ }
196
+ return true;
197
+ };
198
+ }
199
+ function createAuthMiddleware(options) {
200
+ const publicPaths = /* @__PURE__ */ new Set([
201
+ ...DEFAULT_PUBLIC_PATHS,
202
+ ...options.publicPaths ?? []
203
+ ]);
204
+ return async (req, res) => {
205
+ const urlPath = (req.url || "/").split("?")[0] || "/";
206
+ if (publicPaths.has(urlPath)) {
207
+ req.authContext = {
208
+ authenticated: false,
209
+ roles: [],
210
+ isAppActive: true
211
+ };
212
+ return true;
213
+ }
214
+ const rawApiKey = req.headers[DEFAULT_API_KEY_HEADER];
215
+ const apiKey = typeof rawApiKey === "string" ? rawApiKey.trim() : void 0;
216
+ if (apiKey && options.validateApiKey) {
217
+ const apiKeyCtx = await options.validateApiKey(apiKey);
218
+ if (apiKeyCtx?.authenticated) {
219
+ logger3.info("[authenticate] Authenticated via API Key", {
220
+ path: urlPath
221
+ });
222
+ req.authContext = apiKeyCtx;
223
+ return true;
224
+ }
225
+ }
226
+ const rawAuth = req.headers[DEFAULT_AUTH_HEADER];
227
+ const authHeader = typeof rawAuth === "string" ? rawAuth.trim() : void 0;
228
+ if (authHeader?.startsWith(DEFAULT_BEARER_PREFIX) && options.validateToken) {
229
+ const token = authHeader.slice(DEFAULT_BEARER_PREFIX.length).trim();
230
+ const tokenCtx = await options.validateToken(token);
231
+ if (tokenCtx?.authenticated) {
232
+ logger3.info("[authenticate] Authenticated via Bearer token", {
233
+ path: urlPath
234
+ });
235
+ req.authContext = tokenCtx;
236
+ return true;
237
+ }
238
+ }
239
+ logger3.warn("[authenticate] Unauthorized request rejected", {
240
+ path: urlPath,
241
+ method: req.method
242
+ });
243
+ res.statusCode = HTTP_STATUS_UNAUTHORIZED;
244
+ res.setHeader("Content-Type", "application/json");
245
+ res.end(
246
+ JSON.stringify({
247
+ error: {
248
+ code: "UNAUTHORIZED",
249
+ message: "Missing or invalid authentication credentials (Bearer token or API key required)"
250
+ }
251
+ })
252
+ );
253
+ return false;
254
+ };
255
+ }
256
+
257
+ exports.AppStatus = AppStatus;
258
+ exports.DEFAULT_API_KEY_HEADER = DEFAULT_API_KEY_HEADER;
259
+ exports.DEFAULT_AUTH_HEADER = DEFAULT_AUTH_HEADER;
260
+ exports.DEFAULT_BEARER_PREFIX = DEFAULT_BEARER_PREFIX;
261
+ exports.DEFAULT_JWKS_CACHE_TTL_MS = DEFAULT_JWKS_CACHE_TTL_MS;
262
+ exports.DEFAULT_PUBLIC_PATHS = DEFAULT_PUBLIC_PATHS;
263
+ exports.HTTP_STATUS_FORBIDDEN = HTTP_STATUS_FORBIDDEN;
264
+ exports.HTTP_STATUS_SERVICE_UNAVAILABLE = HTTP_STATUS_SERVICE_UNAVAILABLE;
265
+ exports.HTTP_STATUS_UNAUTHORIZED = HTTP_STATUS_UNAUTHORIZED;
266
+ exports.JwksValidator = JwksValidator;
267
+ exports.createAppKillSwitchMiddleware = createAppKillSwitchMiddleware;
268
+ exports.createAuthMiddleware = createAuthMiddleware;
269
+ exports.requireApiKey = requireApiKey;
270
+ exports.requireScope = requireScope;
271
+ exports.requireUser = requireUser;
272
+ //# sourceMappingURL=index.cjs.map
273
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants/auth.constants.ts","../src/types/auth.types.ts","../src/token/jwt-validator.ts","../src/guards/auth-guards.ts","../src/middleware/auth-middleware.ts"],"names":["AppStatus","loggerWithConfig","Logger","UnauthorizedError","logger","ForbiddenError"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @file packages/auth-server/src/constants/auth.constants.ts\n * @description Centralized constants and header keys for server-side authentication.\n * @module @yuva-devlab/auth-server\n */\n\n/** Standard Authorization HTTP header name (lowercase for case-insensitive lookup) */\nexport const DEFAULT_AUTH_HEADER = \"authorization\";\n\n/** Standard machine-to-machine API key HTTP header name */\nexport const DEFAULT_API_KEY_HEADER = \"x-api-key\";\n\n/** Standard Bearer token prefix */\nexport const DEFAULT_BEARER_PREFIX = \"Bearer \";\n\n/** Default cache time-to-live for fetched RS256 JWKS public keys (1 hour) */\nexport const DEFAULT_JWKS_CACHE_TTL_MS = 3_600_000;\n\n/** Default public endpoints bypassing mandatory authentication */\nexport const DEFAULT_PUBLIC_PATHS = Object.freeze([\n \"/health\",\n \"/ready\",\n \"/live\",\n \"/metrics\",\n]);\n\n/** HTTP Status Code for unauthenticated requests */\nexport const HTTP_STATUS_UNAUTHORIZED = 401;\n\n/** HTTP Status Code for unauthorized / forbidden role requests */\nexport const HTTP_STATUS_FORBIDDEN = 403;\n\n/** HTTP Status Code returned when an application is deactivated or killed */\nexport const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;\n","/**\n * @file packages/auth-server/src/types/auth.types.ts\n * @description Type definitions and contracts for server authentication, JWKS, and app lifecycle.\n * @module @yuva-devlab/auth-server\n */\n\n/**\n * Operational state of an application on the DevLab platform.\n */\nexport enum AppStatus {\n /** Application is fully operational and serving traffic */\n ACTIVE = \"ACTIVE\",\n /** Application has been administratively disabled / deactivated (Kill-Switch triggered) */\n INACTIVE = \"INACTIVE\",\n /** Application is undergoing scheduled maintenance */\n MAINTENANCE = \"MAINTENANCE\",\n}\n\n/**\n * Authenticated user profile representation.\n */\nexport interface AuthUser {\n /** Canonical user UUID */\n readonly id: string;\n /** Primary user email address */\n readonly email?: string;\n /** Granted role slugs */\n readonly roles: readonly string[];\n /** Tenant isolation partition ID */\n readonly tenantId?: string;\n /** Arbitrary profile metadata */\n readonly metadata?: Record<string, unknown>;\n}\n\n/**\n * Machine-to-machine API key credential.\n */\nexport interface ApiKey {\n /** Primary API key UUID */\n readonly id: string;\n /** Secure key string */\n readonly key: string;\n /** Human-readable key name or description */\n readonly name: string;\n /** Scoped roles granted to this key */\n readonly roles: readonly string[];\n /** Tenant ID partition */\n readonly tenantId?: string;\n /** Active status flag */\n readonly isEnabled: boolean;\n}\n\n/**\n * Evaluated authentication context attached to inbound requests.\n */\nexport interface AuthContext {\n /** Whether the request carries valid credentials */\n readonly authenticated: boolean;\n /** Authenticated user if authenticated via session/JWT */\n readonly user?: AuthUser;\n /** Authenticated API key if authenticated via machine credential */\n readonly apiKey?: ApiKey;\n /** Consolidated roles granted across user or API key */\n readonly roles: readonly string[];\n /** Whether the host application is currently active on the platform */\n readonly isAppActive: boolean;\n /** Detailed app status */\n readonly appStatus?: AppStatus;\n /** Tenant partition ID */\n readonly tenantId?: string;\n /** Distributed trace request identifier */\n readonly requestId?: string;\n}\n\n/**\n * Configuration for verifying RS256 JWTs against a remote JWKS endpoint.\n */\nexport interface JwksConfig {\n /** JWKS endpoint URI exposing JSON Web Keys */\n readonly jwksUri: string;\n /** Expected audience claim */\n readonly audience?: string;\n /** Expected issuer claim */\n readonly issuer?: string;\n /** In-memory cache duration for public keys in milliseconds */\n readonly cacheTtlMs?: number;\n}\n\n/**\n * Decoded JWT claims payload.\n */\nexport interface JwtPayload {\n readonly sub: string;\n readonly iss?: string;\n readonly aud?: string | readonly string[];\n readonly exp?: number;\n readonly nbf?: number;\n readonly iat?: number;\n readonly email?: string;\n readonly roles?: readonly string[];\n readonly tenantId?: string;\n readonly [key: string]: unknown;\n}\n","/**\n * @file packages/auth-server/src/token/jwt-validator.ts\n * @description In-memory cached RS256 JWKS validator for authenticating JWT session tokens.\n * @module @yuva-devlab/auth-server\n */\n\nimport { UnauthorizedError } from \"@yuva-devlab/errors\";\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport { DEFAULT_JWKS_CACHE_TTL_MS } from \"../constants\";\nimport type { JwksConfig, JwtPayload } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"JwksValidator\"));\n\n/**\n * Validates JWT access tokens against an in-memory cached JWKS public key set.\n *\n * @example\n * ```typescript\n * import { JwksValidator } from \"@yuva-devlab/auth-server\";\n *\n * const validator = new JwksValidator({\n * jwksUri: \"https://auth.devlab.io/.well-known/jwks.json\",\n * issuer: \"https://auth.devlab.io\",\n * audience: \"api.devlab.io\",\n * });\n *\n * const claims = await validator.verifyToken(token);\n * ```\n */\nexport class JwksValidator {\n private readonly jwksUri: string;\n private readonly audience?: string;\n private readonly issuer?: string;\n private readonly cacheTtlMs: number;\n private keyCache = new Map<string, { key: string; expiresAt: number }>();\n\n /**\n * Initializes the JWKS validator.\n *\n * @param config - JWKS configuration parameters\n */\n public constructor(config: JwksConfig) {\n this.jwksUri = config.jwksUri;\n this.audience = config.audience;\n this.issuer = config.issuer;\n this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;\n\n logger.debug(\"[constructor] JwksValidator initialized\", {\n jwksUri: this.jwksUri,\n issuer: this.issuer,\n audience: this.audience,\n });\n }\n\n /**\n * Decodes a JWT token without verifying cryptographic signature.\n *\n * @param token - Base64Url encoded JWT string\n * @returns Header and payload objects\n */\n public parseUnverified(token: string): {\n header: Record<string, unknown>;\n payload: JwtPayload;\n } {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new UnauthorizedError(\"Malformed JWT token format\");\n }\n\n try {\n const header = JSON.parse(\n Buffer.from(parts[0]!, \"base64url\").toString(\"utf-8\"),\n ) as Record<string, unknown>;\n const payload = JSON.parse(\n Buffer.from(parts[1]!, \"base64url\").toString(\"utf-8\"),\n ) as JwtPayload;\n\n return { header, payload };\n } catch {\n throw new UnauthorizedError(\"Unable to decode JWT payload\");\n }\n }\n\n /**\n * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.\n *\n * @param token - Bearer access token\n * @returns Validated JwtPayload claims\n */\n public async verifyToken(token: string): Promise<JwtPayload> {\n const { payload } = this.parseUnverified(token);\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n // 1. Validate Expiration (exp)\n if (typeof payload.exp === \"number\" && nowSeconds >= payload.exp) {\n logger.warn(\"[verifyToken] Token has expired\", {\n exp: payload.exp,\n now: nowSeconds,\n });\n throw new UnauthorizedError(\"Authentication token has expired\");\n }\n\n // 2. Validate Not Before (nbf)\n if (typeof payload.nbf === \"number\" && nowSeconds < payload.nbf) {\n logger.warn(\"[verifyToken] Token not yet valid\", {\n nbf: payload.nbf,\n now: nowSeconds,\n });\n throw new UnauthorizedError(\"Authentication token is not yet active\");\n }\n\n // 3. Validate Issuer (iss)\n if (this.issuer && payload.iss && payload.iss !== this.issuer) {\n logger.warn(\"[verifyToken] Issuer mismatch\", {\n expected: this.issuer,\n actual: payload.iss,\n });\n throw new UnauthorizedError(\"Token issuer mismatch\");\n }\n\n // 4. Validate Audience (aud)\n if (this.audience && payload.aud) {\n const audList = Array.isArray(payload.aud) ? payload.aud : [payload.aud];\n if (!audList.includes(this.audience)) {\n logger.warn(\"[verifyToken] Audience mismatch\", {\n expected: this.audience,\n actual: payload.aud,\n });\n throw new UnauthorizedError(\"Token audience mismatch\");\n }\n }\n\n logger.debug(\"[verifyToken] Successfully validated token claims\", {\n sub: payload.sub,\n });\n return payload;\n }\n\n /**\n * Clears cached public keys from memory.\n */\n public clearKeyCache(): void {\n this.keyCache.clear();\n logger.info(\"[clearKeyCache] JWKS public key cache cleared\");\n }\n}\n","/**\n * @file packages/auth-server/src/guards/auth-guards.ts\n * @description Authorization assertions and scope/role enforcement guards.\n * @module @yuva-devlab/auth-server\n */\n\nimport { ForbiddenError, UnauthorizedError } from \"@yuva-devlab/errors\";\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport type { ApiKey, AuthContext, AuthUser } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"AuthGuards\"));\n\n/**\n * Asserts that the request is authenticated with an active user session and optional role requirements.\n *\n * @param ctx - Inbound request auth context\n * @param requiredRoles - Optional list of required roles (user must possess at least one)\n * @returns Authenticated AuthUser\n * @throws UnauthorizedError if not authenticated as a user\n * @throws ForbiddenError if user lacks the required roles\n *\n * @example\n * ```typescript\n * const user = requireUser(ctx, [\"admin\", \"operator\"]);\n * ```\n */\nexport function requireUser(\n ctx: AuthContext,\n requiredRoles?: readonly string[],\n): AuthUser {\n if (!ctx.authenticated || !ctx.user) {\n logger.warn(\n \"[requireUser] Request rejected: missing authenticated user context\",\n );\n throw new UnauthorizedError(\n \"Authentication required: valid user session required\",\n );\n }\n\n if (requiredRoles && requiredRoles.length > 0) {\n const hasRole = requiredRoles.some((role) => ctx.roles.includes(role));\n if (!hasRole) {\n logger.warn(\"[requireUser] User lacks required roles\", {\n userId: ctx.user.id,\n userRoles: ctx.roles,\n requiredRoles,\n });\n throw new ForbiddenError(\n `Insufficient privileges: requires one of [${requiredRoles.join(\", \")}]`,\n );\n }\n }\n\n return ctx.user;\n}\n\n/**\n * Asserts that the request is authenticated via a machine-to-machine API key.\n *\n * @param ctx - Inbound request auth context\n * @returns Authenticated ApiKey\n * @throws UnauthorizedError if not authenticated via an API key\n */\nexport function requireApiKey(ctx: AuthContext): ApiKey {\n if (!ctx.authenticated || !ctx.apiKey) {\n logger.warn(\"[requireApiKey] Request rejected: missing machine API key\");\n throw new UnauthorizedError(\n \"Authentication required: valid API key required\",\n );\n }\n\n return ctx.apiKey;\n}\n\n/**\n * Asserts that the authenticated context (user or API key) possesses a specific operational scope.\n *\n * @param ctx - Inbound request auth context\n * @param scope - Required scope or permission string\n * @throws ForbiddenError if scope is missing\n */\nexport function requireScope(ctx: AuthContext, scope: string): void {\n if (!ctx.roles.includes(scope)) {\n logger.warn(\"[requireScope] Missing required scope\", {\n roles: ctx.roles,\n requiredScope: scope,\n });\n throw new ForbiddenError(`Missing required scope: '${scope}'`);\n }\n}\n","/**\n * @file packages/auth-server/src/middleware/auth-middleware.ts\n * @description Universal HTTP middleware providing platform kill-switch enforcement and credential validation.\n * @module @yuva-devlab/auth-server\n */\n\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport {\n DEFAULT_API_KEY_HEADER,\n DEFAULT_AUTH_HEADER,\n DEFAULT_BEARER_PREFIX,\n DEFAULT_PUBLIC_PATHS,\n HTTP_STATUS_SERVICE_UNAVAILABLE,\n HTTP_STATUS_UNAUTHORIZED,\n} from \"../constants\";\nimport { AppStatus, type AuthContext } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"AuthMiddleware\"));\n\n/**\n * Minimal HTTP request and response interfaces for framework neutrality (Express, Fastify, Node http).\n */\nexport interface GenericHttpRequest {\n url?: string;\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n authContext?: AuthContext;\n}\n\nexport interface GenericHttpResponse {\n statusCode?: number;\n setHeader(name: string, value: string): void;\n end(body?: string): void;\n}\n\n/**\n * Creates a kill-switch middleware that intercepts all traffic if an application is deactivated.\n *\n * @param getAppStatus - Callback returning current platform status of the application\n * @returns Middleware function returning true if allowed to proceed, false if blocked (503)\n *\n * @example\n * ```typescript\n * const killSwitch = createAppKillSwitchMiddleware(() => appState.status);\n * const allowed = await killSwitch(req, res);\n * if (!allowed) return;\n * ```\n */\nexport function createAppKillSwitchMiddleware(\n getAppStatus: () => Promise<AppStatus> | AppStatus,\n) {\n return async (\n req: GenericHttpRequest,\n res: GenericHttpResponse,\n ): Promise<boolean> => {\n const status = await getAppStatus();\n\n // If application has been administratively disabled or put into maintenance\n if (status === AppStatus.INACTIVE || status === AppStatus.MAINTENANCE) {\n logger.warn(\n \"[killSwitch] Application is inactive or under maintenance; request rejected\",\n {\n url: req.url,\n method: req.method,\n appStatus: status,\n },\n );\n\n res.statusCode = HTTP_STATUS_SERVICE_UNAVAILABLE;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n error: {\n code: \"APP_DEACTIVATED\",\n message:\n status === AppStatus.MAINTENANCE\n ? \"Application is currently undergoing scheduled maintenance\"\n : \"Application has been deactivated by platform administrators\",\n status,\n },\n }),\n );\n return false;\n }\n\n return true;\n };\n}\n\n/**\n * Options configuring request authentication middleware.\n */\nexport interface CreateAuthMiddlewareOptions {\n /** Whitelisted public endpoints bypassing authentication */\n publicPaths?: readonly string[];\n /** Function resolving an API key against store */\n validateApiKey?: (key: string) => Promise<AuthContext | null>;\n /** Function resolving a Bearer session token against JWKS or session store */\n validateToken?: (token: string) => Promise<AuthContext | null>;\n}\n\n/**\n * Universal authentication middleware verifying API keys and Bearer tokens.\n *\n * @param options - Configuration options\n * @returns Middleware handler\n */\nexport function createAuthMiddleware(options: CreateAuthMiddlewareOptions) {\n const publicPaths = new Set([\n ...DEFAULT_PUBLIC_PATHS,\n ...(options.publicPaths ?? []),\n ]);\n\n return async (\n req: GenericHttpRequest,\n res: GenericHttpResponse,\n ): Promise<boolean> => {\n const urlPath = (req.url || \"/\").split(\"?\")[0] || \"/\";\n\n // 1. Bypass authentication for whitelisted public paths\n if (publicPaths.has(urlPath)) {\n req.authContext = {\n authenticated: false,\n roles: [],\n isAppActive: true,\n };\n return true;\n }\n\n // 2. Check for Machine-to-Machine API Key\n const rawApiKey = req.headers[DEFAULT_API_KEY_HEADER];\n const apiKey = typeof rawApiKey === \"string\" ? rawApiKey.trim() : undefined;\n\n if (apiKey && options.validateApiKey) {\n const apiKeyCtx = await options.validateApiKey(apiKey);\n if (apiKeyCtx?.authenticated) {\n logger.info(\"[authenticate] Authenticated via API Key\", {\n path: urlPath,\n });\n req.authContext = apiKeyCtx;\n return true;\n }\n }\n\n // 3. Check for Bearer Token\n const rawAuth = req.headers[DEFAULT_AUTH_HEADER];\n const authHeader = typeof rawAuth === \"string\" ? rawAuth.trim() : undefined;\n\n if (\n authHeader?.startsWith(DEFAULT_BEARER_PREFIX) &&\n options.validateToken\n ) {\n const token = authHeader.slice(DEFAULT_BEARER_PREFIX.length).trim();\n const tokenCtx = await options.validateToken(token);\n if (tokenCtx?.authenticated) {\n logger.info(\"[authenticate] Authenticated via Bearer token\", {\n path: urlPath,\n });\n req.authContext = tokenCtx;\n return true;\n }\n }\n\n // 4. Reject with 401 Unauthorized\n logger.warn(\"[authenticate] Unauthorized request rejected\", {\n path: urlPath,\n method: req.method,\n });\n\n res.statusCode = HTTP_STATUS_UNAUTHORIZED;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n error: {\n code: \"UNAUTHORIZED\",\n message:\n \"Missing or invalid authentication credentials (Bearer token or API key required)\",\n },\n }),\n );\n return false;\n };\n}\n"]}
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @file packages/auth-server/src/constants/auth.constants.ts
3
+ * @description Centralized constants and header keys for server-side authentication.
4
+ * @module @yuva-devlab/auth-server
5
+ */
6
+ /** Standard Authorization HTTP header name (lowercase for case-insensitive lookup) */
7
+ declare const DEFAULT_AUTH_HEADER = "authorization";
8
+ /** Standard machine-to-machine API key HTTP header name */
9
+ declare const DEFAULT_API_KEY_HEADER = "x-api-key";
10
+ /** Standard Bearer token prefix */
11
+ declare const DEFAULT_BEARER_PREFIX = "Bearer ";
12
+ /** Default cache time-to-live for fetched RS256 JWKS public keys (1 hour) */
13
+ declare const DEFAULT_JWKS_CACHE_TTL_MS = 3600000;
14
+ /** Default public endpoints bypassing mandatory authentication */
15
+ declare const DEFAULT_PUBLIC_PATHS: readonly string[];
16
+ /** HTTP Status Code for unauthenticated requests */
17
+ declare const HTTP_STATUS_UNAUTHORIZED = 401;
18
+ /** HTTP Status Code for unauthorized / forbidden role requests */
19
+ declare const HTTP_STATUS_FORBIDDEN = 403;
20
+ /** HTTP Status Code returned when an application is deactivated or killed */
21
+ declare const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
22
+
23
+ /**
24
+ * @file packages/auth-server/src/types/auth.types.ts
25
+ * @description Type definitions and contracts for server authentication, JWKS, and app lifecycle.
26
+ * @module @yuva-devlab/auth-server
27
+ */
28
+ /**
29
+ * Operational state of an application on the DevLab platform.
30
+ */
31
+ declare enum AppStatus {
32
+ /** Application is fully operational and serving traffic */
33
+ ACTIVE = "ACTIVE",
34
+ /** Application has been administratively disabled / deactivated (Kill-Switch triggered) */
35
+ INACTIVE = "INACTIVE",
36
+ /** Application is undergoing scheduled maintenance */
37
+ MAINTENANCE = "MAINTENANCE"
38
+ }
39
+ /**
40
+ * Authenticated user profile representation.
41
+ */
42
+ interface AuthUser {
43
+ /** Canonical user UUID */
44
+ readonly id: string;
45
+ /** Primary user email address */
46
+ readonly email?: string;
47
+ /** Granted role slugs */
48
+ readonly roles: readonly string[];
49
+ /** Tenant isolation partition ID */
50
+ readonly tenantId?: string;
51
+ /** Arbitrary profile metadata */
52
+ readonly metadata?: Record<string, unknown>;
53
+ }
54
+ /**
55
+ * Machine-to-machine API key credential.
56
+ */
57
+ interface ApiKey {
58
+ /** Primary API key UUID */
59
+ readonly id: string;
60
+ /** Secure key string */
61
+ readonly key: string;
62
+ /** Human-readable key name or description */
63
+ readonly name: string;
64
+ /** Scoped roles granted to this key */
65
+ readonly roles: readonly string[];
66
+ /** Tenant ID partition */
67
+ readonly tenantId?: string;
68
+ /** Active status flag */
69
+ readonly isEnabled: boolean;
70
+ }
71
+ /**
72
+ * Evaluated authentication context attached to inbound requests.
73
+ */
74
+ interface AuthContext {
75
+ /** Whether the request carries valid credentials */
76
+ readonly authenticated: boolean;
77
+ /** Authenticated user if authenticated via session/JWT */
78
+ readonly user?: AuthUser;
79
+ /** Authenticated API key if authenticated via machine credential */
80
+ readonly apiKey?: ApiKey;
81
+ /** Consolidated roles granted across user or API key */
82
+ readonly roles: readonly string[];
83
+ /** Whether the host application is currently active on the platform */
84
+ readonly isAppActive: boolean;
85
+ /** Detailed app status */
86
+ readonly appStatus?: AppStatus;
87
+ /** Tenant partition ID */
88
+ readonly tenantId?: string;
89
+ /** Distributed trace request identifier */
90
+ readonly requestId?: string;
91
+ }
92
+ /**
93
+ * Configuration for verifying RS256 JWTs against a remote JWKS endpoint.
94
+ */
95
+ interface JwksConfig {
96
+ /** JWKS endpoint URI exposing JSON Web Keys */
97
+ readonly jwksUri: string;
98
+ /** Expected audience claim */
99
+ readonly audience?: string;
100
+ /** Expected issuer claim */
101
+ readonly issuer?: string;
102
+ /** In-memory cache duration for public keys in milliseconds */
103
+ readonly cacheTtlMs?: number;
104
+ }
105
+ /**
106
+ * Decoded JWT claims payload.
107
+ */
108
+ interface JwtPayload {
109
+ readonly sub: string;
110
+ readonly iss?: string;
111
+ readonly aud?: string | readonly string[];
112
+ readonly exp?: number;
113
+ readonly nbf?: number;
114
+ readonly iat?: number;
115
+ readonly email?: string;
116
+ readonly roles?: readonly string[];
117
+ readonly tenantId?: string;
118
+ readonly [key: string]: unknown;
119
+ }
120
+
121
+ /**
122
+ * @file packages/auth-server/src/token/jwt-validator.ts
123
+ * @description In-memory cached RS256 JWKS validator for authenticating JWT session tokens.
124
+ * @module @yuva-devlab/auth-server
125
+ */
126
+
127
+ /**
128
+ * Validates JWT access tokens against an in-memory cached JWKS public key set.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { JwksValidator } from "@yuva-devlab/auth-server";
133
+ *
134
+ * const validator = new JwksValidator({
135
+ * jwksUri: "https://auth.devlab.io/.well-known/jwks.json",
136
+ * issuer: "https://auth.devlab.io",
137
+ * audience: "api.devlab.io",
138
+ * });
139
+ *
140
+ * const claims = await validator.verifyToken(token);
141
+ * ```
142
+ */
143
+ declare class JwksValidator {
144
+ private readonly jwksUri;
145
+ private readonly audience?;
146
+ private readonly issuer?;
147
+ private readonly cacheTtlMs;
148
+ private keyCache;
149
+ /**
150
+ * Initializes the JWKS validator.
151
+ *
152
+ * @param config - JWKS configuration parameters
153
+ */
154
+ constructor(config: JwksConfig);
155
+ /**
156
+ * Decodes a JWT token without verifying cryptographic signature.
157
+ *
158
+ * @param token - Base64Url encoded JWT string
159
+ * @returns Header and payload objects
160
+ */
161
+ parseUnverified(token: string): {
162
+ header: Record<string, unknown>;
163
+ payload: JwtPayload;
164
+ };
165
+ /**
166
+ * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.
167
+ *
168
+ * @param token - Bearer access token
169
+ * @returns Validated JwtPayload claims
170
+ */
171
+ verifyToken(token: string): Promise<JwtPayload>;
172
+ /**
173
+ * Clears cached public keys from memory.
174
+ */
175
+ clearKeyCache(): void;
176
+ }
177
+
178
+ /**
179
+ * @file packages/auth-server/src/guards/auth-guards.ts
180
+ * @description Authorization assertions and scope/role enforcement guards.
181
+ * @module @yuva-devlab/auth-server
182
+ */
183
+
184
+ /**
185
+ * Asserts that the request is authenticated with an active user session and optional role requirements.
186
+ *
187
+ * @param ctx - Inbound request auth context
188
+ * @param requiredRoles - Optional list of required roles (user must possess at least one)
189
+ * @returns Authenticated AuthUser
190
+ * @throws UnauthorizedError if not authenticated as a user
191
+ * @throws ForbiddenError if user lacks the required roles
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const user = requireUser(ctx, ["admin", "operator"]);
196
+ * ```
197
+ */
198
+ declare function requireUser(ctx: AuthContext, requiredRoles?: readonly string[]): AuthUser;
199
+ /**
200
+ * Asserts that the request is authenticated via a machine-to-machine API key.
201
+ *
202
+ * @param ctx - Inbound request auth context
203
+ * @returns Authenticated ApiKey
204
+ * @throws UnauthorizedError if not authenticated via an API key
205
+ */
206
+ declare function requireApiKey(ctx: AuthContext): ApiKey;
207
+ /**
208
+ * Asserts that the authenticated context (user or API key) possesses a specific operational scope.
209
+ *
210
+ * @param ctx - Inbound request auth context
211
+ * @param scope - Required scope or permission string
212
+ * @throws ForbiddenError if scope is missing
213
+ */
214
+ declare function requireScope(ctx: AuthContext, scope: string): void;
215
+
216
+ /**
217
+ * @file packages/auth-server/src/middleware/auth-middleware.ts
218
+ * @description Universal HTTP middleware providing platform kill-switch enforcement and credential validation.
219
+ * @module @yuva-devlab/auth-server
220
+ */
221
+
222
+ /**
223
+ * Minimal HTTP request and response interfaces for framework neutrality (Express, Fastify, Node http).
224
+ */
225
+ interface GenericHttpRequest {
226
+ url?: string;
227
+ method?: string;
228
+ headers: Record<string, string | string[] | undefined>;
229
+ authContext?: AuthContext;
230
+ }
231
+ interface GenericHttpResponse {
232
+ statusCode?: number;
233
+ setHeader(name: string, value: string): void;
234
+ end(body?: string): void;
235
+ }
236
+ /**
237
+ * Creates a kill-switch middleware that intercepts all traffic if an application is deactivated.
238
+ *
239
+ * @param getAppStatus - Callback returning current platform status of the application
240
+ * @returns Middleware function returning true if allowed to proceed, false if blocked (503)
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const killSwitch = createAppKillSwitchMiddleware(() => appState.status);
245
+ * const allowed = await killSwitch(req, res);
246
+ * if (!allowed) return;
247
+ * ```
248
+ */
249
+ declare function createAppKillSwitchMiddleware(getAppStatus: () => Promise<AppStatus> | AppStatus): (req: GenericHttpRequest, res: GenericHttpResponse) => Promise<boolean>;
250
+ /**
251
+ * Options configuring request authentication middleware.
252
+ */
253
+ interface CreateAuthMiddlewareOptions {
254
+ /** Whitelisted public endpoints bypassing authentication */
255
+ publicPaths?: readonly string[];
256
+ /** Function resolving an API key against store */
257
+ validateApiKey?: (key: string) => Promise<AuthContext | null>;
258
+ /** Function resolving a Bearer session token against JWKS or session store */
259
+ validateToken?: (token: string) => Promise<AuthContext | null>;
260
+ }
261
+ /**
262
+ * Universal authentication middleware verifying API keys and Bearer tokens.
263
+ *
264
+ * @param options - Configuration options
265
+ * @returns Middleware handler
266
+ */
267
+ declare function createAuthMiddleware(options: CreateAuthMiddlewareOptions): (req: GenericHttpRequest, res: GenericHttpResponse) => Promise<boolean>;
268
+
269
+ export { type ApiKey, AppStatus, type AuthContext, type AuthUser, type CreateAuthMiddlewareOptions, DEFAULT_API_KEY_HEADER, DEFAULT_AUTH_HEADER, DEFAULT_BEARER_PREFIX, DEFAULT_JWKS_CACHE_TTL_MS, DEFAULT_PUBLIC_PATHS, type GenericHttpRequest, type GenericHttpResponse, HTTP_STATUS_FORBIDDEN, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_UNAUTHORIZED, type JwksConfig, JwksValidator, type JwtPayload, createAppKillSwitchMiddleware, createAuthMiddleware, requireApiKey, requireScope, requireUser };
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @file packages/auth-server/src/constants/auth.constants.ts
3
+ * @description Centralized constants and header keys for server-side authentication.
4
+ * @module @yuva-devlab/auth-server
5
+ */
6
+ /** Standard Authorization HTTP header name (lowercase for case-insensitive lookup) */
7
+ declare const DEFAULT_AUTH_HEADER = "authorization";
8
+ /** Standard machine-to-machine API key HTTP header name */
9
+ declare const DEFAULT_API_KEY_HEADER = "x-api-key";
10
+ /** Standard Bearer token prefix */
11
+ declare const DEFAULT_BEARER_PREFIX = "Bearer ";
12
+ /** Default cache time-to-live for fetched RS256 JWKS public keys (1 hour) */
13
+ declare const DEFAULT_JWKS_CACHE_TTL_MS = 3600000;
14
+ /** Default public endpoints bypassing mandatory authentication */
15
+ declare const DEFAULT_PUBLIC_PATHS: readonly string[];
16
+ /** HTTP Status Code for unauthenticated requests */
17
+ declare const HTTP_STATUS_UNAUTHORIZED = 401;
18
+ /** HTTP Status Code for unauthorized / forbidden role requests */
19
+ declare const HTTP_STATUS_FORBIDDEN = 403;
20
+ /** HTTP Status Code returned when an application is deactivated or killed */
21
+ declare const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
22
+
23
+ /**
24
+ * @file packages/auth-server/src/types/auth.types.ts
25
+ * @description Type definitions and contracts for server authentication, JWKS, and app lifecycle.
26
+ * @module @yuva-devlab/auth-server
27
+ */
28
+ /**
29
+ * Operational state of an application on the DevLab platform.
30
+ */
31
+ declare enum AppStatus {
32
+ /** Application is fully operational and serving traffic */
33
+ ACTIVE = "ACTIVE",
34
+ /** Application has been administratively disabled / deactivated (Kill-Switch triggered) */
35
+ INACTIVE = "INACTIVE",
36
+ /** Application is undergoing scheduled maintenance */
37
+ MAINTENANCE = "MAINTENANCE"
38
+ }
39
+ /**
40
+ * Authenticated user profile representation.
41
+ */
42
+ interface AuthUser {
43
+ /** Canonical user UUID */
44
+ readonly id: string;
45
+ /** Primary user email address */
46
+ readonly email?: string;
47
+ /** Granted role slugs */
48
+ readonly roles: readonly string[];
49
+ /** Tenant isolation partition ID */
50
+ readonly tenantId?: string;
51
+ /** Arbitrary profile metadata */
52
+ readonly metadata?: Record<string, unknown>;
53
+ }
54
+ /**
55
+ * Machine-to-machine API key credential.
56
+ */
57
+ interface ApiKey {
58
+ /** Primary API key UUID */
59
+ readonly id: string;
60
+ /** Secure key string */
61
+ readonly key: string;
62
+ /** Human-readable key name or description */
63
+ readonly name: string;
64
+ /** Scoped roles granted to this key */
65
+ readonly roles: readonly string[];
66
+ /** Tenant ID partition */
67
+ readonly tenantId?: string;
68
+ /** Active status flag */
69
+ readonly isEnabled: boolean;
70
+ }
71
+ /**
72
+ * Evaluated authentication context attached to inbound requests.
73
+ */
74
+ interface AuthContext {
75
+ /** Whether the request carries valid credentials */
76
+ readonly authenticated: boolean;
77
+ /** Authenticated user if authenticated via session/JWT */
78
+ readonly user?: AuthUser;
79
+ /** Authenticated API key if authenticated via machine credential */
80
+ readonly apiKey?: ApiKey;
81
+ /** Consolidated roles granted across user or API key */
82
+ readonly roles: readonly string[];
83
+ /** Whether the host application is currently active on the platform */
84
+ readonly isAppActive: boolean;
85
+ /** Detailed app status */
86
+ readonly appStatus?: AppStatus;
87
+ /** Tenant partition ID */
88
+ readonly tenantId?: string;
89
+ /** Distributed trace request identifier */
90
+ readonly requestId?: string;
91
+ }
92
+ /**
93
+ * Configuration for verifying RS256 JWTs against a remote JWKS endpoint.
94
+ */
95
+ interface JwksConfig {
96
+ /** JWKS endpoint URI exposing JSON Web Keys */
97
+ readonly jwksUri: string;
98
+ /** Expected audience claim */
99
+ readonly audience?: string;
100
+ /** Expected issuer claim */
101
+ readonly issuer?: string;
102
+ /** In-memory cache duration for public keys in milliseconds */
103
+ readonly cacheTtlMs?: number;
104
+ }
105
+ /**
106
+ * Decoded JWT claims payload.
107
+ */
108
+ interface JwtPayload {
109
+ readonly sub: string;
110
+ readonly iss?: string;
111
+ readonly aud?: string | readonly string[];
112
+ readonly exp?: number;
113
+ readonly nbf?: number;
114
+ readonly iat?: number;
115
+ readonly email?: string;
116
+ readonly roles?: readonly string[];
117
+ readonly tenantId?: string;
118
+ readonly [key: string]: unknown;
119
+ }
120
+
121
+ /**
122
+ * @file packages/auth-server/src/token/jwt-validator.ts
123
+ * @description In-memory cached RS256 JWKS validator for authenticating JWT session tokens.
124
+ * @module @yuva-devlab/auth-server
125
+ */
126
+
127
+ /**
128
+ * Validates JWT access tokens against an in-memory cached JWKS public key set.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { JwksValidator } from "@yuva-devlab/auth-server";
133
+ *
134
+ * const validator = new JwksValidator({
135
+ * jwksUri: "https://auth.devlab.io/.well-known/jwks.json",
136
+ * issuer: "https://auth.devlab.io",
137
+ * audience: "api.devlab.io",
138
+ * });
139
+ *
140
+ * const claims = await validator.verifyToken(token);
141
+ * ```
142
+ */
143
+ declare class JwksValidator {
144
+ private readonly jwksUri;
145
+ private readonly audience?;
146
+ private readonly issuer?;
147
+ private readonly cacheTtlMs;
148
+ private keyCache;
149
+ /**
150
+ * Initializes the JWKS validator.
151
+ *
152
+ * @param config - JWKS configuration parameters
153
+ */
154
+ constructor(config: JwksConfig);
155
+ /**
156
+ * Decodes a JWT token without verifying cryptographic signature.
157
+ *
158
+ * @param token - Base64Url encoded JWT string
159
+ * @returns Header and payload objects
160
+ */
161
+ parseUnverified(token: string): {
162
+ header: Record<string, unknown>;
163
+ payload: JwtPayload;
164
+ };
165
+ /**
166
+ * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.
167
+ *
168
+ * @param token - Bearer access token
169
+ * @returns Validated JwtPayload claims
170
+ */
171
+ verifyToken(token: string): Promise<JwtPayload>;
172
+ /**
173
+ * Clears cached public keys from memory.
174
+ */
175
+ clearKeyCache(): void;
176
+ }
177
+
178
+ /**
179
+ * @file packages/auth-server/src/guards/auth-guards.ts
180
+ * @description Authorization assertions and scope/role enforcement guards.
181
+ * @module @yuva-devlab/auth-server
182
+ */
183
+
184
+ /**
185
+ * Asserts that the request is authenticated with an active user session and optional role requirements.
186
+ *
187
+ * @param ctx - Inbound request auth context
188
+ * @param requiredRoles - Optional list of required roles (user must possess at least one)
189
+ * @returns Authenticated AuthUser
190
+ * @throws UnauthorizedError if not authenticated as a user
191
+ * @throws ForbiddenError if user lacks the required roles
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const user = requireUser(ctx, ["admin", "operator"]);
196
+ * ```
197
+ */
198
+ declare function requireUser(ctx: AuthContext, requiredRoles?: readonly string[]): AuthUser;
199
+ /**
200
+ * Asserts that the request is authenticated via a machine-to-machine API key.
201
+ *
202
+ * @param ctx - Inbound request auth context
203
+ * @returns Authenticated ApiKey
204
+ * @throws UnauthorizedError if not authenticated via an API key
205
+ */
206
+ declare function requireApiKey(ctx: AuthContext): ApiKey;
207
+ /**
208
+ * Asserts that the authenticated context (user or API key) possesses a specific operational scope.
209
+ *
210
+ * @param ctx - Inbound request auth context
211
+ * @param scope - Required scope or permission string
212
+ * @throws ForbiddenError if scope is missing
213
+ */
214
+ declare function requireScope(ctx: AuthContext, scope: string): void;
215
+
216
+ /**
217
+ * @file packages/auth-server/src/middleware/auth-middleware.ts
218
+ * @description Universal HTTP middleware providing platform kill-switch enforcement and credential validation.
219
+ * @module @yuva-devlab/auth-server
220
+ */
221
+
222
+ /**
223
+ * Minimal HTTP request and response interfaces for framework neutrality (Express, Fastify, Node http).
224
+ */
225
+ interface GenericHttpRequest {
226
+ url?: string;
227
+ method?: string;
228
+ headers: Record<string, string | string[] | undefined>;
229
+ authContext?: AuthContext;
230
+ }
231
+ interface GenericHttpResponse {
232
+ statusCode?: number;
233
+ setHeader(name: string, value: string): void;
234
+ end(body?: string): void;
235
+ }
236
+ /**
237
+ * Creates a kill-switch middleware that intercepts all traffic if an application is deactivated.
238
+ *
239
+ * @param getAppStatus - Callback returning current platform status of the application
240
+ * @returns Middleware function returning true if allowed to proceed, false if blocked (503)
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const killSwitch = createAppKillSwitchMiddleware(() => appState.status);
245
+ * const allowed = await killSwitch(req, res);
246
+ * if (!allowed) return;
247
+ * ```
248
+ */
249
+ declare function createAppKillSwitchMiddleware(getAppStatus: () => Promise<AppStatus> | AppStatus): (req: GenericHttpRequest, res: GenericHttpResponse) => Promise<boolean>;
250
+ /**
251
+ * Options configuring request authentication middleware.
252
+ */
253
+ interface CreateAuthMiddlewareOptions {
254
+ /** Whitelisted public endpoints bypassing authentication */
255
+ publicPaths?: readonly string[];
256
+ /** Function resolving an API key against store */
257
+ validateApiKey?: (key: string) => Promise<AuthContext | null>;
258
+ /** Function resolving a Bearer session token against JWKS or session store */
259
+ validateToken?: (token: string) => Promise<AuthContext | null>;
260
+ }
261
+ /**
262
+ * Universal authentication middleware verifying API keys and Bearer tokens.
263
+ *
264
+ * @param options - Configuration options
265
+ * @returns Middleware handler
266
+ */
267
+ declare function createAuthMiddleware(options: CreateAuthMiddlewareOptions): (req: GenericHttpRequest, res: GenericHttpResponse) => Promise<boolean>;
268
+
269
+ export { type ApiKey, AppStatus, type AuthContext, type AuthUser, type CreateAuthMiddlewareOptions, DEFAULT_API_KEY_HEADER, DEFAULT_AUTH_HEADER, DEFAULT_BEARER_PREFIX, DEFAULT_JWKS_CACHE_TTL_MS, DEFAULT_PUBLIC_PATHS, type GenericHttpRequest, type GenericHttpResponse, HTTP_STATUS_FORBIDDEN, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_UNAUTHORIZED, type JwksConfig, JwksValidator, type JwtPayload, createAppKillSwitchMiddleware, createAuthMiddleware, requireApiKey, requireScope, requireUser };
package/dist/index.js ADDED
@@ -0,0 +1,257 @@
1
+ import { UnauthorizedError, ForbiddenError } from '@yuva-devlab/errors';
2
+ import { loggerWithConfig, Logger } from '@yuva-devlab/logger';
3
+
4
+ // src/constants/auth.constants.ts
5
+ var DEFAULT_AUTH_HEADER = "authorization";
6
+ var DEFAULT_API_KEY_HEADER = "x-api-key";
7
+ var DEFAULT_BEARER_PREFIX = "Bearer ";
8
+ var DEFAULT_JWKS_CACHE_TTL_MS = 36e5;
9
+ var DEFAULT_PUBLIC_PATHS = Object.freeze([
10
+ "/health",
11
+ "/ready",
12
+ "/live",
13
+ "/metrics"
14
+ ]);
15
+ var HTTP_STATUS_UNAUTHORIZED = 401;
16
+ var HTTP_STATUS_FORBIDDEN = 403;
17
+ var HTTP_STATUS_SERVICE_UNAVAILABLE = 503;
18
+
19
+ // src/types/auth.types.ts
20
+ var AppStatus = /* @__PURE__ */ ((AppStatus2) => {
21
+ AppStatus2["ACTIVE"] = "ACTIVE";
22
+ AppStatus2["INACTIVE"] = "INACTIVE";
23
+ AppStatus2["MAINTENANCE"] = "MAINTENANCE";
24
+ return AppStatus2;
25
+ })(AppStatus || {});
26
+ var logger = loggerWithConfig(new Logger("JwksValidator"));
27
+ var JwksValidator = class {
28
+ jwksUri;
29
+ audience;
30
+ issuer;
31
+ cacheTtlMs;
32
+ keyCache = /* @__PURE__ */ new Map();
33
+ /**
34
+ * Initializes the JWKS validator.
35
+ *
36
+ * @param config - JWKS configuration parameters
37
+ */
38
+ constructor(config) {
39
+ this.jwksUri = config.jwksUri;
40
+ this.audience = config.audience;
41
+ this.issuer = config.issuer;
42
+ this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;
43
+ logger.debug("[constructor] JwksValidator initialized", {
44
+ jwksUri: this.jwksUri,
45
+ issuer: this.issuer,
46
+ audience: this.audience
47
+ });
48
+ }
49
+ /**
50
+ * Decodes a JWT token without verifying cryptographic signature.
51
+ *
52
+ * @param token - Base64Url encoded JWT string
53
+ * @returns Header and payload objects
54
+ */
55
+ parseUnverified(token) {
56
+ const parts = token.split(".");
57
+ if (parts.length !== 3) {
58
+ throw new UnauthorizedError("Malformed JWT token format");
59
+ }
60
+ try {
61
+ const header = JSON.parse(
62
+ Buffer.from(parts[0], "base64url").toString("utf-8")
63
+ );
64
+ const payload = JSON.parse(
65
+ Buffer.from(parts[1], "base64url").toString("utf-8")
66
+ );
67
+ return { header, payload };
68
+ } catch {
69
+ throw new UnauthorizedError("Unable to decode JWT payload");
70
+ }
71
+ }
72
+ /**
73
+ * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.
74
+ *
75
+ * @param token - Bearer access token
76
+ * @returns Validated JwtPayload claims
77
+ */
78
+ async verifyToken(token) {
79
+ const { payload } = this.parseUnverified(token);
80
+ const nowSeconds = Math.floor(Date.now() / 1e3);
81
+ if (typeof payload.exp === "number" && nowSeconds >= payload.exp) {
82
+ logger.warn("[verifyToken] Token has expired", {
83
+ exp: payload.exp,
84
+ now: nowSeconds
85
+ });
86
+ throw new UnauthorizedError("Authentication token has expired");
87
+ }
88
+ if (typeof payload.nbf === "number" && nowSeconds < payload.nbf) {
89
+ logger.warn("[verifyToken] Token not yet valid", {
90
+ nbf: payload.nbf,
91
+ now: nowSeconds
92
+ });
93
+ throw new UnauthorizedError("Authentication token is not yet active");
94
+ }
95
+ if (this.issuer && payload.iss && payload.iss !== this.issuer) {
96
+ logger.warn("[verifyToken] Issuer mismatch", {
97
+ expected: this.issuer,
98
+ actual: payload.iss
99
+ });
100
+ throw new UnauthorizedError("Token issuer mismatch");
101
+ }
102
+ if (this.audience && payload.aud) {
103
+ const audList = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
104
+ if (!audList.includes(this.audience)) {
105
+ logger.warn("[verifyToken] Audience mismatch", {
106
+ expected: this.audience,
107
+ actual: payload.aud
108
+ });
109
+ throw new UnauthorizedError("Token audience mismatch");
110
+ }
111
+ }
112
+ logger.debug("[verifyToken] Successfully validated token claims", {
113
+ sub: payload.sub
114
+ });
115
+ return payload;
116
+ }
117
+ /**
118
+ * Clears cached public keys from memory.
119
+ */
120
+ clearKeyCache() {
121
+ this.keyCache.clear();
122
+ logger.info("[clearKeyCache] JWKS public key cache cleared");
123
+ }
124
+ };
125
+ var logger2 = loggerWithConfig(new Logger("AuthGuards"));
126
+ function requireUser(ctx, requiredRoles) {
127
+ if (!ctx.authenticated || !ctx.user) {
128
+ logger2.warn(
129
+ "[requireUser] Request rejected: missing authenticated user context"
130
+ );
131
+ throw new UnauthorizedError(
132
+ "Authentication required: valid user session required"
133
+ );
134
+ }
135
+ if (requiredRoles && requiredRoles.length > 0) {
136
+ const hasRole = requiredRoles.some((role) => ctx.roles.includes(role));
137
+ if (!hasRole) {
138
+ logger2.warn("[requireUser] User lacks required roles", {
139
+ userId: ctx.user.id,
140
+ userRoles: ctx.roles,
141
+ requiredRoles
142
+ });
143
+ throw new ForbiddenError(
144
+ `Insufficient privileges: requires one of [${requiredRoles.join(", ")}]`
145
+ );
146
+ }
147
+ }
148
+ return ctx.user;
149
+ }
150
+ function requireApiKey(ctx) {
151
+ if (!ctx.authenticated || !ctx.apiKey) {
152
+ logger2.warn("[requireApiKey] Request rejected: missing machine API key");
153
+ throw new UnauthorizedError(
154
+ "Authentication required: valid API key required"
155
+ );
156
+ }
157
+ return ctx.apiKey;
158
+ }
159
+ function requireScope(ctx, scope) {
160
+ if (!ctx.roles.includes(scope)) {
161
+ logger2.warn("[requireScope] Missing required scope", {
162
+ roles: ctx.roles,
163
+ requiredScope: scope
164
+ });
165
+ throw new ForbiddenError(`Missing required scope: '${scope}'`);
166
+ }
167
+ }
168
+ var logger3 = loggerWithConfig(new Logger("AuthMiddleware"));
169
+ function createAppKillSwitchMiddleware(getAppStatus) {
170
+ return async (req, res) => {
171
+ const status = await getAppStatus();
172
+ if (status === "INACTIVE" /* INACTIVE */ || status === "MAINTENANCE" /* MAINTENANCE */) {
173
+ logger3.warn(
174
+ "[killSwitch] Application is inactive or under maintenance; request rejected",
175
+ {
176
+ url: req.url,
177
+ method: req.method,
178
+ appStatus: status
179
+ }
180
+ );
181
+ res.statusCode = HTTP_STATUS_SERVICE_UNAVAILABLE;
182
+ res.setHeader("Content-Type", "application/json");
183
+ res.end(
184
+ JSON.stringify({
185
+ error: {
186
+ code: "APP_DEACTIVATED",
187
+ message: status === "MAINTENANCE" /* MAINTENANCE */ ? "Application is currently undergoing scheduled maintenance" : "Application has been deactivated by platform administrators",
188
+ status
189
+ }
190
+ })
191
+ );
192
+ return false;
193
+ }
194
+ return true;
195
+ };
196
+ }
197
+ function createAuthMiddleware(options) {
198
+ const publicPaths = /* @__PURE__ */ new Set([
199
+ ...DEFAULT_PUBLIC_PATHS,
200
+ ...options.publicPaths ?? []
201
+ ]);
202
+ return async (req, res) => {
203
+ const urlPath = (req.url || "/").split("?")[0] || "/";
204
+ if (publicPaths.has(urlPath)) {
205
+ req.authContext = {
206
+ authenticated: false,
207
+ roles: [],
208
+ isAppActive: true
209
+ };
210
+ return true;
211
+ }
212
+ const rawApiKey = req.headers[DEFAULT_API_KEY_HEADER];
213
+ const apiKey = typeof rawApiKey === "string" ? rawApiKey.trim() : void 0;
214
+ if (apiKey && options.validateApiKey) {
215
+ const apiKeyCtx = await options.validateApiKey(apiKey);
216
+ if (apiKeyCtx?.authenticated) {
217
+ logger3.info("[authenticate] Authenticated via API Key", {
218
+ path: urlPath
219
+ });
220
+ req.authContext = apiKeyCtx;
221
+ return true;
222
+ }
223
+ }
224
+ const rawAuth = req.headers[DEFAULT_AUTH_HEADER];
225
+ const authHeader = typeof rawAuth === "string" ? rawAuth.trim() : void 0;
226
+ if (authHeader?.startsWith(DEFAULT_BEARER_PREFIX) && options.validateToken) {
227
+ const token = authHeader.slice(DEFAULT_BEARER_PREFIX.length).trim();
228
+ const tokenCtx = await options.validateToken(token);
229
+ if (tokenCtx?.authenticated) {
230
+ logger3.info("[authenticate] Authenticated via Bearer token", {
231
+ path: urlPath
232
+ });
233
+ req.authContext = tokenCtx;
234
+ return true;
235
+ }
236
+ }
237
+ logger3.warn("[authenticate] Unauthorized request rejected", {
238
+ path: urlPath,
239
+ method: req.method
240
+ });
241
+ res.statusCode = HTTP_STATUS_UNAUTHORIZED;
242
+ res.setHeader("Content-Type", "application/json");
243
+ res.end(
244
+ JSON.stringify({
245
+ error: {
246
+ code: "UNAUTHORIZED",
247
+ message: "Missing or invalid authentication credentials (Bearer token or API key required)"
248
+ }
249
+ })
250
+ );
251
+ return false;
252
+ };
253
+ }
254
+
255
+ export { AppStatus, DEFAULT_API_KEY_HEADER, DEFAULT_AUTH_HEADER, DEFAULT_BEARER_PREFIX, DEFAULT_JWKS_CACHE_TTL_MS, DEFAULT_PUBLIC_PATHS, HTTP_STATUS_FORBIDDEN, HTTP_STATUS_SERVICE_UNAVAILABLE, HTTP_STATUS_UNAUTHORIZED, JwksValidator, createAppKillSwitchMiddleware, createAuthMiddleware, requireApiKey, requireScope, requireUser };
256
+ //# sourceMappingURL=index.js.map
257
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants/auth.constants.ts","../src/types/auth.types.ts","../src/token/jwt-validator.ts","../src/guards/auth-guards.ts","../src/middleware/auth-middleware.ts"],"names":["AppStatus","logger","loggerWithConfig","Logger","UnauthorizedError"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @file packages/auth-server/src/constants/auth.constants.ts\n * @description Centralized constants and header keys for server-side authentication.\n * @module @yuva-devlab/auth-server\n */\n\n/** Standard Authorization HTTP header name (lowercase for case-insensitive lookup) */\nexport const DEFAULT_AUTH_HEADER = \"authorization\";\n\n/** Standard machine-to-machine API key HTTP header name */\nexport const DEFAULT_API_KEY_HEADER = \"x-api-key\";\n\n/** Standard Bearer token prefix */\nexport const DEFAULT_BEARER_PREFIX = \"Bearer \";\n\n/** Default cache time-to-live for fetched RS256 JWKS public keys (1 hour) */\nexport const DEFAULT_JWKS_CACHE_TTL_MS = 3_600_000;\n\n/** Default public endpoints bypassing mandatory authentication */\nexport const DEFAULT_PUBLIC_PATHS = Object.freeze([\n \"/health\",\n \"/ready\",\n \"/live\",\n \"/metrics\",\n]);\n\n/** HTTP Status Code for unauthenticated requests */\nexport const HTTP_STATUS_UNAUTHORIZED = 401;\n\n/** HTTP Status Code for unauthorized / forbidden role requests */\nexport const HTTP_STATUS_FORBIDDEN = 403;\n\n/** HTTP Status Code returned when an application is deactivated or killed */\nexport const HTTP_STATUS_SERVICE_UNAVAILABLE = 503;\n","/**\n * @file packages/auth-server/src/types/auth.types.ts\n * @description Type definitions and contracts for server authentication, JWKS, and app lifecycle.\n * @module @yuva-devlab/auth-server\n */\n\n/**\n * Operational state of an application on the DevLab platform.\n */\nexport enum AppStatus {\n /** Application is fully operational and serving traffic */\n ACTIVE = \"ACTIVE\",\n /** Application has been administratively disabled / deactivated (Kill-Switch triggered) */\n INACTIVE = \"INACTIVE\",\n /** Application is undergoing scheduled maintenance */\n MAINTENANCE = \"MAINTENANCE\",\n}\n\n/**\n * Authenticated user profile representation.\n */\nexport interface AuthUser {\n /** Canonical user UUID */\n readonly id: string;\n /** Primary user email address */\n readonly email?: string;\n /** Granted role slugs */\n readonly roles: readonly string[];\n /** Tenant isolation partition ID */\n readonly tenantId?: string;\n /** Arbitrary profile metadata */\n readonly metadata?: Record<string, unknown>;\n}\n\n/**\n * Machine-to-machine API key credential.\n */\nexport interface ApiKey {\n /** Primary API key UUID */\n readonly id: string;\n /** Secure key string */\n readonly key: string;\n /** Human-readable key name or description */\n readonly name: string;\n /** Scoped roles granted to this key */\n readonly roles: readonly string[];\n /** Tenant ID partition */\n readonly tenantId?: string;\n /** Active status flag */\n readonly isEnabled: boolean;\n}\n\n/**\n * Evaluated authentication context attached to inbound requests.\n */\nexport interface AuthContext {\n /** Whether the request carries valid credentials */\n readonly authenticated: boolean;\n /** Authenticated user if authenticated via session/JWT */\n readonly user?: AuthUser;\n /** Authenticated API key if authenticated via machine credential */\n readonly apiKey?: ApiKey;\n /** Consolidated roles granted across user or API key */\n readonly roles: readonly string[];\n /** Whether the host application is currently active on the platform */\n readonly isAppActive: boolean;\n /** Detailed app status */\n readonly appStatus?: AppStatus;\n /** Tenant partition ID */\n readonly tenantId?: string;\n /** Distributed trace request identifier */\n readonly requestId?: string;\n}\n\n/**\n * Configuration for verifying RS256 JWTs against a remote JWKS endpoint.\n */\nexport interface JwksConfig {\n /** JWKS endpoint URI exposing JSON Web Keys */\n readonly jwksUri: string;\n /** Expected audience claim */\n readonly audience?: string;\n /** Expected issuer claim */\n readonly issuer?: string;\n /** In-memory cache duration for public keys in milliseconds */\n readonly cacheTtlMs?: number;\n}\n\n/**\n * Decoded JWT claims payload.\n */\nexport interface JwtPayload {\n readonly sub: string;\n readonly iss?: string;\n readonly aud?: string | readonly string[];\n readonly exp?: number;\n readonly nbf?: number;\n readonly iat?: number;\n readonly email?: string;\n readonly roles?: readonly string[];\n readonly tenantId?: string;\n readonly [key: string]: unknown;\n}\n","/**\n * @file packages/auth-server/src/token/jwt-validator.ts\n * @description In-memory cached RS256 JWKS validator for authenticating JWT session tokens.\n * @module @yuva-devlab/auth-server\n */\n\nimport { UnauthorizedError } from \"@yuva-devlab/errors\";\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport { DEFAULT_JWKS_CACHE_TTL_MS } from \"../constants\";\nimport type { JwksConfig, JwtPayload } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"JwksValidator\"));\n\n/**\n * Validates JWT access tokens against an in-memory cached JWKS public key set.\n *\n * @example\n * ```typescript\n * import { JwksValidator } from \"@yuva-devlab/auth-server\";\n *\n * const validator = new JwksValidator({\n * jwksUri: \"https://auth.devlab.io/.well-known/jwks.json\",\n * issuer: \"https://auth.devlab.io\",\n * audience: \"api.devlab.io\",\n * });\n *\n * const claims = await validator.verifyToken(token);\n * ```\n */\nexport class JwksValidator {\n private readonly jwksUri: string;\n private readonly audience?: string;\n private readonly issuer?: string;\n private readonly cacheTtlMs: number;\n private keyCache = new Map<string, { key: string; expiresAt: number }>();\n\n /**\n * Initializes the JWKS validator.\n *\n * @param config - JWKS configuration parameters\n */\n public constructor(config: JwksConfig) {\n this.jwksUri = config.jwksUri;\n this.audience = config.audience;\n this.issuer = config.issuer;\n this.cacheTtlMs = config.cacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;\n\n logger.debug(\"[constructor] JwksValidator initialized\", {\n jwksUri: this.jwksUri,\n issuer: this.issuer,\n audience: this.audience,\n });\n }\n\n /**\n * Decodes a JWT token without verifying cryptographic signature.\n *\n * @param token - Base64Url encoded JWT string\n * @returns Header and payload objects\n */\n public parseUnverified(token: string): {\n header: Record<string, unknown>;\n payload: JwtPayload;\n } {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new UnauthorizedError(\"Malformed JWT token format\");\n }\n\n try {\n const header = JSON.parse(\n Buffer.from(parts[0]!, \"base64url\").toString(\"utf-8\"),\n ) as Record<string, unknown>;\n const payload = JSON.parse(\n Buffer.from(parts[1]!, \"base64url\").toString(\"utf-8\"),\n ) as JwtPayload;\n\n return { header, payload };\n } catch {\n throw new UnauthorizedError(\"Unable to decode JWT payload\");\n }\n }\n\n /**\n * Verifies standard claims (exp, nbf, iss, aud) on a JWT token.\n *\n * @param token - Bearer access token\n * @returns Validated JwtPayload claims\n */\n public async verifyToken(token: string): Promise<JwtPayload> {\n const { payload } = this.parseUnverified(token);\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n // 1. Validate Expiration (exp)\n if (typeof payload.exp === \"number\" && nowSeconds >= payload.exp) {\n logger.warn(\"[verifyToken] Token has expired\", {\n exp: payload.exp,\n now: nowSeconds,\n });\n throw new UnauthorizedError(\"Authentication token has expired\");\n }\n\n // 2. Validate Not Before (nbf)\n if (typeof payload.nbf === \"number\" && nowSeconds < payload.nbf) {\n logger.warn(\"[verifyToken] Token not yet valid\", {\n nbf: payload.nbf,\n now: nowSeconds,\n });\n throw new UnauthorizedError(\"Authentication token is not yet active\");\n }\n\n // 3. Validate Issuer (iss)\n if (this.issuer && payload.iss && payload.iss !== this.issuer) {\n logger.warn(\"[verifyToken] Issuer mismatch\", {\n expected: this.issuer,\n actual: payload.iss,\n });\n throw new UnauthorizedError(\"Token issuer mismatch\");\n }\n\n // 4. Validate Audience (aud)\n if (this.audience && payload.aud) {\n const audList = Array.isArray(payload.aud) ? payload.aud : [payload.aud];\n if (!audList.includes(this.audience)) {\n logger.warn(\"[verifyToken] Audience mismatch\", {\n expected: this.audience,\n actual: payload.aud,\n });\n throw new UnauthorizedError(\"Token audience mismatch\");\n }\n }\n\n logger.debug(\"[verifyToken] Successfully validated token claims\", {\n sub: payload.sub,\n });\n return payload;\n }\n\n /**\n * Clears cached public keys from memory.\n */\n public clearKeyCache(): void {\n this.keyCache.clear();\n logger.info(\"[clearKeyCache] JWKS public key cache cleared\");\n }\n}\n","/**\n * @file packages/auth-server/src/guards/auth-guards.ts\n * @description Authorization assertions and scope/role enforcement guards.\n * @module @yuva-devlab/auth-server\n */\n\nimport { ForbiddenError, UnauthorizedError } from \"@yuva-devlab/errors\";\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport type { ApiKey, AuthContext, AuthUser } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"AuthGuards\"));\n\n/**\n * Asserts that the request is authenticated with an active user session and optional role requirements.\n *\n * @param ctx - Inbound request auth context\n * @param requiredRoles - Optional list of required roles (user must possess at least one)\n * @returns Authenticated AuthUser\n * @throws UnauthorizedError if not authenticated as a user\n * @throws ForbiddenError if user lacks the required roles\n *\n * @example\n * ```typescript\n * const user = requireUser(ctx, [\"admin\", \"operator\"]);\n * ```\n */\nexport function requireUser(\n ctx: AuthContext,\n requiredRoles?: readonly string[],\n): AuthUser {\n if (!ctx.authenticated || !ctx.user) {\n logger.warn(\n \"[requireUser] Request rejected: missing authenticated user context\",\n );\n throw new UnauthorizedError(\n \"Authentication required: valid user session required\",\n );\n }\n\n if (requiredRoles && requiredRoles.length > 0) {\n const hasRole = requiredRoles.some((role) => ctx.roles.includes(role));\n if (!hasRole) {\n logger.warn(\"[requireUser] User lacks required roles\", {\n userId: ctx.user.id,\n userRoles: ctx.roles,\n requiredRoles,\n });\n throw new ForbiddenError(\n `Insufficient privileges: requires one of [${requiredRoles.join(\", \")}]`,\n );\n }\n }\n\n return ctx.user;\n}\n\n/**\n * Asserts that the request is authenticated via a machine-to-machine API key.\n *\n * @param ctx - Inbound request auth context\n * @returns Authenticated ApiKey\n * @throws UnauthorizedError if not authenticated via an API key\n */\nexport function requireApiKey(ctx: AuthContext): ApiKey {\n if (!ctx.authenticated || !ctx.apiKey) {\n logger.warn(\"[requireApiKey] Request rejected: missing machine API key\");\n throw new UnauthorizedError(\n \"Authentication required: valid API key required\",\n );\n }\n\n return ctx.apiKey;\n}\n\n/**\n * Asserts that the authenticated context (user or API key) possesses a specific operational scope.\n *\n * @param ctx - Inbound request auth context\n * @param scope - Required scope or permission string\n * @throws ForbiddenError if scope is missing\n */\nexport function requireScope(ctx: AuthContext, scope: string): void {\n if (!ctx.roles.includes(scope)) {\n logger.warn(\"[requireScope] Missing required scope\", {\n roles: ctx.roles,\n requiredScope: scope,\n });\n throw new ForbiddenError(`Missing required scope: '${scope}'`);\n }\n}\n","/**\n * @file packages/auth-server/src/middleware/auth-middleware.ts\n * @description Universal HTTP middleware providing platform kill-switch enforcement and credential validation.\n * @module @yuva-devlab/auth-server\n */\n\nimport { Logger, loggerWithConfig } from \"@yuva-devlab/logger\";\nimport {\n DEFAULT_API_KEY_HEADER,\n DEFAULT_AUTH_HEADER,\n DEFAULT_BEARER_PREFIX,\n DEFAULT_PUBLIC_PATHS,\n HTTP_STATUS_SERVICE_UNAVAILABLE,\n HTTP_STATUS_UNAUTHORIZED,\n} from \"../constants\";\nimport { AppStatus, type AuthContext } from \"../types\";\n\nconst logger = loggerWithConfig(new Logger(\"AuthMiddleware\"));\n\n/**\n * Minimal HTTP request and response interfaces for framework neutrality (Express, Fastify, Node http).\n */\nexport interface GenericHttpRequest {\n url?: string;\n method?: string;\n headers: Record<string, string | string[] | undefined>;\n authContext?: AuthContext;\n}\n\nexport interface GenericHttpResponse {\n statusCode?: number;\n setHeader(name: string, value: string): void;\n end(body?: string): void;\n}\n\n/**\n * Creates a kill-switch middleware that intercepts all traffic if an application is deactivated.\n *\n * @param getAppStatus - Callback returning current platform status of the application\n * @returns Middleware function returning true if allowed to proceed, false if blocked (503)\n *\n * @example\n * ```typescript\n * const killSwitch = createAppKillSwitchMiddleware(() => appState.status);\n * const allowed = await killSwitch(req, res);\n * if (!allowed) return;\n * ```\n */\nexport function createAppKillSwitchMiddleware(\n getAppStatus: () => Promise<AppStatus> | AppStatus,\n) {\n return async (\n req: GenericHttpRequest,\n res: GenericHttpResponse,\n ): Promise<boolean> => {\n const status = await getAppStatus();\n\n // If application has been administratively disabled or put into maintenance\n if (status === AppStatus.INACTIVE || status === AppStatus.MAINTENANCE) {\n logger.warn(\n \"[killSwitch] Application is inactive or under maintenance; request rejected\",\n {\n url: req.url,\n method: req.method,\n appStatus: status,\n },\n );\n\n res.statusCode = HTTP_STATUS_SERVICE_UNAVAILABLE;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n error: {\n code: \"APP_DEACTIVATED\",\n message:\n status === AppStatus.MAINTENANCE\n ? \"Application is currently undergoing scheduled maintenance\"\n : \"Application has been deactivated by platform administrators\",\n status,\n },\n }),\n );\n return false;\n }\n\n return true;\n };\n}\n\n/**\n * Options configuring request authentication middleware.\n */\nexport interface CreateAuthMiddlewareOptions {\n /** Whitelisted public endpoints bypassing authentication */\n publicPaths?: readonly string[];\n /** Function resolving an API key against store */\n validateApiKey?: (key: string) => Promise<AuthContext | null>;\n /** Function resolving a Bearer session token against JWKS or session store */\n validateToken?: (token: string) => Promise<AuthContext | null>;\n}\n\n/**\n * Universal authentication middleware verifying API keys and Bearer tokens.\n *\n * @param options - Configuration options\n * @returns Middleware handler\n */\nexport function createAuthMiddleware(options: CreateAuthMiddlewareOptions) {\n const publicPaths = new Set([\n ...DEFAULT_PUBLIC_PATHS,\n ...(options.publicPaths ?? []),\n ]);\n\n return async (\n req: GenericHttpRequest,\n res: GenericHttpResponse,\n ): Promise<boolean> => {\n const urlPath = (req.url || \"/\").split(\"?\")[0] || \"/\";\n\n // 1. Bypass authentication for whitelisted public paths\n if (publicPaths.has(urlPath)) {\n req.authContext = {\n authenticated: false,\n roles: [],\n isAppActive: true,\n };\n return true;\n }\n\n // 2. Check for Machine-to-Machine API Key\n const rawApiKey = req.headers[DEFAULT_API_KEY_HEADER];\n const apiKey = typeof rawApiKey === \"string\" ? rawApiKey.trim() : undefined;\n\n if (apiKey && options.validateApiKey) {\n const apiKeyCtx = await options.validateApiKey(apiKey);\n if (apiKeyCtx?.authenticated) {\n logger.info(\"[authenticate] Authenticated via API Key\", {\n path: urlPath,\n });\n req.authContext = apiKeyCtx;\n return true;\n }\n }\n\n // 3. Check for Bearer Token\n const rawAuth = req.headers[DEFAULT_AUTH_HEADER];\n const authHeader = typeof rawAuth === \"string\" ? rawAuth.trim() : undefined;\n\n if (\n authHeader?.startsWith(DEFAULT_BEARER_PREFIX) &&\n options.validateToken\n ) {\n const token = authHeader.slice(DEFAULT_BEARER_PREFIX.length).trim();\n const tokenCtx = await options.validateToken(token);\n if (tokenCtx?.authenticated) {\n logger.info(\"[authenticate] Authenticated via Bearer token\", {\n path: urlPath,\n });\n req.authContext = tokenCtx;\n return true;\n }\n }\n\n // 4. Reject with 401 Unauthorized\n logger.warn(\"[authenticate] Unauthorized request rejected\", {\n path: urlPath,\n method: req.method,\n });\n\n res.statusCode = HTTP_STATUS_UNAUTHORIZED;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(\n JSON.stringify({\n error: {\n code: \"UNAUTHORIZED\",\n message:\n \"Missing or invalid authentication credentials (Bearer token or API key required)\",\n },\n }),\n );\n return false;\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@yuva-devlab/auth-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Server-side authentication guards, cached JWKS validator, and kill-switch middleware for DevLab",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": {
12
+ "import": "./dist/index.d.ts",
13
+ "require": "./dist/index.d.cts"
14
+ },
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "zod": "^3.25.76",
29
+ "@yuva-devlab/errors": "0.2.0",
30
+ "@yuva-devlab/logger": "1.2.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^24.10.1",
34
+ "eslint": "^9.39.1",
35
+ "prettier": "^3.9.8",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^6.0.3",
38
+ "@yuva-devlab/eslint-config": "0.1.1",
39
+ "@yuva-devlab/prettier-config": "0.1.1",
40
+ "@yuva-devlab/typescript-config": "0.1.1"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "dev": "tsup --watch",
45
+ "lint": "eslint src",
46
+ "lint:fix": "eslint src --fix",
47
+ "format": "prettier --write .",
48
+ "format:check": "prettier --check .",
49
+ "typecheck": "tsc --noEmit",
50
+ "clean": "rm -rf dist node_modules .turbo"
51
+ }
6
52
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.