sentri 4.0.0 → 4.1.1

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 CHANGED
@@ -21,6 +21,7 @@ Auth and authorization library for Express. Supports two modes:
21
21
  - [Token Utilities](#token-utilities)
22
22
  - [Error Handling](#error-handling)
23
23
  - [Request Idempotency](#request-idempotency)
24
+ - [Logger](#logger)
24
25
  - [Migration Guide](#migration-guide)
25
26
 
26
27
  ---
@@ -589,6 +590,137 @@ app.use(createIdempotencyMiddleware({ redisUrl: 'redis://localhost:6379' }));
589
590
 
590
591
  ---
591
592
 
593
+ ## Logger
594
+
595
+ Sentri produces structured JSON log entries for every auth event. Logging is **opt-in** — when no logger is configured, Sentri is completely silent (zero overhead).
596
+
597
+ ### Activating the logger
598
+
599
+ Pass any object that implements `{ info, warn, error }` via the `logger` field in your config.
600
+
601
+ **pino** (recommended for production):
602
+ ```typescript
603
+ import pino from 'pino';
604
+
605
+ const auth = createAuth({
606
+ mode: 'server',
607
+ // ...other config
608
+ logger: pino(),
609
+ });
610
+ ```
611
+
612
+ **winston**:
613
+ ```typescript
614
+ import winston from 'winston';
615
+
616
+ const auth = createAuth({
617
+ mode: 'server',
618
+ // ...other config
619
+ logger: winston.createLogger({
620
+ transports: [new winston.transports.Console()],
621
+ }),
622
+ });
623
+ ```
624
+
625
+ **console** (zero setup, good for development):
626
+ ```typescript
627
+ const auth = createAuth({
628
+ mode: 'server',
629
+ // ...other config
630
+ logger: console,
631
+ });
632
+ ```
633
+
634
+ Works identically in **client mode**:
635
+ ```typescript
636
+ const auth = createAuth({
637
+ mode: 'client',
638
+ keyUri: 'https://auth.myapp.com/auth/keys',
639
+ logger: pino(),
640
+ });
641
+ ```
642
+
643
+ ### Customising the service name
644
+
645
+ By default every log entry contains `"service": "sentri"`. Override it with `loggerService`:
646
+
647
+ ```typescript
648
+ const auth = createAuth({
649
+ // ...
650
+ logger: pino(),
651
+ loggerService: 'auth-service',
652
+ });
653
+ ```
654
+
655
+ ### Request ID correlation
656
+
657
+ If you mount a request-ID middleware **before** Sentri, the `requestId` is automatically included in every log entry for that request:
658
+
659
+ ```typescript
660
+ import { randomUUID } from 'crypto';
661
+
662
+ app.use((req, _res, next) => {
663
+ req.requestId = randomUUID();
664
+ next();
665
+ });
666
+
667
+ app.use('/auth', auth.router());
668
+ ```
669
+
670
+ Sample pino output:
671
+ ```json
672
+ {"level":30,"time":1719484800000,"service":"sentri","event":"auth.login.success","userId":"usr_abc","duration_ms":42,"requestId":"d4e5f6"}
673
+ {"level":40,"time":1719484801000,"service":"sentri","event":"auth.authorize.denied","userId":"usr_abc","userRoles":["user"],"requiredRoles":["admin"],"requestId":"d4e5f7"}
674
+ ```
675
+
676
+ ### Log events
677
+
678
+ | Event | Level | Emitted by | Key fields |
679
+ |-------|-------|------------|-----------|
680
+ | `auth.protect.success` | info | `protect()` | `userId`, `mode` |
681
+ | `auth.protect.failure` | warn | `protect()` | `errorCode`, `mode` |
682
+ | `auth.protect.token_revoked` | warn | `protect()` | `userId`, `mode` |
683
+ | `auth.protect.auto_refresh` | info | `protect()` | `userId`, `mode` |
684
+ | `auth.authorize.passed` | info | `authorize()` | `userId`, `userRoles`, `requiredRoles` |
685
+ | `auth.authorize.denied` | warn | `authorize()` | `userId`, `userRoles`, `requiredRoles` |
686
+ | `auth.authorize.unauthenticated` | warn | `authorize()` | `requiredRoles` |
687
+ | `auth.permit.passed` | info | `permit()` | `userId` |
688
+ | `auth.permit.denied` | warn | `permit()` | `userId` |
689
+ | `auth.permit.role_bypass` | info | `permit()` | `userId`, `bypassedByRole` |
690
+ | `auth.permit.unauthenticated` | warn | `permit()` | — |
691
+ | `auth.register.success` | info | router | `userId`, `duration_ms` |
692
+ | `auth.register.failure` | warn | router | `errorCode`, `duration_ms` |
693
+ | `auth.login.success` | info | router | `userId`, `duration_ms` |
694
+ | `auth.login.failure` | warn | router | `errorCode`, `duration_ms` |
695
+ | `auth.refresh.success` | info | router | `userId`, `duration_ms` |
696
+ | `auth.refresh.failure` | warn | router | `errorCode`, `duration_ms` |
697
+ | `auth.logout` | info | router | `duration_ms` |
698
+ | `auth.logout_all` | info | router | `userId`, `duration_ms` |
699
+ | `auth.password.changed` | info | router | `userId`, `duration_ms` |
700
+ | `auth.password.change_failure` | warn | router | `userId`, `errorCode`, `duration_ms` |
701
+ | `auth.roles.assigned` | info | router | `targetUserId`, `roles`, `duration_ms` |
702
+ | `auth.roles.assign_failure` | warn | router | `targetUserId`, `errorCode`, `duration_ms` |
703
+ | `auth.identifiers.created` | info | router | `userId`, `count`, `duration_ms` |
704
+ | `auth.identifiers.updated` | info | router | `userId`, `count`, `duration_ms` |
705
+ | `auth.identifiers.deleted` | info | router | `userId`, `duration_ms` |
706
+ | `auth.identifiers.primary_changed` | info | router | `userId`, `duration_ms` |
707
+
708
+ All entries include `service` (configurable via `loggerService`) and `requestId` when available.
709
+
710
+ ### `SentriLogger` interface
711
+
712
+ ```typescript
713
+ import type { SentriLogger } from 'sentri';
714
+
715
+ const myLogger: SentriLogger = {
716
+ info(data: Record<string, unknown>) { /* ... */ },
717
+ warn(data: Record<string, unknown>) { /* ... */ },
718
+ error(data: Record<string, unknown>) { /* ... */ },
719
+ };
720
+ ```
721
+
722
+ ---
723
+
592
724
  ## Migration Guide
593
725
 
594
726
  ### 4.0.0 Breaking Changes
package/dist/cli.js CHANGED
@@ -34,6 +34,12 @@ export const sentriAuth = createAuthServer<Role>({
34
34
 
35
35
  // -- Token revocation (optional) --------------------------------------------
36
36
  // isTokenRevoked: async (sessionId) => false,
37
+
38
+ // -- Logger (optional) ------------------------------------------------------
39
+ // Accepts any logger compatible with pino, winston, or console.
40
+ // When omitted, sentri produces no log output.
41
+ // logger: console,
42
+ // loggerService: 'sentri', // default: 'sentri'
37
43
  });
38
44
  `,d=`# -- Server -------------------------------------------------------------------
39
45
  PORT=3000
@@ -66,14 +72,20 @@ export const sentriAuth = createAuth<Role>({
66
72
  // -- Roles (optional) -------------------------------------------------------
67
73
  // Only used for TypeScript type safety on authorize() \u2014 not validated at runtime.
68
74
  validRoles: ['admin', 'user'],
75
+
76
+ // -- Logger (optional) ------------------------------------------------------
77
+ // Accepts any logger compatible with pino, winston, or console.
78
+ // When omitted, sentri produces no log output.
79
+ // logger: console,
80
+ // loggerService: 'auth-service', // default: 'sentri'
69
81
  });
70
- `,v=`# -- Server -------------------------------------------------------------------
82
+ `,g=`# -- Server -------------------------------------------------------------------
71
83
  PORT=3000
72
84
 
73
85
  # -- Auth Server --------------------------------------------------------------
74
86
  # URL of the auth server's GET /auth/keys endpoint (JWKS).
75
87
  AUTH_KEY_URI=http://localhost:3000/auth/keys
76
- `;function c(){console.log(`
88
+ `;function a(){console.log(`
77
89
  sentri \u2014 auth/authorization library for Express
78
90
 
79
91
  Usage:
@@ -86,9 +98,9 @@ Commands:
86
98
  Examples:
87
99
  npx sentri init server
88
100
  npx sentri init client
89
- `);}async function o(e,t,n){if(existsSync(e)){console.log(` skip ${n} (already exists)`);return}await writeFile(e,t,"utf8"),console.log(` create ${n}`);}async function E(){let e=process.cwd(),t=join(e,"src","lib");await mkdir(t,{recursive:true}),console.log(`
101
+ `);}async function s(e,o,n){if(existsSync(e)){console.log(` skip ${n} (already exists)`);return}await writeFile(e,o,"utf8"),console.log(` create ${n}`);}async function v(){let e=process.cwd(),o=join(e,"src","lib");await mkdir(o,{recursive:true}),console.log(`
90
102
  Generating sentri server mode files...
91
- `),await o(join(t,"sentri.ts"),u,"src/lib/sentri.ts"),await o(join(e,".env.example"),d,".env.example"),console.log(`
103
+ `),await s(join(o,"sentri.ts"),u,"src/lib/sentri.ts"),await s(join(e,".env.example"),d,".env.example"),console.log(`
92
104
  Done. Next steps:
93
105
 
94
106
  1. Copy .env.example to .env and fill in your values
@@ -103,9 +115,9 @@ Done. Next steps:
103
115
  3. Protect routes:
104
116
 
105
117
  app.get('/me', sentriAuth.protect(), (req, res) => res.json(req.user));
106
- `);}async function S(){let e=process.cwd(),t=join(e,"src","lib");await mkdir(t,{recursive:true}),console.log(`
118
+ `);}async function h(){let e=process.cwd(),o=join(e,"src","lib");await mkdir(o,{recursive:true}),console.log(`
107
119
  Generating sentri client mode files...
108
- `),await o(join(t,"sentri.ts"),m,"src/lib/sentri.ts"),await o(join(e,".env.example"),v,".env.example"),console.log(`
120
+ `),await s(join(o,"sentri.ts"),m,"src/lib/sentri.ts"),await s(join(e,".env.example"),g,".env.example"),console.log(`
109
121
  Done. Next steps:
110
122
 
111
123
  1. Copy .env.example to .env and set AUTH_KEY_URI to your auth server's /auth/keys endpoint
@@ -115,4 +127,4 @@ Done. Next steps:
115
127
 
116
128
  app.get('/protected', sentriAuth.protect(), (req, res) => res.json(req.user));
117
129
  app.use(sentriAuth.errorHandler());
118
- `);}var h=process.argv.slice(2),[s,i]=h;(!s||s==="--help"||s==="-h")&&(c(),process.exit(0));s==="init"?i==="server"?E().catch(e=>{console.error(e),process.exit(1);}):i==="client"?S().catch(e=>{console.error(e),process.exit(1);}):(console.error("Usage: npx sentri init <server|client>"),process.exit(1)):(console.error(`Unknown command: ${s}`),c(),process.exit(1));
130
+ `);}var E=process.argv.slice(2),[r,i]=E;(!r||r==="--help"||r==="-h")&&(a(),process.exit(0));r==="init"?i==="server"?v().catch(e=>{console.error(e),process.exit(1);}):i==="client"?h().catch(e=>{console.error(e),process.exit(1);}):(console.error("Usage: npx sentri init <server|client>"),process.exit(1)):(console.error(`Unknown command: ${r}`),a(),process.exit(1));
package/dist/index.d.ts CHANGED
@@ -93,6 +93,39 @@ declare class SentriError extends Error {
93
93
  constructor(code: SentriErrorCode | (string & {}), message: string, statusCode?: number);
94
94
  }
95
95
 
96
+ /**
97
+ * Minimal structured logger interface accepted by Sentri.
98
+ *
99
+ * Compatible out-of-the-box with **pino**, **winston**, and **console** — all
100
+ * three expose `info`, `warn`, and `error` methods that accept an object argument.
101
+ *
102
+ * Pass an instance via `logger` in `ServerAuthConfig` or `ClientAuthConfig`.
103
+ * When omitted, Sentri produces no log output (no-op default).
104
+ *
105
+ * Each call receives a plain `Record<string, unknown>` containing structured
106
+ * fields — never a pre-formatted string — so your logger controls serialisation,
107
+ * output destination, and timestamp format.
108
+ *
109
+ * @example
110
+ * // pino
111
+ * import pino from 'pino';
112
+ * const auth = createAuth({ ..., logger: pino() });
113
+ *
114
+ * @example
115
+ * // winston
116
+ * import winston from 'winston';
117
+ * const auth = createAuth({ ..., logger: winston.createLogger({ ... }) });
118
+ *
119
+ * @example
120
+ * // console (zero setup, useful for development)
121
+ * const auth = createAuth({ ..., logger: console });
122
+ */
123
+ interface SentriLogger {
124
+ info(data: Record<string, unknown>): void;
125
+ warn(data: Record<string, unknown>): void;
126
+ error(data: Record<string, unknown>): void;
127
+ }
128
+
96
129
  interface ApiResponse<T = null> {
97
130
  error: boolean;
98
131
  statusCode: number;
@@ -226,6 +259,7 @@ interface RouterHandlers {
226
259
  refresh?: (refreshToken: string) => Promise<RefreshResult>;
227
260
  logout?: (refreshToken: string | undefined) => Promise<void>;
228
261
  logoutAll?: (userId: string) => Promise<void>;
262
+ getUser?: (userId: string) => Promise<GetUserResult>;
229
263
  assignRoles?: (userId: string, roles: string[]) => Promise<AssignRolesResult>;
230
264
  bulkCreateIdentifiers?: (userId: string, identifiers: IdentifierInput[]) => Promise<BulkIdentifiersResult>;
231
265
  bulkUpdateIdentifiers?: (userId: string, updates: Array<{
@@ -272,6 +306,16 @@ interface ServerAuthConfig<TRole extends string = string> {
272
306
  * instead of an in-memory Map — required for multi-process deployments.
273
307
  */
274
308
  redisUrl?: string;
309
+ /**
310
+ * Structured logger instance. Compatible with pino, winston, or console.
311
+ * When omitted, Sentri produces no log output.
312
+ */
313
+ logger?: SentriLogger;
314
+ /**
315
+ * Service name embedded in every log entry.
316
+ * @default 'sentri'
317
+ */
318
+ loggerService?: string;
275
319
  }
276
320
  interface ClientAuthConfig<TRole extends string = string> {
277
321
  mode: 'client';
@@ -279,6 +323,16 @@ interface ClientAuthConfig<TRole extends string = string> {
279
323
  keyUri: string;
280
324
  /** Optional — only needed for TypeScript type safety on authorize(). */
281
325
  validRoles?: readonly TRole[];
326
+ /**
327
+ * Structured logger instance. Compatible with pino, winston, or console.
328
+ * When omitted, Sentri produces no log output.
329
+ */
330
+ logger?: SentriLogger;
331
+ /**
332
+ * Service name embedded in every log entry.
333
+ * @default 'sentri'
334
+ */
335
+ loggerService?: string;
282
336
  }
283
337
  type AuthConfig<TRole extends string = string> = ServerAuthConfig<TRole> | ClientAuthConfig<TRole>;
284
338
 
@@ -556,6 +610,17 @@ interface CreateServerOptions<TRole extends string = string> {
556
610
  * When set, `auth.idempotencyMiddleware()` uses Redis as the cache backend.
557
611
  */
558
612
  redisUrl?: string;
613
+ /**
614
+ * Structured logger for all auth events.
615
+ * Accepts any object with `info`, `warn`, `error` methods (pino, winston, console).
616
+ * When omitted, sentri produces no log output.
617
+ */
618
+ logger?: SentriLogger;
619
+ /**
620
+ * Service name added to every log entry as `service`.
621
+ * Defaults to `'sentri'`.
622
+ */
623
+ loggerService?: string;
559
624
  }
560
625
  /**
561
626
  * Create a Sentri auth server for PostgreSQL.
@@ -603,4 +668,4 @@ declare global {
603
668
  }
604
669
  }
605
670
 
606
- export { type AccessCookieConfig, type ApiResponse, type AssignRolesResult, type AuthClient, type AuthConfig, type AuthHooks, type AuthResult, type AuthUser, type BulkIdentifiersResult, type ChangePasswordResult, type ChangePrimaryResult, type ClientAuthClient, type ClientAuthConfig, type CookieConfig, type CreateServerOptions, type ErrorHandlerOptions, type GetUserResult, type IdempotencyOptions, type IdentifierInput, type IdentifierRecord, type LoginInput, type PermitCheck, type PermitOptions, type PostgresConfig, type RefreshResult, type RegisterInput, type RegisterResult, type RouterHandlers, SENTRI_ERROR_STATUS, SentriError, type SentriErrorCode, type ServerAuthClient, type ServerAuthConfig, createAuth, createAuthServer, createErrorHandler, createIdempotencyMiddleware, getCurrentAccessToken, register };
671
+ export { type AccessCookieConfig, type ApiResponse, type AssignRolesResult, type AuthClient, type AuthConfig, type AuthHooks, type AuthResult, type AuthUser, type BulkIdentifiersResult, type ChangePasswordResult, type ChangePrimaryResult, type ClientAuthClient, type ClientAuthConfig, type CookieConfig, type CreateServerOptions, type ErrorHandlerOptions, type GetUserResult, type IdempotencyOptions, type IdentifierInput, type IdentifierRecord, type LoginInput, type PermitCheck, type PermitOptions, type PostgresConfig, type RefreshResult, type RegisterInput, type RegisterResult, type RouterHandlers, SENTRI_ERROR_STATUS, SentriError, type SentriErrorCode, type SentriLogger, type ServerAuthClient, type ServerAuthConfig, createAuth, createAuthServer, createErrorHandler, createIdempotencyMiddleware, getCurrentAccessToken, register };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import xe from'bcrypt';import B from'jsonwebtoken';import {randomUUID,generateKeyPairSync,createPublicKey,createPrivateKey}from'crypto';import {Kysely,sql,PostgresDialect}from'kysely';import {Router}from'express';import {Redis}from'ioredis';import {Pool}from'pg';var be=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500}),o=class extends Error{code;statusCode;constructor(r,s,t){super(s),this.name="SentriError",this.code=r,this.statusCode=t??be[r]??500;}};async function M(e,r=12){return xe.hash(e,r)}async function V(e,r){return xe.compare(e,r)}var Pe=new Map,Ne=new Map,Ar=3600*1e3;function Oe(e){let r=Pe.get(e);if(!r){let s=createPrivateKey(e),t=createPublicKey(s),i=t.export({format:"jwk"}),n=Buffer.from(e).slice(0,8).toString("base64url"),d={...i,use:"sig",kid:n};r={kid:n,publicKey:t,jwk:d},Pe.set(e,r);}return r}function Ue(e){let{jwk:r}=Oe(e);return {keys:[r]}}function Ke(e){return Oe(e).publicKey}async function He(e){let r=Date.now(),s=Ne.get(e);if(s&&r-s.fetchedAt<Ar)return s.publicKey;let t=await fetch(e);if(!t.ok)throw new o("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${t.status}`);let i=await t.json();if(!i.keys||i.keys.length===0)throw new o("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let n=i.keys[0],d=createPublicKey({key:n,format:"jwk"});return Ne.set(e,{publicKey:d,fetchedAt:r}),d}var Fe=new WeakMap,je=32,Le=10,qe=31;function Me(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new o("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new o("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<je)throw new o("CONFIGURATION_ERROR",`secret must be at least ${je} characters for HMAC algorithms`);let t=e.saltRounds??12;if(!Number.isInteger(t)||t<Le||t>qe)throw new o("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Le} and ${qe}`);if(!e.validRoles||e.validRoles.length===0)throw new o("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new o("CONFIGURATION_ERROR","dialect is required in server mode")}function A(e){let r=Fe.get(e);if(r)return r;let s={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return Fe.set(e,s),s}var vr=/^(\d+)([smhdw])$/,Tr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},$e=new Map;function O(e){if(typeof e=="number")return e*1e3;let r=$e.get(e);if(r!==void 0)return r;let s=vr.exec(e);if(!s?.[1]||!s?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let t=Tr[s[2]];if(t===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let i=parseInt(s[1],10)*t;return $e.set(e,i),i}var Ve=new Map,Be=new Map,Je=new Map;function Xe(e){return e.startsWith("RS")||e.startsWith("PS")}function Ge(e){let r=Ve.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},Ve.set(e,r)),r}function Cr(e){let r=Be.get(e);return r||(r=createPrivateKey(e),Be.set(e,r)),r}function ze(e){let r=A(e);if(Xe(r.algorithm)){let i=Cr(e.secret);return {accessKey:i,refreshKey:i}}let{access:s,refresh:t}=Ge(e.secret);return {accessKey:s,refreshKey:t}}function We(e,r){let s=A(e);if(Xe(s.algorithm))return Ke(e.secret);let{access:t,refresh:i}=Ge(e.secret);return r==="access"?t:i}function Ze(e,r,s,t){let i=`${s}:${t}`,n=Je.get(i);return n||(n={expiresIn:s,algorithm:t},Je.set(i,n)),B.sign(e,r,n)}function Ye(e,r,s){try{let t=B.verify(e,r,{algorithms:[s]});if(typeof t=="string"||t===null)throw new o("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof o?t:t instanceof B.TokenExpiredError?new o("TOKEN_EXPIRED","Token has expired"):new o("TOKEN_INVALID","Token is invalid or malformed")}}function J(e,r){let s=A(r),{accessKey:t}=ze(r);return Ze(e,t,s.accessExpiresIn,s.algorithm)}function X(e,r){let s=A(r),{refreshKey:t}=ze(r);return Ze({sessionId:e},t,s.refreshExpiresIn,s.algorithm)}function se(e,r){let s=A(r),t=We(r,"access");return Ye(e,t,s.algorithm)}function G(e,r){let s=A(r),t=We(r,"refresh");return Ye(e,t,s.algorithm)}function Qe(e,r){try{let s=B.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof s=="string"||s===null)throw new o("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof o?s:s instanceof B.TokenExpiredError?new o("TOKEN_EXPIRED","Token has expired"):new o("TOKEN_INVALID","Token is invalid or malformed")}}function D(e){return A(e).cookieName}function U(e,r){if(!e)return;let s=`${r}=`,t=0;for(;t<e.length;){for(;t<e.length&&e[t]===" ";)t++;let i=e.indexOf(";",t),n=i===-1?e.length:i;if(e.startsWith(s,t))return e.slice(t+s.length,n);t=n+1;}}function z(e,r,s){let t=s.cookie??{},i=A(s),n=O(i.refreshExpiresIn);e.cookie(D(s),r,{httpOnly:t.httpOnly??true,secure:t.secure??false,sameSite:t.sameSite??"strict",path:t.path??"/",maxAge:n});}function ie(e,r){let s=r.cookie??{};e.clearCookie(D(r),{path:s.path??"/"});}function we(e){return A(e).accessCookieName}function W(e,r,s){if(!s.accessCookie)return;let t=s.accessCookie,i=A(s),n=O(i.accessExpiresIn);e.cookie(we(s),r,{httpOnly:false,secure:t.secure??false,sameSite:t.sameSite??"strict",path:t.path??"/",maxAge:n});}function Ie(e,r){if(!r.accessCookie)return;let s=r.accessCookie;e.clearCookie(we(r),{path:s.path??"/"});}function Z(e,r){let s=e.headers.authorization;return s?.startsWith("Bearer ")?s.slice(7):U(e.headers.cookie,we(r))}var er=new Map;function T(e){let r=er.get(e);return r||(r=new Kysely({dialect:e}),er.set(e,r)),r}function Ae(e){try{return JSON.parse(e)}catch{return []}}function Sr(e){return JSON.stringify(e)}function tr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,isPrimary:e.is_primary===1,createdAt:new Date(e.created_at)}}async function Y(e,r){let s=await e.selectFrom("sentri_identifiers as i_login").innerJoin("sentri_users as u","u.id","i_login.user_id").innerJoin("sentri_identifiers as i_primary",t=>t.onRef("i_primary.user_id","=","u.id").on("i_primary.is_primary","=",1)).select(["u.id","u.password_hash","u.roles","i_primary.value as primary_value","i_primary.type as primary_type"]).where("i_login.value","=",r).executeTakeFirst();return s?{id:s.id,identifier:s.primary_value,identifierType:s.primary_type,passwordHash:s.password_hash,roles:Ae(s.roles)}:null}async function ne(e,r){let s=await e.selectFrom("sentri_users as u").innerJoin("sentri_identifiers as i",t=>t.onRef("i.user_id","=","u.id").on("i.is_primary","=",1)).select(["u.id","u.password_hash","u.roles","i.value as primary_value","i.type as primary_type"]).where("u.id","=",r).executeTakeFirst();return s?{id:s.id,identifier:s.primary_value,identifierType:s.primary_type,passwordHash:s.password_hash,roles:Ae(s.roles)}:null}async function sr(e,r,s){let t=s.map(i=>({id:randomUUID(),user_id:r,type:i.type,value:i.value,is_primary:i.isPrimary?1:0}));return await e.insertInto("sentri_identifiers").values(t).execute(),t.map(i=>({id:i.id,userId:i.user_id,type:i.type,value:i.value,isPrimary:i.is_primary===1,createdAt:new Date}))}async function K(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(tr)}async function oe(e,r,s){let t=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",s).executeTakeFirst();return t?tr(t):null}async function ir(e,r){let s=await e.selectFrom("sentri_identifiers").select(t=>t.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(s?.count??0)}async function nr(e,r,s,t){await e.updateTable("sentri_identifiers").set({type:t.type,value:t.value}).where("id","=",r).where("user_id","=",s).execute();}async function or(e,r,s){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",s).execute();}async function ar(e,r,s){await e.transaction().execute(async t=>{await t.updateTable("sentri_identifiers").set({is_primary:0}).where("user_id","=",r).execute(),await t.updateTable("sentri_identifiers").set({is_primary:1}).where("id","=",s).where("user_id","=",r).execute();});}async function ur(e,r,s){await e.updateTable("sentri_users").set({password_hash:s}).where("id","=",r).execute();}async function dr(e,r,s){await e.updateTable("sentri_users").set({roles:Sr(s)}).where("id","=",r).execute();}async function ve(e,r){let s=randomUUID();return await e.insertInto("sentri_sessions").values({id:s,user_id:r.userId,expires_at:r.expiresAt}).execute(),{id:s}}async function cr(e,r){let s=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").innerJoin("sentri_identifiers as i",t=>t.onRef("i.user_id","=","u.id").on("i.is_primary","=",1)).select(["s.id as session_id","s.user_id","s.expires_at","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles","i.value as primary_identifier","i.type as primary_identifier_type"]).where("s.id","=",r).executeTakeFirst();return s?{id:s.session_id,userId:s.user_id,expiresAt:new Date(s.expires_at),createdAt:new Date(s.session_created_at),user:{id:s.user_id_col,identifier:s.primary_identifier,identifierType:s.primary_identifier_type,passwordHash:s.password_hash,roles:Ae(s.roles)}}:null}async function ae(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}async function Te(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}async function Q(e,r){let s=A(r),t=T(r.dialect),i=e.roles??[],n=i.filter(R=>!s.validRolesSet.has(R));if(n.length>0)return {success:false,error:new o("INVALID_ROLE",`Invalid roles: ${n.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new o("VALIDATION_ERROR","At least one identifier is required")};let d=e.identifiers.map(R=>({type:R.type.trim(),value:R.value.trim()}));if(new Set(d.map(R=>R.value)).size!==d.length)return {success:false,error:new o("VALIDATION_ERROR","Duplicate identifier values in request")};for(let R of d)if(await Y(t,R.value))return {success:false,error:new o("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${R.value}`)};let l=await M(e.password,s.saltRounds),{userId:g,identifierRows:v}=await t.transaction().execute(async R=>{let x=randomUUID();await R.insertInto("sentri_users").values({id:x,password_hash:l,roles:JSON.stringify(i)}).execute();let E=d.map((u,c)=>({id:randomUUID(),user_id:x,type:u.type,value:u.value,is_primary:c===0?1:0}));return await R.insertInto("sentri_identifiers").values(E).execute(),{userId:x,identifierRows:E}}),S=v.map(R=>({id:R.id,type:R.type,value:R.value,isPrimary:R.is_primary===1})),k=S[0];return {success:true,user:{id:g,identifier:k.value,identifierType:k.type,roles:i,identifiers:S}}}async function ue(e,r){let s=A(r),t=T(r.dialect),i=await Y(t,e.identifier.trim());if(!i)return {success:false,error:new o("INVALID_CREDENTIALS","Invalid credentials")};if(!await V(e.password,i.passwordHash))return {success:false,error:new o("INVALID_CREDENTIALS","Invalid credentials")};let d=new Date(Date.now()+O(s.refreshExpiresIn)),a=await ve(t,{userId:i.id,expiresAt:d}),l={id:i.id,identifier:i.identifier,identifierType:i.identifierType,roles:i.roles},g=J({id:i.id,identifier:i.identifier,identifierType:i.identifierType,roles:i.roles,sessionId:a.id},r),v=X(a.id,r);return {success:true,accessToken:g,refreshToken:v,user:l}}async function H(e,r){let s=A(r),t=T(r.dialect),i;try{({sessionId:i}=G(e,r));}catch(S){return S instanceof o?{success:false,error:S}:{success:false,error:new o("TOKEN_INVALID","Invalid refresh token")}}let n=await cr(t,i);if(!n)return {success:false,error:new o("UNAUTHORIZED","Session not found or revoked")};if(n.expiresAt.getTime()<Date.now())return await ae(t,i),{success:false,error:new o("TOKEN_EXPIRED","Session has expired")};await ae(t,i);let d=new Date(Date.now()+O(s.refreshExpiresIn)),a=await ve(t,{userId:n.userId,expiresAt:d}),l={id:n.user.id,identifier:n.user.identifier,identifierType:n.user.identifierType,roles:n.user.roles},g=J({...l,sessionId:a.id},r),v=X(a.id,r);return {success:true,accessToken:g,refreshToken:v,user:l}}async function de(e,r){let s=T(r.dialect),t;try{({sessionId:t}=G(e,r));}catch{return}await ae(s,t);}async function ce(e,r){let s=T(r.dialect);await Te(s,e);}async function fr(e,r){let s=T(r.dialect),t=await ne(s,e);if(!t)return {success:false,error:new o("USER_NOT_FOUND","User not found")};let i=await K(s,e);return {success:true,user:{id:t.id,identifier:t.identifier,identifierType:t.identifierType,roles:t.roles,identifiers:i.map(n=>({id:n.id,type:n.type,value:n.value,isPrimary:n.isPrimary}))}}}async function le(e,r,s,t){let i=A(t),n=T(t.dialect),d=await ne(n,e);if(!d)return {success:false,error:new o("USER_NOT_FOUND","User not found")};if(!await V(r,d.passwordHash))return {success:false,error:new o("INVALID_CREDENTIALS","Invalid credentials")};let l=await M(s,i.saltRounds);return await ur(n,e,l),await Te(n,e),{success:true}}async function fe(e,r,s){let t=A(s),i=T(s.dialect),n=r.filter(g=>!t.validRolesSet.has(g));if(n.length>0)return {success:false,error:new o("INVALID_ROLE",`Invalid roles: ${n.join(", ")}`)};let d=await ne(i,e);if(!d)return {success:false,error:new o("USER_NOT_FOUND","User not found")};let a=new Set(d.roles);for(let g of r)a.add(g);let l=Array.from(a);return await dr(i,e,l),{success:true,user:{id:d.id,identifier:d.identifier,identifierType:d.identifierType,roles:l}}}async function pe(e,r,s){let t=T(s.dialect);if(r.length===0)return {success:false,error:new o("VALIDATION_ERROR","At least one identifier is required")};let i=r.map(l=>({type:l.type.trim(),value:l.value.trim()}));if(new Set(i.map(l=>l.value)).size!==i.length)return {success:false,error:new o("VALIDATION_ERROR","Duplicate identifier values in request")};for(let l of i)if(await Y(t,l.value))return {success:false,error:new o("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${l.value}`)};await sr(t,e,i.map(l=>({...l,isPrimary:false})));return {success:true,identifiers:(await K(t,e)).map(l=>({id:l.id,type:l.type,value:l.value,isPrimary:l.isPrimary}))}}async function me(e,r,s){let t=T(s.dialect);if(r.length===0)return {success:false,error:new o("VALIDATION_ERROR","At least one update is required")};let i=r.map(a=>({id:a.id,type:a.type.trim(),value:a.value.trim()}));if(new Set(i.map(a=>a.value)).size!==i.length)return {success:false,error:new o("VALIDATION_ERROR","Duplicate identifier values in request")};for(let a of i){let l=await oe(t,a.id,e);if(!l)return {success:false,error:new o("IDENTIFIER_NOT_FOUND",`Identifier not found: ${a.id}`)};if(l.value!==a.value&&await Y(t,a.value))return {success:false,error:new o("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${a.value}`)}}for(let a of i)await nr(t,a.id,e,{type:a.type,value:a.value});return {success:true,identifiers:(await K(t,e)).map(a=>({id:a.id,type:a.type,value:a.value,isPrimary:a.isPrimary}))}}async function ye(e,r,s){let t=T(s.dialect);if(r.length===0)return {success:false,error:new o("VALIDATION_ERROR","At least one ID is required")};for(let d of r)if(!await oe(t,d,e))return {success:false,error:new o("IDENTIFIER_NOT_FOUND",`Identifier not found: ${d}`)};return await ir(t,e)-r.length<1?{success:false,error:new o("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await or(t,e,r),{success:true,identifiers:(await K(t,e)).map(d=>({id:d.id,type:d.type,value:d.value,isPrimary:d.isPrimary}))})}async function ge(e,r,s){let t=T(s.dialect);return await oe(t,r,e)?(await ar(t,e,r),{success:true,identifiers:(await K(t,e)).map(d=>({id:d.id,type:d.type,value:d.value,isPrimary:d.isPrimary}))}):{success:false,error:new o("IDENTIFIER_NOT_FOUND",`Identifier not found: ${r}`)}}function C(e){return e.mode==="client"?Er(e.keyUri):br(e)}function Er(e){return async(r,s,t)=>{let i=r.headers.authorization,n=i?.startsWith("Bearer ")?i.slice(7):void 0;if(!n)return t(new o("UNAUTHORIZED","Missing or malformed Authorization header"));try{let d=await He(e),a=Qe(n,d);r.user={id:a.id,identifier:a.identifier,identifierType:a.identifierType,roles:a.roles},t();}catch(d){t(d);}}}function br(e){return async(r,s,t)=>{let i=Z(r,e);if(!i)return t(new o("UNAUTHORIZED","Missing or malformed Authorization header"));try{let n=se(i,e);if(e.isTokenRevoked&&await e.isTokenRevoked(n.sessionId))return t(new o("UNAUTHORIZED","Token has been revoked"));r.user={id:n.id,identifier:n.identifier,identifierType:n.identifierType,roles:n.roles},t();}catch(n){if(n instanceof o&&n.code==="TOKEN_EXPIRED"){let d=U(r.headers.cookie,D(e));if(!d)return t(new o("UNAUTHORIZED","Token expired. Please login again."));try{let a=await H(d,e);if(!a.success)return t(new o("UNAUTHORIZED","Session expired. Please login again."));z(s,a.refreshToken,e),W(s,a.accessToken,e),s.setHeader("X-New-Access-Token",a.accessToken),r.user=a.user,t();}catch{t(new o("UNAUTHORIZED","Session expired. Please login again."));}}else t(n);}}}function ee(...e){let r=`Requires one of roles: ${e.join(", ")}`;return (s,t,i)=>{if(!s.user)return i(new o("UNAUTHORIZED","Not authenticated"));let n=s.user.roles;if(!e.some(d=>n.includes(d)))return i(new o("FORBIDDEN",r));i();}}var xr=new o("FORBIDDEN","You do not have permission to perform this action");function re(e){let r=typeof e=="function"?{check:e}:e;return async(s,t,i)=>{if(!s.user)return i(new o("UNAUTHORIZED","Not authenticated"));if(r.roles&&r.roles.length>0){let n=s.user.roles;if(r.roles.some(d=>n.includes(d)))return i()}try{let n=r.check(s);(n instanceof Promise?await n:n)?i():i(xr);}catch(n){i(n);}}}function F(e){return (r,s,t,i)=>{if(r instanceof o){t.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),t.status(500).json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}var he=8,j=72,Re=255,pr=100,L=50;function y(e){return new o("VALIDATION_ERROR",e)}function _(e,r,s,t){e.status(r).json({error:false,statusCode:r,message:s,data:t});}function P(e,r){e.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}function N(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new o("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you apply express.json()?");return e}function Nr(e,r){if(!r.apiKey)return;let s=e.headers["x-api-key"];if(typeof s!="string"||s!==r.apiKey)throw new o("UNAUTHORIZED","Invalid or missing API key")}function ke(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}function Dr(e){return e.startsWith("RS")||e.startsWith("PS")}function Ce(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw y(`identifiers[${r}] must be an object`);let s=e;if(typeof s.type!="string"||s.type.trim().length===0)throw y(`identifiers[${r}].type is required and must be a non-empty string`);if(s.type.length>pr)throw y(`identifiers[${r}].type must not exceed ${pr} characters`);if(typeof s.value!="string"||s.value.trim().length===0)throw y(`identifiers[${r}].value is required and must be a non-empty string`);if(s.value.length>Re)throw y(`identifiers[${r}].value must not exceed ${Re} characters`);return {type:s.type,value:s.value}}function mr(e){let r=Router(),s=e,t=A(s),i=e.router?.register??(u=>Q(u,s)),n=e.router?.login??(u=>ue(u,s)),d=e.router?.refresh??(u=>H(u,s)),a=e.router?.logout??(u=>u!==void 0?de(u,s):Promise.resolve()),l=e.router?.logoutAll??(u=>ce(u,s)),g=e.router?.assignRoles??((u,c)=>fe(u,c,s)),v=e.router?.changePassword??((u,c,w)=>le(u,c,w,s)),S=e.router?.bulkCreateIdentifiers??((u,c)=>pe(u,c,s)),k=e.router?.bulkUpdateIdentifiers??((u,c)=>me(u,c,s)),R=e.router?.bulkDeleteIdentifiers??((u,c)=>ye(u,c,s)),x=e.router?.changePrimaryIdentifier??((u,c)=>ge(u,c,s));Dr(t.algorithm)&&r.get("/keys",(u,c)=>{c.setHeader("Cache-Control","public, max-age=3600"),c.json(Ue(e.secret));}),r.post("/register",async(u,c,w)=>{try{Nr(u,e);let f=N(u.body),{identifiers:p,password:m,roles:h}=f;if(!Array.isArray(p)||p.length===0)throw y("identifiers is required and must be a non-empty array");if(p.length>L)throw y(`identifiers must not exceed ${L} entries`);let I=p.map(($,wr)=>Ce($,wr));if(typeof m!="string"||m.length<he)throw y(`password is required and must be at least ${he} characters`);if(m.length>j)throw y(`password must not exceed ${j} characters`);if(h!==void 0&&!Array.isArray(h))throw y("roles must be an array of strings when provided");if(Array.isArray(h)&&!h.every($=>typeof $=="string"))throw y("each role must be a string");let b=Array.isArray(h)?h:void 0,q=await i(b!==void 0?{identifiers:I,password:m,roles:b}:{identifiers:I,password:m});if(!q.success){P(c,q.error);return}_(c,201,"User registered successfully",{user:q.user});}catch(f){w(f);}}),r.post("/login",async(u,c,w)=>{try{let f=N(u.body),{identifier:p,password:m}=f;if(typeof p!="string"||p.trim().length===0)throw y("identifier is required and must be a non-empty string");if(p.length>Re)throw y(`identifier must not exceed ${Re} characters`);if(typeof m!="string"||m.length===0)throw y("password is required");if(m.length>j)throw y(`password must not exceed ${j} characters`);let h=p.trim(),I=await n({identifier:h,password:m});if(!I.success){ke(()=>e.hooks?.onFailedLogin?.(h,I.error)),P(c,I.error);return}ke(()=>e.hooks?.onLogin?.(I.user)),z(c,I.refreshToken,e),W(c,I.accessToken,e),_(c,200,"Login successful",{accessToken:I.accessToken,user:I.user});}catch(f){w(f);}}),r.post("/refresh",async(u,c,w)=>{try{let f=U(u.headers.cookie,D(e));if(!f)throw new o("UNAUTHORIZED","Refresh token cookie is missing");let p=await d(f);if(!p.success){ie(c,e),P(c,p.error);return}z(c,p.refreshToken,e),W(c,p.accessToken,e),_(c,200,"Token refreshed",{accessToken:p.accessToken});}catch(f){w(f);}}),r.post("/logout",async(u,c,w)=>{try{let f=U(u.headers.cookie,D(e));await a(f),ie(c,e),Ie(c,e),_(c,200,"Logged out",null);}catch(f){w(f);}}),r.post("/logout-all",C(e),async(u,c,w)=>{try{let f=u.user.id;await l(f),ke(()=>e.hooks?.onLogout?.(f)),ie(c,e),Ie(c,e),_(c,200,"All sessions revoked",null);}catch(f){w(f);}}),r.get("/me",C(e),(u,c)=>{_(c,200,"OK",u.user);});let E=re(u=>!!u.user);return r.post("/me/identifiers",C(e),E,async(u,c,w)=>{try{let f=N(u.body),{identifiers:p}=f;if(!Array.isArray(p)||p.length===0)throw y("identifiers is required and must be a non-empty array");if(p.length>L)throw y(`identifiers must not exceed ${L} entries`);let m=p.map((I,b)=>Ce(I,b)),h=await S(u.user.id,m);if(!h.success){P(c,h.error);return}_(c,201,"Identifiers added successfully",{identifiers:h.identifiers});}catch(f){w(f);}}),r.put("/me/identifiers",C(e),E,async(u,c,w)=>{try{let f=N(u.body),{identifiers:p}=f;if(!Array.isArray(p)||p.length===0)throw y("identifiers is required and must be a non-empty array");if(p.length>L)throw y(`identifiers must not exceed ${L} entries`);let m=p.map((I,b)=>{if(typeof I!="object"||I===null||Array.isArray(I))throw y(`identifiers[${b}] must be an object`);let te=I;if(typeof te.id!="string"||te.id.trim().length===0)throw y(`identifiers[${b}].id is required and must be a non-empty string`);let{type:q,value:$}=Ce(I,b);return {id:te.id,type:q,value:$}}),h=await k(u.user.id,m);if(!h.success){P(c,h.error);return}_(c,200,"Identifiers updated successfully",{identifiers:h.identifiers});}catch(f){w(f);}}),r.delete("/me/identifiers",C(e),E,async(u,c,w)=>{try{let f=N(u.body),{ids:p}=f;if(!Array.isArray(p)||p.length===0)throw y("ids is required and must be a non-empty array of strings");if(!p.every(h=>typeof h=="string"))throw y("each id must be a string");let m=await R(u.user.id,p);if(!m.success){P(c,m.error);return}_(c,200,"Identifiers deleted successfully",{identifiers:m.identifiers});}catch(f){w(f);}}),r.patch("/me/identifiers/primary",C(e),E,async(u,c,w)=>{try{let f=N(u.body),{id:p}=f;if(typeof p!="string"||p.trim().length===0)throw y("id is required and must be a non-empty string");let m=await x(u.user.id,p.trim());if(!m.success){P(c,m.error);return}_(c,200,"Primary identifier updated successfully",{identifiers:m.identifiers});}catch(f){w(f);}}),r.patch("/me/password",C(e),E,async(u,c,w)=>{try{let f=N(u.body),{currentPassword:p,newPassword:m}=f;if(typeof p!="string"||p.length===0)throw y("currentPassword is required");if(typeof m!="string"||m.length<he)throw y(`newPassword must be at least ${he} characters`);if(m.length>j)throw y(`newPassword must not exceed ${j} characters`);if(p===m)throw y("newPassword must be different from currentPassword");let h=await v(u.user.id,p,m);if(!h.success){P(c,h.error);return}_(c,200,"Password updated successfully. All sessions have been revoked.",null);}catch(f){w(f);}}),r.post("/users/:userId/roles",C(e),ee("admin"),async(u,c,w)=>{try{let f=N(u.body),{roles:p}=f,m=u.params.userId,h=typeof m=="string"?m:void 0;if(!h)throw y("userId is required");if(!Array.isArray(p)||p.length===0)throw y("roles must be a non-empty array of strings");if(!p.every(b=>typeof b=="string"))throw y("each role must be a string");let I=await g(h,p);if(!I.success){P(c,I.error);return}_(c,200,"Roles assigned successfully",{user:I.user});}catch(f){w(f);}}),r.use(F()),r}var yr=new Map;function gr(e){let r=yr.get(e);return r||(r=new Redis(e,{lazyConnect:false,enableOfflineQueue:false}),yr.set(e,r)),r}function _e(e){let r=e?.ttl??3e5,s=(e?.header??"X-Idempotency-Key").toLowerCase(),t=new Set((e?.methods??["POST","PUT","PATCH"]).map(n=>n.toUpperCase())),i=e?.redisUrl;return i?Ur(i,r,s,t):Kr(r,s,t,e?.maxSize??1e4)}function Ur(e,r,s,t){let i=gr(e),n="sentri:idempotency:";return async(d,a,l)=>{let g=d.headers[s];if(!g||typeof g!="string"||!t.has(d.method))return l();d.requestId=g,a.setHeader("X-Request-Id",g);let v=await i.get(`${n}${g}`);if(v){let k=JSON.parse(v);return a.setHeader("X-Idempotent-Replayed","true"),a.status(k.statusCode).json(k.body)}let S=a.json.bind(a);a.json=function(R){if(a.statusCode>=200&&a.statusCode<300){let x={statusCode:a.statusCode,body:R,expiresAt:Date.now()+r};i.set(`${n}${g}`,JSON.stringify(x),"PX",r).catch(()=>{});}return S(R)},l();}}function Kr(e,r,s,t){let i=Math.max(e,5e3),n=new Map,d=setInterval(()=>{let a=Date.now();for(let[l,g]of n)g.expiresAt<=a&&n.delete(l);},i);return typeof d=="object"&&d!==null&&"unref"in d&&d.unref(),(a,l,g)=>{let v=a.headers[r];if(!v||typeof v!="string"||!s.has(a.method))return g();a.requestId=v,l.setHeader("X-Request-Id",v);let S=Date.now(),k=n.get(v);if(k&&k.expiresAt>S)return l.setHeader("X-Idempotent-Replayed","true"),l.status(k.statusCode).json(k.body);let R=l.json.bind(l);l.json=function(E){if(l.statusCode>=200&&l.statusCode<300){if(n.size>=t){let u=n.keys().next().value;u!==void 0&&n.delete(u);}n.set(v,{statusCode:l.statusCode,body:E,expiresAt:Date.now()+e});}return R(E)},g();}}async function hr(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("is_primary","integer",r=>r.notNull().defaultTo(0)).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute();}function Ee(e){if(Me(e),e.mode==="client")return {protect:()=>C(e),authorize:(...t)=>ee(...t),permit:t=>re(t),errorHandler:t=>F(t)};let r=e,s=A(r);return {protect:()=>C(r),authorize:(...t)=>ee(...t),permit:t=>re(t),hashPassword:t=>M(t,s.saltRounds),verifyPassword:(t,i)=>V(t,i),signAccessToken:t=>J(t,r),signRefreshToken:t=>X(t,r),verifyAccessToken:t=>se(t,r),verifyRefreshToken:t=>G(t,r),getCurrentAccessToken:t=>Z(t,r),router:()=>mr(r),migrate:()=>hr(T(r.dialect)),errorHandler:t=>F(t),idempotencyMiddleware:t=>_e({...t,...r.redisUrl!==void 0&&{redisUrl:r.redisUrl}}),register:t=>Q(t,r),login:t=>ue(t,r),refresh:t=>H(t,r),logout:t=>de(t,r),logoutAll:t=>ce(t,r),getUser:t=>fr(t,r),changePassword:(t,i,n)=>le(t,i,n,r),assignRoles:(t,i)=>fe(t,i,r),bulkCreateIdentifiers:(t,i)=>pe(t,i,r),bulkUpdateIdentifiers:(t,i)=>me(t,i,r),bulkDeleteIdentifiers:(t,i)=>ye(t,i,r),changePrimaryIdentifier:(t,i)=>ge(t,i,r)}}function Rr(e){return new PostgresDialect({pool:new Pool(e)})}function Lr(e){let{privateKey:r}=generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),s={mode:"server",dialect:Rr(e.db),secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl}};return Ee(s)}export{be as SENTRI_ERROR_STATUS,o as SentriError,Ee as createAuth,Lr as createAuthServer,F as createErrorHandler,_e as createIdempotencyMiddleware,Z as getCurrentAccessToken,Q as register};
1
+ import je from'bcrypt';import W from'jsonwebtoken';import {randomUUID,generateKeyPairSync,createPublicKey,createPrivateKey}from'crypto';import {Kysely,sql,PostgresDialect}from'kysely';import {Router}from'express';import {Redis}from'ioredis';import {Pool}from'pg';var Fe=Object.assign(Object.create(null),{UNAUTHORIZED:401,TOKEN_EXPIRED:401,TOKEN_INVALID:401,INVALID_CREDENTIALS:401,FORBIDDEN:403,USER_NOT_FOUND:404,IDENTIFIER_NOT_FOUND:404,USER_ALREADY_EXISTS:409,IDENTIFIER_ALREADY_EXISTS:409,INVALID_ROLE:400,VALIDATION_ERROR:400,CONFIGURATION_ERROR:500}),c=class extends Error{code;statusCode;constructor(r,t,s){super(t),this.name="SentriError",this.code=r,this.statusCode=s??Fe[r]??500;}};async function G(e,r=12){return je.hash(e,r)}async function Z(e,r){return je.compare(e,r)}var qe=new Map,$e=new Map,Or=3600*1e3;function Ve(e){let r=qe.get(e);if(!r){let t=createPrivateKey(e),s=createPublicKey(t),n=s.export({format:"jwk"}),i=Buffer.from(e).slice(0,8).toString("base64url"),u={...n,use:"sig",kid:i};r={kid:i,publicKey:s,jwk:u},qe.set(e,r);}return r}function Be(e){let{jwk:r}=Ve(e);return {keys:[r]}}function Xe(e){return Ve(e).publicKey}async function Je(e){let r=Date.now(),t=$e.get(e);if(t&&r-t.fetchedAt<Or)return t.publicKey;let s=await fetch(e);if(!s.ok)throw new c("CONFIGURATION_ERROR",`Failed to fetch public key from ${e}: HTTP ${s.status}`);let n=await s.json();if(!n.keys||n.keys.length===0)throw new c("CONFIGURATION_ERROR",`No keys found in JWKS response from ${e}`);let i=n.keys[0],u=createPublicKey({key:i,format:"jwk"});return $e.set(e,{publicKey:u,fetchedAt:r}),u}var ze=new WeakMap,Ge=32,Ze=10,We=31;function Qe(e){if(e.mode==="client"){if(!e.keyUri||e.keyUri.trim().length===0)throw new c("CONFIGURATION_ERROR","keyUri must not be empty");return}if(!e.secret||e.secret.trim().length===0)throw new c("CONFIGURATION_ERROR","secret must not be empty");if((e.algorithm??"HS256").startsWith("HS")&&e.secret.length<Ge)throw new c("CONFIGURATION_ERROR",`secret must be at least ${Ge} characters for HMAC algorithms`);let s=e.saltRounds??12;if(!Number.isInteger(s)||s<Ze||s>We)throw new c("CONFIGURATION_ERROR",`saltRounds must be an integer between ${Ze} and ${We}`);if(!e.validRoles||e.validRoles.length===0)throw new c("CONFIGURATION_ERROR","validRoles must contain at least one role");if(!e.dialect)throw new c("CONFIGURATION_ERROR","dialect is required in server mode")}function C(e){let r=ze.get(e);if(r)return r;let t={algorithm:e.algorithm??"HS256",accessExpiresIn:e.accessExpiresIn??"15m",refreshExpiresIn:e.refreshExpiresIn??"7d",saltRounds:e.saltRounds??12,validRoles:e.validRoles,validRolesSet:new Set(e.validRoles),cookieName:e.cookie?.name??"refresh_token",accessCookieName:e.accessCookie?.name??"access_token"};return ze.set(e,t),t}var Ur=/^(\d+)([smhdw])$/,Kr={s:1e3,m:6e4,h:36e5,d:864e5,w:6048e5},Ye=new Map;function j(e){if(typeof e=="number")return e*1e3;let r=Ye.get(e);if(r!==void 0)return r;let t=Ur.exec(e);if(!t?.[1]||!t?.[2])throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let s=Kr[t[2]];if(s===void 0)throw new Error(`Invalid expiresIn: "${e}". Use e.g. "15m", "7d", "1h".`);let n=parseInt(t[1],10)*s;return Ye.set(e,n),n}var er=new Map,rr=new Map,tr=new Map;function sr(e){return e.startsWith("RS")||e.startsWith("PS")}function ir(e){let r=er.get(e);return r||(r={access:`${e}:access`,refresh:`${e}:refresh`},er.set(e,r)),r}function Hr(e){let r=rr.get(e);return r||(r=createPrivateKey(e),rr.set(e,r)),r}function nr(e){let r=C(e);if(sr(r.algorithm)){let n=Hr(e.secret);return {accessKey:n,refreshKey:n}}let{access:t,refresh:s}=ir(e.secret);return {accessKey:t,refreshKey:s}}function or(e,r){let t=C(e);if(sr(t.algorithm))return Xe(e.secret);let{access:s,refresh:n}=ir(e.secret);return r==="access"?s:n}function ar(e,r,t,s){let n=`${t}:${s}`,i=tr.get(n);return i||(i={expiresIn:t,algorithm:s},tr.set(n,i)),W.sign(e,r,i)}function ur(e,r,t){try{let s=W.verify(e,r,{algorithms:[t]});if(typeof s=="string"||s===null)throw new c("TOKEN_INVALID","Token payload is not an object");return s}catch(s){throw s instanceof c?s:s instanceof W.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}function Y(e,r){let t=C(r),{accessKey:s}=nr(r);return ar(e,s,t.accessExpiresIn,t.algorithm)}function Q(e,r){let t=C(r),{refreshKey:s}=nr(r);return ar({sessionId:e},s,t.refreshExpiresIn,t.algorithm)}function de(e,r){let t=C(r),s=or(r,"access");return ur(e,s,t.algorithm)}function ee(e,r){let t=C(r),s=or(r,"refresh");return ur(e,s,t.algorithm)}function dr(e,r){try{let t=W.verify(e,r,{algorithms:["RS256","RS384","RS512","PS256","PS384","PS512"]});if(typeof t=="string"||t===null)throw new c("TOKEN_INVALID","Token payload is not an object");return t}catch(t){throw t instanceof c?t:t instanceof W.TokenExpiredError?new c("TOKEN_EXPIRED","Token has expired"):new c("TOKEN_INVALID","Token is invalid or malformed")}}function F(e){return C(e).cookieName}function q(e,r){if(!e)return;let t=`${r}=`,s=0;for(;s<e.length;){for(;s<e.length&&e[s]===" ";)s++;let n=e.indexOf(";",s),i=n===-1?e.length:n;if(e.startsWith(t,s))return e.slice(s+t.length,i);s=i+1;}}function re(e,r,t){let s=t.cookie??{},n=C(t),i=j(n.refreshExpiresIn);e.cookie(F(t),r,{httpOnly:s.httpOnly??true,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}function ce(e,r){let t=r.cookie??{};e.clearCookie(F(r),{path:t.path??"/"});}function Se(e){return C(e).accessCookieName}function te(e,r,t){if(!t.accessCookie)return;let s=t.accessCookie,n=C(t),i=j(n.accessExpiresIn);e.cookie(Se(t),r,{httpOnly:false,secure:s.secure??false,sameSite:s.sameSite??"strict",path:s.path??"/",maxAge:i});}function Ee(e,r){if(!r.accessCookie)return;let t=r.accessCookie;e.clearCookie(Se(r),{path:t.path??"/"});}function se(e,r){let t=e.headers.authorization;return t?.startsWith("Bearer ")?t.slice(7):q(e.headers.cookie,Se(r))}var cr=new Map;function k(e){let r=cr.get(e);return r||(r=new Kysely({dialect:e}),cr.set(e,r)),r}function be(e){try{return JSON.parse(e)}catch{return []}}function jr(e){return JSON.stringify(e)}function fr(e){return {id:e.id,userId:e.user_id,type:e.type,value:e.value,isPrimary:e.is_primary===1,createdAt:new Date(e.created_at)}}async function ie(e,r){let t=await e.selectFrom("sentri_identifiers as i_login").innerJoin("sentri_users as u","u.id","i_login.user_id").innerJoin("sentri_identifiers as i_primary",s=>s.onRef("i_primary.user_id","=","u.id").on("i_primary.is_primary","=",1)).select(["u.id","u.password_hash","u.roles","i_primary.value as primary_value","i_primary.type as primary_type"]).where("i_login.value","=",r).executeTakeFirst();return t?{id:t.id,identifier:t.primary_value,identifierType:t.primary_type,passwordHash:t.password_hash,roles:be(t.roles)}:null}async function le(e,r){let t=await e.selectFrom("sentri_users as u").innerJoin("sentri_identifiers as i",s=>s.onRef("i.user_id","=","u.id").on("i.is_primary","=",1)).select(["u.id","u.password_hash","u.roles","i.value as primary_value","i.type as primary_type"]).where("u.id","=",r).executeTakeFirst();return t?{id:t.id,identifier:t.primary_value,identifierType:t.primary_type,passwordHash:t.password_hash,roles:be(t.roles)}:null}async function pr(e,r,t){let s=t.map(n=>({id:randomUUID(),user_id:r,type:n.type,value:n.value,is_primary:n.isPrimary?1:0}));return await e.insertInto("sentri_identifiers").values(s).execute(),s.map(n=>({id:n.id,userId:n.user_id,type:n.type,value:n.value,isPrimary:n.is_primary===1,createdAt:new Date}))}async function $(e,r){return (await e.selectFrom("sentri_identifiers").selectAll().where("user_id","=",r).orderBy("created_at","asc").execute()).map(fr)}async function fe(e,r,t){let s=await e.selectFrom("sentri_identifiers").selectAll().where("id","=",r).where("user_id","=",t).executeTakeFirst();return s?fr(s):null}async function mr(e,r){let t=await e.selectFrom("sentri_identifiers").select(s=>s.fn.countAll().as("count")).where("user_id","=",r).executeTakeFirst();return Number(t?.count??0)}async function gr(e,r,t,s){await e.updateTable("sentri_identifiers").set({type:s.type,value:s.value}).where("id","=",r).where("user_id","=",t).execute();}async function hr(e,r,t){await e.deleteFrom("sentri_identifiers").where("user_id","=",r).where("id","in",t).execute();}async function yr(e,r,t){await e.transaction().execute(async s=>{await s.updateTable("sentri_identifiers").set({is_primary:0}).where("user_id","=",r).execute(),await s.updateTable("sentri_identifiers").set({is_primary:1}).where("id","=",t).where("user_id","=",r).execute();});}async function Rr(e,r,t){await e.updateTable("sentri_users").set({password_hash:t}).where("id","=",r).execute();}async function wr(e,r,t){await e.updateTable("sentri_users").set({roles:jr(t)}).where("id","=",r).execute();}async function xe(e,r){let t=randomUUID();return await e.insertInto("sentri_sessions").values({id:t,user_id:r.userId,expires_at:r.expiresAt}).execute(),{id:t}}async function Ir(e,r){let t=await e.selectFrom("sentri_sessions as s").innerJoin("sentri_users as u","u.id","s.user_id").innerJoin("sentri_identifiers as i",s=>s.onRef("i.user_id","=","u.id").on("i.is_primary","=",1)).select(["s.id as session_id","s.user_id","s.expires_at","s.created_at as session_created_at","u.id as user_id_col","u.password_hash","u.roles","i.value as primary_identifier","i.type as primary_identifier_type"]).where("s.id","=",r).executeTakeFirst();return t?{id:t.session_id,userId:t.user_id,expiresAt:new Date(t.expires_at),createdAt:new Date(t.session_created_at),user:{id:t.user_id_col,identifier:t.primary_identifier,identifierType:t.primary_identifier_type,passwordHash:t.password_hash,roles:be(t.roles)}}:null}async function pe(e,r){await e.deleteFrom("sentri_sessions").where("id","=",r).execute();}async function De(e,r){await e.deleteFrom("sentri_sessions").where("user_id","=",r).execute();}async function ne(e,r){let t=C(r),s=k(r.dialect),n=e.roles??[],i=n.filter(_=>!t.validRolesSet.has(_));if(i.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${i.join(", ")}`)};if(!e.identifiers||e.identifiers.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let u=e.identifiers.map(_=>({type:_.type.trim(),value:_.value.trim()}));if(new Set(u.map(_=>_.value)).size!==u.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let _ of u)if(await ie(s,_.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${_.value}`)};let d=await G(e.password,t.saltRounds),{userId:g,identifierRows:y}=await s.transaction().execute(async _=>{let N=randomUUID();await _.insertInto("sentri_users").values({id:N,password_hash:d,roles:JSON.stringify(n)}).execute();let K=u.map((L,ke)=>({id:randomUUID(),user_id:N,type:L.type,value:L.value,is_primary:ke===0?1:0}));return await _.insertInto("sentri_identifiers").values(K).execute(),{userId:N,identifierRows:K}}),x=y.map(_=>({id:_.id,type:_.type,value:_.value,isPrimary:_.is_primary===1})),S=x[0];return {success:true,user:{id:g,identifier:S.value,identifierType:S.type,roles:n,identifiers:x}}}async function me(e,r){let t=C(r),s=k(r.dialect),n=await ie(s,e.identifier.trim());if(!n)return {success:false,error:new c("INVALID_CREDENTIALS","Invalid credentials")};if(!await Z(e.password,n.passwordHash))return {success:false,error:new c("INVALID_CREDENTIALS","Invalid credentials")};let u=new Date(Date.now()+j(t.refreshExpiresIn)),a=await xe(s,{userId:n.id,expiresAt:u}),d={id:n.id,identifier:n.identifier,identifierType:n.identifierType,roles:n.roles},g=Y({id:n.id,identifier:n.identifier,identifierType:n.identifierType,roles:n.roles,sessionId:a.id},r),y=Q(a.id,r);return {success:true,accessToken:g,refreshToken:y,user:d}}async function M(e,r){let t=C(r),s=k(r.dialect),n;try{({sessionId:n}=ee(e,r));}catch(x){return x instanceof c?{success:false,error:x}:{success:false,error:new c("TOKEN_INVALID","Invalid refresh token")}}let i=await Ir(s,n);if(!i)return {success:false,error:new c("UNAUTHORIZED","Session not found or revoked")};if(i.expiresAt.getTime()<Date.now())return await pe(s,n),{success:false,error:new c("TOKEN_EXPIRED","Session has expired")};await pe(s,n);let u=new Date(Date.now()+j(t.refreshExpiresIn)),a=await xe(s,{userId:i.userId,expiresAt:u}),d={id:i.user.id,identifier:i.user.identifier,identifierType:i.user.identifierType,roles:i.user.roles},g=Y({...d,sessionId:a.id},r),y=Q(a.id,r);return {success:true,accessToken:g,refreshToken:y,user:d}}async function ge(e,r){let t=k(r.dialect),s;try{({sessionId:s}=ee(e,r));}catch{return}await pe(t,s);}async function he(e,r){let t=k(r.dialect);await De(t,e);}async function ye(e,r){let t=k(r.dialect),s=await le(t,e);if(!s)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let n=await $(t,e);return {success:true,user:{id:s.id,identifier:s.identifier,identifierType:s.identifierType,roles:s.roles,identifiers:n.map(i=>({id:i.id,type:i.type,value:i.value,isPrimary:i.isPrimary}))}}}async function Re(e,r,t,s){let n=C(s),i=k(s.dialect),u=await le(i,e);if(!u)return {success:false,error:new c("USER_NOT_FOUND","User not found")};if(!await Z(r,u.passwordHash))return {success:false,error:new c("INVALID_CREDENTIALS","Invalid credentials")};let d=await G(t,n.saltRounds);return await Rr(i,e,d),await De(i,e),{success:true}}async function we(e,r,t){let s=C(t),n=k(t.dialect),i=r.filter(g=>!s.validRolesSet.has(g));if(i.length>0)return {success:false,error:new c("INVALID_ROLE",`Invalid roles: ${i.join(", ")}`)};let u=await le(n,e);if(!u)return {success:false,error:new c("USER_NOT_FOUND","User not found")};let a=new Set(u.roles);for(let g of r)a.add(g);let d=Array.from(a);return await wr(n,e,d),{success:true,user:{id:u.id,identifier:u.identifier,identifierType:u.identifierType,roles:d}}}async function Ie(e,r,t){let s=k(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one identifier is required")};let n=r.map(d=>({type:d.type.trim(),value:d.value.trim()}));if(new Set(n.map(d=>d.value)).size!==n.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let d of n)if(await ie(s,d.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${d.value}`)};await pr(s,e,n.map(d=>({...d,isPrimary:false})));return {success:true,identifiers:(await $(s,e)).map(d=>({id:d.id,type:d.type,value:d.value,isPrimary:d.isPrimary}))}}async function Ae(e,r,t){let s=k(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one update is required")};let n=r.map(a=>({id:a.id,type:a.type.trim(),value:a.value.trim()}));if(new Set(n.map(a=>a.value)).size!==n.length)return {success:false,error:new c("VALIDATION_ERROR","Duplicate identifier values in request")};for(let a of n){let d=await fe(s,a.id,e);if(!d)return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${a.id}`)};if(d.value!==a.value&&await ie(s,a.value))return {success:false,error:new c("IDENTIFIER_ALREADY_EXISTS",`Identifier already taken: ${a.value}`)}}for(let a of n)await gr(s,a.id,e,{type:a.type,value:a.value});return {success:true,identifiers:(await $(s,e)).map(a=>({id:a.id,type:a.type,value:a.value,isPrimary:a.isPrimary}))}}async function ve(e,r,t){let s=k(t.dialect);if(r.length===0)return {success:false,error:new c("VALIDATION_ERROR","At least one ID is required")};for(let u of r)if(!await fe(s,u,e))return {success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${u}`)};return await mr(s,e)-r.length<1?{success:false,error:new c("VALIDATION_ERROR","Cannot delete all identifiers \u2014 at least one must remain")}:(await hr(s,e,r),{success:true,identifiers:(await $(s,e)).map(u=>({id:u.id,type:u.type,value:u.value,isPrimary:u.isPrimary}))})}async function _e(e,r,t){let s=k(t.dialect);return await fe(s,r,e)?(await yr(s,e,r),{success:true,identifiers:(await $(s,e)).map(u=>({id:u.id,type:u.type,value:u.value,isPrimary:u.isPrimary}))}):{success:false,error:new c("IDENTIFIER_NOT_FOUND",`Identifier not found: ${r}`)}}var O={info:()=>{},warn:()=>{},error:()=>{}};function m(e,r,t){return {service:e,event:r,...t}}function E(e){let r=e.logger??O,t=e.loggerService??"sentri";return e.mode==="client"?qr(e.keyUri,r,t):$r(e,r,t)}function qr(e,r,t){return async(s,n,i)=>{let u=s.headers.authorization,a=u?.startsWith("Bearer ")?u.slice(7):void 0,d=s.requestId;if(!a)return r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:"UNAUTHORIZED",...d!==void 0&&{requestId:d}})),i(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let g=await Je(e),y=dr(a,g);s.user={id:y.id,identifier:y.identifier,identifierType:y.identifierType,roles:y.roles},r.info(m(t,"auth.protect.success",{mode:"client",userId:y.id,...d!==void 0&&{requestId:d}})),i();}catch(g){let y=g instanceof c?g.code:"TOKEN_INVALID";r.warn(m(t,"auth.protect.failure",{mode:"client",errorCode:y,...d!==void 0&&{requestId:d}})),i(g);}}}function $r(e,r,t){return async(s,n,i)=>{let u=se(s,e),a=s.requestId;if(!u)return r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"UNAUTHORIZED",...a!==void 0&&{requestId:a}})),i(new c("UNAUTHORIZED","Missing or malformed Authorization header"));try{let d=de(u,e);if(e.isTokenRevoked&&await e.isTokenRevoked(d.sessionId))return r.warn(m(t,"auth.protect.token_revoked",{mode:"server",userId:d.id,...a!==void 0&&{requestId:a}})),i(new c("UNAUTHORIZED","Token has been revoked"));s.user={id:d.id,identifier:d.identifier,identifierType:d.identifierType,roles:d.roles},r.info(m(t,"auth.protect.success",{mode:"server",userId:d.id,...a!==void 0&&{requestId:a}})),i();}catch(d){if(d instanceof c&&d.code==="TOKEN_EXPIRED"){let g=q(s.headers.cookie,F(e));if(!g)return r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...a!==void 0&&{requestId:a}})),i(new c("UNAUTHORIZED","Token expired. Please login again."));try{let y=await M(g,e);if(!y.success)return r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:y.error.code,...a!==void 0&&{requestId:a}})),i(new c("UNAUTHORIZED","Session expired. Please login again."));re(n,y.refreshToken,e),te(n,y.accessToken,e),n.setHeader("X-New-Access-Token",y.accessToken),s.user=y.user,r.info(m(t,"auth.protect.auto_refresh",{mode:"server",userId:y.user.id,...a!==void 0&&{requestId:a}})),i();}catch{r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:"TOKEN_EXPIRED",...a!==void 0&&{requestId:a}})),i(new c("UNAUTHORIZED","Session expired. Please login again."));}}else {let g=d instanceof c?d.code:"TOKEN_INVALID";r.warn(m(t,"auth.protect.failure",{mode:"server",errorCode:g,...a!==void 0&&{requestId:a}})),i(d);}}}}function vr(e,r,t){let s=`Requires one of roles: ${e.join(", ")}`;return (n,i,u)=>{let a=n.requestId;if(!n.user)return r.warn(m(t,"auth.authorize.unauthenticated",{requiredRoles:e,...a!==void 0&&{requestId:a}})),u(new c("UNAUTHORIZED","Not authenticated"));let d=n.user.roles;if(!e.some(g=>d.includes(g)))return r.warn(m(t,"auth.authorize.denied",{userId:n.user.id,userRoles:[...d],requiredRoles:e,...a!==void 0&&{requestId:a}})),u(new c("FORBIDDEN",s));r.info(m(t,"auth.authorize.passed",{userId:n.user.id,userRoles:[...d],requiredRoles:e,...a!==void 0&&{requestId:a}})),u();}}function oe(e,r){return function(...s){return vr(s,e,r)}}function Pe(...e){return vr(e,O,"sentri")}var Mr=new c("FORBIDDEN","You do not have permission to perform this action");function _r(e,r,t){return async(s,n,i)=>{let u=s.requestId;if(!s.user)return r.warn(m(t,"auth.permit.unauthenticated",{...u!==void 0&&{requestId:u}})),i(new c("UNAUTHORIZED","Not authenticated"));let a=s.user.id;if(e.roles&&e.roles.length>0){let d=s.user.roles;if(e.roles.some(g=>d.includes(g)))return r.info(m(t,"auth.permit.role_bypass",{userId:a,bypassedByRole:true,...u!==void 0&&{requestId:u}})),i()}try{let d=e.check(s);(d instanceof Promise?await d:d)?(r.info(m(t,"auth.permit.passed",{userId:a,...u!==void 0&&{requestId:u}})),i()):(r.warn(m(t,"auth.permit.denied",{userId:a,...u!==void 0&&{requestId:u}})),i(Mr));}catch(d){i(d);}}}function ae(e,r){return function(s){return _r(typeof s=="function"?{check:s}:s,e,r)}}function Ne(e){return _r(typeof e=="function"?{check:e}:e,O,"sentri")}function V(e){return (r,t,s,n)=>{if(r instanceof c){s.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});return}e?.onUnhandled?.(r),s.status(500).json({error:true,statusCode:500,code:"INTERNAL_SERVER_ERROR",message:"Internal server error",data:null});}}var Te=8,B=72,Ce=255,Tr=100,X=50;function I(e){return new c("VALIDATION_ERROR",e)}function b(e,r,t,s){e.status(r).json({error:false,statusCode:r,message:t,data:s});}function D(e,r){e.status(r.statusCode).json({error:true,statusCode:r.statusCode,code:r.code,message:r.message,data:null});}function U(e){if(e==null||typeof e!="object"||Array.isArray(e))throw new c("VALIDATION_ERROR","Request body is missing or not a JSON object. Did you apply express.json()?");return e}function Br(e,r){if(!r.apiKey)return;let t=e.headers["x-api-key"];if(typeof t!="string"||t!==r.apiKey)throw new c("UNAUTHORIZED","Invalid or missing API key")}function Oe(e){if(e)try{let r=e();r instanceof Promise&&r.catch(()=>{});}catch{}}function Xr(e){return e.startsWith("RS")||e.startsWith("PS")}function Ue(e,r){if(typeof e!="object"||e===null||Array.isArray(e))throw I(`identifiers[${r}] must be an object`);let t=e;if(typeof t.type!="string"||t.type.trim().length===0)throw I(`identifiers[${r}].type is required and must be a non-empty string`);if(t.type.length>Tr)throw I(`identifiers[${r}].type must not exceed ${Tr} characters`);if(typeof t.value!="string"||t.value.trim().length===0)throw I(`identifiers[${r}].value is required and must be a non-empty string`);if(t.value.length>Ce)throw I(`identifiers[${r}].value must not exceed ${Ce} characters`);return {type:t.type,value:t.value}}function T(e){return e.requestId!==void 0?{requestId:e.requestId}:{}}function Cr(e){let r=Router(),t=e,s=C(t),n=e.logger??O,i=e.loggerService??"sentri",u=oe(n,i),a=ae(n,i),d=e.router?.register??(o=>ne(o,t)),g=e.router?.login??(o=>me(o,t)),y=e.router?.refresh??(o=>M(o,t)),x=e.router?.logout??(o=>o!==void 0?ge(o,t):Promise.resolve()),S=e.router?.logoutAll??(o=>he(o,t)),_=e.router?.getUser??(o=>ye(o,t)),N=e.router?.assignRoles??((o,f)=>we(o,f,t)),K=e.router?.changePassword??((o,f,A)=>Re(o,f,A,t)),L=e.router?.bulkCreateIdentifiers??((o,f)=>Ie(o,f,t)),ke=e.router?.bulkUpdateIdentifiers??((o,f)=>Ae(o,f,t)),xr=e.router?.bulkDeleteIdentifiers??((o,f)=>ve(o,f,t)),Dr=e.router?.changePrimaryIdentifier??((o,f)=>_e(o,f,t));Xr(s.algorithm)&&r.get("/keys",(o,f)=>{f.setHeader("Cache-Control","public, max-age=3600"),f.json(Be(e.secret));}),r.post("/register",async(o,f,A)=>{let h=Date.now();try{Br(o,e);let l=U(o.body),{identifiers:p,password:R,roles:w}=l;if(!Array.isArray(p)||p.length===0)throw I("identifiers is required and must be a non-empty array");if(p.length>X)throw I(`identifiers must not exceed ${X} entries`);let v=p.map((z,Pr)=>Ue(z,Pr));if(typeof R!="string"||R.length<Te)throw I(`password is required and must be at least ${Te} characters`);if(R.length>B)throw I(`password must not exceed ${B} characters`);if(w!==void 0&&!Array.isArray(w))throw I("roles must be an array of strings when provided");if(Array.isArray(w)&&!w.every(z=>typeof z=="string"))throw I("each role must be a string");let P=Array.isArray(w)?w:void 0,H=await d(P!==void 0?{identifiers:v,password:R,roles:P}:{identifiers:v,password:R});if(!H.success){n.warn(m(i,"auth.register.failure",{errorCode:H.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,H.error);return}n.info(m(i,"auth.register.success",{userId:H.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,201,"User registered successfully",{user:H.user});}catch(l){A(l);}}),r.post("/login",async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{identifier:p,password:R}=l;if(typeof p!="string"||p.trim().length===0)throw I("identifier is required and must be a non-empty string");if(p.length>Ce)throw I(`identifier must not exceed ${Ce} characters`);if(typeof R!="string"||R.length===0)throw I("password is required");if(R.length>B)throw I(`password must not exceed ${B} characters`);let w=p.trim(),v=await g({identifier:w,password:R});if(!v.success){Oe(()=>e.hooks?.onFailedLogin?.(w,v.error)),n.warn(m(i,"auth.login.failure",{errorCode:v.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,v.error);return}Oe(()=>e.hooks?.onLogin?.(v.user)),re(f,v.refreshToken,e),te(f,v.accessToken,e),n.info(m(i,"auth.login.success",{userId:v.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Login successful",{accessToken:v.accessToken,user:v.user});}catch(l){A(l);}}),r.post("/refresh",async(o,f,A)=>{let h=Date.now();try{let l=q(o.headers.cookie,F(e));if(!l)throw new c("UNAUTHORIZED","Refresh token cookie is missing");let p=await y(l);if(!p.success){ce(f,e),n.warn(m(i,"auth.refresh.failure",{errorCode:p.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,p.error);return}re(f,p.refreshToken,e),te(f,p.accessToken,e),n.info(m(i,"auth.refresh.success",{userId:p.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Token refreshed",{accessToken:p.accessToken});}catch(l){A(l);}}),r.post("/logout",async(o,f,A)=>{let h=Date.now();try{let l=q(o.headers.cookie,F(e));await x(l),ce(f,e),Ee(f,e),n.info(m(i,"auth.logout",{duration_ms:Date.now()-h,...T(o)})),b(f,200,"Logged out",null);}catch(l){A(l);}}),r.post("/logout-all",E(e),async(o,f,A)=>{let h=Date.now();try{let l=o.user.id;await S(l),Oe(()=>e.hooks?.onLogout?.(l)),ce(f,e),Ee(f,e),n.info(m(i,"auth.logout_all",{userId:l,duration_ms:Date.now()-h,...T(o)})),b(f,200,"All sessions revoked",null);}catch(l){A(l);}}),r.get("/me",E(e),async(o,f,A)=>{let h=Date.now();try{let l=await _(o.user.id);if(!l.success){n.warn(m(i,"auth.me.failure",{userId:o.user.id,errorCode:l.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,l.error);return}n.info(m(i,"auth.me.success",{userId:l.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"OK",l.user);}catch(l){A(l);}}),r.get("/me/identifiers",E(e),async(o,f,A)=>{let h=Date.now();try{let l=await _(o.user.id);if(!l.success){n.warn(m(i,"auth.me.identifiers.failure",{userId:o.user.id,errorCode:l.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,l.error);return}n.info(m(i,"auth.me.identifiers.success",{userId:l.user.id,count:l.user.identifiers?.length??0,duration_ms:Date.now()-h,...T(o)})),b(f,200,"OK",{identifiers:l.user.identifiers??[]});}catch(l){A(l);}});let J=a(o=>!!o.user);return r.post("/me/identifiers",E(e),J,async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{identifiers:p}=l;if(!Array.isArray(p)||p.length===0)throw I("identifiers is required and must be a non-empty array");if(p.length>X)throw I(`identifiers must not exceed ${X} entries`);let R=p.map((v,P)=>Ue(v,P)),w=await L(o.user.id,R);if(!w.success){n.warn(m(i,"auth.identifiers.create_failure",{userId:o.user.id,errorCode:w.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,w.error);return}n.info(m(i,"auth.identifiers.created",{userId:o.user.id,count:w.identifiers.length,duration_ms:Date.now()-h,...T(o)})),b(f,201,"Identifiers added successfully",{identifiers:w.identifiers});}catch(l){A(l);}}),r.put("/me/identifiers",E(e),J,async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{identifiers:p}=l;if(!Array.isArray(p)||p.length===0)throw I("identifiers is required and must be a non-empty array");if(p.length>X)throw I(`identifiers must not exceed ${X} entries`);let R=p.map((v,P)=>{if(typeof v!="object"||v===null||Array.isArray(v))throw I(`identifiers[${P}] must be an object`);let ue=v;if(typeof ue.id!="string"||ue.id.trim().length===0)throw I(`identifiers[${P}].id is required and must be a non-empty string`);let{type:H,value:z}=Ue(v,P);return {id:ue.id,type:H,value:z}}),w=await ke(o.user.id,R);if(!w.success){n.warn(m(i,"auth.identifiers.update_failure",{userId:o.user.id,errorCode:w.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,w.error);return}n.info(m(i,"auth.identifiers.updated",{userId:o.user.id,count:w.identifiers.length,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Identifiers updated successfully",{identifiers:w.identifiers});}catch(l){A(l);}}),r.delete("/me/identifiers",E(e),J,async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{ids:p}=l;if(!Array.isArray(p)||p.length===0)throw I("ids is required and must be a non-empty array of strings");if(!p.every(w=>typeof w=="string"))throw I("each id must be a string");let R=await xr(o.user.id,p);if(!R.success){n.warn(m(i,"auth.identifiers.delete_failure",{userId:o.user.id,errorCode:R.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,R.error);return}n.info(m(i,"auth.identifiers.deleted",{userId:o.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Identifiers deleted successfully",{identifiers:R.identifiers});}catch(l){A(l);}}),r.patch("/me/identifiers/primary",E(e),J,async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{id:p}=l;if(typeof p!="string"||p.trim().length===0)throw I("id is required and must be a non-empty string");let R=await Dr(o.user.id,p.trim());if(!R.success){n.warn(m(i,"auth.identifiers.primary_change_failure",{userId:o.user.id,errorCode:R.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,R.error);return}n.info(m(i,"auth.identifiers.primary_changed",{userId:o.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Primary identifier updated successfully",{identifiers:R.identifiers});}catch(l){A(l);}}),r.patch("/me/password",E(e),J,async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{currentPassword:p,newPassword:R}=l;if(typeof p!="string"||p.length===0)throw I("currentPassword is required");if(typeof R!="string"||R.length<Te)throw I(`newPassword must be at least ${Te} characters`);if(R.length>B)throw I(`newPassword must not exceed ${B} characters`);if(p===R)throw I("newPassword must be different from currentPassword");let w=await K(o.user.id,p,R);if(!w.success){n.warn(m(i,"auth.password.change_failure",{userId:o.user.id,errorCode:w.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,w.error);return}n.info(m(i,"auth.password.changed",{userId:o.user.id,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Password updated successfully. All sessions have been revoked.",null);}catch(l){A(l);}}),r.post("/users/:userId/roles",E(e),u("admin"),async(o,f,A)=>{let h=Date.now();try{let l=U(o.body),{roles:p}=l,R=o.params.userId,w=typeof R=="string"?R:void 0;if(!w)throw I("userId is required");if(!Array.isArray(p)||p.length===0)throw I("roles must be a non-empty array of strings");if(!p.every(P=>typeof P=="string"))throw I("each role must be a string");let v=await N(w,p);if(!v.success){n.warn(m(i,"auth.roles.assign_failure",{targetUserId:w,errorCode:v.error.code,duration_ms:Date.now()-h,...T(o)})),D(f,v.error);return}n.info(m(i,"auth.roles.assigned",{targetUserId:w,roles:p,duration_ms:Date.now()-h,...T(o)})),b(f,200,"Roles assigned successfully",{user:v.user});}catch(l){A(l);}}),r.use(V()),r}var kr=new Map;function Sr(e){let r=kr.get(e);return r||(r=new Redis(e,{lazyConnect:false,enableOfflineQueue:false}),kr.set(e,r)),r}function Ke(e){let r=e?.ttl??3e5,t=(e?.header??"X-Idempotency-Key").toLowerCase(),s=new Set((e?.methods??["POST","PUT","PATCH"]).map(i=>i.toUpperCase())),n=e?.redisUrl;return n?zr(n,r,t,s):Gr(r,t,s,e?.maxSize??1e4)}function zr(e,r,t,s){let n=Sr(e),i="sentri:idempotency:";return async(u,a,d)=>{let g=u.headers[t];if(!g||typeof g!="string"||!s.has(u.method))return d();u.requestId=g,a.setHeader("X-Request-Id",g);let y=await n.get(`${i}${g}`);if(y){let S=JSON.parse(y);return a.setHeader("X-Idempotent-Replayed","true"),a.status(S.statusCode).json(S.body)}let x=a.json.bind(a);a.json=function(_){if(a.statusCode>=200&&a.statusCode<300){let N={statusCode:a.statusCode,body:_,expiresAt:Date.now()+r};n.set(`${i}${g}`,JSON.stringify(N),"PX",r).catch(()=>{});}return x(_)},d();}}function Gr(e,r,t,s){let n=Math.max(e,5e3),i=new Map,u=setInterval(()=>{let a=Date.now();for(let[d,g]of i)g.expiresAt<=a&&i.delete(d);},n);return typeof u=="object"&&u!==null&&"unref"in u&&u.unref(),(a,d,g)=>{let y=a.headers[r];if(!y||typeof y!="string"||!t.has(a.method))return g();a.requestId=y,d.setHeader("X-Request-Id",y);let x=Date.now(),S=i.get(y);if(S&&S.expiresAt>x)return d.setHeader("X-Idempotent-Replayed","true"),d.status(S.statusCode).json(S.body);let _=d.json.bind(d);d.json=function(K){if(d.statusCode>=200&&d.statusCode<300){if(i.size>=s){let L=i.keys().next().value;L!==void 0&&i.delete(L);}i.set(y,{statusCode:d.statusCode,body:K,expiresAt:Date.now()+e});}return _(K)},g();}}async function Er(e){await e.schema.createTable("sentri_users").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("password_hash","varchar(255)",r=>r.notNull()).addColumn("roles","text",r=>r.notNull().defaultTo("[]")).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_sessions").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("expires_at","timestamp",r=>r.notNull()).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute(),await e.schema.createTable("sentri_identifiers").ifNotExists().addColumn("id","varchar(36)",r=>r.primaryKey().notNull()).addColumn("user_id","varchar(36)",r=>r.notNull().references("sentri_users.id").onDelete("cascade")).addColumn("type","varchar(100)",r=>r.notNull()).addColumn("value","varchar(255)",r=>r.notNull().unique()).addColumn("is_primary","integer",r=>r.notNull().defaultTo(0)).addColumn("created_at","timestamp",r=>r.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)).execute();}function He(e){if(Qe(e),e.mode==="client"){let i=e.logger?oe(e.logger,e.loggerService??"sentri"):Pe,u=e.logger?ae(e.logger,e.loggerService??"sentri"):Ne;return {protect:()=>E(e),authorize:(...a)=>i(...a),permit:a=>u(a),errorHandler:a=>V(a)}}let r=e,t=C(r),s=r.logger?oe(r.logger,r.loggerService??"sentri"):Pe,n=r.logger?ae(r.logger,r.loggerService??"sentri"):Ne;return {protect:()=>E(r),authorize:(...i)=>s(...i),permit:i=>n(i),hashPassword:i=>G(i,t.saltRounds),verifyPassword:(i,u)=>Z(i,u),signAccessToken:i=>Y(i,r),signRefreshToken:i=>Q(i,r),verifyAccessToken:i=>de(i,r),verifyRefreshToken:i=>ee(i,r),getCurrentAccessToken:i=>se(i,r),router:()=>Cr(r),migrate:()=>Er(k(r.dialect)),errorHandler:i=>V(i),idempotencyMiddleware:i=>Ke({...i,...r.redisUrl!==void 0&&{redisUrl:r.redisUrl}}),register:i=>ne(i,r),login:i=>me(i,r),refresh:i=>M(i,r),logout:i=>ge(i,r),logoutAll:i=>he(i,r),getUser:i=>ye(i,r),changePassword:(i,u,a)=>Re(i,u,a,r),assignRoles:(i,u)=>we(i,u,r),bulkCreateIdentifiers:(i,u)=>Ie(i,u,r),bulkUpdateIdentifiers:(i,u)=>Ae(i,u,r),bulkDeleteIdentifiers:(i,u)=>ve(i,u,r),changePrimaryIdentifier:(i,u)=>_e(i,u,r)}}function br(e){return new PostgresDialect({pool:new Pool(e)})}function Qr(e){let{privateKey:r}=generateKeyPairSync("rsa",{modulusLength:2048,privateKeyEncoding:{type:"pkcs8",format:"pem"},publicKeyEncoding:{type:"spki",format:"pem"}}),t={mode:"server",dialect:br(e.db),secret:r,algorithm:"RS256",validRoles:e.validRoles,...e.accessExpiresIn!==void 0&&{accessExpiresIn:e.accessExpiresIn},...e.refreshExpiresIn!==void 0&&{refreshExpiresIn:e.refreshExpiresIn},...e.saltRounds!==void 0&&{saltRounds:e.saltRounds},...e.apiKey!==void 0&&{apiKey:e.apiKey},...e.cookie!==void 0&&{cookie:e.cookie},...e.accessCookie!==void 0&&{accessCookie:e.accessCookie},...e.hooks!==void 0&&{hooks:e.hooks},...e.router!==void 0&&{router:e.router},...e.isTokenRevoked!==void 0&&{isTokenRevoked:e.isTokenRevoked},...e.redisUrl!==void 0&&{redisUrl:e.redisUrl},...e.logger!==void 0&&{logger:e.logger},...e.loggerService!==void 0&&{loggerService:e.loggerService}};return He(t)}export{Fe as SENTRI_ERROR_STATUS,c as SentriError,He as createAuth,Qr as createAuthServer,V as createErrorHandler,Ke as createIdempotencyMiddleware,se as getCurrentAccessToken,ne as register};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentri",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "Personal auth/authorization library for Express + Postgres",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -54,10 +54,13 @@
54
54
  "bcrypt": "^6.0.0",
55
55
  "ioredis": "^5.11.1",
56
56
  "jsonwebtoken": "^9.0.3",
57
- "kysely": "^0.27.4",
57
+ "kysely": "^0.29.2",
58
58
  "pg": "^8.13.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "express": ">=4.0.0"
62
+ },
63
+ "overrides": {
64
+ "esbuild": "0.28.1"
62
65
  }
63
66
  }