sentri 4.1.1 → 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.
@@ -0,0 +1,308 @@
1
+ import * as kysely from 'kysely';
2
+ import { RequestHandler, Request, ErrorRequestHandler, Router } from 'express';
3
+ import { AuthConfig, SentriLogger, ServerAuthConfig, AuthUser, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.cjs';
4
+ export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.cjs';
5
+
6
+ declare function protect(config: AuthConfig): RequestHandler;
7
+
8
+ /**
9
+ * Returns a logger-aware `authorize` factory bound to the given logger and service name.
10
+ * Used internally by `createAuth()` and `createAuthRouter()` to inject logging.
11
+ * Not part of the public API.
12
+ *
13
+ * @internal
14
+ */
15
+ declare function createAuthorize(logger: SentriLogger, service: string): <TRole extends string>(...allowedRoles: TRole[]) => RequestHandler;
16
+ /**
17
+ * Express middleware factory for role-based access control (RBAC).
18
+ *
19
+ * Passes if the authenticated user (set by `protect()`) has **at least one**
20
+ * of the specified `allowedRoles`. Calls `next(SentriError)` with code `FORBIDDEN`
21
+ * if no roles match, or `UNAUTHORIZED` if `req.user` is absent.
22
+ *
23
+ * Must be used **after** `protect()`.
24
+ *
25
+ * > **Logging** — when used via `auth.authorize()` from `createAuth()`, log entries
26
+ * > are emitted automatically using the logger configured in `AuthConfig`.
27
+ * > When called directly (standalone), no logging occurs.
28
+ *
29
+ * @param allowedRoles - One or more role strings. The user needs at least one of them.
30
+ * @returns An Express `RequestHandler` that enforces the role check.
31
+ *
32
+ * @example
33
+ * router.delete('/posts/:id', protect(config), authorize('admin', 'moderator'), handler);
34
+ */
35
+ declare function authorize<TRole extends string>(...allowedRoles: TRole[]): RequestHandler;
36
+
37
+ /** A function that determines whether the current request is permitted. */
38
+ type PermitCheck = (request: Request) => boolean | Promise<boolean>;
39
+ /**
40
+ * Options for {@link permit} when you need role-bypass alongside a resource check.
41
+ *
42
+ * @example
43
+ * // Admins can edit any post; others only their own
44
+ * auth.permit({
45
+ * roles: ['admin'],
46
+ * check: async (request) => {
47
+ * const post = await db.findPost(request.params['id']);
48
+ * return post?.authorId === request.user!.id;
49
+ * },
50
+ * })
51
+ */
52
+ interface PermitOptions<TRole extends string> {
53
+ /**
54
+ * Roles whose members are granted access without running `check`.
55
+ * Use for privileged roles like `'admin'` that should bypass ownership checks.
56
+ */
57
+ roles?: TRole[];
58
+ /**
59
+ * Permission check executed when the user has none of the bypass `roles`.
60
+ * Return `true` to allow, `false` to deny with `FORBIDDEN`.
61
+ * May be async — useful for database-backed ownership checks.
62
+ */
63
+ check: PermitCheck;
64
+ }
65
+ /**
66
+ * Returns a logger-aware `permit` factory bound to the given logger and service name.
67
+ * Used internally by `createAuth()` and `createAuthRouter()` to inject logging.
68
+ * Not part of the public API.
69
+ *
70
+ * @internal
71
+ */
72
+ declare function createPermit(logger: SentriLogger, service: string): <TRole extends string>(optionsOrCheck: PermitOptions<TRole> | PermitCheck) => RequestHandler;
73
+ /**
74
+ * Express middleware factory for resource-level permission checks.
75
+ *
76
+ * Must be used **after** `protect()`. Evaluates a check function against the
77
+ * current request; calls `next(SentriError)` with code `FORBIDDEN` if it returns `false`.
78
+ *
79
+ * Accepts either a bare check function or an options object with an optional
80
+ * `roles` list whose members bypass the check entirely.
81
+ *
82
+ * **Performance:** when `check` returns a plain `boolean` (not a Promise) no
83
+ * extra microtask is queued — the result is handled synchronously. Async checks
84
+ * are awaited normally.
85
+ *
86
+ * > **Logging** — when used via `auth.permit()` from `createAuth()`, log entries
87
+ * > are emitted automatically using the logger configured in `AuthConfig`.
88
+ * > When called directly (standalone), no logging occurs.
89
+ *
90
+ * @example
91
+ * // Simple ownership check (sync — zero async overhead)
92
+ * router.put('/users/:id',
93
+ * auth.protect(),
94
+ * auth.permit((request) => request.user!.id === request.params['id']),
95
+ * updateUserHandler,
96
+ * );
97
+ *
98
+ * @example
99
+ * // Admins bypass the check; others must own the post (async DB lookup)
100
+ * router.delete('/posts/:id',
101
+ * auth.protect(),
102
+ * auth.permit({
103
+ * roles: ['admin'],
104
+ * check: async (request) => {
105
+ * const post = await db.post.findUnique({ where: { id: request.params['id'] } });
106
+ * return post?.authorId === request.user!.id;
107
+ * },
108
+ * }),
109
+ * deletePostHandler,
110
+ * );
111
+ */
112
+ declare function permit<TRole extends string>(optionsOrCheck: PermitOptions<TRole> | PermitCheck): RequestHandler;
113
+
114
+ /**
115
+ * Options for {@link createErrorHandler}.
116
+ */
117
+ interface ErrorHandlerOptions {
118
+ /**
119
+ * Called for errors that are **not** a `SentriError` instance (or subclass).
120
+ *
121
+ * Use this to log unexpected server errors before the generic 500 response
122
+ * is sent. The error is passed as-is and may be any unknown value.
123
+ *
124
+ * @example
125
+ * app.use(auth.errorHandler({
126
+ * onUnhandled: (err) => logger.error('Unhandled error', { err }),
127
+ * }));
128
+ */
129
+ onUnhandled?: (error: unknown) => void;
130
+ }
131
+ /**
132
+ * Creates an Express error-handling middleware that formats every `SentriError`
133
+ * (including subclasses) into the standard sentri response envelope:
134
+ *
135
+ * ```json
136
+ * { "error": true, "statusCode": 401, "code": "UNAUTHORIZED", "message": "...", "data": null }
137
+ * ```
138
+ *
139
+ * Prefer using `auth.errorHandler()` instead of calling this directly:
140
+ *
141
+ * ```typescript
142
+ * app.use('/auth', auth.router());
143
+ * app.use('/api', apiRouter);
144
+ *
145
+ * // Must come after all route/middleware registrations
146
+ * app.use(auth.errorHandler());
147
+ * ```
148
+ *
149
+ * ---
150
+ *
151
+ * **Works with built-in sentri errors and your own subclasses**
152
+ *
153
+ * Because `instanceof SentriError` matches any subclass, you can define
154
+ * application-specific error types and have them automatically formatted
155
+ * by this handler:
156
+ *
157
+ * ```typescript
158
+ * import { SentriError } from 'sentri';
159
+ *
160
+ * // Extend SentriError for domain-specific failures
161
+ * export class NotFoundError extends SentriError {
162
+ * constructor(resource: string) {
163
+ * super('NOT_FOUND', `${resource} not found`, 404);
164
+ * }
165
+ * }
166
+ *
167
+ * export class PaymentError extends SentriError {
168
+ * constructor(message: string) {
169
+ * super('PAYMENT_FAILED', message, 402);
170
+ * }
171
+ * }
172
+ *
173
+ * // All of the above are caught and formatted by one handler
174
+ * app.use(auth.errorHandler({
175
+ * onUnhandled: (err) => console.error('Unexpected error:', err),
176
+ * }));
177
+ * ```
178
+ *
179
+ * @param options - Optional configuration (see {@link ErrorHandlerOptions}).
180
+ * @returns An Express `ErrorRequestHandler` (4-argument middleware).
181
+ */
182
+ declare function createErrorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
183
+
184
+ interface IdempotencyOptions {
185
+ /** @default 300_000 (5 minutes) */
186
+ ttl?: number;
187
+ /** @default 'X-Idempotency-Key' */
188
+ header?: string;
189
+ /** @default ['POST', 'PUT', 'PATCH'] */
190
+ methods?: string[];
191
+ /**
192
+ * Max in-memory entries (ignored when redisUrl is set).
193
+ * @default 10_000
194
+ */
195
+ maxSize?: number;
196
+ /**
197
+ * Redis connection URL (e.g. `redis://localhost:6379`).
198
+ * When set, uses Redis as the cache backend instead of in-memory Map.
199
+ */
200
+ redisUrl?: string;
201
+ }
202
+ /**
203
+ * Middleware that deduplicates non-idempotent requests.
204
+ *
205
+ * When a request arrives with a matching idempotency key header, the cached
206
+ * response is replayed immediately — the handler is not called again.
207
+ * Responses are only cached for 2xx status codes.
208
+ *
209
+ * Two backends are available:
210
+ * - **In-memory** (default) — zero dependencies, single-process only.
211
+ * - **Redis** — set `redisUrl` to share state across processes/instances.
212
+ *
213
+ * When using `createAuthServer()`, prefer `auth.idempotencyMiddleware()` instead —
214
+ * it automatically inherits the `redisUrl` from server config.
215
+ *
216
+ * @example
217
+ * // Standalone usage
218
+ * app.use(createIdempotencyMiddleware({ ttl: 60_000 }));
219
+ *
220
+ * @example
221
+ * // Multi-process (Redis)
222
+ * app.use(createIdempotencyMiddleware({ redisUrl: 'redis://localhost:6379' }));
223
+ */
224
+ declare function createIdempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
225
+
226
+ /**
227
+ * Extract the raw access token string from an Express request.
228
+ * Reads `Authorization: Bearer <token>` header first; falls back to the
229
+ * `access_token` cookie (or the name set in `accessCookie.name`).
230
+ * Returns `undefined` when no token is present.
231
+ */
232
+ declare function getCurrentAccessToken(request: Request, config: ServerAuthConfig): string | undefined;
233
+
234
+ declare function createAuthRouter<TRole extends string>(config: ServerAuthConfig<TRole>): Router;
235
+
236
+ declare global {
237
+ namespace Express {
238
+ interface Request {
239
+ user?: AuthUser;
240
+ requestId?: string;
241
+ }
242
+ }
243
+ }
244
+ interface CreateExpressServerOptions<TRole extends string = string> {
245
+ mode: 'server';
246
+ validRoles: readonly TRole[];
247
+ dialect: kysely.Dialect;
248
+ accessExpiresIn?: string | number;
249
+ refreshExpiresIn?: string | number;
250
+ saltRounds?: number;
251
+ apiKey?: string;
252
+ cookie?: CookieConfig;
253
+ accessCookie?: AccessCookieConfig;
254
+ hooks?: AuthHooks;
255
+ router?: RouterHandlers;
256
+ isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
257
+ redisUrl?: string;
258
+ logger?: SentriLogger;
259
+ loggerService?: string;
260
+ }
261
+ interface CreateExpressClientOptions<TRole extends string = string> {
262
+ mode: 'client';
263
+ keyUri: string;
264
+ validRoles?: readonly TRole[];
265
+ logger?: SentriLogger;
266
+ loggerService?: string;
267
+ }
268
+ interface AuthExpressClient<TRole extends string = string> {
269
+ protect(): RequestHandler;
270
+ authorize(...roles: TRole[]): RequestHandler;
271
+ permit(check: PermitCheck): RequestHandler;
272
+ permit(options: PermitOptions<TRole>): RequestHandler;
273
+ errorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
274
+ }
275
+ interface ServerAuthExpressClient<TRole extends string = string> extends AuthExpressClient<TRole> {
276
+ hashPassword(plain: string): Promise<string>;
277
+ verifyPassword(plain: string, hash: string): Promise<boolean>;
278
+ signAccessToken(payload: AuthUser<TRole>): string;
279
+ signRefreshToken(sessionId: string): string;
280
+ verifyAccessToken(token: string): AuthUser<TRole>;
281
+ verifyRefreshToken(token: string): {
282
+ sessionId: string;
283
+ };
284
+ getCurrentAccessToken(request: Request): string | undefined;
285
+ router(): Router;
286
+ migrate(): Promise<void>;
287
+ idempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
288
+ register(input: RegisterInput<TRole>): Promise<RegisterResult<TRole>>;
289
+ login(input: LoginInput): Promise<AuthResult<TRole>>;
290
+ refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
291
+ logout(refreshToken: string): Promise<void>;
292
+ logoutAll(userId: string): Promise<void>;
293
+ getUser(userId: string): Promise<GetUserResult<TRole>>;
294
+ changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
295
+ assignRoles(userId: string, roles: TRole[]): Promise<AssignRolesResult<TRole>>;
296
+ bulkCreateIdentifiers(userId: string, identifiers: IdentifierInput[]): Promise<BulkIdentifiersResult>;
297
+ bulkUpdateIdentifiers(userId: string, updates: Array<{
298
+ id: string;
299
+ type: string;
300
+ value: string;
301
+ }>): Promise<BulkIdentifiersResult>;
302
+ bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
303
+ }
304
+ type ClientAuthExpressClient<TRole extends string = string> = AuthExpressClient<TRole>;
305
+ declare function createAuthExpress<TRole extends string = string>(options: CreateExpressServerOptions<TRole>): ServerAuthExpressClient<TRole>;
306
+ declare function createAuthExpress<TRole extends string = string>(options: CreateExpressClientOptions<TRole>): ClientAuthExpressClient<TRole>;
307
+
308
+ export { AccessCookieConfig, AssignRolesResult, AuthConfig, type AuthExpressClient, AuthHooks, AuthResult, AuthUser, BulkIdentifiersResult, ChangePasswordResult, type ClientAuthExpressClient, CookieConfig, type CreateExpressClientOptions, type CreateExpressServerOptions, type ErrorHandlerOptions, GetUserResult, type IdempotencyOptions, IdentifierInput, LoginInput, type PermitCheck, type PermitOptions, RefreshResult, RegisterInput, RegisterResult, RouterHandlers, SentriLogger, ServerAuthConfig, type ServerAuthExpressClient, authorize, createAuthExpress, createAuthRouter, createAuthorize, createErrorHandler, createIdempotencyMiddleware, createPermit, getCurrentAccessToken, permit, protect };
@@ -0,0 +1,308 @@
1
+ import * as kysely from 'kysely';
2
+ import { RequestHandler, Request, ErrorRequestHandler, Router } from 'express';
3
+ import { AuthConfig, SentriLogger, ServerAuthConfig, AuthUser, CookieConfig, AccessCookieConfig, AuthHooks, RouterHandlers, RegisterInput, RegisterResult, LoginInput, AuthResult, RefreshResult, GetUserResult, ChangePasswordResult, AssignRolesResult, IdentifierInput, BulkIdentifiersResult } from '../../core/index.js';
4
+ export { ApiResponse, ClientAuthConfig, IdentifierRecord, SENTRI_ERROR_STATUS, SentriError, SentriErrorCode } from '../../core/index.js';
5
+
6
+ declare function protect(config: AuthConfig): RequestHandler;
7
+
8
+ /**
9
+ * Returns a logger-aware `authorize` factory bound to the given logger and service name.
10
+ * Used internally by `createAuth()` and `createAuthRouter()` to inject logging.
11
+ * Not part of the public API.
12
+ *
13
+ * @internal
14
+ */
15
+ declare function createAuthorize(logger: SentriLogger, service: string): <TRole extends string>(...allowedRoles: TRole[]) => RequestHandler;
16
+ /**
17
+ * Express middleware factory for role-based access control (RBAC).
18
+ *
19
+ * Passes if the authenticated user (set by `protect()`) has **at least one**
20
+ * of the specified `allowedRoles`. Calls `next(SentriError)` with code `FORBIDDEN`
21
+ * if no roles match, or `UNAUTHORIZED` if `req.user` is absent.
22
+ *
23
+ * Must be used **after** `protect()`.
24
+ *
25
+ * > **Logging** — when used via `auth.authorize()` from `createAuth()`, log entries
26
+ * > are emitted automatically using the logger configured in `AuthConfig`.
27
+ * > When called directly (standalone), no logging occurs.
28
+ *
29
+ * @param allowedRoles - One or more role strings. The user needs at least one of them.
30
+ * @returns An Express `RequestHandler` that enforces the role check.
31
+ *
32
+ * @example
33
+ * router.delete('/posts/:id', protect(config), authorize('admin', 'moderator'), handler);
34
+ */
35
+ declare function authorize<TRole extends string>(...allowedRoles: TRole[]): RequestHandler;
36
+
37
+ /** A function that determines whether the current request is permitted. */
38
+ type PermitCheck = (request: Request) => boolean | Promise<boolean>;
39
+ /**
40
+ * Options for {@link permit} when you need role-bypass alongside a resource check.
41
+ *
42
+ * @example
43
+ * // Admins can edit any post; others only their own
44
+ * auth.permit({
45
+ * roles: ['admin'],
46
+ * check: async (request) => {
47
+ * const post = await db.findPost(request.params['id']);
48
+ * return post?.authorId === request.user!.id;
49
+ * },
50
+ * })
51
+ */
52
+ interface PermitOptions<TRole extends string> {
53
+ /**
54
+ * Roles whose members are granted access without running `check`.
55
+ * Use for privileged roles like `'admin'` that should bypass ownership checks.
56
+ */
57
+ roles?: TRole[];
58
+ /**
59
+ * Permission check executed when the user has none of the bypass `roles`.
60
+ * Return `true` to allow, `false` to deny with `FORBIDDEN`.
61
+ * May be async — useful for database-backed ownership checks.
62
+ */
63
+ check: PermitCheck;
64
+ }
65
+ /**
66
+ * Returns a logger-aware `permit` factory bound to the given logger and service name.
67
+ * Used internally by `createAuth()` and `createAuthRouter()` to inject logging.
68
+ * Not part of the public API.
69
+ *
70
+ * @internal
71
+ */
72
+ declare function createPermit(logger: SentriLogger, service: string): <TRole extends string>(optionsOrCheck: PermitOptions<TRole> | PermitCheck) => RequestHandler;
73
+ /**
74
+ * Express middleware factory for resource-level permission checks.
75
+ *
76
+ * Must be used **after** `protect()`. Evaluates a check function against the
77
+ * current request; calls `next(SentriError)` with code `FORBIDDEN` if it returns `false`.
78
+ *
79
+ * Accepts either a bare check function or an options object with an optional
80
+ * `roles` list whose members bypass the check entirely.
81
+ *
82
+ * **Performance:** when `check` returns a plain `boolean` (not a Promise) no
83
+ * extra microtask is queued — the result is handled synchronously. Async checks
84
+ * are awaited normally.
85
+ *
86
+ * > **Logging** — when used via `auth.permit()` from `createAuth()`, log entries
87
+ * > are emitted automatically using the logger configured in `AuthConfig`.
88
+ * > When called directly (standalone), no logging occurs.
89
+ *
90
+ * @example
91
+ * // Simple ownership check (sync — zero async overhead)
92
+ * router.put('/users/:id',
93
+ * auth.protect(),
94
+ * auth.permit((request) => request.user!.id === request.params['id']),
95
+ * updateUserHandler,
96
+ * );
97
+ *
98
+ * @example
99
+ * // Admins bypass the check; others must own the post (async DB lookup)
100
+ * router.delete('/posts/:id',
101
+ * auth.protect(),
102
+ * auth.permit({
103
+ * roles: ['admin'],
104
+ * check: async (request) => {
105
+ * const post = await db.post.findUnique({ where: { id: request.params['id'] } });
106
+ * return post?.authorId === request.user!.id;
107
+ * },
108
+ * }),
109
+ * deletePostHandler,
110
+ * );
111
+ */
112
+ declare function permit<TRole extends string>(optionsOrCheck: PermitOptions<TRole> | PermitCheck): RequestHandler;
113
+
114
+ /**
115
+ * Options for {@link createErrorHandler}.
116
+ */
117
+ interface ErrorHandlerOptions {
118
+ /**
119
+ * Called for errors that are **not** a `SentriError` instance (or subclass).
120
+ *
121
+ * Use this to log unexpected server errors before the generic 500 response
122
+ * is sent. The error is passed as-is and may be any unknown value.
123
+ *
124
+ * @example
125
+ * app.use(auth.errorHandler({
126
+ * onUnhandled: (err) => logger.error('Unhandled error', { err }),
127
+ * }));
128
+ */
129
+ onUnhandled?: (error: unknown) => void;
130
+ }
131
+ /**
132
+ * Creates an Express error-handling middleware that formats every `SentriError`
133
+ * (including subclasses) into the standard sentri response envelope:
134
+ *
135
+ * ```json
136
+ * { "error": true, "statusCode": 401, "code": "UNAUTHORIZED", "message": "...", "data": null }
137
+ * ```
138
+ *
139
+ * Prefer using `auth.errorHandler()` instead of calling this directly:
140
+ *
141
+ * ```typescript
142
+ * app.use('/auth', auth.router());
143
+ * app.use('/api', apiRouter);
144
+ *
145
+ * // Must come after all route/middleware registrations
146
+ * app.use(auth.errorHandler());
147
+ * ```
148
+ *
149
+ * ---
150
+ *
151
+ * **Works with built-in sentri errors and your own subclasses**
152
+ *
153
+ * Because `instanceof SentriError` matches any subclass, you can define
154
+ * application-specific error types and have them automatically formatted
155
+ * by this handler:
156
+ *
157
+ * ```typescript
158
+ * import { SentriError } from 'sentri';
159
+ *
160
+ * // Extend SentriError for domain-specific failures
161
+ * export class NotFoundError extends SentriError {
162
+ * constructor(resource: string) {
163
+ * super('NOT_FOUND', `${resource} not found`, 404);
164
+ * }
165
+ * }
166
+ *
167
+ * export class PaymentError extends SentriError {
168
+ * constructor(message: string) {
169
+ * super('PAYMENT_FAILED', message, 402);
170
+ * }
171
+ * }
172
+ *
173
+ * // All of the above are caught and formatted by one handler
174
+ * app.use(auth.errorHandler({
175
+ * onUnhandled: (err) => console.error('Unexpected error:', err),
176
+ * }));
177
+ * ```
178
+ *
179
+ * @param options - Optional configuration (see {@link ErrorHandlerOptions}).
180
+ * @returns An Express `ErrorRequestHandler` (4-argument middleware).
181
+ */
182
+ declare function createErrorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
183
+
184
+ interface IdempotencyOptions {
185
+ /** @default 300_000 (5 minutes) */
186
+ ttl?: number;
187
+ /** @default 'X-Idempotency-Key' */
188
+ header?: string;
189
+ /** @default ['POST', 'PUT', 'PATCH'] */
190
+ methods?: string[];
191
+ /**
192
+ * Max in-memory entries (ignored when redisUrl is set).
193
+ * @default 10_000
194
+ */
195
+ maxSize?: number;
196
+ /**
197
+ * Redis connection URL (e.g. `redis://localhost:6379`).
198
+ * When set, uses Redis as the cache backend instead of in-memory Map.
199
+ */
200
+ redisUrl?: string;
201
+ }
202
+ /**
203
+ * Middleware that deduplicates non-idempotent requests.
204
+ *
205
+ * When a request arrives with a matching idempotency key header, the cached
206
+ * response is replayed immediately — the handler is not called again.
207
+ * Responses are only cached for 2xx status codes.
208
+ *
209
+ * Two backends are available:
210
+ * - **In-memory** (default) — zero dependencies, single-process only.
211
+ * - **Redis** — set `redisUrl` to share state across processes/instances.
212
+ *
213
+ * When using `createAuthServer()`, prefer `auth.idempotencyMiddleware()` instead —
214
+ * it automatically inherits the `redisUrl` from server config.
215
+ *
216
+ * @example
217
+ * // Standalone usage
218
+ * app.use(createIdempotencyMiddleware({ ttl: 60_000 }));
219
+ *
220
+ * @example
221
+ * // Multi-process (Redis)
222
+ * app.use(createIdempotencyMiddleware({ redisUrl: 'redis://localhost:6379' }));
223
+ */
224
+ declare function createIdempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
225
+
226
+ /**
227
+ * Extract the raw access token string from an Express request.
228
+ * Reads `Authorization: Bearer <token>` header first; falls back to the
229
+ * `access_token` cookie (or the name set in `accessCookie.name`).
230
+ * Returns `undefined` when no token is present.
231
+ */
232
+ declare function getCurrentAccessToken(request: Request, config: ServerAuthConfig): string | undefined;
233
+
234
+ declare function createAuthRouter<TRole extends string>(config: ServerAuthConfig<TRole>): Router;
235
+
236
+ declare global {
237
+ namespace Express {
238
+ interface Request {
239
+ user?: AuthUser;
240
+ requestId?: string;
241
+ }
242
+ }
243
+ }
244
+ interface CreateExpressServerOptions<TRole extends string = string> {
245
+ mode: 'server';
246
+ validRoles: readonly TRole[];
247
+ dialect: kysely.Dialect;
248
+ accessExpiresIn?: string | number;
249
+ refreshExpiresIn?: string | number;
250
+ saltRounds?: number;
251
+ apiKey?: string;
252
+ cookie?: CookieConfig;
253
+ accessCookie?: AccessCookieConfig;
254
+ hooks?: AuthHooks;
255
+ router?: RouterHandlers;
256
+ isTokenRevoked?: (sessionId: string) => boolean | Promise<boolean>;
257
+ redisUrl?: string;
258
+ logger?: SentriLogger;
259
+ loggerService?: string;
260
+ }
261
+ interface CreateExpressClientOptions<TRole extends string = string> {
262
+ mode: 'client';
263
+ keyUri: string;
264
+ validRoles?: readonly TRole[];
265
+ logger?: SentriLogger;
266
+ loggerService?: string;
267
+ }
268
+ interface AuthExpressClient<TRole extends string = string> {
269
+ protect(): RequestHandler;
270
+ authorize(...roles: TRole[]): RequestHandler;
271
+ permit(check: PermitCheck): RequestHandler;
272
+ permit(options: PermitOptions<TRole>): RequestHandler;
273
+ errorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler;
274
+ }
275
+ interface ServerAuthExpressClient<TRole extends string = string> extends AuthExpressClient<TRole> {
276
+ hashPassword(plain: string): Promise<string>;
277
+ verifyPassword(plain: string, hash: string): Promise<boolean>;
278
+ signAccessToken(payload: AuthUser<TRole>): string;
279
+ signRefreshToken(sessionId: string): string;
280
+ verifyAccessToken(token: string): AuthUser<TRole>;
281
+ verifyRefreshToken(token: string): {
282
+ sessionId: string;
283
+ };
284
+ getCurrentAccessToken(request: Request): string | undefined;
285
+ router(): Router;
286
+ migrate(): Promise<void>;
287
+ idempotencyMiddleware(options?: IdempotencyOptions): RequestHandler;
288
+ register(input: RegisterInput<TRole>): Promise<RegisterResult<TRole>>;
289
+ login(input: LoginInput): Promise<AuthResult<TRole>>;
290
+ refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
291
+ logout(refreshToken: string): Promise<void>;
292
+ logoutAll(userId: string): Promise<void>;
293
+ getUser(userId: string): Promise<GetUserResult<TRole>>;
294
+ changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
295
+ assignRoles(userId: string, roles: TRole[]): Promise<AssignRolesResult<TRole>>;
296
+ bulkCreateIdentifiers(userId: string, identifiers: IdentifierInput[]): Promise<BulkIdentifiersResult>;
297
+ bulkUpdateIdentifiers(userId: string, updates: Array<{
298
+ id: string;
299
+ type: string;
300
+ value: string;
301
+ }>): Promise<BulkIdentifiersResult>;
302
+ bulkDeleteIdentifiers(userId: string, ids: string[]): Promise<BulkIdentifiersResult>;
303
+ }
304
+ type ClientAuthExpressClient<TRole extends string = string> = AuthExpressClient<TRole>;
305
+ declare function createAuthExpress<TRole extends string = string>(options: CreateExpressServerOptions<TRole>): ServerAuthExpressClient<TRole>;
306
+ declare function createAuthExpress<TRole extends string = string>(options: CreateExpressClientOptions<TRole>): ClientAuthExpressClient<TRole>;
307
+
308
+ export { AccessCookieConfig, AssignRolesResult, AuthConfig, type AuthExpressClient, AuthHooks, AuthResult, AuthUser, BulkIdentifiersResult, ChangePasswordResult, type ClientAuthExpressClient, CookieConfig, type CreateExpressClientOptions, type CreateExpressServerOptions, type ErrorHandlerOptions, GetUserResult, type IdempotencyOptions, IdentifierInput, LoginInput, type PermitCheck, type PermitOptions, RefreshResult, RegisterInput, RegisterResult, RouterHandlers, SentriLogger, ServerAuthConfig, type ServerAuthExpressClient, authorize, createAuthExpress, createAuthRouter, createAuthorize, createErrorHandler, createIdempotencyMiddleware, createPermit, getCurrentAccessToken, permit, protect };