@dotpress/auth 1.3.1 → 2.0.0-alpha.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/LICENSE +1 -1
- package/README.md +355 -329
- package/dist/index.d.ts +679 -7
- package/dist/index.js +1226 -19
- package/dist/index.js.map +1 -0
- package/package.json +32 -46
- package/dist/__tests__/functional/cookies.test.d.ts +0 -1
- package/dist/__tests__/functional/cookies.test.js +0 -621
- package/dist/__tests__/functional/handlers.test.d.ts +0 -1
- package/dist/__tests__/functional/handlers.test.js +0 -239
- package/dist/__tests__/functional/middlewares.test.d.ts +0 -1
- package/dist/__tests__/functional/middlewares.test.js +0 -111
- package/dist/__tests__/unit/cookies.test.d.ts +0 -1
- package/dist/__tests__/unit/cookies.test.js +0 -466
- package/dist/__tests__/unit/tokens.test.d.ts +0 -1
- package/dist/__tests__/unit/tokens.test.js +0 -225
- package/dist/lib/cookies.d.ts +0 -42
- package/dist/lib/cookies.js +0 -136
- package/dist/lib/generators.d.ts +0 -1
- package/dist/lib/generators.js +0 -4
- package/dist/lib/handlers.d.ts +0 -2
- package/dist/lib/handlers.js +0 -263
- package/dist/lib/ip.d.ts +0 -2
- package/dist/lib/ip.js +0 -15
- package/dist/lib/middleware.d.ts +0 -2
- package/dist/lib/middleware.js +0 -23
- package/dist/lib/settings.d.ts +0 -3
- package/dist/lib/settings.js +0 -42
- package/dist/lib/tokens.d.ts +0 -38
- package/dist/lib/tokens.js +0 -112
- package/dist/types/types.d.ts +0 -107
- package/dist/types/types.js +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,682 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import * as _dotpress_core from '@dotpress/core';
|
|
2
|
+
import { DotpressRequest, HttpError, PluginCors, DotpressResponse } from '@dotpress/core';
|
|
3
|
+
import { JWK } from 'jose';
|
|
4
|
+
|
|
5
|
+
/** State of a rate limit counter. */
|
|
6
|
+
interface RateLimitState {
|
|
7
|
+
/** Number of hits in the current window, including the last one. */
|
|
8
|
+
count: number;
|
|
9
|
+
/** End of the current window. */
|
|
10
|
+
resetAt: Date;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Storage of rate limit counters (fixed windows). Use a shared store
|
|
14
|
+
* (Redis, database) when the app runs on several instances.
|
|
15
|
+
*/
|
|
16
|
+
interface RateLimitStore {
|
|
17
|
+
/**
|
|
18
|
+
* Atomically increments the counter of a key and returns its state. A
|
|
19
|
+
* missing or expired counter starts a new window of `windowSeconds`.
|
|
20
|
+
*/
|
|
21
|
+
increment(key: string, windowSeconds: number): Promise<RateLimitState>;
|
|
22
|
+
/** Deletes the counter of a key. */
|
|
23
|
+
reset(key: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface MemoryRateLimitStoreOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Maximum number of counters kept in memory: expired counters are purged
|
|
28
|
+
* first, then the oldest ones. Defaults to 10 000.
|
|
29
|
+
*/
|
|
30
|
+
maxKeys?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* In-memory rate limit store, for a single instance. With several
|
|
34
|
+
* instances, each one counts on its own: use a shared store.
|
|
35
|
+
*/
|
|
36
|
+
declare const memoryRateLimitStore: (options?: MemoryRateLimitStoreOptions) => RateLimitStore;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Authenticated user, as returned by `DataProvider.findUserById()`.
|
|
40
|
+
* Declare its shape through module augmentation:
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* declare module '@dotpress/auth' {
|
|
44
|
+
* interface AuthUser {
|
|
45
|
+
* id: string
|
|
46
|
+
* roles: string[]
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
*/
|
|
50
|
+
interface AuthUser {
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Data of the access token of the current request.
|
|
54
|
+
*/
|
|
55
|
+
interface AuthSession {
|
|
56
|
+
readonly userId: string;
|
|
57
|
+
/** `username` claim, with the `usernameClaim` setting only. */
|
|
58
|
+
readonly username: string | undefined;
|
|
59
|
+
/** Token identifier (`jti`), shared with its refresh token. */
|
|
60
|
+
readonly tokenId: string;
|
|
61
|
+
/**
|
|
62
|
+
* Session of the token (`sid` claim): shared by the tokens rotated from
|
|
63
|
+
* the same login. `undefined` for tokens issued without refresh token
|
|
64
|
+
* (`generateAccessToken()`).
|
|
65
|
+
*/
|
|
66
|
+
readonly sessionId: string | undefined;
|
|
67
|
+
/** Client the token was issued for (`client_id` claim). */
|
|
68
|
+
readonly clientId: string | undefined;
|
|
69
|
+
/** Roles of the user, used by `requireRole()`. */
|
|
70
|
+
readonly roles: readonly string[];
|
|
71
|
+
/** Decoded token claims. */
|
|
72
|
+
readonly claims: Readonly<Record<string, unknown>>;
|
|
73
|
+
}
|
|
74
|
+
interface KeyPairSettings {
|
|
75
|
+
/** RSA private key, PEM encoded in PKCS#8 (`BEGIN PRIVATE KEY`). */
|
|
76
|
+
secretKey: string;
|
|
77
|
+
/** RSA public key, PEM encoded in SPKI (`BEGIN PUBLIC KEY`). */
|
|
78
|
+
publicKey: string;
|
|
79
|
+
/**
|
|
80
|
+
* Key identifier (`kid` header of the tokens). Defaults to the RFC 7638
|
|
81
|
+
* thumbprint of the public key.
|
|
82
|
+
*/
|
|
83
|
+
kid?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Other public keys accepted to verify tokens (SPKI PEM, with an optional
|
|
86
|
+
* `kid`, defaulting to their thumbprint): the previous key after a
|
|
87
|
+
* rotation, or the next key before it, during rolling deployments.
|
|
88
|
+
*/
|
|
89
|
+
additionalPublicKeys?: readonly (string | AdditionalPublicKey)[];
|
|
90
|
+
}
|
|
91
|
+
interface AdditionalPublicKey {
|
|
92
|
+
publicKey: string;
|
|
93
|
+
kid?: string;
|
|
94
|
+
}
|
|
95
|
+
interface CookieSettings {
|
|
96
|
+
/** Enables cookie mode. Defaults to `false`. */
|
|
97
|
+
enabled?: boolean;
|
|
98
|
+
/** Defaults to `true`. */
|
|
99
|
+
httpOnly?: boolean;
|
|
100
|
+
/** Defaults to `true` (browsers accept secure cookies on `localhost`). */
|
|
101
|
+
secure?: boolean;
|
|
102
|
+
/** Defaults to `lax`. */
|
|
103
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
104
|
+
/**
|
|
105
|
+
* `SameSite` attribute of the refresh token cookie, which never needs to
|
|
106
|
+
* accompany a navigation from another site. Defaults to `strict`, or to
|
|
107
|
+
* `none` when `sameSite` is `none`.
|
|
108
|
+
*/
|
|
109
|
+
refreshTokenSameSite?: 'strict' | 'lax' | 'none';
|
|
110
|
+
/**
|
|
111
|
+
* Origins allowed to send requests authenticated by the cookies, besides
|
|
112
|
+
* the API origin: for example `https://app.example.com` when the app is
|
|
113
|
+
* served from another subdomain. Other cross-origin requests do not use
|
|
114
|
+
* the cookies (protection against cross-site request forgery).
|
|
115
|
+
*
|
|
116
|
+
* Defaults to the origins allowed by the `cors` option of the app, when
|
|
117
|
+
* it allows credentials (`authPlugin()`), else to none.
|
|
118
|
+
*/
|
|
119
|
+
trustedOrigins?: readonly string[];
|
|
120
|
+
domain?: string;
|
|
121
|
+
/** Path of the access token cookie. Defaults to `/`. */
|
|
122
|
+
path?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Path of the refresh token cookie: the browser path of the auth routes,
|
|
125
|
+
* the only ones that need it. With `authPlugin()`, defaults to the path
|
|
126
|
+
* of its routes, including the `basePath` or `publicBasePath` of the app
|
|
127
|
+
* (for example `/my-app/api/auth`). Defaults to `/auth` otherwise.
|
|
128
|
+
*/
|
|
129
|
+
refreshTokenPath?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Defaults to `dotpress_access_token`, prefixed with `__Host-` when the
|
|
132
|
+
* cookie is secure, without domain and with path `/`, or with
|
|
133
|
+
* `__Secure-` when it is only secure.
|
|
134
|
+
*/
|
|
135
|
+
accessTokenName?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Defaults to `dotpress_refresh_token`, prefixed like the access token
|
|
138
|
+
* cookie (`__Secure-` with the default path).
|
|
139
|
+
*/
|
|
140
|
+
refreshTokenName?: string;
|
|
141
|
+
/** Also stores the access token in a cookie. Defaults to `false`. */
|
|
142
|
+
accessTokenInCookie?: boolean;
|
|
143
|
+
}
|
|
144
|
+
type ResolvedCookieSettings = Required<Omit<CookieSettings, 'domain'>> & {
|
|
145
|
+
domain: string | undefined;
|
|
146
|
+
};
|
|
147
|
+
interface LoginFailedEvent {
|
|
148
|
+
username: string;
|
|
149
|
+
errorType: 'invalid_username' | 'invalid_password' | 'invalid_role'
|
|
150
|
+
/** Blocked by the login throttle, before checking the password. */
|
|
151
|
+
| 'too_many_attempts';
|
|
152
|
+
/** `clientId` sent at login; empty when missing. */
|
|
153
|
+
clientId: string;
|
|
154
|
+
ipAddress: string;
|
|
155
|
+
}
|
|
156
|
+
/** Login attempt, before the password is checked. */
|
|
157
|
+
interface BeforeLoginEvent {
|
|
158
|
+
username: string;
|
|
159
|
+
/** `clientId` sent at login; empty when missing. */
|
|
160
|
+
clientId: string;
|
|
161
|
+
ipAddress: string;
|
|
162
|
+
req: DotpressRequest;
|
|
163
|
+
}
|
|
164
|
+
/** At most `max` failed logins per `window` seconds. */
|
|
165
|
+
interface RateLimit {
|
|
166
|
+
max: number;
|
|
167
|
+
window: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Login throttle: failed logins are counted per username and IP address,
|
|
171
|
+
* and per username, whether the account exists or not.
|
|
172
|
+
*/
|
|
173
|
+
interface LoginThrottleSettings {
|
|
174
|
+
/**
|
|
175
|
+
* Counters storage. Defaults to an in-memory store, for a single
|
|
176
|
+
* instance: use a shared store (Redis, database) with several instances.
|
|
177
|
+
*/
|
|
178
|
+
store?: RateLimitStore;
|
|
179
|
+
limits?: {
|
|
180
|
+
/**
|
|
181
|
+
* Against brute force from one client, without letting others block
|
|
182
|
+
* the account. Defaults to 5 per minute; `false` disables it.
|
|
183
|
+
*/
|
|
184
|
+
usernameAndIp?: RateLimit | false;
|
|
185
|
+
/**
|
|
186
|
+
* Against distributed brute force on an account. Defaults to 100 per
|
|
187
|
+
* hour (NIST 800-63B); `false` disables it.
|
|
188
|
+
*/
|
|
189
|
+
username?: RateLimit | false;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
interface ResolvedLoginThrottleSettings {
|
|
193
|
+
store: RateLimitStore;
|
|
194
|
+
limits: {
|
|
195
|
+
usernameAndIp: RateLimit | undefined;
|
|
196
|
+
username: RateLimit | undefined;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
interface RefreshTokenReuseEvent {
|
|
200
|
+
userId: string;
|
|
201
|
+
/** Revoked session; undefined for tokens stored without family. */
|
|
202
|
+
familyId: string | undefined;
|
|
203
|
+
/** `jti` of the access token the reused refresh token was issued with. */
|
|
204
|
+
jwtId: string;
|
|
205
|
+
}
|
|
206
|
+
interface LoginSuccessEvent {
|
|
207
|
+
userId: string;
|
|
208
|
+
username: string;
|
|
209
|
+
/** `clientId` sent at login; empty when missing. */
|
|
210
|
+
clientId: string;
|
|
211
|
+
ipAddress: string;
|
|
212
|
+
}
|
|
213
|
+
interface StoreRefreshTokenDto {
|
|
214
|
+
token: string;
|
|
215
|
+
tokenHash: string;
|
|
216
|
+
userId: string;
|
|
217
|
+
jwtId: string;
|
|
218
|
+
/**
|
|
219
|
+
* Session the token belongs to: created at login, kept by every
|
|
220
|
+
* rotation. Used to revoke the whole session when a token is reused.
|
|
221
|
+
*/
|
|
222
|
+
familyId: string;
|
|
223
|
+
expiresAt: Date;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Sessions revoked by a logout: the current session, or every session of
|
|
227
|
+
* the user.
|
|
228
|
+
*/
|
|
229
|
+
type LogoutScope = 'current' | 'all';
|
|
230
|
+
/** Session revoked by `revokeSession()`. */
|
|
231
|
+
interface RevokedSession {
|
|
232
|
+
userId: string;
|
|
233
|
+
/** `undefined` for tokens stored without family (before V2). */
|
|
234
|
+
sessionId: string | undefined;
|
|
235
|
+
}
|
|
236
|
+
interface RefreshTokenData {
|
|
237
|
+
userId: string;
|
|
238
|
+
jwtId: string;
|
|
239
|
+
/** `null` or missing for tokens stored before families existed. */
|
|
240
|
+
familyId?: string | null;
|
|
241
|
+
expiresAt: Date;
|
|
242
|
+
isUsed: boolean;
|
|
243
|
+
/** When the token was used (see `refreshTokenReuseInterval`). */
|
|
244
|
+
usedAt?: Date | null;
|
|
245
|
+
revokedAt?: Date | null;
|
|
246
|
+
}
|
|
247
|
+
interface UserIdentifiers {
|
|
248
|
+
userId: string;
|
|
249
|
+
/**
|
|
250
|
+
* bcrypt hash of the password. `null` (or an empty string) for accounts
|
|
251
|
+
* without password (SSO, pending invitation): their login fails like a
|
|
252
|
+
* wrong password, in the same time.
|
|
253
|
+
*/
|
|
254
|
+
passwordHash: string | null;
|
|
255
|
+
role?: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Storage of users and refresh tokens, implemented by the application
|
|
259
|
+
* (Prisma, Drizzle, SQL…).
|
|
260
|
+
*/
|
|
261
|
+
interface DataProvider {
|
|
262
|
+
storeRefreshToken(payload: StoreRefreshTokenDto): Promise<void>;
|
|
263
|
+
findRefreshToken(tokenHash: string): Promise<RefreshTokenData | undefined>;
|
|
264
|
+
/** Marks a token as used, storing `usedAt`. */
|
|
265
|
+
markRefreshTokenAsUsed(tokenHash: string, usedAt: Date): Promise<void>;
|
|
266
|
+
/** Revokes every refresh token of a user. */
|
|
267
|
+
revokeTokens(userId: string): Promise<void>;
|
|
268
|
+
/** Revokes every refresh token of a session (`familyId`). */
|
|
269
|
+
revokeTokenFamily(familyId: string): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* User of a token, on every authenticated request and every refresh.
|
|
272
|
+
* Return `undefined` for a deleted, disabled or banned account: its
|
|
273
|
+
* requests are rejected at once, and its sessions end at their next
|
|
274
|
+
* refresh.
|
|
275
|
+
*/
|
|
276
|
+
findUserById(userId: string): Promise<AuthUser | undefined>;
|
|
277
|
+
findUserIdentifiers(username: string): Promise<UserIdentifiers | undefined>;
|
|
278
|
+
/**
|
|
279
|
+
* Optional. Whether a session (`familyId`, the `sid` claim) was revoked.
|
|
280
|
+
* When defined, it is checked on every authenticated request, so that
|
|
281
|
+
* the access tokens of a revoked session (logout, refresh token reuse,
|
|
282
|
+
* password change) stop working at once, instead of at their expiry.
|
|
283
|
+
* It costs one lookup per request: see the README for implementations.
|
|
284
|
+
*/
|
|
285
|
+
isSessionRevoked?(sessionId: string): Promise<boolean>;
|
|
286
|
+
/**
|
|
287
|
+
* Optional. Stores a new hash of the password of a user, computed at
|
|
288
|
+
* login when the stored hash has another cost than `passwordHashRounds`
|
|
289
|
+
* (for example after raising it). Errors are logged, and do not fail
|
|
290
|
+
* the login.
|
|
291
|
+
*/
|
|
292
|
+
updatePasswordHash?(userId: string, passwordHash: string): Promise<void>;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Client applications (`client_id` claim), such as `app` or `backoffice`.
|
|
296
|
+
*
|
|
297
|
+
* The client identifier is declared by the caller at login: it does not
|
|
298
|
+
* prove which application sends a request. It means that the token was
|
|
299
|
+
* issued under the policy of that client (see `isUserRoleAllowed`).
|
|
300
|
+
*/
|
|
301
|
+
interface ClientSettings {
|
|
302
|
+
/** Accepted client identifiers. Others are rejected at login (`400`). */
|
|
303
|
+
allowed: readonly string[];
|
|
304
|
+
/** Requires a `clientId` at login. Defaults to `false`. */
|
|
305
|
+
required?: boolean;
|
|
306
|
+
/** Checks at login that a user may get a token for a client. */
|
|
307
|
+
isUserRoleAllowed?: (role: string, clientId: string) => boolean;
|
|
308
|
+
/**
|
|
309
|
+
* Session lifetimes of some clients, overriding the `session` setting
|
|
310
|
+
* (an explicit `absoluteTimeout: undefined` removes the global limit).
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* sessions: { backoffice: { idleTimeout: 86400, absoluteTimeout: 86400 } }
|
|
314
|
+
*/
|
|
315
|
+
sessions?: Readonly<Record<string, SessionSettings>>;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Session lifetimes, in seconds. A session starts at login and is extended
|
|
319
|
+
* by each refresh.
|
|
320
|
+
*/
|
|
321
|
+
interface SessionSettings {
|
|
322
|
+
/**
|
|
323
|
+
* Inactivity timeout: the session ends when it is not refreshed during
|
|
324
|
+
* this delay (lifetime of each refresh token). Defaults to 14 days.
|
|
325
|
+
*/
|
|
326
|
+
idleTimeout?: number;
|
|
327
|
+
/**
|
|
328
|
+
* Absolute timeout: the session ends this delay after login, even when
|
|
329
|
+
* it is active. Defaults to none: active sessions never end.
|
|
330
|
+
*/
|
|
331
|
+
absoluteTimeout?: number | undefined;
|
|
332
|
+
}
|
|
333
|
+
interface ResolvedSessionSettings {
|
|
334
|
+
idleTimeout: number;
|
|
335
|
+
absoluteTimeout: number | undefined;
|
|
336
|
+
}
|
|
337
|
+
interface AuthSettings {
|
|
5
338
|
keys: KeyPairSettings;
|
|
6
339
|
dataProvider: DataProvider;
|
|
340
|
+
/**
|
|
341
|
+
* Access token lifetime, in seconds. Defaults to 900 (15 minutes): the
|
|
342
|
+
* longest delay during which the token of a revoked session still
|
|
343
|
+
* works, without `DataProvider.isSessionRevoked()`.
|
|
344
|
+
*/
|
|
345
|
+
accessTokenTTL?: number;
|
|
346
|
+
/** Session lifetimes (inactivity and absolute timeouts). */
|
|
347
|
+
session?: SessionSettings;
|
|
348
|
+
/**
|
|
349
|
+
* `iss` claim of the access tokens, checked on every request: for
|
|
350
|
+
* example the URL of the API. Use a different value per environment
|
|
351
|
+
* and application, especially when they share a key pair.
|
|
352
|
+
*/
|
|
353
|
+
issuer: string;
|
|
354
|
+
/**
|
|
355
|
+
* `aud` claim of the access tokens: the API that accepts them, checked
|
|
356
|
+
* on every request. Defaults to `issuer`.
|
|
357
|
+
*/
|
|
358
|
+
audience?: string;
|
|
359
|
+
cookies?: CookieSettings;
|
|
360
|
+
clients?: ClientSettings;
|
|
361
|
+
onLoginFailed?: (event: LoginFailedEvent) => Promise<void>;
|
|
362
|
+
onLoginSuccess?: (event: LoginSuccessEvent) => Promise<void>;
|
|
363
|
+
/**
|
|
364
|
+
* Login throttle (brute force protection), enabled by default. `false`
|
|
365
|
+
* disables it, when the infrastructure (gateway, WAF) already limits
|
|
366
|
+
* `/auth/login`.
|
|
367
|
+
*/
|
|
368
|
+
loginThrottle?: LoginThrottleSettings | false;
|
|
369
|
+
/**
|
|
370
|
+
* Called before checking the password, after the login throttle: return
|
|
371
|
+
* an error (`tooManyRequestsError()`, `forbiddenError()`…) to reject the
|
|
372
|
+
* attempt, for example to require a CAPTCHA or to block an IP address.
|
|
373
|
+
*/
|
|
374
|
+
beforeLogin?: (event: BeforeLoginEvent) => Promise<HttpError | void> | HttpError | void;
|
|
375
|
+
/**
|
|
376
|
+
* bcrypt cost of `hashPassword()`, and of the dummy hash compared when
|
|
377
|
+
* the user is unknown or has no password, so that every login takes the
|
|
378
|
+
* same time. Hashes with another cost are upgraded at login when
|
|
379
|
+
* `DataProvider.updatePasswordHash()` is defined. Defaults to 12.
|
|
380
|
+
*/
|
|
381
|
+
passwordHashRounds?: number;
|
|
382
|
+
/**
|
|
383
|
+
* Writes the `username` claim in the access tokens. Defaults to `false`:
|
|
384
|
+
* a username is often an email address, readable by anyone holding the
|
|
385
|
+
* token (it is only base64 encoded); `sub` identifies the user.
|
|
386
|
+
*/
|
|
387
|
+
usernameClaim?: boolean;
|
|
388
|
+
/**
|
|
389
|
+
* Delay, in seconds after its first use, during which a refresh token can
|
|
390
|
+
* be presented again without being treated as stolen: concurrent
|
|
391
|
+
* refreshes (several tabs) and retries after a lost response get a new
|
|
392
|
+
* token pair in the same session. Defaults to 10; 0 disables it.
|
|
393
|
+
*/
|
|
394
|
+
refreshTokenReuseInterval?: number;
|
|
395
|
+
/**
|
|
396
|
+
* Called when a used refresh token is presented again after the reuse
|
|
397
|
+
* interval: the session has been revoked, as the token was probably
|
|
398
|
+
* stolen. Errors thrown by the hook are ignored.
|
|
399
|
+
*/
|
|
400
|
+
onRefreshTokenReuse?: (event: RefreshTokenReuseEvent) => Promise<void>;
|
|
401
|
+
/**
|
|
402
|
+
* Roles of a user, checked by `requireRole()`. Defaults to its `roles`
|
|
403
|
+
* array, or to its `role` string.
|
|
404
|
+
*/
|
|
405
|
+
getUserRoles?: (user: AuthUser) => readonly string[];
|
|
406
|
+
}
|
|
407
|
+
/** Options of `auth.init()`. */
|
|
408
|
+
interface AuthInitOptions {
|
|
409
|
+
/**
|
|
410
|
+
* Browser path of the auth routes, used as the default path of the
|
|
411
|
+
* refresh token cookie. An auth service serves a single path.
|
|
412
|
+
*/
|
|
413
|
+
routesPath?: string;
|
|
414
|
+
/**
|
|
415
|
+
* CORS settings of the app (`api.cors`). When `cookies.trustedOrigins`
|
|
416
|
+
* is not set and CORS allows credentials, the origins allowed by CORS
|
|
417
|
+
* are trusted.
|
|
418
|
+
*/
|
|
419
|
+
cors?: PluginCors;
|
|
420
|
+
}
|
|
421
|
+
interface ResolvedAuthSettings extends Required<Omit<AuthSettings, 'cookies' | 'clients' | 'session' | 'onLoginFailed' | 'onLoginSuccess' | 'loginThrottle' | 'beforeLogin' | 'onRefreshTokenReuse'>> {
|
|
422
|
+
cookies: ResolvedCookieSettings;
|
|
423
|
+
clients: ClientSettings | undefined;
|
|
424
|
+
session: ResolvedSessionSettings;
|
|
425
|
+
onLoginFailed: ((event: LoginFailedEvent) => Promise<void>) | undefined;
|
|
426
|
+
onLoginSuccess: ((event: LoginSuccessEvent) => Promise<void>) | undefined;
|
|
427
|
+
loginThrottle: ResolvedLoginThrottleSettings | undefined;
|
|
428
|
+
beforeLogin: AuthSettings['beforeLogin'];
|
|
429
|
+
onRefreshTokenReuse: ((event: RefreshTokenReuseEvent) => Promise<void>) | undefined;
|
|
430
|
+
}
|
|
431
|
+
interface TokenPair {
|
|
432
|
+
accessToken: string;
|
|
433
|
+
refreshToken: string;
|
|
434
|
+
}
|
|
435
|
+
interface TokenSubject {
|
|
436
|
+
userId: string;
|
|
437
|
+
/** Written in the token with the `usernameClaim` setting only. */
|
|
438
|
+
username?: string;
|
|
439
|
+
/** `client_id` claim. Must be allowed by the `clients` setting. */
|
|
440
|
+
clientId?: string;
|
|
441
|
+
}
|
|
442
|
+
/** Token pair issued at login or by a refresh. */
|
|
443
|
+
interface IssuedTokens extends TokenPair {
|
|
444
|
+
accessTokenExpiresAt: Date;
|
|
445
|
+
refreshTokenExpiresAt: Date;
|
|
446
|
+
/** End of the session, when an absolute timeout applies. */
|
|
447
|
+
sessionExpiresAt: Date | undefined;
|
|
448
|
+
}
|
|
449
|
+
interface CreatedAccessToken {
|
|
450
|
+
jwtId: string;
|
|
451
|
+
accessToken: string;
|
|
452
|
+
expiresAt: Date;
|
|
453
|
+
}
|
|
454
|
+
interface CreatedRefreshToken {
|
|
455
|
+
token: string;
|
|
456
|
+
tokenHash: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
declare module '@dotpress/core' {
|
|
460
|
+
interface RequestContext {
|
|
461
|
+
/** Authenticated user (`authPlugin`). Use `requireAuth` to require it. */
|
|
462
|
+
user: AuthUser | undefined;
|
|
463
|
+
/** Access token data of the authenticated user (`authPlugin`). */
|
|
464
|
+
auth: AuthSession | undefined;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
type AuthErrorCode = 'INVALID_ACCESS_TOKEN' | 'INVALID_REFRESH_TOKEN' | 'EXPIRED_REFRESH_TOKEN'
|
|
469
|
+
/** A used refresh token was presented again: its session is revoked. */
|
|
470
|
+
| 'REFRESH_TOKEN_REUSED'
|
|
471
|
+
/** Valid access token of a revoked session (`isSessionRevoked()`). */
|
|
472
|
+
| 'SESSION_REVOKED';
|
|
473
|
+
/**
|
|
474
|
+
* Error raised by the token helpers.
|
|
475
|
+
*/
|
|
476
|
+
declare class AuthError extends Error {
|
|
477
|
+
readonly code: AuthErrorCode;
|
|
478
|
+
constructor(code: AuthErrorCode, options?: ErrorOptions);
|
|
479
|
+
}
|
|
480
|
+
interface Auth {
|
|
481
|
+
readonly settings: ResolvedAuthSettings;
|
|
482
|
+
/**
|
|
483
|
+
* Imports the keys. Fails with an explicit message on invalid keys.
|
|
484
|
+
* `authPlugin()` calls it at startup, with the browser path of its routes.
|
|
485
|
+
*/
|
|
486
|
+
init(options?: AuthInitOptions): Promise<void>;
|
|
487
|
+
generateAccessToken(subject: TokenSubject): Promise<CreatedAccessToken>;
|
|
488
|
+
generateRefreshToken(): Promise<CreatedRefreshToken>;
|
|
489
|
+
/** Generates and stores a token pair, starting a session. */
|
|
490
|
+
generateTokensForUser(subject: TokenSubject): Promise<IssuedTokens>;
|
|
491
|
+
/**
|
|
492
|
+
* Rotates a token pair. The access token may be expired, but must be
|
|
493
|
+
* signed by the key pair and match the refresh token.
|
|
494
|
+
*
|
|
495
|
+
* A refresh token can only be used once. Within
|
|
496
|
+
* `refreshTokenReuseInterval` after its first use, it may be presented
|
|
497
|
+
* again (concurrent refreshes, retries after a lost response): a new pair
|
|
498
|
+
* is issued in the same session. After, presenting it again means it was
|
|
499
|
+
* probably stolen: the whole session (token family) is revoked, and
|
|
500
|
+
* `REFRESH_TOKEN_REUSED` is thrown.
|
|
501
|
+
*
|
|
502
|
+
* Throws `EXPIRED_REFRESH_TOKEN` when the session reached its inactivity
|
|
503
|
+
* or absolute timeout, and `INVALID_REFRESH_TOKEN` when
|
|
504
|
+
* `findUserById()` no longer returns the user (the session is revoked).
|
|
505
|
+
*/
|
|
506
|
+
refreshTokens(tokens: TokenPair): Promise<IssuedTokens & {
|
|
507
|
+
userId: string;
|
|
508
|
+
user: AuthUser;
|
|
509
|
+
}>;
|
|
510
|
+
/**
|
|
511
|
+
* Verifies a valid (not expired) access token. With
|
|
512
|
+
* `DataProvider.isSessionRevoked()`, throws `SESSION_REVOKED` for the
|
|
513
|
+
* tokens of a revoked session.
|
|
514
|
+
*/
|
|
515
|
+
verifyAccessToken(accessToken: string): Promise<AuthSession>;
|
|
516
|
+
/**
|
|
517
|
+
* Revokes the session of a refresh token (logout). The token may be
|
|
518
|
+
* used, revoked or expired: it still designates its session. Tokens
|
|
519
|
+
* stored without family (before V2) revoke every session of the user.
|
|
520
|
+
*
|
|
521
|
+
* With `scope: 'all'`, revokes every session of the user, on every
|
|
522
|
+
* device. The token must then be active (neither used, revoked nor
|
|
523
|
+
* expired).
|
|
524
|
+
*
|
|
525
|
+
* Returns `undefined`, without revoking anything, for an unknown token,
|
|
526
|
+
* or an inactive token with `scope: 'all'`.
|
|
527
|
+
*/
|
|
528
|
+
revokeSession(refreshToken: string, options?: {
|
|
529
|
+
scope?: LogoutScope;
|
|
530
|
+
}): Promise<RevokedSession | undefined>;
|
|
531
|
+
/**
|
|
532
|
+
* End of a session, when an absolute timeout applies to it (computed
|
|
533
|
+
* from the `auth_time` claim).
|
|
534
|
+
*/
|
|
535
|
+
getSessionExpiresAt(session: AuthSession): Date | undefined;
|
|
536
|
+
/**
|
|
537
|
+
* Public keys as a JSON Web Key Set (RFC 7517): the active key first,
|
|
538
|
+
* then the additional keys. Other services can verify the tokens with
|
|
539
|
+
* it (`createRemoteJWKSet()` of jose).
|
|
540
|
+
*/
|
|
541
|
+
getJwks(): Promise<{
|
|
542
|
+
keys: JWK[];
|
|
543
|
+
}>;
|
|
544
|
+
/** Roles of a user, from the `getUserRoles` setting. */
|
|
545
|
+
getUserRoles(user: AuthUser): readonly string[];
|
|
546
|
+
/**
|
|
547
|
+
* Sets the token cookies (cookie mode only). They expire with the
|
|
548
|
+
* refresh token (`refreshTokenExpiresAt` of issued tokens), or after the
|
|
549
|
+
* inactivity timeout.
|
|
550
|
+
*/
|
|
551
|
+
setAuthCookies(res: DotpressResponse, tokens: TokenPair & {
|
|
552
|
+
refreshTokenExpiresAt?: Date;
|
|
553
|
+
}, expiresInSeconds?: number): void;
|
|
554
|
+
clearAuthCookies(res: DotpressResponse): void;
|
|
555
|
+
/**
|
|
556
|
+
* Whether a request may be authenticated by the cookies: protection
|
|
557
|
+
* against cross-site request forgery (see `cookies.trustedOrigins`).
|
|
558
|
+
* `authPlugin()` checks it on its routes and before reading the access
|
|
559
|
+
* token cookie.
|
|
560
|
+
*/
|
|
561
|
+
isCookieAuthenticationAllowed(req: DotpressRequest): Promise<boolean>;
|
|
562
|
+
/**
|
|
563
|
+
* Hashes a password with bcrypt. Defaults to `passwordHashRounds`.
|
|
564
|
+
* Throws a `RangeError` for passwords longer than 72 bytes (see
|
|
565
|
+
* `isPasswordTooLong()`).
|
|
566
|
+
*/
|
|
567
|
+
hashPassword(password: string, saltRounds?: number): Promise<string>;
|
|
568
|
+
/** Whether a bcrypt hash has another cost than `passwordHashRounds`. */
|
|
569
|
+
passwordNeedsRehash(passwordHash: string): boolean;
|
|
570
|
+
/**
|
|
571
|
+
* Hashes a verified password with `passwordHashRounds`, to upgrade its
|
|
572
|
+
* stored hash. Unlike `hashPassword()`, it accepts passwords longer than
|
|
573
|
+
* 72 bytes, created before the check.
|
|
574
|
+
*/
|
|
575
|
+
rehashPassword(password: string): Promise<string>;
|
|
576
|
+
/**
|
|
577
|
+
* Verifies a password against its bcrypt hash. Without valid hash
|
|
578
|
+
* (unknown user, account without password), it compares the password
|
|
579
|
+
* with a dummy hash and returns `false`: the answer takes the same time,
|
|
580
|
+
* and does not reveal whether the account exists.
|
|
581
|
+
*/
|
|
582
|
+
verifyPassword(password: string, passwordHash: string | null | undefined): Promise<boolean>;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Creates the authentication service. It works without an app (scripts,
|
|
586
|
+
* workers), and is plugged into an app with `authPlugin()`.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* const auth = createAuth({ keys, dataProvider })
|
|
590
|
+
* await createApp({ adapter, plugins: [authPlugin(auth)] })
|
|
591
|
+
*/
|
|
592
|
+
declare const createAuth: (input: AuthSettings) => Auth;
|
|
593
|
+
/** Longest password bcrypt reads, in UTF-8 bytes. */
|
|
594
|
+
declare const MAX_PASSWORD_BYTES = 72;
|
|
595
|
+
/**
|
|
596
|
+
* Whether a password exceeds the 72 bytes (UTF-8) read by bcrypt. Check
|
|
597
|
+
* it in the sign-up and password change schemas: `hashPassword()` rejects
|
|
598
|
+
* these passwords instead of silently truncating them.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* password: z.string().min(12).refine((p) => !isPasswordTooLong(p), 'Password too long')
|
|
602
|
+
*/
|
|
603
|
+
declare const isPasswordTooLong: (password: string) => boolean;
|
|
604
|
+
/**
|
|
605
|
+
* Hashes a password with bcrypt (cost 12 by default). Throws a
|
|
606
|
+
* `RangeError` for passwords longer than 72 bytes (see
|
|
607
|
+
* `isPasswordTooLong()`).
|
|
608
|
+
*/
|
|
609
|
+
declare const generatePasswordHash: (password: string, saltRounds?: number) => Promise<string>;
|
|
610
|
+
|
|
611
|
+
/** Context guaranteed by `requireAuth`, `requireRole()` and `requireClient()`. */
|
|
612
|
+
interface AuthenticatedContext {
|
|
613
|
+
user: AuthUser;
|
|
614
|
+
auth: AuthSession;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Requires an authenticated user (`401 UNAUTHORIZED` otherwise). In the
|
|
618
|
+
* handlers of the routes using it, `ctx.user` and `ctx.auth` are defined.
|
|
619
|
+
*/
|
|
620
|
+
declare const requireAuth: _dotpress_core.Middleware<AuthenticatedContext>;
|
|
621
|
+
/**
|
|
622
|
+
* Requires an authenticated user with at least one of the roles
|
|
623
|
+
* (`401 UNAUTHORIZED` without user, `403 FORBIDDEN` without role). Roles
|
|
624
|
+
* come from the `getUserRoles` setting of `createAuth()`.
|
|
625
|
+
*/
|
|
626
|
+
declare const requireRole: (...roles: [string, ...string[]]) => _dotpress_core.Middleware<AuthenticatedContext>;
|
|
627
|
+
/**
|
|
628
|
+
* Requires an access token issued for one of the clients (`401
|
|
629
|
+
* UNAUTHORIZED` without user, `403 FORBIDDEN` for another client). For
|
|
630
|
+
* example, keeps the tokens of a mobile app away from the backoffice
|
|
631
|
+
* routes. It does not prove which application sends the request: combine
|
|
632
|
+
* it with `requireRole()`.
|
|
633
|
+
*/
|
|
634
|
+
declare const requireClient: (...clientIds: [string, ...string[]]) => _dotpress_core.Middleware<AuthenticatedContext>;
|
|
635
|
+
|
|
636
|
+
interface AuthPluginOptions {
|
|
637
|
+
/** Prefix of the authentication routes. Defaults to `/auth`. */
|
|
638
|
+
prefix?: string;
|
|
639
|
+
/** Registers the authentication routes. Defaults to `true`. */
|
|
640
|
+
routes?: boolean;
|
|
641
|
+
/**
|
|
642
|
+
* Publishes the public keys at `GET /.well-known/jwks.json`, for other
|
|
643
|
+
* services verifying the tokens. Defaults to `false`.
|
|
644
|
+
*/
|
|
645
|
+
jwks?: boolean;
|
|
646
|
+
}
|
|
647
|
+
/** Security schemes of the plugin, for documentation generators. */
|
|
648
|
+
declare const AUTH_SECURITY_SCHEMES: {
|
|
649
|
+
readonly bearerAuth: {
|
|
650
|
+
readonly type: "http";
|
|
651
|
+
readonly scheme: "bearer";
|
|
652
|
+
readonly bearerFormat: "JWT";
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
/**
|
|
656
|
+
* Access token of a request: `Authorization: Bearer` header, then cookie
|
|
657
|
+
* (when enabled). The request body is never read.
|
|
658
|
+
*/
|
|
659
|
+
declare const extractAccessToken: (req: DotpressRequest, cookies: ResolvedCookieSettings) => string | undefined;
|
|
660
|
+
/**
|
|
661
|
+
* Plugs an authentication service into an app:
|
|
662
|
+
*
|
|
663
|
+
* - reads the access token of every request, and sets `ctx.user` and
|
|
664
|
+
* `ctx.auth` when it is valid;
|
|
665
|
+
* - registers the `POST /auth/login`, `POST /auth/refresh`,
|
|
666
|
+
* `GET /auth/me`, `POST /auth/restore` and `POST /auth/logout` routes.
|
|
667
|
+
*/
|
|
668
|
+
declare const authPlugin: (auth: Auth, options?: AuthPluginOptions) => {
|
|
669
|
+
readonly name: "auth";
|
|
670
|
+
readonly auth: Auth;
|
|
671
|
+
readonly securitySchemes: {
|
|
672
|
+
readonly bearerAuth: {
|
|
673
|
+
readonly type: "http";
|
|
674
|
+
readonly scheme: "bearer";
|
|
675
|
+
readonly bearerFormat: "JWT";
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
readonly setup: (api: _dotpress_core.PluginAPI) => Promise<void>;
|
|
7
679
|
};
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
export {
|
|
680
|
+
|
|
681
|
+
export { AUTH_SECURITY_SCHEMES, AuthError, MAX_PASSWORD_BYTES, authPlugin, createAuth, extractAccessToken, generatePasswordHash, isPasswordTooLong, memoryRateLimitStore, requireAuth, requireClient, requireRole };
|
|
682
|
+
export type { AdditionalPublicKey, Auth, AuthErrorCode, AuthInitOptions, AuthPluginOptions, AuthSession, AuthSettings, AuthUser, AuthenticatedContext, BeforeLoginEvent, ClientSettings, CookieSettings, CreatedAccessToken, CreatedRefreshToken, DataProvider, IssuedTokens, KeyPairSettings, LoginFailedEvent, LoginSuccessEvent, LoginThrottleSettings, LogoutScope, MemoryRateLimitStoreOptions, RateLimit, RateLimitState, RateLimitStore, RefreshTokenData, RefreshTokenReuseEvent, ResolvedAuthSettings, ResolvedCookieSettings, ResolvedLoginThrottleSettings, ResolvedSessionSettings, RevokedSession, SessionSettings, StoreRefreshTokenDto, TokenPair, TokenSubject, UserIdentifiers };
|