@azlib/identity 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.
Files changed (69) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +387 -0
  3. package/dist/errors-BGMwaW5s.d.mts +481 -0
  4. package/dist/errors-BGMwaW5s.d.mts.map +1 -0
  5. package/dist/errors-Bcjx9o6g.cjs +111 -0
  6. package/dist/errors-C2xZAatu.d.cts +481 -0
  7. package/dist/errors-C2xZAatu.d.cts.map +1 -0
  8. package/dist/errors-CEmnZxIn.mjs +66 -0
  9. package/dist/errors-CEmnZxIn.mjs.map +1 -0
  10. package/dist/express.cjs +405 -0
  11. package/dist/express.d.cts +87 -0
  12. package/dist/express.d.cts.map +1 -0
  13. package/dist/express.d.mts +87 -0
  14. package/dist/express.d.mts.map +1 -0
  15. package/dist/express.mjs +401 -0
  16. package/dist/express.mjs.map +1 -0
  17. package/dist/identity-4eP45YIP.cjs +461 -0
  18. package/dist/identity-Bz9RDOvT.mjs +410 -0
  19. package/dist/identity-Bz9RDOvT.mjs.map +1 -0
  20. package/dist/identity-router-DBL20UWT.d.mts +115 -0
  21. package/dist/identity-router-DBL20UWT.d.mts.map +1 -0
  22. package/dist/identity-router-Dib30Waj.d.cts +115 -0
  23. package/dist/identity-router-Dib30Waj.d.cts.map +1 -0
  24. package/dist/identity-service-B9zrvE9z.d.mts +128 -0
  25. package/dist/identity-service-B9zrvE9z.d.mts.map +1 -0
  26. package/dist/identity-service-CLzKx8Z7.d.cts +128 -0
  27. package/dist/identity-service-CLzKx8Z7.d.cts.map +1 -0
  28. package/dist/identity-store-BRRahxcS.d.cts +272 -0
  29. package/dist/identity-store-BRRahxcS.d.cts.map +1 -0
  30. package/dist/identity-store-BRRahxcS.d.mts +272 -0
  31. package/dist/identity-store-BRRahxcS.d.mts.map +1 -0
  32. package/dist/index-CpufYgyn.d.cts +30 -0
  33. package/dist/index-CpufYgyn.d.cts.map +1 -0
  34. package/dist/index-CpufYgyn.d.mts +30 -0
  35. package/dist/index-CpufYgyn.d.mts.map +1 -0
  36. package/dist/index.cjs +18 -0
  37. package/dist/index.d.cts +4 -0
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.mjs +3 -0
  40. package/dist/logger-Be1wDzBC.cjs +48 -0
  41. package/dist/logger-CcCHJVVe.mjs +33 -0
  42. package/dist/logger-CcCHJVVe.mjs.map +1 -0
  43. package/dist/nestjs.cjs +516 -0
  44. package/dist/nestjs.d.cts +102 -0
  45. package/dist/nestjs.d.cts.map +1 -0
  46. package/dist/nestjs.d.mts +102 -0
  47. package/dist/nestjs.d.mts.map +1 -0
  48. package/dist/nestjs.mjs +500 -0
  49. package/dist/nestjs.mjs.map +1 -0
  50. package/dist/node.cjs +946 -0
  51. package/dist/node.d.cts +260 -0
  52. package/dist/node.d.cts.map +1 -0
  53. package/dist/node.d.mts +260 -0
  54. package/dist/node.d.mts.map +1 -0
  55. package/dist/node.mjs +890 -0
  56. package/dist/node.mjs.map +1 -0
  57. package/dist/test-utils.cjs +169 -0
  58. package/dist/test-utils.d.cts +12 -0
  59. package/dist/test-utils.d.cts.map +1 -0
  60. package/dist/test-utils.d.mts +12 -0
  61. package/dist/test-utils.d.mts.map +1 -0
  62. package/dist/test-utils.mjs +170 -0
  63. package/dist/test-utils.mjs.map +1 -0
  64. package/package.json +92 -0
  65. package/schema/model.ts +100 -0
  66. package/schema/mysql.sql +102 -0
  67. package/schema/postgres.sql +92 -0
  68. package/schema/prisma.schema +122 -0
  69. package/schema/sqlite.sql +92 -0
