@azlib/identity 0.2.0 → 0.3.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/README.md +149 -40
- package/dist/cookie-header-DptIxRmW.mjs +76 -0
- package/dist/cookie-header-DptIxRmW.mjs.map +1 -0
- package/dist/cookie-header-Dqd5k5BU.cjs +115 -0
- package/dist/cookie-header-LFWHKQ04.d.cts +86 -0
- package/dist/cookie-header-LFWHKQ04.d.cts.map +1 -0
- package/dist/cookie-header-LFWHKQ04.d.mts +86 -0
- package/dist/cookie-header-LFWHKQ04.d.mts.map +1 -0
- package/dist/{errors-C2xZAatu.d.cts → errors-0Zh7EG06.d.cts} +4 -3
- package/dist/errors-0Zh7EG06.d.cts.map +1 -0
- package/dist/{errors-BGMwaW5s.d.mts → errors-DVDEUk9-.d.mts} +4 -3
- package/dist/errors-DVDEUk9-.d.mts.map +1 -0
- package/dist/express.cjs +34 -0
- package/dist/express.d.cts +5 -6
- package/dist/express.d.cts.map +1 -1
- package/dist/express.d.mts +5 -6
- package/dist/express.d.mts.map +1 -1
- package/dist/express.mjs +34 -1
- package/dist/express.mjs.map +1 -1
- package/dist/{identity-Bz9RDOvT.mjs → identity-D0qyca_c.mjs} +37 -3
- package/dist/identity-D0qyca_c.mjs.map +1 -0
- package/dist/{identity-4eP45YIP.cjs → identity-Yz1BVkrM.cjs} +46 -0
- package/dist/{identity-router-DBL20UWT.d.mts → identity-router-BiratpJR.d.mts} +22 -6
- package/dist/identity-router-BiratpJR.d.mts.map +1 -0
- package/dist/{identity-router-Dib30Waj.d.cts → identity-router-D4oqYI60.d.cts} +22 -6
- package/dist/identity-router-D4oqYI60.d.cts.map +1 -0
- package/dist/{identity-service-CLzKx8Z7.d.cts → identity-service-DE0Ffguz.d.cts} +3 -4
- package/dist/identity-service-DE0Ffguz.d.cts.map +1 -0
- package/dist/{identity-service-B9zrvE9z.d.mts → identity-service-JtsoA6lv.d.mts} +3 -4
- package/dist/identity-service-JtsoA6lv.d.mts.map +1 -0
- package/dist/identity-store-BRRahxcS.d.cts.map +1 -1
- package/dist/identity-store-BRRahxcS.d.mts.map +1 -1
- package/dist/index-CsYqHPvK.d.cts +54 -0
- package/dist/index-CsYqHPvK.d.cts.map +1 -0
- package/dist/index-LhSdoWEP.d.mts +54 -0
- package/dist/index-LhSdoWEP.d.mts.map +1 -0
- package/dist/index.cjs +11 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +3 -2
- package/dist/logger-CcCHJVVe.mjs.map +1 -1
- package/dist/nestjs.cjs +4 -5
- package/dist/nestjs.d.cts +3 -4
- package/dist/nestjs.d.cts.map +1 -1
- package/dist/nestjs.d.mts +3 -4
- package/dist/nestjs.d.mts.map +1 -1
- package/dist/nestjs.mjs +4 -5
- package/dist/nestjs.mjs.map +1 -1
- package/dist/next.cjs +285 -0
- package/dist/next.d.cts +58 -0
- package/dist/next.d.cts.map +1 -0
- package/dist/next.d.mts +58 -0
- package/dist/next.d.mts.map +1 -0
- package/dist/next.mjs +274 -0
- package/dist/next.mjs.map +1 -0
- package/dist/node.cjs +18 -19
- package/dist/node.d.cts +5 -5
- package/dist/node.d.cts.map +1 -1
- package/dist/node.d.mts +5 -5
- package/dist/node.d.mts.map +1 -1
- package/dist/node.mjs +10 -20
- package/dist/node.mjs.map +1 -1
- package/dist/test-utils.d.cts +0 -1
- package/dist/test-utils.d.cts.map +1 -1
- package/dist/test-utils.d.mts +0 -1
- package/dist/test-utils.d.mts.map +1 -1
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +18 -18
- package/schema/model.ts +110 -22
- package/dist/errors-BGMwaW5s.d.mts.map +0 -1
- package/dist/errors-C2xZAatu.d.cts.map +0 -1
- package/dist/identity-Bz9RDOvT.mjs.map +0 -1
- package/dist/identity-router-DBL20UWT.d.mts.map +0 -1
- package/dist/identity-router-Dib30Waj.d.cts.map +0 -1
- package/dist/identity-service-B9zrvE9z.d.mts.map +0 -1
- package/dist/identity-service-CLzKx8Z7.d.cts.map +0 -1
- package/dist/index-CpufYgyn.d.cts +0 -30
- package/dist/index-CpufYgyn.d.cts.map +0 -1
- package/dist/index-CpufYgyn.d.mts +0 -30
- package/dist/index-CpufYgyn.d.mts.map +0 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//#region core/cookie-header.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cookie names and Cookie-header parsing for identity sessions.
|
|
4
|
+
*
|
|
5
|
+
* Edge-safe: no Node APIs. Use {@link hasIdentityCookies} in Next.js `proxy.ts` /
|
|
6
|
+
* middleware to gate routes without hitting the database.
|
|
7
|
+
*/
|
|
8
|
+
/** Default HttpOnly cookie that holds the short-lived access JWT. */
|
|
9
|
+
declare const DEFAULT_ACCESS_COOKIE = "azlib_at";
|
|
10
|
+
/** Default HttpOnly cookie that holds the rotating refresh token. */
|
|
11
|
+
declare const DEFAULT_REFRESH_COOKIE = "azlib_rt";
|
|
12
|
+
/** Cookie attribute set shared by access and refresh cookies. */
|
|
13
|
+
interface IdentityCookieAttributes {
|
|
14
|
+
/** Mark cookies as `HttpOnly`. Default: `true`. */
|
|
15
|
+
httpOnly?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Mark cookies as `Secure`. Defaults to `true` when `NODE_ENV` is `"production"`,
|
|
18
|
+
* `false` otherwise.
|
|
19
|
+
*/
|
|
20
|
+
secure?: boolean;
|
|
21
|
+
/** `SameSite` policy. Default: `"lax"`. */
|
|
22
|
+
sameSite?: "strict" | "lax" | "none";
|
|
23
|
+
/** Cookie path. Default: `"/"`. */
|
|
24
|
+
path?: string;
|
|
25
|
+
/** Cookie domain. Omit to use the current host. */
|
|
26
|
+
domain?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Names and attributes for the access + refresh cookie pair. */
|
|
29
|
+
interface IdentityCookieOptions extends IdentityCookieAttributes {
|
|
30
|
+
/** Access-token cookie name. Default: {@link DEFAULT_ACCESS_COOKIE}. */
|
|
31
|
+
accessCookie?: string;
|
|
32
|
+
/** Refresh-token cookie name. Default: {@link DEFAULT_REFRESH_COOKIE}. */
|
|
33
|
+
refreshCookie?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Resolved cookie names. */
|
|
36
|
+
interface IdentityCookieNames {
|
|
37
|
+
access: string;
|
|
38
|
+
refresh: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A Next.js `cookies().get` / Request-cookies compatible reader.
|
|
42
|
+
*/
|
|
43
|
+
interface CookieReader {
|
|
44
|
+
get(name: string): {
|
|
45
|
+
value: string;
|
|
46
|
+
} | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A Next.js `cookies().set` / `cookies().delete` compatible writer.
|
|
50
|
+
* `maxAge` is in **seconds** (Web / Next.js convention, not Express milliseconds).
|
|
51
|
+
*/
|
|
52
|
+
interface CookieWriter {
|
|
53
|
+
set(name: string, value: string, options?: {
|
|
54
|
+
httpOnly?: boolean;
|
|
55
|
+
secure?: boolean;
|
|
56
|
+
sameSite?: "strict" | "lax" | "none";
|
|
57
|
+
path?: string;
|
|
58
|
+
domain?: string;
|
|
59
|
+
maxAge?: number;
|
|
60
|
+
expires?: Date;
|
|
61
|
+
}): unknown;
|
|
62
|
+
delete(name: string, options?: {
|
|
63
|
+
path?: string;
|
|
64
|
+
domain?: string;
|
|
65
|
+
}): unknown;
|
|
66
|
+
}
|
|
67
|
+
type CookieStore = CookieReader & CookieWriter;
|
|
68
|
+
/** Anything {@link hasIdentityCookies} / {@link readCookieValue} can inspect. */
|
|
69
|
+
type CookieSource = CookieReader | Headers | string | null | undefined;
|
|
70
|
+
declare function resolveCookieNames(options?: IdentityCookieOptions): IdentityCookieNames;
|
|
71
|
+
declare function resolveCookieAttributes(options?: IdentityCookieOptions): Required<Pick<IdentityCookieAttributes, "httpOnly" | "sameSite" | "path">> & IdentityCookieAttributes;
|
|
72
|
+
/**
|
|
73
|
+
* Parses a raw `Cookie` header into a name → value map.
|
|
74
|
+
* Values are `decodeURIComponent`'d when possible.
|
|
75
|
+
*/
|
|
76
|
+
declare function parseCookieHeader(header: string | null | undefined): Map<string, string>;
|
|
77
|
+
/** Reads a single cookie value from a header string, `Headers`, or cookie reader. */
|
|
78
|
+
declare function readCookieValue(source: CookieSource, name: string): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Returns true when the request carries an identity access and/or refresh cookie.
|
|
81
|
+
* Intended for Edge/proxy gates: presence only — do not treat this as authentication.
|
|
82
|
+
*/
|
|
83
|
+
declare function hasIdentityCookies(source: CookieSource, options?: IdentityCookieOptions): boolean;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { DEFAULT_ACCESS_COOKIE as a, IdentityCookieNames as c, parseCookieHeader as d, readCookieValue as f, CookieWriter as i, IdentityCookieOptions as l, resolveCookieNames as m, CookieSource as n, DEFAULT_REFRESH_COOKIE as o, resolveCookieAttributes as p, CookieStore as r, IdentityCookieAttributes as s, CookieReader as t, hasIdentityCookies as u };
|
|
86
|
+
//# sourceMappingURL=cookie-header-LFWHKQ04.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-header-LFWHKQ04.d.mts","names":[],"sources":["../core/cookie-header.ts"],"mappings":";;;;;;;;cAQa;;cAGA;;UAGI;;EAEf;;;;;EAKA;;EAEA;;EAEA;;EAEA;;;UAIe,8BAA8B;;EAE7C;;EAEA;;;UAIe;EACf;EACA;;;;;UAMe;EACf,IAAI;IAAiB;;;;;;;UAON;EACf,IACE,cACA,eACA;IACE;IACA;IACA;IACA;IACA;IACA;IACA,UAAU;;EAGd,OAAO,cAAc;IAAY;IAAe;;;KAGtC,cAAc,eAAe;;KAG7B,eACR,eACA;iBAKY,mBACd,UAAS,wBACR;iBAOa,wBACd,UAAS,wBACR,SAAS,KAAK,+DACf;;;;;iBAcc,kBACd,oCACC;;iBA8Ba,gBACd,QAAQ,cACR;;;;;iBAqBc,mBACd,QAAQ,cACR,UAAS"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { c as AuthResult, d as AuthenticatedIdentity, h as IdentityEventType, m as IdentityEvent, r as IdentityStore, s as AccessTokenClaims, x as Permission } from "./identity-store-BRRahxcS.cjs";
|
|
2
|
-
|
|
3
2
|
//#region core/authorization.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Context passed to authorization checks. `resource` is an opaque consumer-supplied
|
|
@@ -238,6 +237,8 @@ interface IdentityConfig {
|
|
|
238
237
|
generateId: () => string;
|
|
239
238
|
onEvent: ((event: IdentityEvent) => void | Promise<void>) | undefined;
|
|
240
239
|
}
|
|
240
|
+
/** Default JWT `iss` claim used when `IdentityConfigInput.issuer` is omitted. */
|
|
241
|
+
declare const DEFAULT_ISSUER = "azlib-identity";
|
|
241
242
|
/**
|
|
242
243
|
* Validates and applies defaults to consumer-provided configuration.
|
|
243
244
|
* Throws {@link IdentityConfigError} when required values are missing or invalid.
|
|
@@ -477,5 +478,5 @@ declare class AccountUnavailableError extends IdentityError {
|
|
|
477
478
|
constructor(message?: string);
|
|
478
479
|
}
|
|
479
480
|
//#endregion
|
|
480
|
-
export {
|
|
481
|
-
//# sourceMappingURL=errors-
|
|
481
|
+
export { LockoutConfig as A, AuthorizationContext as B, createTokenService as C, IdentityConfig as D, DEFAULT_ISSUER as E, TwoFactorSmsParams as F, isAuthorized as G, AuthorizationRequirement as H, IdentityLogger as I, consoleLogger as L, EmailVerificationParams as M, NotificationService as N, IdentityConfigInput as O, PasswordResetParams as P, noopLogger as R, TokenService as S, createAuditLogger as T, PolicyRule as U, AuthorizationDecision as V, evaluateAuthorization as W, ExchangeCodeParams as _, IdentityError as a, OAuthUserInfo as b, UnauthenticatedError as c, OAuthProviderNotFoundError as d, OAuthService as f, BuildAuthUrlParams as g, createOAuthService as h, IdentityConfigError as i, resolveIdentityConfig as j, IdentityOverrides as k, OAuthAuthorizationUrl as l, OAuthStateMismatchError as m, EmailAlreadyRegisteredError as n, InvalidCredentialsError as o, OAuthServiceDeps as p, ForbiddenError as r, InvalidTokenError as s, AccountUnavailableError as t, OAuthCallbackParams as u, OAuthProvider as v, AuditLogger as w, SessionDeps as x, OAuthTokens as y, resolveLogger as z };
|
|
482
|
+
//# sourceMappingURL=errors-0Zh7EG06.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-0Zh7EG06.d.cts","names":[],"sources":["../core/authorization.ts","../core/logger.ts","../core/notification.ts","../core/config.ts","../core/audit.ts","../core/token-service.ts","../core/session-state.ts","../core/oauth/oauth-provider.ts","../core/oauth/oauth-service.ts","../core/errors.ts"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { c as AuthResult, d as AuthenticatedIdentity, h as IdentityEventType, m as IdentityEvent, r as IdentityStore, s as AccessTokenClaims, x as Permission } from "./identity-store-BRRahxcS.mjs";
|
|
2
|
-
|
|
3
2
|
//#region core/authorization.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Context passed to authorization checks. `resource` is an opaque consumer-supplied
|
|
@@ -238,6 +237,8 @@ interface IdentityConfig {
|
|
|
238
237
|
generateId: () => string;
|
|
239
238
|
onEvent: ((event: IdentityEvent) => void | Promise<void>) | undefined;
|
|
240
239
|
}
|
|
240
|
+
/** Default JWT `iss` claim used when `IdentityConfigInput.issuer` is omitted. */
|
|
241
|
+
declare const DEFAULT_ISSUER = "azlib-identity";
|
|
241
242
|
/**
|
|
242
243
|
* Validates and applies defaults to consumer-provided configuration.
|
|
243
244
|
* Throws {@link IdentityConfigError} when required values are missing or invalid.
|
|
@@ -477,5 +478,5 @@ declare class AccountUnavailableError extends IdentityError {
|
|
|
477
478
|
constructor(message?: string);
|
|
478
479
|
}
|
|
479
480
|
//#endregion
|
|
480
|
-
export {
|
|
481
|
-
//# sourceMappingURL=errors-
|
|
481
|
+
export { LockoutConfig as A, AuthorizationContext as B, createTokenService as C, IdentityConfig as D, DEFAULT_ISSUER as E, TwoFactorSmsParams as F, isAuthorized as G, AuthorizationRequirement as H, IdentityLogger as I, consoleLogger as L, EmailVerificationParams as M, NotificationService as N, IdentityConfigInput as O, PasswordResetParams as P, noopLogger as R, TokenService as S, createAuditLogger as T, PolicyRule as U, AuthorizationDecision as V, evaluateAuthorization as W, ExchangeCodeParams as _, IdentityError as a, OAuthUserInfo as b, UnauthenticatedError as c, OAuthProviderNotFoundError as d, OAuthService as f, BuildAuthUrlParams as g, createOAuthService as h, IdentityConfigError as i, resolveIdentityConfig as j, IdentityOverrides as k, OAuthAuthorizationUrl as l, OAuthStateMismatchError as m, EmailAlreadyRegisteredError as n, InvalidCredentialsError as o, OAuthServiceDeps as p, ForbiddenError as r, InvalidTokenError as s, AccountUnavailableError as t, OAuthCallbackParams as u, OAuthProvider as v, AuditLogger as w, SessionDeps as x, OAuthTokens as y, resolveLogger as z };
|
|
482
|
+
//# sourceMappingURL=errors-DVDEUk9-.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-DVDEUk9-.d.mts","names":[],"sources":["../core/authorization.ts","../core/logger.ts","../core/notification.ts","../core/config.ts","../core/audit.ts","../core/token-service.ts","../core/session-state.ts","../core/oauth/oauth-provider.ts","../core/oauth/oauth-service.ts","../core/errors.ts"],"mappings":";;;;;;UAMiB,qBAAqB;EACpC,WAAW;;EAEX;;EAEA,WAAW;;;;;;;;KASD,WAAW,wBACrB,SAAS,qBAAqB,qCACL;;UAGV,yBAAyB;;EAExC,aAAa;;EAEb,SAAS,WAAW;;;UAIL;EACf;;EAEA;;;;;;;;iBAcoB,sBAAsB,qBAC1C,aAAa,yBAAyB,YACtC,SAAS,qBAAqB,aAC7B,QAAQ;;iBA0BW,aAAa,qBACjC,aAAa,yBAAyB,YACtC,SAAS,qBAAqB,aAC7B;;;;;;;;;;;;;;;;;;UCnEc;EACf,MAAM,iBAAiB,OAAO;EAC9B,KAAK,iBAAiB,OAAO;EAC7B,KAAK,iBAAiB,OAAO;EAC7B,MAAM,iBAAiB,OAAO;;;;;;cAOnB,eAAe;;cAYf,YAAY;;;;;;;;iBAcT,cAAc,SAAS,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UChB/C;EACf;EACA;;;;;EAKA;;;UAIe;EACf;EACA;;;;;EAKA;;;UAIe;EACf;;EAEA;;;;;;;;UASe;;EAEf,uBAAuB,QAAQ,0BAA0B;;EAEzD,mBAAmB,QAAQ,sBAAsB;;EAEjD,mBAAmB,QAAQ,qBAAqB;;;;;;;;UClEjC;;EAEf,YAAY;;EAEZ;;;;;;EAMA,WAAW,OAAO,yBAAyB;;;UAI5B;;;;;EAKf;;;;;EAKA;;;UAIe;;;;;EAKf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA,UAAU,QAAQ;;;;;EAKlB,gBAAgB;;;;;;EAMhB,SAAS;;EAET,YAAY;;;UAIG;EACf;EACA;EACA;EACA;EACA;EACA;EACA,SAAS;EACT,eAAe;;EAEf,QAAQ;EACR,WAAW;EACX;EACA,WAAW,OAAO,yBAAyB;;;cAMhC;;;;;iBASG,sBACd,OAAO,sBACN;;;;;;;;;UChGc;EACf,OACE,MAAM,mBACN,uBACA,WAAW,4BACV;;iBAGW,kBACd,OAAO,eACP,WAAW,MACX,WAAW,OAAO,yBAAyB,gBAC1C;;;;;;;UCVc;EACf,iBACE,gBACA,sBACC;IAAU;IAAe,WAAW;;EACvC,kBAAkB,gBAAgB,QAAQ;;;;;EAK1C,mBAAmB;IAAsB;IAAe;;;EAExD,eAAe;;EAEf,iBAAiB;;iBAMH,mBAAmB,QAAQ,iBAAiB;;;;UCzB3C;EACf,QAAQ;EACR,OAAO;EACP,cAAc;;;;;;;;;;;UCDC;EACf;;EAEA;EACA;;EAEA;EACA;EACA;;;;;;UAOe;;EAEf;EACA;;EAEA;EACA;;;UAIe;;EAEf;;;;;EAKA;;EAEA;;;UAIe;EACf;EACA;;;;;;UAOe;;;;;WAKN;;;;;;EAOT,sBAAsB,QAAQ;;;;;EAM9B,aAAa,QAAQ,qBAAqB,QAAQ;;;;;EAMlD,cAAc,QAAQ,cAAc,QAAQ;;;;;UCvE7B;;EAEf;;;;;EAKA;;;UAIe;;EAEf;;EAEA;;;;;EAKA;;EAEA;;EAEA;;;UAIe;;WAEN;;;;;EAMT,sBACE,sBACA,qBACA,oBACC;;;;;;;;EASH,eACE,sBACA,QAAQ,sBACP,QAAQ;;;cAIA,mCAAmC;EAC9C,YAAY;;;cAOD,gCAAgC;EAC3C;;;UASe;EACf,oBAAoB;EACpB,QAAQ;EACR,OAAO;EACP,aAAa;EACb,OAAO;;;;;;;iBAQO,mBAAmB,MAAM,mBAAmB;;;;;;;;;;cCvF/C,sBAAsB;;WAExB;;WAEA;EAET,YAAY,cAAc,iBAAiB;;;cAShC,4BAA4B;EACvC,YAAY;;;cAMD,gCAAgC;EAC3C;;;cAMW,oCAAoC;EAC/C;;;cAUW,0BAA0B;EACrC,YAAY;;;cAMD,6BAA6B;EACxC,YAAY;;;cAMD,uBAAuB;EAClC,YAAY;;;cAMD,gCAAgC;EAC3C,YAAY"}
|
package/dist/express.cjs
CHANGED
|
@@ -396,8 +396,42 @@ function createIdentityRouter(service, options = {}) {
|
|
|
396
396
|
else outer.use(router);
|
|
397
397
|
return outer;
|
|
398
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Factory that creates an Express error-handling middleware which converts
|
|
401
|
+
* {@link IdentityError} instances to structured JSON responses.
|
|
402
|
+
* Mount it after the identity router:
|
|
403
|
+
*
|
|
404
|
+
* ```ts
|
|
405
|
+
* app.use(createIdentityRouter(service, { prefix: "auth" }));
|
|
406
|
+
* app.use(identityErrorHandler()); // default console logger
|
|
407
|
+
* app.use(identityErrorHandler({ logger: false })); // silent
|
|
408
|
+
* ```
|
|
409
|
+
*
|
|
410
|
+
* @param options.logger - Logger for recording serialised error details.
|
|
411
|
+
* Pass `false` to disable. Defaults to the console logger.
|
|
412
|
+
*/
|
|
413
|
+
function identityErrorHandler(options = {}) {
|
|
414
|
+
const log = require_logger.resolveLogger(options.logger);
|
|
415
|
+
return (err, _req, res, next) => {
|
|
416
|
+
if (err instanceof require_errors.IdentityError) {
|
|
417
|
+
log.debug("identity error", {
|
|
418
|
+
code: err.code,
|
|
419
|
+
status: err.statusCode,
|
|
420
|
+
message: err.message
|
|
421
|
+
});
|
|
422
|
+
res.status(err.statusCode).json({
|
|
423
|
+
code: err.code,
|
|
424
|
+
message: err.message
|
|
425
|
+
});
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
log.error("unhandled error in identity router", { error: String(err) });
|
|
429
|
+
next(err);
|
|
430
|
+
};
|
|
431
|
+
}
|
|
399
432
|
//#endregion
|
|
400
433
|
exports.createIdentityRouter = createIdentityRouter;
|
|
434
|
+
exports.identityErrorHandler = identityErrorHandler;
|
|
401
435
|
exports.oauthAuthorize = oauthAuthorize;
|
|
402
436
|
exports.oauthCallback = oauthCallback;
|
|
403
437
|
exports.requireAuth = requireAuth;
|
package/dist/express.d.cts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { c as AuthResult, d as AuthenticatedIdentity } from "./identity-store-BRRahxcS.cjs";
|
|
2
|
-
import {
|
|
3
|
-
import { t as IdentityService } from "./identity-service-
|
|
4
|
-
import { n as IdentityRouterOptions, r as createIdentityRouter, t as CookieRefreshOptions } from "./identity-router-
|
|
5
|
-
import { Request, RequestHandler, Response } from "express";
|
|
6
|
-
|
|
2
|
+
import { H as AuthorizationRequirement, f as OAuthService } from "./errors-0Zh7EG06.cjs";
|
|
3
|
+
import { t as IdentityService } from "./identity-service-DE0Ffguz.cjs";
|
|
4
|
+
import { i as identityErrorHandler, n as IdentityRouterOptions, r as createIdentityRouter, t as CookieRefreshOptions } from "./identity-router-D4oqYI60.cjs";
|
|
5
|
+
import { NextFunction, Request, RequestHandler, Response } from "express";
|
|
7
6
|
//#region core/express/middleware.d.ts
|
|
8
7
|
declare global {
|
|
9
8
|
namespace Express {
|
|
@@ -83,5 +82,5 @@ declare function oauthAuthorize(oauthService: OAuthService, providerName: string
|
|
|
83
82
|
*/
|
|
84
83
|
declare function oauthCallback(oauthService: OAuthService, providerName: string, options: OAuthRouteOptions): RequestHandler;
|
|
85
84
|
//#endregion
|
|
86
|
-
export { CookieRefreshOptions, IdentityRouterOptions, OAuthRouteOptions, ResourceLoader, createIdentityRouter, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
85
|
+
export { CookieRefreshOptions, IdentityRouterOptions, OAuthRouteOptions, type ResourceLoader, createIdentityRouter, identityErrorHandler, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
87
86
|
//# sourceMappingURL=express.d.cts.map
|
package/dist/express.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.cts","names":[],"sources":["../core/express/middleware.ts","../core/express/oauth-routes.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.d.cts","names":[],"sources":["../core/express/middleware.ts","../core/express/oauth-routes.ts"],"mappings":";;;;;;QAOQ;YAEI;cACE;;MAER,WAAW;;;;;KAML,eAAe,cACzB,KAAK,YACF,YAAY,QAAQ;;;;;;iBAcT,YAAY,SAAS,kBAAkB;;;;;iBAuBvC,qBAAqB,qBACnC,SAAS,iBACT,aAAa,yBAAyB,YACtC,eAAe,eAAe,aAC7B;;iBA4Ba,kBACd,SAAS,iBACT,qBACC;;;;;;UCpFc;;EAEf;;;;;EAKA,SAAS,KAAK,0BAA0B;;;;;EAKxC,SAAS,KAAK,SAAS,KAAK,UAAU,uBAAuB;;;;;EAK7D,UACE,KAAK,SACL,KAAK,UACL,QADK,oBAEG;;;;;;;;;;;;;;;;iBAiBI,eACd,cAAc,cACd,sBACA,SAAS,KAAK,iDACb;;;;;;;;;;;;;;;iBAiCa,cACd,cAAc,cACd,sBACA,SAAS,oBACR"}
|
package/dist/express.d.mts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { c as AuthResult, d as AuthenticatedIdentity } from "./identity-store-BRRahxcS.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { t as IdentityService } from "./identity-service-
|
|
4
|
-
import { n as IdentityRouterOptions, r as createIdentityRouter, t as CookieRefreshOptions } from "./identity-router-
|
|
5
|
-
import { Request, RequestHandler, Response } from "express";
|
|
6
|
-
|
|
2
|
+
import { H as AuthorizationRequirement, f as OAuthService } from "./errors-DVDEUk9-.mjs";
|
|
3
|
+
import { t as IdentityService } from "./identity-service-JtsoA6lv.mjs";
|
|
4
|
+
import { i as identityErrorHandler, n as IdentityRouterOptions, r as createIdentityRouter, t as CookieRefreshOptions } from "./identity-router-BiratpJR.mjs";
|
|
5
|
+
import { NextFunction, Request, RequestHandler, Response } from "express";
|
|
7
6
|
//#region core/express/middleware.d.ts
|
|
8
7
|
declare global {
|
|
9
8
|
namespace Express {
|
|
@@ -83,5 +82,5 @@ declare function oauthAuthorize(oauthService: OAuthService, providerName: string
|
|
|
83
82
|
*/
|
|
84
83
|
declare function oauthCallback(oauthService: OAuthService, providerName: string, options: OAuthRouteOptions): RequestHandler;
|
|
85
84
|
//#endregion
|
|
86
|
-
export { CookieRefreshOptions, IdentityRouterOptions, OAuthRouteOptions, ResourceLoader, createIdentityRouter, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
85
|
+
export { CookieRefreshOptions, IdentityRouterOptions, OAuthRouteOptions, type ResourceLoader, createIdentityRouter, identityErrorHandler, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
87
86
|
//# sourceMappingURL=express.d.mts.map
|
package/dist/express.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.mts","names":[],"sources":["../core/express/middleware.ts","../core/express/oauth-routes.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"express.d.mts","names":[],"sources":["../core/express/middleware.ts","../core/express/oauth-routes.ts"],"mappings":";;;;;;QAOQ;YAEI;cACE;;MAER,WAAW;;;;;KAML,eAAe,cACzB,KAAK,YACF,YAAY,QAAQ;;;;;;iBAcT,YAAY,SAAS,kBAAkB;;;;;iBAuBvC,qBAAqB,qBACnC,SAAS,iBACT,aAAa,yBAAyB,YACtC,eAAe,eAAe,aAC7B;;iBA4Ba,kBACd,SAAS,iBACT,qBACC;;;;;;UCpFc;;EAEf;;;;;EAKA,SAAS,KAAK,0BAA0B;;;;;EAKxC,SAAS,KAAK,SAAS,KAAK,UAAU,uBAAuB;;;;;EAK7D,UACE,KAAK,SACL,KAAK,UACL,QADK,oBAEG;;;;;;;;;;;;;;;;iBAiBI,eACd,cAAc,cACd,sBACA,SAAS,KAAK,iDACb;;;;;;;;;;;;;;;iBAiCa,cACd,cAAc,cACd,sBACA,SAAS,oBACR"}
|
package/dist/express.mjs
CHANGED
|
@@ -395,7 +395,40 @@ function createIdentityRouter(service, options = {}) {
|
|
|
395
395
|
else outer.use(router);
|
|
396
396
|
return outer;
|
|
397
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Factory that creates an Express error-handling middleware which converts
|
|
400
|
+
* {@link IdentityError} instances to structured JSON responses.
|
|
401
|
+
* Mount it after the identity router:
|
|
402
|
+
*
|
|
403
|
+
* ```ts
|
|
404
|
+
* app.use(createIdentityRouter(service, { prefix: "auth" }));
|
|
405
|
+
* app.use(identityErrorHandler()); // default console logger
|
|
406
|
+
* app.use(identityErrorHandler({ logger: false })); // silent
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* @param options.logger - Logger for recording serialised error details.
|
|
410
|
+
* Pass `false` to disable. Defaults to the console logger.
|
|
411
|
+
*/
|
|
412
|
+
function identityErrorHandler(options = {}) {
|
|
413
|
+
const log = resolveLogger(options.logger);
|
|
414
|
+
return (err, _req, res, next) => {
|
|
415
|
+
if (err instanceof IdentityError) {
|
|
416
|
+
log.debug("identity error", {
|
|
417
|
+
code: err.code,
|
|
418
|
+
status: err.statusCode,
|
|
419
|
+
message: err.message
|
|
420
|
+
});
|
|
421
|
+
res.status(err.statusCode).json({
|
|
422
|
+
code: err.code,
|
|
423
|
+
message: err.message
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
log.error("unhandled error in identity router", { error: String(err) });
|
|
428
|
+
next(err);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
398
431
|
//#endregion
|
|
399
|
-
export { createIdentityRouter, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
432
|
+
export { createIdentityRouter, identityErrorHandler, oauthAuthorize, oauthCallback, requireAuth, requireAuthorization, requirePermission };
|
|
400
433
|
|
|
401
434
|
//# sourceMappingURL=express.mjs.map
|
package/dist/express.mjs.map
CHANGED
|
@@ -1 +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":";;;;AAoBA,MAAM,sBAAsB,QAAgC;CAC1D,MAAM,SAAS,IAAI,QAAQ;CAC3B,IAAI,CAAC,UAAU,CAAC,OAAO,WAAW,SAAS,GAAG,OAAO;CACrD,MAAM,QAAQ,OAAO,MAAM,CAAgB,EAAE,KAAK;CAClD,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;;;;;AAOA,SAAgB,YAAY,SAA0C;CACpE,OAAO,OAAO,KAAc,MAAgB,SAAsC;EAChF,IAAI;GACF,MAAM,QAAQ,mBAAmB,GAAG;GACpC,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB;GAEjC,IAAI,WAAW,MAAM,QAAQ,aAAa,KAAK;GAC/C,KAAK;EACP,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;;;AAMA,SAAgB,qBACd,SACA,aACA,cACgB;CAChB,OAAO,OAAO,KAAc,MAAgB,SAAsC;EAChF,IAAI;GACF,MAAM,YAAY,IAAI;GACtB,IAAI,CAAC,WACH,MAAM,IAAI,qBAAqB;GAEjC,MAAM,WAAW,eAAe,MAAM,aAAa,GAAG,IAAI,KAAA;GAM1D,IAAI,EAAC,MALkB,QAAQ,UAAU,aAAa;IACpD;IACA,QAAQ,YAAY,cAAc;IAClC;GACF,CAAC,GACa,SACZ,MAAM,IAAI,eAAe;GAE3B,KAAK;EACP,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;AAGA,SAAgB,kBACd,SACA,YACgB;CAChB,OAAO,qBAAqB,SAAS,EAAE,WAAW,CAAC;AACrD;;;;;;;;;;;;;;;;;AC1CA,SAAgB,eACd,cACA,cACA,SACgB;CAChB,OAAO,OAAO,KAAc,KAAe,SAAsC;EAC/E,IAAI;GACF,MAAM,EAAE,KAAK,UAAU,aAAa,sBAAsB,cAAc,QAAQ,WAAW;GAC3F,MAAM,QAAQ,SAAS,KAAK,KAAK,KAAK;GACtC,IAAI,SAAS,GAAG;EAClB,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,cACd,cACA,cACA,SACgB;CAChB,OAAO,OAAO,KAAc,KAAe,SAAsC;EAC/E,IAAI;GACF,MAAM,OAAO,OAAO,IAAI,MAAM,YAAY,WAAW,IAAI,MAAM,UAAU;GACzE,MAAM,QAAQ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW;GAC5E,MAAM,QAAQ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW;GAE5E,IAAI,OAKF,MAAM,IAAI,cAAc,wBAAwB,mBAH9C,OAAO,IAAI,MAAM,yBAAyB,WACtC,IAAI,MAAM,uBACV,SACiF,GAAG;GAG5F,IAAI,CAAC,QAAQ,CAAC,OACZ,MAAM,IAAI,cACR,mCACA,4CACA,GACF;GAGF,MAAM,gBAAgB,MAAM,QAAQ,SAAS,GAAG;GAChD,IAAI,CAAC,eACH,MAAM,IAAI,cACR,gCACA,kEACA,GACF;GAGF,MAAM,SAAS,MAAM,aAAa,eAAe,cAAc;IAC7D;IACA;IACA;IACA,aAAa,QAAQ;GACvB,CAAC;GAED,MAAM,QAAQ,UAAU,KAAK,KAAK,MAAM;EAC1C,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;ACzBA,MAAM,qBAAqB;AAE3B,SAAS,kBAAkB,MAAoC;CAC7D,OAAO,KAAK,QAAQ;AACtB;;AAGA,SAAS,WAAW,KAAc,MAAkC;CAClE,MAAM,MAAM,IAAI,QAAQ,UAAU;CAClC,KAAK,MAAM,QAAQ,IAAI,MAAM,GAAG,GAAG;EACjC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;EAEf,IADY,KAAK,MAAM,GAAG,EAAE,EAAE,KACxB,MAAM,MACV,IAAI;GACF,OAAO,mBAAmB,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC;EACrD,QAAQ;GACN,OAAO,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK;EACjC;CAEJ;AAEF;;;;;AAMA,SAAS,8BACP,KACA,QACA,MAC4D;CAC5D,MAAM,SAAS,OAAO,sBAAsB,QAAQ,IAAI,KAAK,IAAI;CACjE,IAAI,OAAO,kBAAkB,IAAI,GAAG,OAAO,cAAc;EACvD,UAAU,KAAK,YAAY;EAC3B,QAAQ,KAAK,UAAU,QAAQ,IAAI,aAAa;EAChD,UAAU,KAAK,YAAY;EAC3B,MAAM,KAAK,QAAQ;EACnB,QAAQ,KAAK;EACb;CACF,CAAC;CACD,MAAM,EAAE,cAAc,KAAK,uBAAuB,MAAM,GAAG,eAAe;CAC1E,OAAO;AACT;;;;;;;;AASA,SAAS,gBACP,KACA,QACA,YACM;CACN,IAAI,cAAc,QAAQ;EACxB,IAAI,KAAK,MAAM;EACf;CACF;CACA,eAAe,KAAK,QAAQ,UAAU;AACxC;AAEA,SAAS,eACP,KACA,QACA,YACM;CACN,IAAI,YAAY;EACd,MAAM,SAAS,8BAA8B,KAAK,OAAO,QAAQ,UAAU;EAC3E,IAAI,KAAK;GAAE,MAAM,OAAO;GAAM;EAAO,CAAC;CACxC,OACE,IAAI,KAAK,MAAM;AAEnB;;;;;AAMA,SAAS,iBAAiB,KAAc,YAAkE;CACxG,IAAI,YACF,OAAO,WAAW,KAAK,kBAAkB,UAAU,CAAC;CAGtD,MAAM,QADO,IAAI,OACI;CACrB,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,qBACd,SACA,UAAiC,CAAC,GAC1B;CACR,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,MAAM,cAAc,QAAQ,MAAM;CAExC,MAAM,SAAS,OAAO;CACtB,MAAM,aACJ,OAAO,QAAQ,iBAAiB,YAAY,YAAY,QAAQ,eAC5D,QAAQ,aAAa,SACrB,KAAA;CAGN,OAAO,KAAK,MAAe,KAAe,SAA6B;EACrE,MAAM,QAAQ,KAAK,IAAI;EACvB,IAAI,GAAG,gBAAgB;GACrB,MAAM,KAAK,KAAK,IAAI,IAAI;GACxB,IAAI,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,YAAY,GAAG,IAAI,cAAc,EAAE,YAAY,GAAG,CAAC;EACrF,CAAC;EACD,KAAK;CACP,CAAC;CAGD,OAAO,KAAK,aAAa,OAAO,KAAc,KAAe,SAAsC;EACjG,IAAI;GAEF,eAAe,KAAK,MADC,QAAQ,SAAS,IAAI,IAAiE,GAC/E,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAGD,OAAO,KAAK,UAAU,OAAO,KAAc,KAAe,SAAsC;EAC9F,IAAI;GAEF,gBAAgB,KAAK,MADA,QAAQ,MAAM,IAAI,IAA2C,GACrD,UAAU;EACzC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAGD,OAAO,KAAK,YAAY,OAAO,KAAc,KAAe,SAAsC;EAChG,IAAI;GACF,MAAM,QAAQ,iBAAiB,KAAK,UAAU;GAC9C,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,2BAA2B;GAG5D,eAAe,KAAK,MADC,QAAQ,QAAQ,KAAK,GACd,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAGD,OAAO,KAAK,WAAW,OAAO,KAAc,KAAe,SAAsC;EAC/F,IAAI;GACF,MAAM,QAAQ,iBAAiB,KAAK,UAAU;GAC9C,IAAI,OACF,MAAM,QAAQ,OAAO,KAAK;GAE5B,IAAI,YACF,IAAI,YAAY,kBAAkB,UAAU,GAAG,EAAE,MAAM,WAAW,QAAQ,IAAI,CAAC;GAEjF,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAGD,OAAO,IAAI,OAAO,YAAY,OAAO,IAAI,MAAe,QAAwB;EAC9E,IAAI,KAAK,KAAK,QAAQ;CACxB,CAAC;CAOD,OAAO,KACL,+BACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,yBAAyB,IAAI,SAAU,MAAM;GAC3D,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KAAK,iBAAiB,OAAO,KAAc,KAAe,SAAsC;EACrG,IAAI;GACF,MAAM,EAAE,UAAU,IAAI;GACtB,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,oBAAoB;GAErD,MAAM,QAAQ,YAAY,KAAK;GAC/B,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAOD,OAAO,KACL,2BACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,UAAU,IAAI;GACtB,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,oBAAoB;GAErD,MAAM,QAAQ,qBAAqB,KAAK;GACxC,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KAAK,mBAAmB,OAAO,KAAc,KAAe,SAAsC;EACvG,IAAI;GACF,MAAM,EAAE,OAAO,gBAAgB,IAAI;GACnC,IAAI,CAAC,SAAS,CAAC,aACb,MAAM,IAAI,qBAAqB,qCAAqC;GAEtE,MAAM,QAAQ,cAAc,OAAO,WAAW;GAC9C,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAOD,OAAO,KAAK,eAAe,OAAO,KAAc,KAAe,SAAsC;EACnG,IAAI;GACF,MAAM,EAAE,UAAU,SAAS,IAAI;GAC/B,IAAI,CAAC,YAAY,CAAC,MAChB,MAAM,IAAI,qBAAqB,iCAAiC;GAGlE,eAAe,KAAK,MADC,QAAQ,mBAAmB,UAAU,IAAI,GAClC,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CAAC;CAGD,OAAO,KACL,cACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,SAAS,MAAM,QAAQ,SAAS,IAAI,SAAU,MAAM;GAC1D,IAAI,KAAK,MAAM;EACjB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,eACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,SAAS,IAAI;GACrB,IAAI,CAAC,MACH,MAAM,IAAI,qBAAqB,mBAAmB;GAEpD,MAAM,QAAQ,UAAU,IAAI,SAAU,QAAQ,IAAI;GAClD,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,gBACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,SAAS,IAAI;GACrB,IAAI,CAAC,MACH,MAAM,IAAI,qBAAqB,mBAAmB;GAEpD,MAAM,QAAQ,WAAW,IAAI,SAAU,QAAQ,IAAI;GACnD,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAOA,OAAO,KACL,uBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,YAAY,IAAI,OAAO,SAAU;GAC/C,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,yBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,cAAc,IAAI,OAAO,SAAU;GACjD,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,0BACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,eAAe,IAAI,OAAO,SAAU;GAClD,IAAI,OAAO,GAAG,EAAE,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,IAAI,QAAQ,OAAO;EACjB,MAAM,eAAe,QAAQ;EAC7B,MAAM,UAAU,QAAQ,gBAAgB;EAExC,MAAM,qBAAqB;GACzB,UAAU;GACV,UAAU;GACV,QAAQ,QAAQ,IAAI,aAAa;GACjC,QAAQ,MAAU;EACpB;EAEA,KAAK,MAAM,gBAAgB,aAAa,WAAW;GACjD,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa;GAE/C,OAAO,IACL,IAAI,gBACJ,eAAe,cAAc,cAAc;IACzC;IACA,WAAW,MAAM,UAAU,UAAU;KACnC,SAAS,OAAO,oBAAoB,OAAO,kBAAkB;IAC/D;GACF,CAAC,CACH;GAEA,OAAO,IACL,IAAI,aAAa,YACjB,cAAc,cAAc,cAAc;IACxC;IACA,WAAW,aAAa,WAAW,UAAU,kBAAkB,KAAK;IACpE,WAAW,WAAW,UAAU,WAAW;KACzC,SAAS,YAAY,kBAAkB;IACzC;IACA,YAAY,WAAW,UAAU,WAAW;KAC1C,eAAe,UAAU,QAAQ,UAAU;IAC7C;GACF,CAAC,CACH;EACF;CACF;CAGA,MAAM,QAAQ,OAAO;CACrB,IAAI,QACF,MAAM,IAAI,IAAI,UAAU,MAAM;MAE9B,MAAM,IAAI,MAAM;CAElB,OAAO;AACT"}
|
|
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> = (\n req: Request,\n) => 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 (\n req: Request,\n _res: Response,\n next: NextFunction,\n ): 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 (\n req: Request,\n _res: Response,\n next: NextFunction,\n ): 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 (\n err: unknown,\n _req: Request,\n res: Response,\n next: NextFunction,\n ): void => {\n if (err instanceof IdentityError) {\n res\n .status(err.statusCode)\n .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(\n req: Request,\n res: Response,\n result: import(\"../types\").AuthResult,\n ): 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 (\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> => {\n try {\n const { url, state } = oauthService.buildAuthorizationUrl(\n providerName,\n options.redirectUri,\n );\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 (\n req: Request,\n res: Response,\n next: NextFunction,\n ): Promise<void> => {\n try {\n const code =\n typeof req.query[\"code\"] === \"string\" ? req.query[\"code\"] : null;\n const state =\n typeof req.query[\"state\"] === \"string\" ? req.query[\"state\"] : null;\n const error =\n 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(\n \"identity/oauth-error\",\n `Provider error: ${errorDescription}`,\n 400,\n );\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 {\n Router,\n type NextFunction,\n type Request,\n type Response,\n} 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 {\n refreshToken: _rt,\n refreshTokenExpiresAt: _exp,\n ...bodyTokens\n } = 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(\n res,\n result.tokens,\n cookieOpts,\n );\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(\n req: Request,\n cookieOpts: CookieRefreshOptions | undefined,\n): 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}`, {\n durationMs: ms,\n });\n });\n next();\n });\n\n // POST /register\n router.post(\n \"/register\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const result = await service.register(\n req.body as { email: string; password: string; displayName?: string },\n );\n sendAuthResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /login\n router.post(\n \"/login\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const result = await service.login(\n req.body as { email: string; password: string },\n );\n sendLoginResult(res, result, cookieOpts);\n } catch (err) {\n next(err);\n }\n },\n );\n\n // POST /refresh\n router.post(\n \"/refresh\",\n 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\n // POST /logout\n router.post(\n \"/logout\",\n 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), {\n path: cookieOpts.path ?? \"/\",\n });\n }\n res.status(204).end();\n } catch (err) {\n next(err);\n }\n },\n );\n\n // GET /me — requires a valid Bearer access token\n router.get(\n \"/me\",\n requireAuth(service),\n (_req: Request, res: Response): void => {\n res.json(_req.identity);\n },\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(\n \"/verify-email\",\n 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 // ---------------------------------------------------------------------------\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(\n \"/reset-password\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { token, newPassword } = req.body as {\n token?: string;\n newPassword?: string;\n };\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 // ---------------------------------------------------------------------------\n // Two-factor authentication (2FA / TOTP)\n // ---------------------------------------------------------------------------\n\n // POST /mfa/verify — complete an MFA challenge after login\n router.post(\n \"/mfa/verify\",\n async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n const { mfaToken, code } = req.body as {\n mfaToken?: string;\n code?: string;\n };\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\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\"] as string);\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\"] as string);\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\"] as string);\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) =>\n 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 (\n err: unknown,\n _req: Request,\n res: Response,\n next: NextFunction,\n ): void => {\n if (err instanceof IdentityError) {\n log.debug(\"identity error\", {\n code: err.code,\n status: err.statusCode,\n message: err.message,\n });\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":";;;;AAsBA,MAAM,sBAAsB,QAAgC;CAC1D,MAAM,SAAS,IAAI,QAAQ;CAC3B,IAAI,CAAC,UAAU,CAAC,OAAO,WAAW,SAAS,GAAG,OAAO;CACrD,MAAM,QAAQ,OAAO,MAAM,CAAgB,CAAC,CAAC,KAAK;CAClD,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;;;;;AAOA,SAAgB,YAAY,SAA0C;CACpE,OAAO,OACL,KACA,MACA,SACkB;EAClB,IAAI;GACF,MAAM,QAAQ,mBAAmB,GAAG;GACpC,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB;GAEjC,IAAI,WAAW,MAAM,QAAQ,aAAa,KAAK;GAC/C,KAAK;EACP,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;;;AAMA,SAAgB,qBACd,SACA,aACA,cACgB;CAChB,OAAO,OACL,KACA,MACA,SACkB;EAClB,IAAI;GACF,MAAM,YAAY,IAAI;GACtB,IAAI,CAAC,WACH,MAAM,IAAI,qBAAqB;GAEjC,MAAM,WAAW,eAAe,MAAM,aAAa,GAAG,IAAI,KAAA;GAM1D,IAAI,EAAC,MALkB,QAAQ,UAAU,aAAa;IACpD;IACA,QAAQ,YAAY,cAAc;IAClC;GACF,CAAC,EAAA,CACa,SACZ,MAAM,IAAI,eAAe;GAE3B,KAAK;EACP,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;AAGA,SAAgB,kBACd,SACA,YACgB;CAChB,OAAO,qBAAqB,SAAS,EAAE,WAAW,CAAC;AACrD;;;;;;;;;;;;;;;;;AChDA,SAAgB,eACd,cACA,cACA,SACgB;CAChB,OAAO,OACL,KACA,KACA,SACkB;EAClB,IAAI;GACF,MAAM,EAAE,KAAK,UAAU,aAAa,sBAClC,cACA,QAAQ,WACV;GACA,MAAM,QAAQ,SAAS,KAAK,KAAK,KAAK;GACtC,IAAI,SAAS,GAAG;EAClB,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,cACd,cACA,cACA,SACgB;CAChB,OAAO,OACL,KACA,KACA,SACkB;EAClB,IAAI;GACF,MAAM,OACJ,OAAO,IAAI,MAAM,YAAY,WAAW,IAAI,MAAM,UAAU;GAC9D,MAAM,QACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW;GAChE,MAAM,QACJ,OAAO,IAAI,MAAM,aAAa,WAAW,IAAI,MAAM,WAAW;GAEhE,IAAI,OAKF,MAAM,IAAI,cACR,wBACA,mBALA,OAAO,IAAI,MAAM,yBAAyB,WACtC,IAAI,MAAM,uBACV,SAIJ,GACF;GAGF,IAAI,CAAC,QAAQ,CAAC,OACZ,MAAM,IAAI,cACR,mCACA,4CACA,GACF;GAGF,MAAM,gBAAgB,MAAM,QAAQ,SAAS,GAAG;GAChD,IAAI,CAAC,eACH,MAAM,IAAI,cACR,gCACA,kEACA,GACF;GAGF,MAAM,SAAS,MAAM,aAAa,eAAe,cAAc;IAC7D;IACA;IACA;IACA,aAAa,QAAQ;GACvB,CAAC;GAED,MAAM,QAAQ,UAAU,KAAK,KAAK,MAAM;EAC1C,SAAS,OAAO;GACd,KAAK,KAAK;EACZ;CACF;AACF;;;AC1CA,MAAM,qBAAqB;AAE3B,SAAS,kBAAkB,MAAoC;CAC7D,OAAO,KAAK,QAAQ;AACtB;;AAGA,SAAS,WAAW,KAAc,MAAkC;CAClE,MAAM,MAAM,IAAI,QAAQ,UAAU;CAClC,KAAK,MAAM,QAAQ,IAAI,MAAM,GAAG,GAAG;EACjC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;EAEf,IADY,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KACxB,MAAM,MACV,IAAI;GACF,OAAO,mBAAmB,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;EACrD,QAAQ;GACN,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;EACjC;CAEJ;AAEF;;;;;AAMA,SAAS,8BACP,KACA,QACA,MAC4D;CAC5D,MAAM,SAAS,OAAO,sBAAsB,QAAQ,IAAI,KAAK,IAAI;CACjE,IAAI,OAAO,kBAAkB,IAAI,GAAG,OAAO,cAAc;EACvD,UAAU,KAAK,YAAY;EAC3B,QAAQ,KAAK,UAAU,QAAQ,IAAI,aAAa;EAChD,UAAU,KAAK,YAAY;EAC3B,MAAM,KAAK,QAAQ;EACnB,QAAQ,KAAK;EACb;CACF,CAAC;CACD,MAAM,EACJ,cAAc,KACd,uBAAuB,MACvB,GAAG,eACD;CACJ,OAAO;AACT;;;;;;;;AASA,SAAS,gBACP,KACA,QACA,YACM;CACN,IAAI,cAAc,QAAQ;EACxB,IAAI,KAAK,MAAM;EACf;CACF;CACA,eAAe,KAAK,QAAQ,UAAU;AACxC;AAEA,SAAS,eACP,KACA,QACA,YACM;CACN,IAAI,YAAY;EACd,MAAM,SAAS,8BACb,KACA,OAAO,QACP,UACF;EACA,IAAI,KAAK;GAAE,MAAM,OAAO;GAAM;EAAO,CAAC;CACxC,OACE,IAAI,KAAK,MAAM;AAEnB;;;;;AAMA,SAAS,iBACP,KACA,YACoB;CACpB,IAAI,YACF,OAAO,WAAW,KAAK,kBAAkB,UAAU,CAAC;CAGtD,MAAM,QADO,IAAI,OACI;CACrB,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,qBACd,SACA,UAAiC,CAAC,GAC1B;CACR,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,MAAM,cAAc,QAAQ,MAAM;CAExC,MAAM,SAAS,OAAO;CACtB,MAAM,aACJ,OAAO,QAAQ,iBAAiB,YAAY,YAAY,QAAQ,eAC5D,QAAQ,aAAa,SACrB,KAAA;CAGN,OAAO,KAAK,MAAe,KAAe,SAA6B;EACrE,MAAM,QAAQ,KAAK,IAAI;EACvB,IAAI,GAAG,gBAAgB;GACrB,MAAM,KAAK,KAAK,IAAI,IAAI;GACxB,IAAI,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,YAAY,GAAG,IAAI,cAAc,EAC/D,YAAY,GACd,CAAC;EACH,CAAC;EACD,KAAK;CACP,CAAC;CAGD,OAAO,KACL,aACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GAIF,eAAe,KAAK,MAHC,QAAQ,SAC3B,IAAI,IACN,GAC4B,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,UACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GAIF,gBAAgB,KAAK,MAHA,QAAQ,MAC3B,IAAI,IACN,GAC6B,UAAU;EACzC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,YACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,iBAAiB,KAAK,UAAU;GAC9C,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,2BAA2B;GAG5D,eAAe,KAAK,MADC,QAAQ,QAAQ,KAAK,GACd,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,WACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,iBAAiB,KAAK,UAAU;GAC9C,IAAI,OACF,MAAM,QAAQ,OAAO,KAAK;GAE5B,IAAI,YACF,IAAI,YAAY,kBAAkB,UAAU,GAAG,EAC7C,MAAM,WAAW,QAAQ,IAC3B,CAAC;GAEH,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,IACL,OACA,YAAY,OAAO,IAClB,MAAe,QAAwB;EACtC,IAAI,KAAK,KAAK,QAAQ;CACxB,CACF;CAOA,OAAO,KACL,+BACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,yBAAyB,IAAI,SAAU,MAAM;GAC3D,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,iBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,UAAU,IAAI;GACtB,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,oBAAoB;GAErD,MAAM,QAAQ,YAAY,KAAK;GAC/B,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAOA,OAAO,KACL,2BACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,UAAU,IAAI;GACtB,IAAI,CAAC,OACH,MAAM,IAAI,qBAAqB,oBAAoB;GAErD,MAAM,QAAQ,qBAAqB,KAAK;GACxC,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,mBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,OAAO,gBAAgB,IAAI;GAInC,IAAI,CAAC,SAAS,CAAC,aACb,MAAM,IAAI,qBAAqB,qCAAqC;GAEtE,MAAM,QAAQ,cAAc,OAAO,WAAW;GAC9C,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAOA,OAAO,KACL,eACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,UAAU,SAAS,IAAI;GAI/B,IAAI,CAAC,YAAY,CAAC,MAChB,MAAM,IAAI,qBAAqB,iCAAiC;GAGlE,eAAe,KAAK,MADC,QAAQ,mBAAmB,UAAU,IAAI,GAClC,UAAU;EACxC,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,cACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,SAAS,MAAM,QAAQ,SAAS,IAAI,SAAU,MAAM;GAC1D,IAAI,KAAK,MAAM;EACjB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,eACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,SAAS,IAAI;GACrB,IAAI,CAAC,MACH,MAAM,IAAI,qBAAqB,mBAAmB;GAEpD,MAAM,QAAQ,UAAU,IAAI,SAAU,QAAQ,IAAI;GAClD,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,gBACA,YAAY,OAAO,GACnB,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,EAAE,SAAS,IAAI;GACrB,IAAI,CAAC,MACH,MAAM,IAAI,qBAAqB,mBAAmB;GAEpD,MAAM,QAAQ,WAAW,IAAI,SAAU,QAAQ,IAAI;GACnD,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAOA,OAAO,KACL,uBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,YAAY,IAAI,OAAO,SAAmB;GACxD,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,yBACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,cAAc,IAAI,OAAO,SAAmB;GAC1D,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,OAAO,KACL,0BACA,OAAO,KAAc,KAAe,SAAsC;EACxE,IAAI;GACF,MAAM,QAAQ,eAAe,IAAI,OAAO,SAAmB;GAC3D,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI;EACtB,SAAS,KAAK;GACZ,KAAK,GAAG;EACV;CACF,CACF;CAGA,IAAI,QAAQ,OAAO;EACjB,MAAM,eAAe,QAAQ;EAC7B,MAAM,UAAU,QAAQ,gBAAgB;EAExC,MAAM,qBAAqB;GACzB,UAAU;GACV,UAAU;GACV,QAAQ,QAAQ,IAAI,aAAa;GACjC,QAAQ,MAAU;EACpB;EAEA,KAAK,MAAM,gBAAgB,aAAa,WAAW;GACjD,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa;GAE/C,OAAO,IACL,IAAI,gBACJ,eAAe,cAAc,cAAc;IACzC;IACA,WAAW,MAAM,UAAU,UAAU;KACnC,SAAS,OAAO,oBAAoB,OAAO,kBAAkB;IAC/D;GACF,CAAC,CACH;GAEA,OAAO,IACL,IAAI,aAAa,YACjB,cAAc,cAAc,cAAc;IACxC;IACA,WAAW,aACT,WAAW,UAAU,kBAAkB,KAAK;IAC9C,WAAW,WAAW,UAAU,WAAW;KACzC,SAAS,YAAY,kBAAkB;IACzC;IACA,YAAY,WAAW,UAAU,WAAW;KAC1C,eAAe,UAAU,QAAQ,UAAU;IAC7C;GACF,CAAC,CACH;EACF;CACF;CAGA,MAAM,QAAQ,OAAO;CACrB,IAAI,QACF,MAAM,IAAI,IAAI,UAAU,MAAM;MAE9B,MAAM,IAAI,MAAM;CAElB,OAAO;AACT;;;;;;;;;;;;;;;AAoBA,SAAgB,qBACd,UAA+C,CAAC,GACyB;CACzE,MAAM,MAAM,cAAc,QAAQ,MAAM;CACxC,QACE,KACA,MACA,KACA,SACS;EACT,IAAI,eAAe,eAAe;GAChC,IAAI,MAAM,kBAAkB;IAC1B,MAAM,IAAI;IACV,QAAQ,IAAI;IACZ,SAAS,IAAI;GACf,CAAC;GACD,IAAI,OAAO,IAAI,UAAU,CAAC,CAAC,KAAK;IAAE,MAAM,IAAI;IAAM,SAAS,IAAI;GAAQ,CAAC;GACxE;EACF;EACA,IAAI,MAAM,sCAAsC,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;EACtE,KAAK,GAAG;CACV;AACF"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { i as IdentityConfigError } from "./errors-CEmnZxIn.mjs";
|
|
1
|
+
import { i as IdentityConfigError, s as InvalidTokenError } from "./errors-CEmnZxIn.mjs";
|
|
2
2
|
import { r as resolveLogger } from "./logger-CcCHJVVe.mjs";
|
|
3
|
+
import { jwtVerify } from "jose";
|
|
3
4
|
//#region core/config.ts
|
|
4
5
|
const MIN_SECRET_LENGTH = 32;
|
|
6
|
+
/** Default JWT `iss` claim used when `IdentityConfigInput.issuer` is omitted. */
|
|
7
|
+
const DEFAULT_ISSUER = "azlib-identity";
|
|
5
8
|
const defaultGenerateId = () => globalThis.crypto.randomUUID();
|
|
6
9
|
const defaultNow = () => /* @__PURE__ */ new Date();
|
|
7
10
|
/**
|
|
@@ -238,6 +241,37 @@ async function linkOAuthAccount(store, userId, provider, userInfo, now) {
|
|
|
238
241
|
await store.createOAuthLink(link);
|
|
239
242
|
}
|
|
240
243
|
//#endregion
|
|
244
|
+
//#region core/access-token.ts
|
|
245
|
+
/**
|
|
246
|
+
* Verifies an HS256 access token and returns its claims.
|
|
247
|
+
*
|
|
248
|
+
* This helper is framework-agnostic and safe to run on the Edge (no `node:crypto`).
|
|
249
|
+
* Hydrating the principal still requires {@link IdentityService.authenticate} in Node.
|
|
250
|
+
*
|
|
251
|
+
* Throws {@link InvalidTokenError} when the token is missing, malformed, expired,
|
|
252
|
+
* signed with the wrong secret, or missing required claims.
|
|
253
|
+
*/
|
|
254
|
+
async function verifyAccessToken(token, options) {
|
|
255
|
+
if (typeof token !== "string" || token.trim().length === 0) throw new InvalidTokenError();
|
|
256
|
+
try {
|
|
257
|
+
const secret = new TextEncoder().encode(options.secret);
|
|
258
|
+
const { payload } = await jwtVerify(token.trim(), secret, {
|
|
259
|
+
issuer: options.issuer ?? "azlib-identity",
|
|
260
|
+
audience: options.audience
|
|
261
|
+
});
|
|
262
|
+
if (typeof payload.sub !== "string" || payload.sub.length === 0 || typeof payload.authVersion !== "number" || !Number.isFinite(payload.authVersion)) throw new InvalidTokenError();
|
|
263
|
+
return {
|
|
264
|
+
sub: payload.sub,
|
|
265
|
+
authVersion: payload.authVersion,
|
|
266
|
+
iat: payload.iat ?? 0,
|
|
267
|
+
exp: payload.exp ?? 0
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof InvalidTokenError) throw error;
|
|
271
|
+
throw new InvalidTokenError();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
241
275
|
//#region schema/model.ts
|
|
242
276
|
const identitySchemaModel = { tables: [
|
|
243
277
|
{
|
|
@@ -405,6 +439,6 @@ const identitySchemaModel = { tables: [
|
|
|
405
439
|
}
|
|
406
440
|
] };
|
|
407
441
|
//#endregion
|
|
408
|
-
export {
|
|
442
|
+
export { createOAuthService as a, evaluateAuthorization as c, resolveIdentityConfig as d, OAuthStateMismatchError as i, isAuthorized as l, verifyAccessToken as n, hydratePrincipal as o, OAuthProviderNotFoundError as r, issueSession as s, identitySchemaModel as t, DEFAULT_ISSUER as u };
|
|
409
443
|
|
|
410
|
-
//# sourceMappingURL=identity-
|
|
444
|
+
//# sourceMappingURL=identity-D0qyca_c.mjs.map
|