sentri 4.1.0 → 5.0.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/dist/index.d.ts CHANGED
@@ -1,670 +1,2 @@
1
- import { Dialect } from 'kysely';
2
- import { Request, ErrorRequestHandler, RequestHandler, Router } from 'express';
3
-
4
- /**
5
- * Discriminant codes for built-in {@link SentriError} instances.
6
- *
7
- * - `INVALID_CREDENTIALS` — identifier or password did not match (intentionally vague to prevent user enumeration)
8
- * - `USER_NOT_FOUND` — an operation required a user that does not exist
9
- * - `USER_ALREADY_EXISTS` — registration was attempted with an identifier already in the database
10
- * - `IDENTIFIER_NOT_FOUND` — a referenced identifier ID does not exist or does not belong to the user
11
- * - `IDENTIFIER_ALREADY_EXISTS` — the identifier value is already taken by another user
12
- * - `TOKEN_EXPIRED` — the JWT was valid but its `exp` claim is in the past
13
- * - `TOKEN_INVALID` — the JWT could not be verified (bad signature, malformed, wrong type)
14
- * - `FORBIDDEN` — the user is authenticated but lacks the required role
15
- * - `UNAUTHORIZED` — no valid access token was present on the request, or the session was revoked
16
- * - `INVALID_ROLE` — a role name was used that is not in `validRoles`
17
- * - `VALIDATION_ERROR` — a required field was missing or had an invalid value
18
- * - `CONFIGURATION_ERROR` — `createAuth` was called with an invalid configuration
19
- *
20
- * When you extend {@link SentriError} for your own error types you can use any
21
- * string as `code` — it does not need to be one of these built-in values.
22
- */
23
- type SentriErrorCode = 'INVALID_CREDENTIALS' | 'USER_NOT_FOUND' | 'USER_ALREADY_EXISTS' | 'IDENTIFIER_NOT_FOUND' | 'IDENTIFIER_ALREADY_EXISTS' | 'TOKEN_EXPIRED' | 'TOKEN_INVALID' | 'FORBIDDEN' | 'UNAUTHORIZED' | 'INVALID_ROLE' | 'VALIDATION_ERROR' | 'CONFIGURATION_ERROR';
24
- /**
25
- * Default HTTP status codes for built-in error codes.
26
- * Custom codes not in this map default to 500.
27
- *
28
- * @internal
29
- */
30
- declare const SENTRI_ERROR_STATUS: Record<string, number>;
31
- /**
32
- * Base error class for all authentication and authorization failures in sentri.
33
- *
34
- * Every error thrown by sentri is an instance of `SentriError`. The `code`
35
- * property is a machine-readable string that lets you distinguish error
36
- * types without string-matching on the message. Built-in codes are listed
37
- * in {@link SentriErrorCode}; custom subclasses may use any string.
38
- *
39
- * The `statusCode` property holds the HTTP status that the built-in router
40
- * and `auth.errorHandler()` will use in the response. For built-in codes
41
- * it is derived automatically. Pass it explicitly when subclassing with a
42
- * custom code.
43
- *
44
- * ---
45
- *
46
- * **Extending SentriError**
47
- *
48
- * You can create application-specific error classes by extending `SentriError`.
49
- * Any subclass will be caught automatically by `auth.errorHandler()` because
50
- * `instanceof SentriError` is `true` for all subclasses.
51
- *
52
- * ```typescript
53
- * import { SentriError } from 'sentri';
54
- *
55
- * export class PaymentError extends SentriError {
56
- * constructor(message: string) {
57
- * super('PAYMENT_FAILED', message, 402);
58
- * }
59
- * }
60
- *
61
- * router.post('/checkout', auth.protect(), async (req, res) => {
62
- * const ok = await chargeCard(req.body.cardToken);
63
- * if (!ok) throw new PaymentError('Card declined');
64
- * res.json({ success: true });
65
- * });
66
- * ```
67
- *
68
- * ---
69
- *
70
- * **Error handling in custom routes**
71
- *
72
- * ```typescript
73
- * app.use('/auth', auth.router());
74
- * app.use('/api', apiRouter);
75
- *
76
- * // Mount after all routes — catches SentriError from sentri AND your subclasses
77
- * app.use(auth.errorHandler());
78
- * ```
79
- */
80
- declare class SentriError extends Error {
81
- /**
82
- * Machine-readable error code.
83
- * Built-in codes are defined by {@link SentriErrorCode}.
84
- * Custom subclasses may use any string.
85
- */
86
- readonly code: string;
87
- /**
88
- * HTTP status code associated with this error.
89
- * Derived automatically for built-in codes; pass it explicitly when
90
- * subclassing with a custom `code`.
91
- */
92
- readonly statusCode: number;
93
- constructor(code: SentriErrorCode | (string & {}), message: string, statusCode?: number);
94
- }
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
-
129
- interface ApiResponse<T = null> {
130
- error: boolean;
131
- statusCode: number;
132
- message: string;
133
- data: T | null;
134
- }
135
- /** A single identifier entry belonging to a user. */
136
- interface IdentifierRecord {
137
- id: string;
138
- type: string;
139
- value: string;
140
- isPrimary: boolean;
141
- }
142
- /**
143
- * The authenticated user shape. `identifier` and `identifierType` always
144
- * reflect the primary identifier. `identifiers` is populated only when the
145
- * full user object is fetched (e.g. GET /me) and is absent from JWT payloads.
146
- */
147
- interface AuthUser<TRole extends string = string> {
148
- id: string;
149
- /** Primary identifier value — embedded in the JWT payload. */
150
- identifier: string;
151
- /** Type of the primary identifier (e.g. 'email', 'username'). */
152
- identifierType: string;
153
- roles: TRole[];
154
- /** All identifiers for this user. Only present in full user responses, not in JWT. */
155
- identifiers?: IdentifierRecord[];
156
- }
157
- /** Input for a single identifier entry. */
158
- interface IdentifierInput {
159
- /** Arbitrary label such as 'email', 'username', or 'phone'. */
160
- type: string;
161
- /** The globally unique identifier value. */
162
- value: string;
163
- }
164
- type RegisterResult<TRole extends string = string> = {
165
- success: true;
166
- user: AuthUser<TRole>;
167
- } | {
168
- success: false;
169
- error: SentriError;
170
- };
171
- type AuthResult<TRole extends string = string> = {
172
- success: true;
173
- accessToken: string;
174
- refreshToken: string;
175
- user: AuthUser<TRole>;
176
- } | {
177
- success: false;
178
- error: SentriError;
179
- };
180
- type AssignRolesResult<TRole extends string = string> = {
181
- success: true;
182
- user: AuthUser<TRole>;
183
- } | {
184
- success: false;
185
- error: SentriError;
186
- };
187
- type GetUserResult<TRole extends string = string> = {
188
- success: true;
189
- user: AuthUser<TRole>;
190
- } | {
191
- success: false;
192
- error: SentriError;
193
- };
194
- type BulkIdentifiersResult = {
195
- success: true;
196
- identifiers: IdentifierRecord[];
197
- } | {
198
- success: false;
199
- error: SentriError;
200
- };
201
- type ChangePrimaryResult = {
202
- success: true;
203
- identifiers: IdentifierRecord[];
204
- } | {
205
- success: false;
206
- error: SentriError;
207
- };
208
- type ChangePasswordResult = {
209
- success: true;
210
- } | {
211
- success: false;
212
- error: SentriError;
213
- };
214
- type RefreshResult<TRole extends string = string> = {
215
- success: true;
216
- accessToken: string;
217
- refreshToken: string;
218
- user: AuthUser<TRole>;
219
- } | {
220
- success: false;
221
- error: SentriError;
222
- };
223
- interface RegisterInput<TRole extends string = string> {
224
- /**
225
- * One or more identifiers for the new user. The first entry becomes the
226
- * primary identifier (embedded in the JWT payload). All values must be
227
- * globally unique. At least one identifier is required.
228
- */
229
- identifiers: IdentifierInput[];
230
- password: string;
231
- roles?: TRole[];
232
- }
233
- interface LoginInput {
234
- /** Any of the user's identifier values — Sentri searches all types. */
235
- identifier: string;
236
- password: string;
237
- }
238
- interface CookieConfig {
239
- name?: string;
240
- httpOnly?: boolean;
241
- secure?: boolean;
242
- sameSite?: 'strict' | 'lax' | 'none';
243
- path?: string;
244
- }
245
- interface AccessCookieConfig {
246
- name?: string;
247
- secure?: boolean;
248
- sameSite?: 'strict' | 'lax' | 'none';
249
- path?: string;
250
- }
251
- interface AuthHooks {
252
- onLogin?: (user: AuthUser) => void | Promise<void>;
253
- onFailedLogin?: (identifier: string, error: SentriError) => void | Promise<void>;
254
- onLogout?: (userId: string) => void | Promise<void>;
255
- }
256
- interface RouterHandlers {
257
- register?: (input: RegisterInput) => Promise<RegisterResult>;
258
- login?: (input: LoginInput) => Promise<AuthResult>;
259
- refresh?: (refreshToken: string) => Promise<RefreshResult>;
260
- logout?: (refreshToken: string | undefined) => Promise<void>;
261
- logoutAll?: (userId: string) => Promise<void>;
262
- assignRoles?: (userId: string, roles: string[]) => Promise<AssignRolesResult>;
263
- bulkCreateIdentifiers?: (userId: string, identifiers: IdentifierInput[]) => Promise<BulkIdentifiersResult>;
264
- bulkUpdateIdentifiers?: (userId: string, updates: Array<{
265
- id: string;
266
- type: string;
267
- value: string;
268
- }>) => Promise<BulkIdentifiersResult>;
269
- bulkDeleteIdentifiers?: (userId: string, ids: string[]) => Promise<BulkIdentifiersResult>;
270
- changePrimaryIdentifier?: (userId: string, identifierId: string) => Promise<ChangePrimaryResult>;
271
- changePassword?: (userId: string, currentPassword: string, newPassword: string) => Promise<ChangePasswordResult>;
272
- }
273
- interface ServerAuthConfig<TRole extends string = string> {
274
- mode: 'server';
275
- /** Kysely Dialect (e.g. PostgresDialect, MysqlDialect, SqliteDialect). */
276
- dialect: Dialect;
277
- /**
278
- * JWT signing secret.
279
- * - HS256/HS384/HS512: plain string, minimum 32 characters.
280
- * - RS256/RS384/RS512: RSA private key in PEM format.
281
- */
282
- secret: string;
283
- /**
284
- * JWT signing algorithm.
285
- * Use RS256/RS384/RS512 to enable the GET /keys endpoint for SSO.
286
- * @default 'HS256'
287
- */
288
- algorithm?: 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512';
289
- validRoles: readonly TRole[];
290
- /** @default '15m' */
291
- accessExpiresIn?: string | number;
292
- /** @default '7d' */
293
- refreshExpiresIn?: string | number;
294
- /** @default 12 */
295
- saltRounds?: number;
296
- apiKey?: string;
297
- cookie?: CookieConfig;
298
- accessCookie?: AccessCookieConfig;
299
- hooks?: AuthHooks;
300
- router?: RouterHandlers;
301
- isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
302
- /**
303
- * Redis connection URL (e.g. `redis://localhost:6379`).
304
- * When set, `auth.idempotencyMiddleware()` uses Redis as the cache backend
305
- * instead of an in-memory Map — required for multi-process deployments.
306
- */
307
- redisUrl?: string;
308
- /**
309
- * Structured logger instance. Compatible with pino, winston, or console.
310
- * When omitted, Sentri produces no log output.
311
- */
312
- logger?: SentriLogger;
313
- /**
314
- * Service name embedded in every log entry.
315
- * @default 'sentri'
316
- */
317
- loggerService?: string;
318
- }
319
- interface ClientAuthConfig<TRole extends string = string> {
320
- mode: 'client';
321
- /** URL of the auth server's public key endpoint (e.g. https://auth.myapp.com/auth/keys). */
322
- keyUri: string;
323
- /** Optional — only needed for TypeScript type safety on authorize(). */
324
- validRoles?: readonly TRole[];
325
- /**
326
- * Structured logger instance. Compatible with pino, winston, or console.
327
- * When omitted, Sentri produces no log output.
328
- */
329
- logger?: SentriLogger;
330
- /**
331
- * Service name embedded in every log entry.
332
- * @default 'sentri'
333
- */
334
- loggerService?: string;
335
- }
336
- type AuthConfig<TRole extends string = string> = ServerAuthConfig<TRole> | ClientAuthConfig<TRole>;
337
-
338
- /** A function that determines whether the current request is permitted. */
339
- type PermitCheck = (request: Request) => boolean | Promise<boolean>;
340
- /**
341
- * Options for {@link permit} when you need role-bypass alongside a resource check.
342
- *
343
- * @example
344
- * // Admins can edit any post; others only their own
345
- * auth.permit({
346
- * roles: ['admin'],
347
- * check: async (request) => {
348
- * const post = await db.findPost(request.params['id']);
349
- * return post?.authorId === request.user!.id;
350
- * },
351
- * })
352
- */
353
- interface PermitOptions<TRole extends string> {
354
- /**
355
- * Roles whose members are granted access without running `check`.
356
- * Use for privileged roles like `'admin'` that should bypass ownership checks.
357
- */
358
- roles?: TRole[];
359
- /**
360
- * Permission check executed when the user has none of the bypass `roles`.
361
- * Return `true` to allow, `false` to deny with `FORBIDDEN`.
362
- * May be async — useful for database-backed ownership checks.
363
- */
364
- check: PermitCheck;
365
- }
366
-
367
- /**
368
- * Options for {@link createErrorHandler}.
369
- */
370
- interface ErrorHandlerOptions {
371
- /**
372
- * Called for errors that are **not** a `SentriError` instance (or subclass).
373
- *
374
- * Use this to log unexpected server errors before the generic 500 response
375
- * is sent. The error is passed as-is and may be any unknown value.
376
- *
377
- * @example
378
- * app.use(auth.errorHandler({
379
- * onUnhandled: (err) => logger.error('Unhandled error', { err }),
380
- * }));
381
- */
382
- onUnhandled?: (error: unknown) => void;
383
- }
384
- /**
385
- * Creates an Express error-handling middleware that formats every `SentriError`
386
- * (including subclasses) into the standard sentri response envelope:
387
- *
388
- * ```json
389
- * { "error": true, "statusCode": 401, "code": "UNAUTHORIZED", "message": "...", "data": null }
390
- * ```
391
- *
392
- * Prefer using `auth.errorHandler()` instead of calling this directly:
393
- *
394
- * ```typescript
395
- * app.use('/auth', auth.router());
396
- * app.use('/api', apiRouter);
397
- *
398
- * // Must come after all route/middleware registrations
399
- * app.use(auth.errorHandler());
400
- * ```
401
- *
402
- * ---
403
- *
404
- * **Works with built-in sentri errors and your own subclasses**
405
- *
406
- * Because `instanceof SentriError` matches any subclass, you can define
407
- * application-specific error types and have them automatically formatted
408
- * by this handler:
409
- *
410
- * ```typescript
411
- * import { SentriError } from 'sentri';
412
- *
413
- * // Extend SentriError for domain-specific failures
414
- * export class NotFoundError extends SentriError {
415
- * constructor(resource: string) {
416
- * super('NOT_FOUND', `${resource} not found`, 404);
417
- * }
418
- * }
419
- *
420
- * export class PaymentError extends SentriError {
421
- * constructor(message: string) {
422
- * super('PAYMENT_FAILED', message, 402);
423
- * }
424
- * }
425
- *
426
- * // All of the above are caught and formatted by one handler
427
- * app.use(auth.errorHandler({
428
- * onUnhandled: (err) => console.error('Unexpected error:', err),
429
- * }));
430
- * ```
431
- *
432
- * @param options - Optional configuration (see {@link ErrorHandlerOptions}).
433
- * @returns An Express `ErrorRequestHandler` (4-argument middleware).
434
- */
435
- declare function createErrorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
436
-
437
- interface IdempotencyOptions {
438
- /** @default 300_000 (5 minutes) */
439
- ttl?: number;
440
- /** @default 'X-Idempotency-Key' */
441
- header?: string;
442
- /** @default ['POST', 'PUT', 'PATCH'] */
443
- methods?: string[];
444
- /**
445
- * Max in-memory entries (ignored when redisUrl is set).
446
- * @default 10_000
447
- */
448
- maxSize?: number;
449
- /**
450
- * Redis connection URL (e.g. `redis://localhost:6379`).
451
- * When set, uses Redis as the cache backend instead of in-memory Map.
452
- */
453
- redisUrl?: string;
454
- }
455
- /**
456
- * Middleware that deduplicates non-idempotent requests.
457
- *
458
- * When a request arrives with a matching idempotency key header, the cached
459
- * response is replayed immediately — the handler is not called again.
460
- * Responses are only cached for 2xx status codes.
461
- *
462
- * Two backends are available:
463
- * - **In-memory** (default) — zero dependencies, single-process only.
464
- * - **Redis** — set `redisUrl` to share state across processes/instances.
465
- *
466
- * When using `createAuthServer()`, prefer `auth.idempotencyMiddleware()` instead —
467
- * it automatically inherits the `redisUrl` from server config.
468
- *
469
- * @example
470
- * // Standalone usage
471
- * app.use(createIdempotencyMiddleware({ ttl: 60_000 }));
472
- *
473
- * @example
474
- * // Multi-process (Redis)
475
- * app.use(createIdempotencyMiddleware({ redisUrl: 'redis://localhost:6379' }));
476
- */
477
- declare function createIdempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
478
-
479
- interface AuthClient<TRole extends string = string> {
480
- /** JWT authentication middleware. Reads Bearer token or access_token cookie. */
481
- protect(): RequestHandler;
482
- /** Role-based access middleware. Must follow protect(). */
483
- authorize(...roles: TRole[]): RequestHandler;
484
- /** Resource-level permission middleware. Must follow protect(). */
485
- permit(check: PermitCheck): RequestHandler;
486
- permit(options: PermitOptions<TRole>): RequestHandler;
487
- /** Global error handler middleware. Mount after all routes. */
488
- errorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
489
- }
490
- interface ServerAuthClient<TRole extends string = string> extends AuthClient<TRole> {
491
- /** Hash a plain-text password. */
492
- hashPassword(plain: string): Promise<string>;
493
- /** Compare a plain-text password against a bcrypt hash. */
494
- verifyPassword(plain: string, hash: string): Promise<boolean>;
495
- /** Sign an access token. */
496
- signAccessToken(payload: AuthUser<TRole>): string;
497
- /** Sign a refresh token. */
498
- signRefreshToken(sessionId: string): string;
499
- /** Verify an access token. */
500
- verifyAccessToken(token: string): AuthUser<TRole>;
501
- /** Verify a refresh token. */
502
- verifyRefreshToken(token: string): {
503
- sessionId: string;
504
- };
505
- /** Extract the raw access token from an Express request. */
506
- getCurrentAccessToken(request: Request): string | undefined;
507
- /**
508
- * Pre-built Express Router with auth endpoints.
509
- * See README for the full endpoint list.
510
- */
511
- router(): Router;
512
- /**
513
- * Run database migrations to create sentri_users, sentri_sessions, and
514
- * sentri_identifiers tables. Safe to call on every startup — uses IF NOT EXISTS.
515
- */
516
- migrate(): Promise<void>;
517
- /**
518
- * Idempotency middleware. Caches successful responses and replays them for
519
- * duplicate requests with the same idempotency key header.
520
- * Uses Redis backend when `redisUrl` is set in server config; otherwise in-memory.
521
- */
522
- idempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
523
- /** Register a new user with one or more identifiers. */
524
- register(input: RegisterInput<TRole>): Promise<RegisterResult<TRole>>;
525
- /** Authenticate a user by any of their identifier values, returns access + refresh tokens. */
526
- login(input: LoginInput): Promise<AuthResult<TRole>>;
527
- /** Rotate a refresh token, returns new token pair. */
528
- refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
529
- /** Invalidate the session associated with the given refresh token. */
530
- logout(refreshToken: string): Promise<void>;
531
- /** Invalidate all sessions for a user. */
532
- logoutAll(userId: string): Promise<void>;
533
- /** Fetch a user by ID, including all their identifiers. Returns `success: false` if not found. */
534
- getUser(userId: string): Promise<GetUserResult<TRole>>;
535
- /** Change a user's password and revoke all their sessions. */
536
- changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
537
- /** Add roles to a user (existing roles are preserved). */
538
- assignRoles(userId: string, roles: TRole[]): Promise<AssignRolesResult<TRole>>;
539
- /** Add new identifiers to a user in bulk. Returns the full updated identifier list. */
540
- bulkCreateIdentifiers(userId: string, identifiers: IdentifierInput[]): Promise<BulkIdentifiersResult>;
541
- /** Update type and value for multiple identifiers in bulk. Returns the full updated identifier list. */
542
- bulkUpdateIdentifiers(userId: string, updates: Array<{
543
- id: string;
544
- type: string;
545
- value: string;
546
- }>): Promise<BulkIdentifiersResult>;
547
- /** Delete multiple identifiers by ID. At least one identifier must remain. Returns the remaining list. */
548
- bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
549
- /** Change which identifier is marked as primary (embedded in JWT on next login/refresh). */
550
- changePrimaryIdentifier(userId: string, identifierId: string): Promise<ChangePrimaryResult>;
551
- }
552
- interface ClientAuthClient<TRole extends string = string> extends AuthClient<TRole> {
553
- }
554
- /**
555
- * Create a Sentri auth client.
556
- *
557
- * Pass `mode: 'server'` to get a full auth server with database, JWT signing,
558
- * and built-in endpoints. Pass `mode: 'client'` to get a stateless verifier
559
- * that validates tokens via the server's JWKS endpoint.
560
- *
561
- * For server mode with PostgreSQL, prefer `createAuthServer()` — it handles
562
- * RSA key generation and database setup automatically.
563
- *
564
- * @example
565
- * // Server mode
566
- * const auth = createAuth({
567
- * mode: 'server',
568
- * dialect: new PostgresDialect({ pool: new Pool({ connectionString: DATABASE_URL }) }),
569
- * secret: process.env.JWT_SECRET!,
570
- * validRoles: ['user', 'admin'] as const,
571
- * });
572
- *
573
- * @example
574
- * // Client mode
575
- * const auth = createAuth({
576
- * mode: 'client',
577
- * keyUri: 'https://auth.myapp.com/auth/keys',
578
- * });
579
- */
580
- declare function createAuth<TRole extends string = string>(config: ServerAuthConfig<TRole>): ServerAuthClient<TRole>;
581
- declare function createAuth<TRole extends string = string>(config: ClientAuthConfig<TRole>): ClientAuthClient<TRole>;
582
-
583
- type PostgresConfig = {
584
- connectionString: string;
585
- max?: number;
586
- } | {
587
- host?: string;
588
- port?: number;
589
- database: string;
590
- user: string;
591
- password: string;
592
- max?: number;
593
- };
594
-
595
- interface CreateServerOptions<TRole extends string = string> {
596
- validRoles: readonly TRole[];
597
- db: PostgresConfig;
598
- accessExpiresIn?: string | number;
599
- refreshExpiresIn?: string | number;
600
- saltRounds?: number;
601
- apiKey?: string;
602
- cookie?: CookieConfig;
603
- accessCookie?: AccessCookieConfig;
604
- hooks?: AuthHooks;
605
- router?: RouterHandlers;
606
- isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
607
- /**
608
- * Redis connection URL (e.g. `redis://localhost:6379`).
609
- * When set, `auth.idempotencyMiddleware()` uses Redis as the cache backend.
610
- */
611
- redisUrl?: string;
612
- /**
613
- * Structured logger for all auth events.
614
- * Accepts any object with `info`, `warn`, `error` methods (pino, winston, console).
615
- * When omitted, sentri produces no log output.
616
- */
617
- logger?: SentriLogger;
618
- /**
619
- * Service name added to every log entry as `service`.
620
- * Defaults to `'sentri'`.
621
- */
622
- loggerService?: string;
623
- }
624
- /**
625
- * Create a Sentri auth server for PostgreSQL.
626
- *
627
- * Convenience wrapper over `createAuth()` that:
628
- * - Accepts plain PostgreSQL connection params instead of a Kysely dialect
629
- * - Generates an RSA-2048 key pair at startup (RS256, ephemeral per process)
630
- * - Exposes `GET /keys` (JWKS) automatically for SSO with client-mode apps
631
- *
632
- * @example
633
- * const auth = createAuthServer({
634
- * validRoles: ['user', 'admin'] as const,
635
- * db: { connectionString: process.env.DATABASE_URL! },
636
- * });
637
- * await auth.migrate();
638
- * app.use('/auth', auth.router());
639
- *
640
- * @example
641
- * // With Redis idempotency cache (multi-process deployments)
642
- * const auth = createAuthServer({
643
- * validRoles: ['user', 'admin'] as const,
644
- * db: { connectionString: process.env.DATABASE_URL! },
645
- * redisUrl: process.env.REDIS_URL,
646
- * });
647
- * app.use(auth.idempotencyMiddleware());
648
- */
649
- declare function createAuthServer<TRole extends string = string>(options: CreateServerOptions<TRole>): ServerAuthClient<TRole>;
650
-
651
- /**
652
- * Extract the raw access token string from an Express request.
653
- * Reads `Authorization: Bearer <token>` header first; falls back to the
654
- * `access_token` cookie (or the name set in `accessCookie.name`).
655
- * Returns `undefined` when no token is present.
656
- */
657
- declare function getCurrentAccessToken(request: Request, config: ServerAuthConfig): string | undefined;
658
-
659
- declare function register(input: RegisterInput, config: ServerAuthConfig): Promise<RegisterResult>;
660
-
661
- declare global {
662
- namespace Express {
663
- interface Request {
664
- user?: AuthUser;
665
- requestId?: string;
666
- }
667
- }
668
- }
669
-
670
- 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 };
1
+ export { AccessCookieConfig, ApiResponse, AssignRolesResult, AuthConfig, AuthHooks, AuthResult, AuthUser, BulkIdentifiersResult, ChangePasswordResult, ClientAuthConfig, CookieConfig, GetUserResult, IdentifierInput, IdentifierRecord, LoginInput, RefreshResult, RegisterInput, RegisterResult, RouterHandlers, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode, SentriLogger, ServerAuthConfig } from './core/index.js';
2
+ import 'kysely';