@@ -0,0 +1,401 @@
1
+ import { a as IdentityError, c as UnauthenticatedError, r as ForbiddenError } from "./errors-CEmnZxIn.mjs";
2
+ import { r as resolveLogger } from "./logger-CcCHJVVe.mjs";
3
+ import { Router } from "express";
4
+ //#region core/express/middleware.ts
5
+ const extractBearerToken = (req) => {
6
+ const header = req.headers.authorization;
7
+ if (!header || !header.startsWith("Bearer ")) return null;
8
+ const token = header.slice(7).trim();
9
+ return token.length > 0 ? token : null;
10
+ };
11
+ /**
12
+ * Express middleware that authenticates the request using a Bearer access token and
13
+ * attaches the hydrated principal to `req.identity`. Forwards identity errors to the
14
+ * error-handling middleware.
15
+ */
16
+ function requireAuth(service) {
17
+ return async (req, _res, next) => {
18
+ try {
19
+ const token = extractBearerToken(req);
20
+ if (!token) throw new UnauthenticatedError();
21
+ req.identity = await service.authenticate(token);
22
+ next();
23
+ } catch (error) {
24
+ next(error);
25
+ }
26
+ };
27
+ }
28
+ /**
29
+ * Express middleware enforcing an authorization requirement (deny-by-default). Must run
30
+ * after {@link requireAuth}. Optionally loads a resource for ownership policies.
31
+ */
32
+ function requireAuthorization(service, requirement, loadResource) {
33
+ return async (req, _res, next) => {
34
+ try {
35
+ const principal = req.identity;
36
+ if (!principal) throw new UnauthenticatedError();
37
+ const resource = loadResource ? await loadResource(req) : void 0;
38
+ if (!(await service.authorize(requirement, {
39
+ principal,
40
+ action: requirement.permission ?? "custom",
41
+ resource
42
+ })).allowed) throw new ForbiddenError();
43
+ next();
44
+ } catch (error) {
45
+ next(error);
46
+ }
47
+ };
48
+ }
49
+ /** Convenience: require a single permission. */
50
+ function requirePermission(service, permission) {
51
+ return requireAuthorization(service, { permission });
52
+ }
53
+ //#endregion
54
+ //#region core/express/oauth-routes.ts
55
+ /**
56
+ * Returns an Express route handler that redirects the user to the OAuth provider's
57
+ * authorization page. Generates and persists a CSRF state value via `options.setState`.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * app.get("/auth/google", oauthAuthorize(identity.oauth!, "google", {
62
+ * redirectUri: `${BASE_URL}/auth/google/callback`,
63
+ * getState: (req) => req.session?.oauthState ?? null,
64
+ * setState: (req, _res, state) => { req.session!.oauthState = state; },
65
+ * onSuccess: (_req, res, result) => res.json(result),
66
+ * }));
67
+ * ```
68
+ */
69
+ function oauthAuthorize(oauthService, providerName, options) {
70
+ return async (req, res, next) => {
71
+ try {
72
+ const { url, state } = oauthService.buildAuthorizationUrl(providerName, options.redirectUri);
73
+ await options.setState(req, res, state);
74
+ res.redirect(url);
75
+ } catch (error) {
76
+ next(error);
77
+ }
78
+ };
79
+ }
80
+ /**
81
+ * Returns an Express route handler that handles the OAuth provider callback. Verifies
82
+ * state, exchanges the code, and calls `options.onSuccess` with the auth result.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * app.get("/auth/google/callback", oauthCallback(identity.oauth!, "google", {
87
+ * redirectUri: `${BASE_URL}/auth/google/callback`,
88
+ * getState: (req) => req.session?.oauthState ?? null,
89
+ * setState: (req, _res, state) => { req.session!.oauthState = state; },
90
+ * onSuccess: (_req, res, result) => res.json(result),
91
+ * }));
92
+ * ```
93
+ */
94
+ function oauthCallback(oauthService, providerName, options) {
95
+ return async (req, res, next) => {
96
+ try {
97
+ const code = typeof req.query["code"] === "string" ? req.query["code"] : null;
98
+ const state = typeof req.query["state"] === "string" ? req.query["state"] : null;
99
+ const error = typeof req.query["error"] === "string" ? req.query["error"] : null;
100
+ if (error) throw new IdentityError("identity/oauth-error", `Provider error: ${typeof req.query["error_description"] === "string" ? req.query["error_description"] : error}`, 400);
101
+ if (!code || !state) throw new IdentityError("identity/oauth-invalid-callback", "Missing code or state in OAuth callback.", 400);
102
+ const expectedState = await options.getState(req);
103
+ if (!expectedState) throw new IdentityError("identity/oauth-state-missing", "No OAuth state found in session. The request may have expired.", 400);
104
+ const result = await oauthService.handleCallback(providerName, {
105
+ code,
106
+ state,
107
+ expectedState,
108
+ redirectUri: options.redirectUri
109
+ });
110
+ await options.onSuccess(req, res, result);
111
+ } catch (error) {
112
+ next(error);
113
+ }
114
+ };
115
+ }
116
+ //#endregion
117
+ //#region core/express/identity-router.ts
118
+ const OAUTH_STATE_COOKIE = "azlib_oauth_state";
119
+ function resolveCookieName(opts) {
120
+ return opts.name ?? "azlib_rt";
121
+ }
122
+ /** Parses cookies from the raw `Cookie` header without a dependency on `cookie-parser`. */
123
+ function readCookie(req, name) {
124
+ const raw = req.headers.cookie ?? "";
125
+ for (const part of raw.split(";")) {
126
+ const eq = part.indexOf("=");
127
+ if (eq === -1) continue;
128
+ if (part.slice(0, eq).trim() === name) try {
129
+ return decodeURIComponent(part.slice(eq + 1).trim());
130
+ } catch {
131
+ return part.slice(eq + 1).trim();
132
+ }
133
+ }
134
+ }
135
+ /**
136
+ * Writes the refresh token into a `Set-Cookie` header and returns a body-safe tokens
137
+ * object that omits the refresh token.
138
+ */
139
+ function setCookieAndStripRefreshToken(res, tokens, opts) {
140
+ const maxAge = tokens.refreshTokenExpiresAt.getTime() - Date.now();
141
+ res.cookie(resolveCookieName(opts), tokens.refreshToken, {
142
+ httpOnly: opts.httpOnly ?? true,
143
+ secure: opts.secure ?? process.env.NODE_ENV === "production",
144
+ sameSite: opts.sameSite ?? "lax",
145
+ path: opts.path ?? "/",
146
+ domain: opts.domain,
147
+ maxAge
148
+ });
149
+ const { refreshToken: _rt, refreshTokenExpiresAt: _exp, ...bodyTokens } = tokens;
150
+ return bodyTokens;
151
+ }
152
+ /**
153
+ * Sends the authentication result as JSON. In cookie mode the refresh token is stored
154
+ * in a `Set-Cookie` header and excluded from the body.
155
+ *
156
+ * When the result is an MFA challenge (`{ kind: "mfa_required" }`), it is forwarded as-is
157
+ * with HTTP 200 so the client knows to complete the TOTP step.
158
+ */
159
+ function sendLoginResult(res, result, cookieOpts) {
160
+ if ("mfaToken" in result) {
161
+ res.json(result);
162
+ return;
163
+ }
164
+ sendAuthResult(res, result, cookieOpts);
165
+ }
166
+ function sendAuthResult(res, result, cookieOpts) {
167
+ if (cookieOpts) {
168
+ const tokens = setCookieAndStripRefreshToken(res, result.tokens, cookieOpts);
169
+ res.json({
170
+ user: result.user,
171
+ tokens
172
+ });
173
+ } else res.json(result);
174
+ }
175
+ /**
176
+ * Reads the refresh token from the request. In cookie mode it is read from the cookie;
177
+ * in body mode it is expected in `req.body.refreshToken`.
178
+ */
179
+ function readRefreshToken(req, cookieOpts) {
180
+ if (cookieOpts) return readCookie(req, resolveCookieName(cookieOpts));
181
+ const token = req.body?.["refreshToken"];
182
+ return typeof token === "string" ? token : void 0;
183
+ }
184
+ /**
185
+ * Creates a pre-wired Express `Router` with all standard identity endpoints.
186
+ *
187
+ * Mount it once on your application:
188
+ * ```ts
189
+ * import express from "express";
190
+ * import { createIdentityService } from "@azlib/identity/node";
191
+ * import { createIdentityRouter, identityErrorHandler } from "@azlib/identity/express";
192
+ *
193
+ * const service = createIdentityService(config, store);
194
+ * const app = express();
195
+ *
196
+ * app.use(express.json());
197
+ * // Routes are served at /account/login, /account/register, etc. (default prefix)
198
+ * app.use(createIdentityRouter(service));
199
+ * // Or use a custom prefix:
200
+ * app.use(createIdentityRouter(service, { prefix: "auth" }));
201
+ * app.use(identityErrorHandler()); // optional convenience error handler
202
+ * ```
203
+ *
204
+ * Pre-wired routes:
205
+ *
206
+ * | Method | Path | Description |
207
+ * |--------|------|-------------|
208
+ * | POST | `/register` | Create a new account |
209
+ * | POST | `/login` | Email + password login |
210
+ * | POST | `/refresh` | Rotate the refresh token |
211
+ * | POST | `/logout` | Revoke the current session |
212
+ * | GET | `/me` | Return the authenticated principal |
213
+ * | GET | `/:provider` | Start an OAuth 2.0 authorisation flow *(optional)* |
214
+ * | GET | `/:provider/callback` | Handle an OAuth 2.0 callback *(optional)* |
215
+ */
216
+ function createIdentityRouter(service, options = {}) {
217
+ const prefix = options.prefix ?? "account";
218
+ const log = resolveLogger(options.logger);
219
+ const router = Router();
220
+ const cookieOpts = typeof options.refreshToken === "object" && "cookie" in options.refreshToken ? options.refreshToken.cookie : void 0;
221
+ router.use((_req, res, next) => {
222
+ const start = Date.now();
223
+ res.on("finish", () => {
224
+ const ms = Date.now() - start;
225
+ log.info(`${_req.method} ${_req.originalUrl} ${res.statusCode}`, { durationMs: ms });
226
+ });
227
+ next();
228
+ });
229
+ router.post("/register", async (req, res, next) => {
230
+ try {
231
+ sendAuthResult(res, await service.register(req.body), cookieOpts);
232
+ } catch (err) {
233
+ next(err);
234
+ }
235
+ });
236
+ router.post("/login", async (req, res, next) => {
237
+ try {
238
+ sendLoginResult(res, await service.login(req.body), cookieOpts);
239
+ } catch (err) {
240
+ next(err);
241
+ }
242
+ });
243
+ router.post("/refresh", async (req, res, next) => {
244
+ try {
245
+ const token = readRefreshToken(req, cookieOpts);
246
+ if (!token) throw new UnauthenticatedError("Refresh token is missing.");
247
+ sendAuthResult(res, await service.refresh(token), cookieOpts);
248
+ } catch (err) {
249
+ next(err);
250
+ }
251
+ });
252
+ router.post("/logout", async (req, res, next) => {
253
+ try {
254
+ const token = readRefreshToken(req, cookieOpts);
255
+ if (token) await service.logout(token);
256
+ if (cookieOpts) res.clearCookie(resolveCookieName(cookieOpts), { path: cookieOpts.path ?? "/" });
257
+ res.status(204).end();
258
+ } catch (err) {
259
+ next(err);
260
+ }
261
+ });
262
+ router.get("/me", requireAuth(service), (_req, res) => {
263
+ res.json(_req.identity);
264
+ });
265
+ router.post("/request-email-verification", requireAuth(service), async (req, res, next) => {
266
+ try {
267
+ await service.requestEmailVerification(req.identity.userId);
268
+ res.status(204).end();
269
+ } catch (err) {
270
+ next(err);
271
+ }
272
+ });
273
+ router.post("/verify-email", async (req, res, next) => {
274
+ try {
275
+ const { token } = req.body;
276
+ if (!token) throw new UnauthenticatedError("token is required.");
277
+ await service.verifyEmail(token);
278
+ res.status(204).end();
279
+ } catch (err) {
280
+ next(err);
281
+ }
282
+ });
283
+ router.post("/request-password-reset", async (req, res, next) => {
284
+ try {
285
+ const { email } = req.body;
286
+ if (!email) throw new UnauthenticatedError("email is required.");
287
+ await service.requestPasswordReset(email);
288
+ res.status(204).end();
289
+ } catch (err) {
290
+ next(err);
291
+ }
292
+ });
293
+ router.post("/reset-password", async (req, res, next) => {
294
+ try {
295
+ const { token, newPassword } = req.body;
296
+ if (!token || !newPassword) throw new UnauthenticatedError("token and newPassword are required.");
297
+ await service.resetPassword(token, newPassword);
298
+ res.status(204).end();
299
+ } catch (err) {
300
+ next(err);
301
+ }
302
+ });
303
+ router.post("/mfa/verify", async (req, res, next) => {
304
+ try {
305
+ const { mfaToken, code } = req.body;
306
+ if (!mfaToken || !code) throw new UnauthenticatedError("mfaToken and code are required.");
307
+ sendAuthResult(res, await service.verifyMfaChallenge(mfaToken, code), cookieOpts);
308
+ } catch (err) {
309
+ next(err);
310
+ }
311
+ });
312
+ router.post("/2fa/setup", requireAuth(service), async (req, res, next) => {
313
+ try {
314
+ const result = await service.setup2FA(req.identity.userId);
315
+ res.json(result);
316
+ } catch (err) {
317
+ next(err);
318
+ }
319
+ });
320
+ router.post("/2fa/enable", requireAuth(service), async (req, res, next) => {
321
+ try {
322
+ const { code } = req.body;
323
+ if (!code) throw new UnauthenticatedError("code is required.");
324
+ await service.enable2FA(req.identity.userId, code);
325
+ res.status(204).end();
326
+ } catch (err) {
327
+ next(err);
328
+ }
329
+ });
330
+ router.post("/2fa/disable", requireAuth(service), async (req, res, next) => {
331
+ try {
332
+ const { code } = req.body;
333
+ if (!code) throw new UnauthenticatedError("code is required.");
334
+ await service.disable2FA(req.identity.userId, code);
335
+ res.status(204).end();
336
+ } catch (err) {
337
+ next(err);
338
+ }
339
+ });
340
+ router.post("/admin/lock/:userId", async (req, res, next) => {
341
+ try {
342
+ await service.lockAccount(req.params["userId"]);
343
+ res.status(204).end();
344
+ } catch (err) {
345
+ next(err);
346
+ }
347
+ });
348
+ router.post("/admin/unlock/:userId", async (req, res, next) => {
349
+ try {
350
+ await service.unlockAccount(req.params["userId"]);
351
+ res.status(204).end();
352
+ } catch (err) {
353
+ next(err);
354
+ }
355
+ });
356
+ router.post("/admin/disable/:userId", async (req, res, next) => {
357
+ try {
358
+ await service.disableAccount(req.params["userId"]);
359
+ res.status(204).end();
360
+ } catch (err) {
361
+ next(err);
362
+ }
363
+ });
364
+ if (service.oauth) {
365
+ const oauthService = service.oauth;
366
+ const baseUrl = options.oauthBaseUrl ?? "";
367
+ const stateCookieOptions = {
368
+ httpOnly: true,
369
+ sameSite: "lax",
370
+ secure: process.env.NODE_ENV === "production",
371
+ maxAge: 600 * 1e3
372
+ };
373
+ for (const providerName of oauthService.providers) {
374
+ const redirectUri = `${baseUrl}/${providerName}/callback`;
375
+ router.get(`/${providerName}`, oauthAuthorize(oauthService, providerName, {
376
+ redirectUri,
377
+ setState: (_req, oauthRes, state) => {
378
+ oauthRes.cookie(OAUTH_STATE_COOKIE, state, stateCookieOptions);
379
+ }
380
+ }));
381
+ router.get(`/${providerName}/callback`, oauthCallback(oauthService, providerName, {
382
+ redirectUri,
383
+ getState: (oauthReq) => readCookie(oauthReq, OAUTH_STATE_COOKIE) ?? null,
384
+ setState: (_oauthReq, oauthRes, _state) => {
385
+ oauthRes.clearCookie(OAUTH_STATE_COOKIE);
386
+ },
387
+ onSuccess: (_oauthReq, oauthRes, result) => {
388
+ sendAuthResult(oauthRes, result, cookieOpts);
389
+ }
390
+ }));
391
+ }
392
+ }
393
+ const outer = Router();
394
+ if (prefix) outer.use(`/${prefix}`, router);
395
+ else outer.use(router);
396
+ return outer;
397
+ }
398
+ //#endregion
399
+ export { createIdentityRouter, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
400
+
401
+ //# sourceMappingURL=express.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.mjs","names":[],"sources":["../core/express/middleware.ts","../core/express/oauth-routes.ts","../core/express/identity-router.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from \"express\";\n\nimport type { AuthorizationRequirement } from \"../authorization\";\nimport { ForbiddenError, IdentityError, UnauthenticatedError } from \"../errors\";\nimport type { IdentityService } from \"../identity-service\";\nimport type { AuthenticatedIdentity } from \"../types\";\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** The authenticated principal, set by {@link requireAuth}. */\n identity?: AuthenticatedIdentity;\n }\n }\n}\n\n/** Loads the resource a policy will be evaluated against. */\nexport type ResourceLoader<TResource> = (req: Request) => TResource | Promise<TResource>;\n\nconst extractBearerToken = (req: Request): string | null => {\n const header = req.headers.authorization;\n if (!header || !header.startsWith(\"Bearer \")) return null;\n const token = header.slice(\"Bearer \".length).trim();\n return token.length > 0 ? token : null;\n};\n\n/**\n * Express middleware that authenticates the request using a Bearer access token and\n * attaches the hydrated principal to `req.identity`. Forwards identity errors to the\n * error-handling middleware.\n */\nexport function requireAuth(service: IdentityService): RequestHandler {\n return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {\n try {\n const token = extractBearerToken(req);\n if (!token) {\n throw new UnauthenticatedError();\n }\n req.identity = await service.authenticate(token);\n next();\n } catch (error) {\n next(error);\n }\n };\n}\n\n/**\n * Express middleware enforcing an authorization requirement (deny-by-default). Must run\n * after {@link requireAuth}. Optionally loads a resource for ownership policies.\n */\nexport function requireAuthorization<TResource = unknown>(\n service: IdentityService,\n requirement: AuthorizationRequirement<TResource>,\n loadResource?: ResourceLoader<TResource>,\n): RequestHandler {\n return async (req: Request, _res: Response, next: NextFunction): Promise<void> => {\n try {\n const principal = req.identity;\n if (!principal) {\n throw new UnauthenticatedError();\n }\n const resource = loadResource ? await loadResource(req) : undefined;\n const decision = await service.authorize(requirement, {\n principal,\n action: requirement.permission ?? \"custom\",\n resource,\n });\n if (!decision.allowed) {\n throw new ForbiddenError();\n }\n next();\n } catch (error) {\n next(error);\n }\n };\n}\n\n/** Convenience: require a single permission. */\nexport function requirePermission(\n service: IdentityService,\n permission: string,\n): RequestHandler {\n return requireAuthorization(service, { permission });\n}\n\n/**\n * Optional Express error handler that serializes {@link IdentityError} instances to JSON.\n * Mount after your routes. Non-identity errors are forwarded unchanged.\n */\nexport function identityErrorHandler() {\n return (err: unknown, _req: Request, res: Response, next: NextFunction): void => {\n if (err instanceof IdentityError) {\n res.status(err.statusCode).json({ error: { code: err.code, message: err.message } });\n return;\n }\n next(err);\n };\n}\n","import type { NextFunction, Request, RequestHandler, Response } from \"express\";\n\nimport { IdentityError } from \"../errors\";\nimport type { OAuthService } from \"../oauth/oauth-service\";\n\n/**\n * Options for {@link oauthAuthorize} and {@link oauthCallback}.\n */\nexport interface OAuthRouteOptions {\n /** The full redirect URI registered with the provider (must match exactly). */\n redirectUri: string;\n /**\n * Retrieves the stored CSRF state value from the current request context (e.g. from a\n * signed cookie or server session). Return `null` if no state has been stored yet.\n */\n getState(req: Request): string | null | Promise<string | null>;\n /**\n * Persists the generated CSRF state value before redirecting the user to the provider.\n * Use a signed cookie or server session.\n */\n setState(req: Request, res: Response, state: string): void | Promise<void>;\n /**\n * Called on a successful OAuth callback with the auth result. Typically sets a session\n * cookie and redirects to the app.\n */\n onSuccess(req: Request, res: Response, result: import(\"../types\").AuthResult): void | Promise<void>;\n}\n\n/**\n * Returns an Express route handler that redirects the user to the OAuth provider's\n * authorization page. Generates and persists a CSRF state value via `options.setState`.\n *\n * @example\n * ```ts\n * app.get(\"/auth/google\", oauthAuthorize(identity.oauth!, \"google\", {\n * redirectUri: `${BASE_URL}/auth/google/callback`,\n * getState: (req) => req.session?.oauthState ?? null,\n * setState: (req, _res, state) => { req.session!.oauthState = state; },\n * onSuccess: (_req, res, result) => res.json(result),\n * }));\n * ```\n */\nexport function oauthAuthorize(\n oauthService: OAuthService,\n providerName: string,\n options: Pick<OAuthRouteOptions, \"redirectUri\" | \"setState\">,\n): RequestHandler {\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { url, state } = oauthService.buildAuthorizationUrl(providerName, options.redirectUri);\n await options.setState(req, res, state);\n res.redirect(url);\n } catch (error) {\n next(error);\n }\n };\n}\n\n/**\n * Returns an Express route handler that handles the OAuth provider callback. Verifies\n * state, exchanges the code, and calls `options.onSuccess` with the auth result.\n *\n * @example\n * ```ts\n * app.get(\"/auth/google/callback\", oauthCallback(identity.oauth!, \"google\", {\n * redirectUri: `${BASE_URL}/auth/google/callback`,\n * getState: (req) => req.session?.oauthState ?? null,\n * setState: (req, _res, state) => { req.session!.oauthState = state; },\n * onSuccess: (_req, res, result) => res.json(result),\n * }));\n * ```\n */\nexport function oauthCallback(\n oauthService: OAuthService,\n providerName: string,\n options: OAuthRouteOptions,\n): RequestHandler {\n return async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const code = typeof req.query[\"code\"] === \"string\" ? req.query[\"code\"] : null;\n const state = typeof req.query[\"state\"] === \"string\" ? req.query[\"state\"] : null;\n const error = typeof req.query[\"error\"] === \"string\" ? req.query[\"error\"] : null;\n\n if (error) {\n const errorDescription =\n typeof req.query[\"error_description\"] === \"string\"\n ? req.query[\"error_description\"]\n : error;\n throw new IdentityError(\"identity/oauth-error\", `Provider error: ${errorDescription}`, 400);\n }\n\n if (!code || !state) {\n throw new IdentityError(\n \"identity/oauth-invalid-callback\",\n \"Missing code or state in OAuth callback.\",\n 400,\n );\n }\n\n const expectedState = await options.getState(req);\n if (!expectedState) {\n throw new IdentityError(\n \"identity/oauth-state-missing\",\n \"No OAuth state found in session. The request may have expired.\",\n 400,\n );\n }\n\n const result = await oauthService.handleCallback(providerName, {\n code,\n state,\n expectedState,\n redirectUri: options.redirectUri,\n });\n\n await options.onSuccess(req, res, result);\n } catch (error) {\n next(error);\n }\n };\n}\n","import { Router, type NextFunction, type Request, type Response } from \"express\";\n\nimport { IdentityError, UnauthenticatedError } from \"../errors\";\nimport type { IdentityService } from \"../identity-service\";\nimport type { IdentityLogger } from \"../logger\";\nimport { resolveLogger } from \"../logger\";\nimport type { AuthResult, AuthTokens, LoginResult } from \"../types\";\nimport { requireAuth } from \"./middleware\";\nimport { oauthAuthorize, oauthCallback } from \"./oauth-routes\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options for controlling the refresh-token cookie. */\nexport interface CookieRefreshOptions {\n /** Cookie name. Default: `\"azlib_rt\"`. */\n name?: string;\n /** Mark the cookie as `HttpOnly`. Default: `true`. */\n httpOnly?: boolean;\n /**\n * Mark the cookie as `Secure`. Defaults to `true` when `NODE_ENV` is `\"production\"`,\n * `false` otherwise.\n */\n secure?: boolean;\n /** `SameSite` policy. Default: `\"lax\"`. */\n sameSite?: \"strict\" | \"lax\" | \"none\";\n /** Cookie path. Default: `\"/\"`. */\n path?: string;\n /** Cookie domain. Omit to use the current host. */\n domain?: string;\n}\n\n/**\n * Options for {@link createIdentityRouter}.\n */\nexport interface IdentityRouterOptions {\n /**\n * Controls how the refresh token is transported between server and client.\n *\n * - **`\"body\"`** (default) — the refresh token is included in the `tokens` object of\n * every successful `register`/`login`/`refresh` response. The client must store it\n * and send it back via the JSON body on `/refresh` and `/logout`.\n *\n * - **`{ cookie: CookieRefreshOptions }`** — the refresh token is sent as an\n * `HttpOnly` cookie. `/refresh` and `/logout` read it automatically; the response\n * body only includes the access token.\n */\n refreshToken?: \"body\" | { cookie: CookieRefreshOptions };\n\n /**\n * Base URL under which the OAuth callback routes are hosted.\n *\n * Example: `\"https://api.example.com/auth\"`.\n *\n * The callback URI for a provider becomes `{oauthBaseUrl}/{providerName}/callback`.\n * Required when the identity service has OAuth providers configured.\n */\n oauthBaseUrl?: string;\n\n /**\n * URL path prefix prepended to all identity routes.\n *\n * Default: `\"account\"`. Routes are served at `/{prefix}/login`, `/{prefix}/register`, etc.\n *\n * Mount the returned router at the application root:\n * ```ts\n * app.use(createIdentityRouter(service, { prefix: \"auth\" }));\n * // → POST /auth/login, POST /auth/register, …\n * ```\n *\n * Set to `\"\"` to omit the prefix and mount routes directly at the router's mount point.\n */\n prefix?: string;\n\n /**\n * Logger for request/response and error diagnostics.\n *\n * - Omit — defaults to a `console`-based logger with an `[identity]` prefix.\n * - Supply your own `IdentityLogger` — route output to Winston, Pino, etc.\n * - Pass `false` — disable all logging from this router.\n *\n * @example\n * ```ts\n * import pino from \"pino\";\n * app.use(createIdentityRouter(service, { logger: pino() }));\n * ```\n */\n logger?: IdentityLogger | false;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nconst OAUTH_STATE_COOKIE = \"azlib_oauth_state\";\n\nfunction resolveCookieName(opts: CookieRefreshOptions): string {\n return opts.name ?? \"azlib_rt\";\n}\n\n/** Parses cookies from the raw `Cookie` header without a dependency on `cookie-parser`. */\nfunction readCookie(req: Request, name: string): string | undefined {\n const raw = req.headers.cookie ?? \"\";\n for (const part of raw.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n if (key === name) {\n try {\n return decodeURIComponent(part.slice(eq + 1).trim());\n } catch {\n return part.slice(eq + 1).trim();\n }\n }\n }\n return undefined;\n}\n\n/**\n * Writes the refresh token into a `Set-Cookie` header and returns a body-safe tokens\n * object that omits the refresh token.\n */\nfunction setCookieAndStripRefreshToken(\n res: Response,\n tokens: AuthTokens,\n opts: CookieRefreshOptions,\n): Omit<AuthTokens, \"refreshToken\" | \"refreshTokenExpiresAt\"> {\n const maxAge = tokens.refreshTokenExpiresAt.getTime() - Date.now();\n res.cookie(resolveCookieName(opts), tokens.refreshToken, {\n httpOnly: opts.httpOnly ?? true,\n secure: opts.secure ?? process.env.NODE_ENV === \"production\",\n sameSite: opts.sameSite ?? \"lax\",\n path: opts.path ?? \"/\",\n domain: opts.domain,\n maxAge,\n });\n const { refreshToken: _rt, refreshTokenExpiresAt: _exp, ...bodyTokens } = tokens;\n return bodyTokens;\n}\n\n/**\n * Sends the authentication result as JSON. In cookie mode the refresh token is stored\n * in a `Set-Cookie` header and excluded from the body.\n *\n * When the result is an MFA challenge (`{ kind: \"mfa_required\" }`), it is forwarded as-is\n * with HTTP 200 so the client knows to complete the TOTP step.\n */\nfunction sendLoginResult(\n res: Response,\n result: LoginResult,\n cookieOpts: CookieRefreshOptions | undefined,\n): void {\n if (\"mfaToken\" in result) {\n res.json(result);\n return;\n }\n sendAuthResult(res, result, cookieOpts);\n}\n\nfunction sendAuthResult(\n res: Response,\n result: AuthResult,\n cookieOpts: CookieRefreshOptions | undefined,\n): void {\n if (cookieOpts) {\n const tokens = setCookieAndStripRefreshToken(res, result.tokens, cookieOpts);\n res.json({ user: result.user, tokens });\n } else {\n res.json(result);\n }\n}\n\n/**\n * Reads the refresh token from the request. In cookie mode it is read from the cookie;\n * in body mode it is expected in `req.body.refreshToken`.\n */\nfunction readRefreshToken(req: Request, cookieOpts: CookieRefreshOptions | undefined): string | undefined {\n if (cookieOpts) {\n return readCookie(req, resolveCookieName(cookieOpts));\n }\n const body = req.body as Record<string, unknown> | undefined;\n const token = body?.[\"refreshToken\"];\n return typeof token === \"string\" ? token : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Router factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a pre-wired Express `Router` with all standard identity endpoints.\n *\n * Mount it once on your application:\n * ```ts\n * import express from \"express\";\n * import { createIdentityService } from \"@azlib/identity/node\";\n * import { createIdentityRouter, identityErrorHandler } from \"@azlib/identity/express\";\n *\n * const service = createIdentityService(config, store);\n * const app = express();\n *\n * app.use(express.json());\n * // Routes are served at /account/login, /account/register, etc. (default prefix)\n * app.use(createIdentityRouter(service));\n * // Or use a custom prefix:\n * app.use(createIdentityRouter(service, { prefix: \"auth\" }));\n * app.use(identityErrorHandler()); // optional convenience error handler\n * ```\n *\n * Pre-wired routes:\n *\n * | Method | Path | Description |\n * |--------|------|-------------|\n * | POST | `/register` | Create a new account |\n * | POST | `/login` | Email + password login |\n * | POST | `/refresh` | Rotate the refresh token |\n * | POST | `/logout` | Revoke the current session |\n * | GET | `/me` | Return the authenticated principal |\n * | GET | `/:provider` | Start an OAuth 2.0 authorisation flow *(optional)* |\n * | GET | `/:provider/callback` | Handle an OAuth 2.0 callback *(optional)* |\n */\nexport function createIdentityRouter(\n service: IdentityService,\n options: IdentityRouterOptions = {},\n): Router {\n const prefix = options.prefix ?? \"account\";\n const log = resolveLogger(options.logger);\n\n const router = Router();\n const cookieOpts: CookieRefreshOptions | undefined =\n typeof options.refreshToken === \"object\" && \"cookie\" in options.refreshToken\n ? options.refreshToken.cookie\n : undefined;\n\n // Request / response logging middleware\n router.use((_req: Request, res: Response, next: NextFunction): void => {\n const start = Date.now();\n res.on(\"finish\", () => {\n const ms = Date.now() - start;\n log.info(`${_req.method} ${_req.originalUrl} ${res.statusCode}`, { durationMs: ms });\n });\n next();\n });\n\n // POST /register\n router.post(\"/register\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const result = await service.register(req.body as { email: string; password: string; displayName?: string });\n sendAuthResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n });\n\n // POST /login\n router.post(\"/login\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const result = await service.login(req.body as { email: string; password: string });\n sendLoginResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n });\n\n // POST /refresh\n router.post(\"/refresh\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const token = readRefreshToken(req, cookieOpts);\n if (!token) {\n throw new UnauthenticatedError(\"Refresh token is missing.\");\n }\n const result = await service.refresh(token);\n sendAuthResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n });\n\n // POST /logout\n router.post(\"/logout\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const token = readRefreshToken(req, cookieOpts);\n if (token) {\n await service.logout(token);\n }\n if (cookieOpts) {\n res.clearCookie(resolveCookieName(cookieOpts), { path: cookieOpts.path ?? \"/\" });\n }\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n });\n\n // GET /me — requires a valid Bearer access token\n router.get(\"/me\", requireAuth(service), (_req: Request, res: Response): void => {\n res.json(_req.identity);\n });\n\n // ---------------------------------------------------------------------------\n // Email verification\n // ---------------------------------------------------------------------------\n\n // POST /request-email-verification — re-send verification link (requires auth)\n router.post(\n \"/request-email-verification\",\n requireAuth(service),\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n await service.requestEmailVerification(req.identity!.userId);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /verify-email — consume the token from the verification link\n router.post(\"/verify-email\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { token } = req.body as { token?: string };\n if (!token) {\n throw new UnauthenticatedError(\"token is required.\");\n }\n await service.verifyEmail(token);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n });\n\n // ---------------------------------------------------------------------------\n // Password reset\n // ---------------------------------------------------------------------------\n\n // POST /request-password-reset — send reset link by email\n router.post(\n \"/request-password-reset\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { email } = req.body as { email?: string };\n if (!email) {\n throw new UnauthenticatedError(\"email is required.\");\n }\n await service.requestPasswordReset(email);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /reset-password — set new password with token\n router.post(\"/reset-password\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { token, newPassword } = req.body as { token?: string; newPassword?: string };\n if (!token || !newPassword) {\n throw new UnauthenticatedError(\"token and newPassword are required.\");\n }\n await service.resetPassword(token, newPassword);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n });\n\n // ---------------------------------------------------------------------------\n // Two-factor authentication (2FA / TOTP)\n // ---------------------------------------------------------------------------\n\n // POST /mfa/verify — complete an MFA challenge after login\n router.post(\"/mfa/verify\", async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { mfaToken, code } = req.body as { mfaToken?: string; code?: string };\n if (!mfaToken || !code) {\n throw new UnauthenticatedError(\"mfaToken and code are required.\");\n }\n const result = await service.verifyMfaChallenge(mfaToken, code);\n sendAuthResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n });\n\n // POST /2fa/setup — begin 2FA setup (requires auth)\n router.post(\n \"/2fa/setup\",\n requireAuth(service),\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const result = await service.setup2FA(req.identity!.userId);\n res.json(result);\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /2fa/enable — confirm and enable 2FA (requires auth)\n router.post(\n \"/2fa/enable\",\n requireAuth(service),\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { code } = req.body as { code?: string };\n if (!code) {\n throw new UnauthenticatedError(\"code is required.\");\n }\n await service.enable2FA(req.identity!.userId, code);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /2fa/disable — disable 2FA (requires auth)\n router.post(\n \"/2fa/disable\",\n requireAuth(service),\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { code } = req.body as { code?: string };\n if (!code) {\n throw new UnauthenticatedError(\"code is required.\");\n }\n await service.disable2FA(req.identity!.userId, code);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // Account management (admin)\n // ---------------------------------------------------------------------------\n\n // POST /admin/lock/:userId — lock an account\n router.post(\n \"/admin/lock/:userId\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n await service.lockAccount(req.params[\"userId\"]!);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /admin/unlock/:userId — unlock an account\n router.post(\n \"/admin/unlock/:userId\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n await service.unlockAccount(req.params[\"userId\"]!);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /admin/disable/:userId — disable an account\n router.post(\n \"/admin/disable/:userId\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n await service.disableAccount(req.params[\"userId\"]!);\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // OAuth routes — only when providers are configured on the service\n if (service.oauth) {\n const oauthService = service.oauth;\n const baseUrl = options.oauthBaseUrl ?? \"\";\n\n const stateCookieOptions = {\n httpOnly: true,\n sameSite: \"lax\" as const,\n secure: process.env.NODE_ENV === \"production\",\n maxAge: 10 * 60 * 1000, // 10 minutes\n };\n\n for (const providerName of oauthService.providers) {\n const redirectUri = `${baseUrl}/${providerName}/callback`;\n\n router.get(\n `/${providerName}`,\n oauthAuthorize(oauthService, providerName, {\n redirectUri,\n setState: (_req, oauthRes, state) => {\n oauthRes.cookie(OAUTH_STATE_COOKIE, state, stateCookieOptions);\n },\n }),\n );\n\n router.get(\n `/${providerName}/callback`,\n oauthCallback(oauthService, providerName, {\n redirectUri,\n getState: (oauthReq) => readCookie(oauthReq, OAUTH_STATE_COOKIE) ?? null,\n setState: (_oauthReq, oauthRes, _state) => {\n oauthRes.clearCookie(OAUTH_STATE_COOKIE);\n },\n onSuccess: (_oauthReq, oauthRes, result) => {\n sendAuthResult(oauthRes, result, cookieOpts);\n },\n }),\n );\n }\n }\n\n // Mount the inner router at the configured prefix.\n const outer = Router();\n if (prefix) {\n outer.use(`/${prefix}`, router);\n } else {\n outer.use(router);\n }\n return outer;\n}\n\n// ---------------------------------------------------------------------------\n// Convenience error handler\n// ---------------------------------------------------------------------------\n\n/**\n * Factory that creates an Express error-handling middleware which converts\n * {@link IdentityError} instances to structured JSON responses.\n * Mount it after the identity router:\n *\n * ```ts\n * app.use(createIdentityRouter(service, { prefix: \"auth\" }));\n * app.use(identityErrorHandler()); // default console logger\n * app.use(identityErrorHandler({ logger: false })); // silent\n * ```\n *\n * @param options.logger - Logger for recording serialised error details.\n * Pass `false` to disable. Defaults to the console logger.\n */\nexport function identityErrorHandler(\n options: { logger?: IdentityLogger | false } = {},\n): (err: unknown, req: Request, res: Response, next: NextFunction) => void {\n const log = resolveLogger(options.logger);\n return (err: unknown, _req: Request, res: Response, next: NextFunction): void => {\n if (err instanceof IdentityError) {\n log.debug(\"identity error\", { code: err.code, status: err.statusCode, message: err.message });\n res.status(err.statusCode).json({ code: err.code, message: err.message });\n return;\n }\n log.error(\"unhandled error in identity router\", { error: String(err) });\n next(err);\n };\n}\n"],"mappings":"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"}