@adonisjs/auth 10.0.0 → 10.1.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/build/auth_manager-Cp_ofh4p.js +384 -0
- package/build/debug-CrTUB4zl.js +12 -0
- package/build/{errors-sGy-K8pd.js → errors-eDV8ejO0.js} +114 -2
- package/build/index.js +46 -4
- package/build/modules/access_tokens_guard/main.js +757 -2
- package/build/modules/basic_auth_guard/main.js +224 -2
- package/build/modules/session_guard/main.js +716 -2
- package/build/providers/auth_provider.js +24 -3
- package/build/services/auth.js +5 -0
- package/build/src/middleware/initialize_auth_middleware.js +29 -0
- package/build/src/mixins/lucid.js +94 -1
- package/build/src/plugins/japa/api_client.js +26 -1
- package/build/src/plugins/japa/browser_client.js +24 -1
- package/build/{symbols-BQLDWwuQ.js → symbols-C5QEqFvJ.js} +10 -1
- package/package.json +20 -20
- package/build/auth_manager-hJTiBA2V.js +0 -129
- package/build/debug-Ckko95-M.js +0 -3
|
@@ -1,29 +1,129 @@
|
|
|
1
|
-
import { n as E_UNAUTHORIZED_ACCESS } from "../../errors-
|
|
2
|
-
import "../../symbols-
|
|
1
|
+
import { n as E_UNAUTHORIZED_ACCESS } from "../../errors-eDV8ejO0.js";
|
|
2
|
+
import "../../symbols-C5QEqFvJ.js";
|
|
3
3
|
import { RuntimeException } from "@adonisjs/core/exceptions";
|
|
4
4
|
import { inspect } from "node:util";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import string from "@adonisjs/core/helpers/string";
|
|
7
7
|
import { Secret, base64, safeEqual } from "@adonisjs/core/helpers";
|
|
8
|
+
//#region modules/session_guard/guard.ts
|
|
9
|
+
/**
|
|
10
|
+
* Session guard uses AdonisJS session store to track logged-in
|
|
11
|
+
* user information.
|
|
12
|
+
*
|
|
13
|
+
* @template UseRememberTokens - Whether the guard supports remember me tokens
|
|
14
|
+
* @template UserProvider - The user provider contract
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const guard = new SessionGuard(
|
|
18
|
+
* 'web',
|
|
19
|
+
* ctx,
|
|
20
|
+
* { useRememberMeTokens: true },
|
|
21
|
+
* emitter,
|
|
22
|
+
* userProvider
|
|
23
|
+
* )
|
|
24
|
+
*
|
|
25
|
+
* const user = await guard.authenticate()
|
|
26
|
+
* console.log('Authenticated user:', user.email)
|
|
27
|
+
*/
|
|
8
28
|
var SessionGuard = class {
|
|
29
|
+
/**
|
|
30
|
+
* A unique name for the guard.
|
|
31
|
+
*/
|
|
9
32
|
#name;
|
|
33
|
+
/**
|
|
34
|
+
* Reference to the current HTTP context
|
|
35
|
+
*/
|
|
10
36
|
#ctx;
|
|
37
|
+
/**
|
|
38
|
+
* Options accepted by the session guard
|
|
39
|
+
*/
|
|
11
40
|
#options;
|
|
41
|
+
/**
|
|
42
|
+
* Provider to lookup user details
|
|
43
|
+
*/
|
|
12
44
|
#userProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Emitter to emit events
|
|
47
|
+
*/
|
|
13
48
|
#emitter;
|
|
49
|
+
/**
|
|
50
|
+
* Driver name of the guard
|
|
51
|
+
*/
|
|
14
52
|
driverName = "session";
|
|
53
|
+
/**
|
|
54
|
+
* Whether or not the authentication has been attempted
|
|
55
|
+
* during the current request.
|
|
56
|
+
*/
|
|
15
57
|
authenticationAttempted = false;
|
|
58
|
+
/**
|
|
59
|
+
* A boolean to know if a remember me token was used in attempt
|
|
60
|
+
* to login a user.
|
|
61
|
+
*/
|
|
16
62
|
attemptedViaRemember = false;
|
|
63
|
+
/**
|
|
64
|
+
* A boolean to know if the current request has
|
|
65
|
+
* been authenticated
|
|
66
|
+
*/
|
|
17
67
|
isAuthenticated = false;
|
|
68
|
+
/**
|
|
69
|
+
* A boolean to know if the current request is authenticated
|
|
70
|
+
* using the "rememember_me" token.
|
|
71
|
+
*/
|
|
18
72
|
viaRemember = false;
|
|
73
|
+
/**
|
|
74
|
+
* Find if the user has been logged out during
|
|
75
|
+
* the current request
|
|
76
|
+
*/
|
|
19
77
|
isLoggedOut = false;
|
|
78
|
+
/**
|
|
79
|
+
* Reference to an instance of the authenticated user.
|
|
80
|
+
* The value only exists after calling one of the
|
|
81
|
+
* following methods.
|
|
82
|
+
*
|
|
83
|
+
* - authenticate
|
|
84
|
+
* - check
|
|
85
|
+
*
|
|
86
|
+
* You can use the "getUserOrFail" method to throw an exception if
|
|
87
|
+
* the request is not authenticated.
|
|
88
|
+
*/
|
|
20
89
|
user;
|
|
90
|
+
/**
|
|
91
|
+
* The key used to store the logged-in user id inside
|
|
92
|
+
* session
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* console.log('Session key:', guard.sessionKeyName) // 'auth_web'
|
|
96
|
+
*/
|
|
21
97
|
get sessionKeyName() {
|
|
22
98
|
return `auth_${this.#name}`;
|
|
23
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The key used to store the remember me token cookie
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* console.log('Remember me key:', guard.rememberMeKeyName) // 'remember_web'
|
|
105
|
+
*/
|
|
24
106
|
get rememberMeKeyName() {
|
|
25
107
|
return `remember_${this.#name}`;
|
|
26
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Creates a new SessionGuard instance
|
|
111
|
+
*
|
|
112
|
+
* @param name - Unique name for the guard instance
|
|
113
|
+
* @param ctx - HTTP context for the current request
|
|
114
|
+
* @param options - Configuration options for the session guard
|
|
115
|
+
* @param emitter - Event emitter for guard events
|
|
116
|
+
* @param userProvider - User provider for authentication
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* const guard = new SessionGuard(
|
|
120
|
+
* 'web',
|
|
121
|
+
* ctx,
|
|
122
|
+
* { useRememberMeTokens: true, rememberMeTokensAge: '30d' },
|
|
123
|
+
* emitter,
|
|
124
|
+
* userProvider
|
|
125
|
+
* )
|
|
126
|
+
*/
|
|
27
127
|
constructor(name, ctx, options, emitter, userProvider) {
|
|
28
128
|
this.#name = name;
|
|
29
129
|
this.#ctx = ctx;
|
|
@@ -34,10 +134,19 @@ var SessionGuard = class {
|
|
|
34
134
|
this.#emitter = emitter;
|
|
35
135
|
this.#userProvider = userProvider;
|
|
36
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Returns the session instance for the given request,
|
|
139
|
+
* ensuring the property exists
|
|
140
|
+
*/
|
|
37
141
|
#getSession() {
|
|
38
142
|
if (!("session" in this.#ctx)) throw new RuntimeException("Cannot authenticate user. Install and configure \"@adonisjs/session\" package");
|
|
39
143
|
return this.#ctx.session;
|
|
40
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Emits authentication failure, updates the local state,
|
|
147
|
+
* and returns an exception to end the authentication
|
|
148
|
+
* cycle.
|
|
149
|
+
*/
|
|
41
150
|
#authenticationFailed(sessionId) {
|
|
42
151
|
this.isAuthenticated = false;
|
|
43
152
|
this.viaRemember = false;
|
|
@@ -52,6 +161,10 @@ var SessionGuard = class {
|
|
|
52
161
|
});
|
|
53
162
|
return error;
|
|
54
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Emits the authentication succeeded event and updates
|
|
166
|
+
* the local state to reflect successful authentication
|
|
167
|
+
*/
|
|
55
168
|
#authenticationSucceeded(sessionId, user, rememberMeToken) {
|
|
56
169
|
this.isAuthenticated = true;
|
|
57
170
|
this.viaRemember = !!rememberMeToken;
|
|
@@ -65,6 +178,10 @@ var SessionGuard = class {
|
|
|
65
178
|
rememberMeToken
|
|
66
179
|
});
|
|
67
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Emits the login succeeded event and updates the login
|
|
183
|
+
* state
|
|
184
|
+
*/
|
|
68
185
|
#loginSucceeded(sessionId, user, rememberMeToken) {
|
|
69
186
|
this.user = user;
|
|
70
187
|
this.isLoggedOut = false;
|
|
@@ -76,39 +193,102 @@ var SessionGuard = class {
|
|
|
76
193
|
rememberMeToken
|
|
77
194
|
});
|
|
78
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Creates session for a given user by their user id.
|
|
198
|
+
*/
|
|
79
199
|
#createSessionForUser(userId) {
|
|
80
200
|
const session = this.#getSession();
|
|
81
201
|
session.put(this.sessionKeyName, userId);
|
|
82
202
|
session.regenerate();
|
|
83
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Creates the remember me cookie
|
|
206
|
+
*/
|
|
84
207
|
#createRememberMeCookie(value) {
|
|
85
208
|
this.#ctx.response.encryptedCookie(this.rememberMeKeyName, value.release(), {
|
|
86
209
|
maxAge: this.#options.rememberMeTokensAge,
|
|
87
210
|
httpOnly: true
|
|
88
211
|
});
|
|
89
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Authenticates the user using its id read from the session
|
|
215
|
+
* store.
|
|
216
|
+
*
|
|
217
|
+
* - We check the user exists in the db
|
|
218
|
+
* - If not, throw exception.
|
|
219
|
+
* - Otherwise, update local state to mark the user as logged-in
|
|
220
|
+
*/
|
|
90
221
|
async #authenticateViaId(userId, sessionId) {
|
|
91
222
|
const providerUser = await this.#userProvider.findById(userId);
|
|
92
223
|
if (!providerUser) throw this.#authenticationFailed(sessionId);
|
|
93
224
|
this.#authenticationSucceeded(sessionId, providerUser.getOriginal());
|
|
94
225
|
return this.user;
|
|
95
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Authenticates user from the remember me cookie. Creates a fresh
|
|
229
|
+
* session for them and recycles the remember me token as well.
|
|
230
|
+
*/
|
|
96
231
|
async #authenticateViaRememberCookie(rememberMeCookie, sessionId) {
|
|
232
|
+
/**
|
|
233
|
+
* This method is only invoked when "options.useRememberTokens" is set to
|
|
234
|
+
* true and hence the user provider will have methods to manage tokens
|
|
235
|
+
*/
|
|
97
236
|
const userProvider = this.#userProvider;
|
|
237
|
+
/**
|
|
238
|
+
* Verify the token using the user provider.
|
|
239
|
+
*/
|
|
98
240
|
const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie));
|
|
99
241
|
if (!token) throw this.#authenticationFailed(sessionId);
|
|
242
|
+
/**
|
|
243
|
+
* Check if a user for the token exists. Otherwise abort
|
|
244
|
+
* authentication
|
|
245
|
+
*/
|
|
100
246
|
const providerUser = await userProvider.findById(token.tokenableId);
|
|
101
247
|
if (!providerUser) throw this.#authenticationFailed(sessionId);
|
|
248
|
+
/**
|
|
249
|
+
* Recycle remember token and the remember me cookie
|
|
250
|
+
*/
|
|
102
251
|
const recycledToken = await userProvider.recycleRememberToken(providerUser.getOriginal(), token.identifier, this.#options.rememberMeTokensAge);
|
|
252
|
+
/**
|
|
253
|
+
* Persist remember token inside the cookie
|
|
254
|
+
*/
|
|
103
255
|
this.#createRememberMeCookie(recycledToken.value);
|
|
256
|
+
/**
|
|
257
|
+
* Create session
|
|
258
|
+
*/
|
|
104
259
|
this.#createSessionForUser(providerUser.getId());
|
|
260
|
+
/**
|
|
261
|
+
* Emit event and update local state
|
|
262
|
+
*/
|
|
105
263
|
this.#authenticationSucceeded(sessionId, providerUser.getOriginal(), token);
|
|
106
264
|
return this.user;
|
|
107
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Returns an instance of the authenticated user. Or throws
|
|
268
|
+
* an exception if the request is not authenticated.
|
|
269
|
+
*
|
|
270
|
+
* @throws {E_UNAUTHORIZED_ACCESS} When user is not authenticated
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* const user = guard.getUserOrFail()
|
|
274
|
+
* console.log('User:', user.email)
|
|
275
|
+
*/
|
|
108
276
|
getUserOrFail() {
|
|
109
277
|
if (!this.user) throw new E_UNAUTHORIZED_ACCESS("Invalid or expired user session", { guardDriverName: this.driverName });
|
|
110
278
|
return this.user;
|
|
111
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Login user using sessions. Optionally, you can also create
|
|
282
|
+
* a remember me token to automatically login user when their
|
|
283
|
+
* session expires.
|
|
284
|
+
*
|
|
285
|
+
* @param user - The user to login
|
|
286
|
+
* @param remember - Whether to create a remember me token
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* await guard.login(user, true)
|
|
290
|
+
* console.log('User logged in with remember me token')
|
|
291
|
+
*/
|
|
112
292
|
async login(user, remember = false) {
|
|
113
293
|
const session = this.#getSession();
|
|
114
294
|
const providerUser = await this.#userProvider.createUserForGuard(user);
|
|
@@ -117,39 +297,102 @@ var SessionGuard = class {
|
|
|
117
297
|
user,
|
|
118
298
|
guardName: this.#name
|
|
119
299
|
});
|
|
300
|
+
/**
|
|
301
|
+
* Create remember me token and persist it with the provider
|
|
302
|
+
* when remember me token is true.
|
|
303
|
+
*/
|
|
120
304
|
let token;
|
|
121
305
|
if (remember) {
|
|
122
306
|
if (!this.#options.useRememberMeTokens) throw new RuntimeException("Cannot use \"rememberMe\" feature. It has been disabled");
|
|
123
307
|
token = await this.#userProvider.createRememberToken(providerUser.getOriginal(), this.#options.rememberMeTokensAge);
|
|
124
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Persist remember token inside the cookie (if exists)
|
|
311
|
+
* Otherwise remove the cookie
|
|
312
|
+
*/
|
|
125
313
|
if (token) this.#createRememberMeCookie(token.value);
|
|
126
314
|
else this.#ctx.response.clearCookie(this.rememberMeKeyName);
|
|
315
|
+
/**
|
|
316
|
+
* Create session
|
|
317
|
+
*/
|
|
127
318
|
this.#createSessionForUser(providerUser.getId());
|
|
319
|
+
/**
|
|
320
|
+
* Mark user as logged-in
|
|
321
|
+
*/
|
|
128
322
|
this.#loginSucceeded(session.sessionId, providerUser.getOriginal(), token);
|
|
129
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Logout a user by removing its state from the session
|
|
326
|
+
* store and delete the remember me cookie (if any).
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* await guard.logout()
|
|
330
|
+
* console.log('User logged out successfully')
|
|
331
|
+
*/
|
|
130
332
|
async logout() {
|
|
131
333
|
const session = this.#getSession();
|
|
132
334
|
const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName);
|
|
335
|
+
/**
|
|
336
|
+
* Clear client side state
|
|
337
|
+
*/
|
|
133
338
|
session.forget(this.sessionKeyName);
|
|
134
339
|
this.#ctx.response.clearCookie(this.rememberMeKeyName);
|
|
340
|
+
/**
|
|
341
|
+
* Delete remember me token when
|
|
342
|
+
*
|
|
343
|
+
* - Tokens are enabled
|
|
344
|
+
* - A cookie exists
|
|
345
|
+
* - And we know about the user already
|
|
346
|
+
*/
|
|
135
347
|
if (this.user && rememberMeCookie && this.#options.useRememberMeTokens) {
|
|
348
|
+
/**
|
|
349
|
+
* Here we assume the userProvider has implemented APIs to manage remember
|
|
350
|
+
* me tokens, since the "useRememberMeTokens" flag is enabled.
|
|
351
|
+
*/
|
|
136
352
|
const userProvider = this.#userProvider;
|
|
137
353
|
const token = await userProvider.verifyRememberToken(new Secret(rememberMeCookie));
|
|
138
354
|
if (token) await userProvider.deleteRemeberToken(this.user, token.identifier);
|
|
139
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Notify the user has been logged out
|
|
358
|
+
*/
|
|
140
359
|
this.#emitter.emit("session_auth:logged_out", {
|
|
141
360
|
ctx: this.#ctx,
|
|
142
361
|
guardName: this.#name,
|
|
143
362
|
user: this.user || null,
|
|
144
363
|
sessionId: session.sessionId
|
|
145
364
|
});
|
|
365
|
+
/**
|
|
366
|
+
* Update local state
|
|
367
|
+
*/
|
|
146
368
|
this.user = void 0;
|
|
147
369
|
this.viaRemember = false;
|
|
148
370
|
this.isAuthenticated = false;
|
|
149
371
|
this.isLoggedOut = true;
|
|
150
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Authenticate the current HTTP request by verifying the session
|
|
375
|
+
* or remember me token and fails with an exception if authentication fails
|
|
376
|
+
*
|
|
377
|
+
* @throws {E_UNAUTHORIZED_ACCESS} When authentication fails
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* try {
|
|
381
|
+
* const user = await guard.authenticate()
|
|
382
|
+
* console.log('Authenticated as:', user.email)
|
|
383
|
+
* } catch (error) {
|
|
384
|
+
* console.log('Authentication failed')
|
|
385
|
+
* }
|
|
386
|
+
*/
|
|
151
387
|
async authenticate() {
|
|
388
|
+
/**
|
|
389
|
+
* Return early when authentication has already been
|
|
390
|
+
* attempted
|
|
391
|
+
*/
|
|
152
392
|
if (this.authenticationAttempted) return this.getUserOrFail();
|
|
393
|
+
/**
|
|
394
|
+
* Notify we begin to attempt the authentication
|
|
395
|
+
*/
|
|
153
396
|
this.authenticationAttempted = true;
|
|
154
397
|
const session = this.#getSession();
|
|
155
398
|
this.#emitter.emit("session_auth:authentication_attempted", {
|
|
@@ -157,15 +400,38 @@ var SessionGuard = class {
|
|
|
157
400
|
sessionId: session.sessionId,
|
|
158
401
|
guardName: this.#name
|
|
159
402
|
});
|
|
403
|
+
/**
|
|
404
|
+
* Check if there is a user id inside the session store.
|
|
405
|
+
* If yes, fetch the user from the persistent storage
|
|
406
|
+
* and mark them as logged-in
|
|
407
|
+
*/
|
|
160
408
|
const authUserId = session.get(this.sessionKeyName);
|
|
161
409
|
if (authUserId) return this.#authenticateViaId(authUserId, session.sessionId);
|
|
410
|
+
/**
|
|
411
|
+
* If user provider supports remember me tokens and the remember me
|
|
412
|
+
* cookie exists, then attempt to login + authenticate via
|
|
413
|
+
* the remember me token.
|
|
414
|
+
*/
|
|
162
415
|
const rememberMeCookie = this.#ctx.request.encryptedCookie(this.rememberMeKeyName);
|
|
163
416
|
if (rememberMeCookie && this.#options.useRememberMeTokens) {
|
|
164
417
|
this.attemptedViaRemember = true;
|
|
165
418
|
return this.#authenticateViaRememberCookie(rememberMeCookie, session.sessionId);
|
|
166
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Otherwise throw an exception
|
|
422
|
+
*/
|
|
167
423
|
throw this.#authenticationFailed(session.sessionId);
|
|
168
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Silently check if the user is authenticated or not, without
|
|
427
|
+
* throwing any exceptions
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* const isAuthenticated = await guard.check()
|
|
431
|
+
* if (isAuthenticated) {
|
|
432
|
+
* console.log('User is authenticated:', guard.user.email)
|
|
433
|
+
* }
|
|
434
|
+
*/
|
|
169
435
|
async check() {
|
|
170
436
|
try {
|
|
171
437
|
await this.authenticate();
|
|
@@ -175,14 +441,63 @@ var SessionGuard = class {
|
|
|
175
441
|
throw error;
|
|
176
442
|
}
|
|
177
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Returns the session info for the clients to send during
|
|
446
|
+
* an HTTP request to mark the user as logged-in.
|
|
447
|
+
*
|
|
448
|
+
* @param user - The user to authenticate as
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* const clientAuth = await guard.authenticateAsClient(user)
|
|
452
|
+
* // Use clientAuth.session in API tests
|
|
453
|
+
*/
|
|
178
454
|
async authenticateAsClient(user) {
|
|
179
455
|
const userId = (await this.#userProvider.createUserForGuard(user)).getId();
|
|
180
456
|
return { session: { [this.sessionKeyName]: userId } };
|
|
181
457
|
}
|
|
182
458
|
};
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region modules/session_guard/remember_me_token.ts
|
|
461
|
+
/**
|
|
462
|
+
* Remember me token represents an opaque token that can be
|
|
463
|
+
* used to automatically login a user without asking them
|
|
464
|
+
* to re-login
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* const token = new RememberMeToken({
|
|
468
|
+
* identifier: 1,
|
|
469
|
+
* tokenableId: 123,
|
|
470
|
+
* hash: 'sha256hash',
|
|
471
|
+
* createdAt: new Date(),
|
|
472
|
+
* updatedAt: new Date(),
|
|
473
|
+
* expiresAt: new Date(Date.now() + 86400000)
|
|
474
|
+
* })
|
|
475
|
+
*/
|
|
183
476
|
var RememberMeToken = class {
|
|
477
|
+
/**
|
|
478
|
+
* Decodes a publicly shared token and return the series
|
|
479
|
+
* and the token value from it.
|
|
480
|
+
*
|
|
481
|
+
* Returns null when unable to decode the token because of
|
|
482
|
+
* invalid format or encoding.
|
|
483
|
+
*
|
|
484
|
+
* @param value - The token value to decode
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* const decoded = RememberMeToken.decode('abc123.def456')
|
|
488
|
+
* if (decoded) {
|
|
489
|
+
* console.log('Token ID:', decoded.identifier)
|
|
490
|
+
* console.log('Secret:', decoded.secret.release())
|
|
491
|
+
* }
|
|
492
|
+
*/
|
|
184
493
|
static decode(value) {
|
|
494
|
+
/**
|
|
495
|
+
* Ensure value is a string and starts with the prefix.
|
|
496
|
+
*/
|
|
185
497
|
if (typeof value !== "string") return null;
|
|
498
|
+
/**
|
|
499
|
+
* Remove prefix from the rest of the token.
|
|
500
|
+
*/
|
|
186
501
|
if (!value) return null;
|
|
187
502
|
const [identifier, ...tokenValue] = value.split(".");
|
|
188
503
|
if (!identifier || tokenValue.length === 0) return null;
|
|
@@ -194,6 +509,18 @@ var RememberMeToken = class {
|
|
|
194
509
|
secret: new Secret(decodedSecret)
|
|
195
510
|
};
|
|
196
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Creates a transient token that can be shared with the persistence
|
|
514
|
+
* layer.
|
|
515
|
+
*
|
|
516
|
+
* @param userId - The ID of the user for whom the token is created
|
|
517
|
+
* @param size - The size of the random secret to generate
|
|
518
|
+
* @param expiresIn - Expiration time (seconds or duration string)
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* const transientToken = RememberMeToken.createTransientToken(123, 32, '30d')
|
|
522
|
+
* // Store transientToken in database
|
|
523
|
+
*/
|
|
197
524
|
static createTransientToken(userId, size, expiresIn) {
|
|
198
525
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
199
526
|
expiresAt.setSeconds(expiresAt.getSeconds() + string.seconds.parse(expiresIn));
|
|
@@ -203,6 +530,16 @@ var RememberMeToken = class {
|
|
|
203
530
|
...this.seed(size)
|
|
204
531
|
};
|
|
205
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* Creates a secret opaque token and its hash.
|
|
535
|
+
*
|
|
536
|
+
* @param size - The size of the random string to generate
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* const { secret, hash } = RememberMeToken.seed(32)
|
|
540
|
+
* console.log('Secret:', secret.release())
|
|
541
|
+
* console.log('Hash:', hash)
|
|
542
|
+
*/
|
|
206
543
|
static seed(size) {
|
|
207
544
|
const secret = new Secret(string.random(size));
|
|
208
545
|
return {
|
|
@@ -210,13 +547,54 @@ var RememberMeToken = class {
|
|
|
210
547
|
hash: createHash("sha256").update(secret.release()).digest("hex")
|
|
211
548
|
};
|
|
212
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Identifer is a unique sequence to identify the
|
|
552
|
+
* token within database. It should be the
|
|
553
|
+
* primary/unique key
|
|
554
|
+
*/
|
|
213
555
|
identifier;
|
|
556
|
+
/**
|
|
557
|
+
* Reference to the user id for whom the token
|
|
558
|
+
* is generated.
|
|
559
|
+
*/
|
|
214
560
|
tokenableId;
|
|
561
|
+
/**
|
|
562
|
+
* The value is a public representation of a token. It is created
|
|
563
|
+
* by combining the "identifier"."secret"
|
|
564
|
+
*/
|
|
215
565
|
value;
|
|
566
|
+
/**
|
|
567
|
+
* Hash is computed from the seed to later verify the validity
|
|
568
|
+
* of seed
|
|
569
|
+
*/
|
|
216
570
|
hash;
|
|
571
|
+
/**
|
|
572
|
+
* Date/time when the token instance was created
|
|
573
|
+
*/
|
|
217
574
|
createdAt;
|
|
575
|
+
/**
|
|
576
|
+
* Date/time when the token was updated
|
|
577
|
+
*/
|
|
218
578
|
updatedAt;
|
|
579
|
+
/**
|
|
580
|
+
* Timestamp at which the token will expire
|
|
581
|
+
*/
|
|
219
582
|
expiresAt;
|
|
583
|
+
/**
|
|
584
|
+
* Creates a new RememberMeToken instance
|
|
585
|
+
*
|
|
586
|
+
* @param attributes - Token attributes including identifier, user ID, hash, etc.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* const token = new RememberMeToken({
|
|
590
|
+
* identifier: 1,
|
|
591
|
+
* tokenableId: 123,
|
|
592
|
+
* hash: 'sha256hash',
|
|
593
|
+
* createdAt: new Date(),
|
|
594
|
+
* updatedAt: new Date(),
|
|
595
|
+
* expiresAt: new Date(Date.now() + 86400000)
|
|
596
|
+
* })
|
|
597
|
+
*/
|
|
220
598
|
constructor(attributes) {
|
|
221
599
|
this.identifier = attributes.identifier;
|
|
222
600
|
this.tokenableId = attributes.tokenableId;
|
|
@@ -224,38 +602,132 @@ var RememberMeToken = class {
|
|
|
224
602
|
this.createdAt = attributes.createdAt;
|
|
225
603
|
this.updatedAt = attributes.updatedAt;
|
|
226
604
|
this.expiresAt = attributes.expiresAt;
|
|
605
|
+
/**
|
|
606
|
+
* Compute value when secret is provided
|
|
607
|
+
*/
|
|
227
608
|
if (attributes.secret) this.value = new Secret(`${base64.urlEncode(String(this.identifier))}.${base64.urlEncode(attributes.secret.release())}`);
|
|
228
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* Check if the token has been expired. Verifies
|
|
612
|
+
* the "expiresAt" timestamp with the current
|
|
613
|
+
* date.
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* if (token.isExpired()) {
|
|
617
|
+
* console.log('Remember me token has expired')
|
|
618
|
+
* } else {
|
|
619
|
+
* console.log('Token is still valid')
|
|
620
|
+
* }
|
|
621
|
+
*/
|
|
229
622
|
isExpired() {
|
|
230
623
|
return this.expiresAt < /* @__PURE__ */ new Date();
|
|
231
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Verifies the value of a token against the pre-defined hash
|
|
627
|
+
*
|
|
628
|
+
* @param secret - The secret to verify against the stored hash
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* const isValid = token.verify(new Secret('user-provided-secret'))
|
|
632
|
+
* if (isValid) {
|
|
633
|
+
* console.log('Remember me token is valid')
|
|
634
|
+
* }
|
|
635
|
+
*/
|
|
232
636
|
verify(secret) {
|
|
233
637
|
const newHash = createHash("sha256").update(secret.release()).digest("hex");
|
|
234
638
|
return safeEqual(this.hash, newHash);
|
|
235
639
|
}
|
|
236
640
|
};
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region modules/session_guard/token_providers/db.ts
|
|
643
|
+
/**
|
|
644
|
+
* DbRememberMeTokensProvider uses lucid database service to fetch and
|
|
645
|
+
* persist tokens for a given user.
|
|
646
|
+
*
|
|
647
|
+
* The user must be an instance of the associated user model.
|
|
648
|
+
*
|
|
649
|
+
* @template TokenableModel - The Lucid model that can have remember me tokens
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* const provider = new DbRememberMeTokensProvider({
|
|
653
|
+
* tokenableModel: () => import('#models/user'),
|
|
654
|
+
* table: 'remember_me_tokens',
|
|
655
|
+
* tokenSecretLength: 32
|
|
656
|
+
* })
|
|
657
|
+
*/
|
|
237
658
|
var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
659
|
+
/**
|
|
660
|
+
* Create tokens provider instance for a given Lucid model
|
|
661
|
+
*
|
|
662
|
+
* @param model - The tokenable model factory function
|
|
663
|
+
* @param options - Optional configuration options
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* const provider = DbRememberMeTokensProvider.forModel(
|
|
667
|
+
* () => import('#models/user'),
|
|
668
|
+
* { table: 'user_remember_tokens' }
|
|
669
|
+
* )
|
|
670
|
+
*/
|
|
238
671
|
static forModel(model, options) {
|
|
239
672
|
return new DbRememberMeTokensProvider({
|
|
240
673
|
tokenableModel: model,
|
|
241
674
|
...options || {}
|
|
242
675
|
});
|
|
243
676
|
}
|
|
677
|
+
/**
|
|
678
|
+
* Database table to use for querying remember me tokens
|
|
679
|
+
*/
|
|
244
680
|
table;
|
|
681
|
+
/**
|
|
682
|
+
* The length for the token secret. A secret is a cryptographically
|
|
683
|
+
* secure random string.
|
|
684
|
+
*/
|
|
245
685
|
tokenSecretLength;
|
|
686
|
+
/**
|
|
687
|
+
* Creates a new DbRememberMeTokensProvider instance
|
|
688
|
+
*
|
|
689
|
+
* @param options - Configuration options for the provider
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* const provider = new DbRememberMeTokensProvider({
|
|
693
|
+
* tokenableModel: () => import('#models/user'),
|
|
694
|
+
* table: 'remember_me_tokens',
|
|
695
|
+
* tokenSecretLength: 40
|
|
696
|
+
* })
|
|
697
|
+
*/
|
|
246
698
|
constructor(options) {
|
|
247
699
|
this.options = options;
|
|
248
700
|
this.table = options.table || "remember_me_tokens";
|
|
249
701
|
this.tokenSecretLength = options.tokenSecretLength || 40;
|
|
250
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* Check if value is an object
|
|
705
|
+
*/
|
|
251
706
|
#isObject(value) {
|
|
252
707
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
253
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Ensure the provided user is an instance of the user model and
|
|
711
|
+
* has a primary key
|
|
712
|
+
*/
|
|
254
713
|
#ensureIsPersisted(user) {
|
|
255
714
|
const model = this.options.tokenableModel;
|
|
256
715
|
if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
|
|
257
716
|
if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for managing remember me tokens. The value of column "${model.primaryKey}" is undefined or null`);
|
|
258
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Maps a database row to an instance token instance
|
|
720
|
+
*
|
|
721
|
+
* @param dbRow - The database row containing token data
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* const token = provider.dbRowToRememberMeToken({
|
|
725
|
+
* id: 1,
|
|
726
|
+
* tokenable_id: 123,
|
|
727
|
+
* hash: 'sha256hash',
|
|
728
|
+
* // ... other columns
|
|
729
|
+
* })
|
|
730
|
+
*/
|
|
259
731
|
dbRowToRememberMeToken(dbRow) {
|
|
260
732
|
return new RememberMeToken({
|
|
261
733
|
identifier: dbRow.id,
|
|
@@ -266,14 +738,40 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
|
266
738
|
expiresAt: typeof dbRow.expires_at === "number" ? new Date(dbRow.expires_at) : dbRow.expires_at
|
|
267
739
|
});
|
|
268
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* Returns a query client instance from the parent model
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* const db = await provider.getDb()
|
|
746
|
+
* const tokens = await db.from('remember_me_tokens').select('*')
|
|
747
|
+
*/
|
|
269
748
|
async getDb() {
|
|
270
749
|
const model = this.options.tokenableModel;
|
|
271
750
|
return model.$adapter.query(model).client;
|
|
272
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* Create a token for a user
|
|
754
|
+
*
|
|
755
|
+
* @param user - The user instance to create a token for
|
|
756
|
+
* @param expiresIn - Token expiration time
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* const token = await provider.create(user, '30d')
|
|
760
|
+
* console.log('Remember token:', token.value.release())
|
|
761
|
+
*/
|
|
273
762
|
async create(user, expiresIn) {
|
|
274
763
|
this.#ensureIsPersisted(user);
|
|
275
764
|
const queryClient = await this.getDb();
|
|
765
|
+
/**
|
|
766
|
+
* Creating a transient token. Transient token abstracts
|
|
767
|
+
* the logic of creating a random secure secret and its
|
|
768
|
+
* hash
|
|
769
|
+
*/
|
|
276
770
|
const transientToken = RememberMeToken.createTransientToken(user.$primaryKeyValue, this.tokenSecretLength, expiresIn);
|
|
771
|
+
/**
|
|
772
|
+
* Row to insert inside the database. We expect exactly these
|
|
773
|
+
* columns to exist.
|
|
774
|
+
*/
|
|
277
775
|
const dbRow = {
|
|
278
776
|
tokenable_id: transientToken.userId,
|
|
279
777
|
hash: transientToken.hash,
|
|
@@ -281,9 +779,19 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
|
281
779
|
updated_at: /* @__PURE__ */ new Date(),
|
|
282
780
|
expires_at: transientToken.expiresAt
|
|
283
781
|
};
|
|
782
|
+
/**
|
|
783
|
+
* Insert data to the database.
|
|
784
|
+
*/
|
|
284
785
|
const result = await queryClient.table(this.table).insert(dbRow).returning("id");
|
|
285
786
|
const id = this.#isObject(result[0]) ? result[0].id : result[0];
|
|
787
|
+
/**
|
|
788
|
+
* Throw error when unable to find id in the return value of
|
|
789
|
+
* the insert query
|
|
790
|
+
*/
|
|
286
791
|
if (!id) throw new RuntimeException(`Cannot save access token. The result "${inspect(result)}" of insert query is unexpected`);
|
|
792
|
+
/**
|
|
793
|
+
* Convert db row to a remember token
|
|
794
|
+
*/
|
|
287
795
|
return new RememberMeToken({
|
|
288
796
|
identifier: id,
|
|
289
797
|
tokenableId: dbRow.tokenable_id,
|
|
@@ -294,6 +802,18 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
|
294
802
|
expiresAt: dbRow.expires_at
|
|
295
803
|
});
|
|
296
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Find a token for a user by the token id
|
|
807
|
+
*
|
|
808
|
+
* @param user - The user instance that owns the token
|
|
809
|
+
* @param identifier - The token identifier to search for
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* const token = await provider.find(user, 123)
|
|
813
|
+
* if (token) {
|
|
814
|
+
* console.log('Found token with id:', token.identifier)
|
|
815
|
+
* }
|
|
816
|
+
*/
|
|
297
817
|
async find(user, identifier) {
|
|
298
818
|
this.#ensureIsPersisted(user);
|
|
299
819
|
const dbRow = await (await this.getDb()).query().from(this.table).where({
|
|
@@ -303,6 +823,16 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
|
303
823
|
if (!dbRow) return null;
|
|
304
824
|
return this.dbRowToRememberMeToken(dbRow);
|
|
305
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Delete a token by its id
|
|
828
|
+
*
|
|
829
|
+
* @param user - The user instance that owns the token
|
|
830
|
+
* @param identifier - The token identifier to delete
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* const deletedCount = await provider.delete(user, 123)
|
|
834
|
+
* console.log('Deleted tokens:', deletedCount)
|
|
835
|
+
*/
|
|
306
836
|
async delete(user, identifier) {
|
|
307
837
|
this.#ensureIsPersisted(user);
|
|
308
838
|
return await (await this.getDb()).query().from(this.table).where({
|
|
@@ -310,46 +840,147 @@ var DbRememberMeTokensProvider = class DbRememberMeTokensProvider {
|
|
|
310
840
|
tokenable_id: user.$primaryKeyValue
|
|
311
841
|
}).del().exec();
|
|
312
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Returns all the tokens a given user
|
|
845
|
+
*
|
|
846
|
+
* @param user - The user instance to get tokens for
|
|
847
|
+
*
|
|
848
|
+
* @example
|
|
849
|
+
* const tokens = await provider.all(user)
|
|
850
|
+
* console.log('User has', tokens.length, 'remember tokens')
|
|
851
|
+
* tokens.forEach(token => console.log(token.identifier))
|
|
852
|
+
*/
|
|
313
853
|
async all(user) {
|
|
314
854
|
this.#ensureIsPersisted(user);
|
|
315
855
|
return (await (await this.getDb()).query().from(this.table).where({ tokenable_id: user.$primaryKeyValue }).orderBy("id", "desc").exec()).map((dbRow) => {
|
|
316
856
|
return this.dbRowToRememberMeToken(dbRow);
|
|
317
857
|
});
|
|
318
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Verifies a publicly shared remember me token and returns an
|
|
861
|
+
* RememberMeToken for it.
|
|
862
|
+
*
|
|
863
|
+
* Returns null when unable to verify the token or find it
|
|
864
|
+
* inside the storage
|
|
865
|
+
*
|
|
866
|
+
* @param tokenValue - The token value to verify
|
|
867
|
+
*
|
|
868
|
+
* @example
|
|
869
|
+
* const token = await provider.verify(new Secret('rmt_abc123.def456'))
|
|
870
|
+
* if (token && !token.isExpired()) {
|
|
871
|
+
* console.log('Valid remember token for user:', token.tokenableId)
|
|
872
|
+
* }
|
|
873
|
+
*/
|
|
319
874
|
async verify(tokenValue) {
|
|
320
875
|
const decodedToken = RememberMeToken.decode(tokenValue.release());
|
|
321
876
|
if (!decodedToken) return null;
|
|
322
877
|
const dbRow = await (await this.getDb()).query().from(this.table).where({ id: decodedToken.identifier }).limit(1).first();
|
|
323
878
|
if (!dbRow) return null;
|
|
879
|
+
/**
|
|
880
|
+
* Convert to remember me token instance
|
|
881
|
+
*/
|
|
324
882
|
const rememberMeToken = this.dbRowToRememberMeToken(dbRow);
|
|
883
|
+
/**
|
|
884
|
+
* Ensure the token secret matches the token hash
|
|
885
|
+
*/
|
|
325
886
|
if (!rememberMeToken.verify(decodedToken.secret) || rememberMeToken.isExpired()) return null;
|
|
326
887
|
return rememberMeToken;
|
|
327
888
|
}
|
|
889
|
+
/**
|
|
890
|
+
* Recycles a remember me token by deleting the old one and
|
|
891
|
+
* creates a new one.
|
|
892
|
+
*
|
|
893
|
+
* Ideally, the recycle should update the existing token, but we
|
|
894
|
+
* skip that for now and come back to it later and handle race
|
|
895
|
+
* conditions as well.
|
|
896
|
+
*
|
|
897
|
+
* @param user - The user that owns the token
|
|
898
|
+
* @param identifier - The token identifier to recycle
|
|
899
|
+
* @param expiresIn - New expiration time
|
|
900
|
+
*
|
|
901
|
+
* @example
|
|
902
|
+
* const newToken = await provider.recycle(user, 123, '30d')
|
|
903
|
+
* console.log('Recycled token:', newToken.value.release())
|
|
904
|
+
*/
|
|
328
905
|
async recycle(user, identifier, expiresIn) {
|
|
329
906
|
await this.delete(user, identifier);
|
|
330
907
|
return this.create(user, expiresIn);
|
|
331
908
|
}
|
|
332
909
|
};
|
|
910
|
+
//#endregion
|
|
911
|
+
//#region modules/session_guard/user_providers/lucid.ts
|
|
912
|
+
/**
|
|
913
|
+
* Uses a lucid model to verify access tokens and find a user during
|
|
914
|
+
* authentication
|
|
915
|
+
*
|
|
916
|
+
* @template UserModel - The Lucid model representing the user
|
|
917
|
+
*
|
|
918
|
+
* @example
|
|
919
|
+
* const userProvider = new SessionLucidUserProvider({
|
|
920
|
+
* model: () => import('#models/user')
|
|
921
|
+
* })
|
|
922
|
+
*/
|
|
333
923
|
var SessionLucidUserProvider = class {
|
|
924
|
+
/**
|
|
925
|
+
* Reference to the lazily imported model
|
|
926
|
+
*/
|
|
334
927
|
model;
|
|
928
|
+
/**
|
|
929
|
+
* Creates a new SessionLucidUserProvider instance
|
|
930
|
+
*
|
|
931
|
+
* @param options - Configuration options for the user provider
|
|
932
|
+
*
|
|
933
|
+
* @example
|
|
934
|
+
* const provider = new SessionLucidUserProvider({
|
|
935
|
+
* model: () => import('#models/user')
|
|
936
|
+
* })
|
|
937
|
+
*/
|
|
335
938
|
constructor(options) {
|
|
336
939
|
this.options = options;
|
|
337
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Imports the model from the provider, returns and caches it
|
|
943
|
+
* for further operations.
|
|
944
|
+
*
|
|
945
|
+
* @example
|
|
946
|
+
* const UserModel = await provider.getModel()
|
|
947
|
+
* const user = await UserModel.find(1)
|
|
948
|
+
*/
|
|
338
949
|
async getModel() {
|
|
339
950
|
if (this.model && !("hot" in import.meta)) return this.model;
|
|
340
951
|
this.model = (await this.options.model()).default;
|
|
341
952
|
return this.model;
|
|
342
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Returns the tokens provider associated with the user model
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* const tokensProvider = await provider.getTokensProvider()
|
|
959
|
+
* const token = await tokensProvider.create(user, '7d')
|
|
960
|
+
*/
|
|
343
961
|
async getTokensProvider() {
|
|
344
962
|
const model = await this.getModel();
|
|
345
963
|
if (!model.rememberMeTokens) throw new RuntimeException(`Cannot use "${model.name}" model for verifying remember me tokens. Make sure to assign a token provider to the model.`);
|
|
346
964
|
return model.rememberMeTokens;
|
|
347
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* Creates an adapter user for the guard
|
|
968
|
+
*
|
|
969
|
+
* @param user - The user model instance
|
|
970
|
+
*
|
|
971
|
+
* @example
|
|
972
|
+
* const guardUser = await provider.createUserForGuard(user)
|
|
973
|
+
* console.log('User ID:', guardUser.getId())
|
|
974
|
+
* console.log('Original user:', guardUser.getOriginal())
|
|
975
|
+
*/
|
|
348
976
|
async createUserForGuard(user) {
|
|
349
977
|
const model = await this.getModel();
|
|
350
978
|
if (user instanceof model === false) throw new RuntimeException(`Invalid user object. It must be an instance of the "${model.name}" model`);
|
|
351
979
|
return {
|
|
352
980
|
getId() {
|
|
981
|
+
/**
|
|
982
|
+
* Ensure user has a primary key
|
|
983
|
+
*/
|
|
353
984
|
if (!user.$primaryKeyValue) throw new RuntimeException(`Cannot use "${model.name}" model for authentication. The value of column "${model.primaryKey}" is undefined or null`);
|
|
354
985
|
return user.$primaryKeyValue;
|
|
355
986
|
},
|
|
@@ -358,24 +989,95 @@ var SessionLucidUserProvider = class {
|
|
|
358
989
|
}
|
|
359
990
|
};
|
|
360
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* Finds a user by their primary key value
|
|
994
|
+
*
|
|
995
|
+
* @param identifier - The user identifier to search for
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* const guardUser = await provider.findById(123)
|
|
999
|
+
* if (guardUser) {
|
|
1000
|
+
* const originalUser = guardUser.getOriginal()
|
|
1001
|
+
* console.log('Found user:', originalUser.email)
|
|
1002
|
+
* }
|
|
1003
|
+
*/
|
|
361
1004
|
async findById(identifier) {
|
|
362
1005
|
const user = await (await this.getModel()).find(identifier);
|
|
363
1006
|
if (!user) return null;
|
|
364
1007
|
return this.createUserForGuard(user);
|
|
365
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Creates a remember token for a given user
|
|
1011
|
+
*
|
|
1012
|
+
* @param user - The user to create a token for
|
|
1013
|
+
* @param expiresIn - Token expiration time
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
* const token = await provider.createRememberToken(user, '30d')
|
|
1017
|
+
* console.log('Remember token:', token.value.release())
|
|
1018
|
+
*/
|
|
366
1019
|
async createRememberToken(user, expiresIn) {
|
|
367
1020
|
return (await this.getTokensProvider()).create(user, expiresIn);
|
|
368
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Verify a token by its publicly shared value
|
|
1024
|
+
*
|
|
1025
|
+
* @param tokenValue - The token value to verify
|
|
1026
|
+
*
|
|
1027
|
+
* @example
|
|
1028
|
+
* const token = await provider.verifyRememberToken(
|
|
1029
|
+
* new Secret('rmt_abc123.def456')
|
|
1030
|
+
* )
|
|
1031
|
+
* if (token && !token.isExpired()) {
|
|
1032
|
+
* console.log('Valid remember token for user:', token.tokenableId)
|
|
1033
|
+
* }
|
|
1034
|
+
*/
|
|
369
1035
|
async verifyRememberToken(tokenValue) {
|
|
370
1036
|
return (await this.getTokensProvider()).verify(tokenValue);
|
|
371
1037
|
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Delete a token for a user by the token identifier
|
|
1040
|
+
*
|
|
1041
|
+
* @param user - The user that owns the token
|
|
1042
|
+
* @param identifier - The token identifier to delete
|
|
1043
|
+
*
|
|
1044
|
+
* @example
|
|
1045
|
+
* const deletedCount = await provider.deleteRemeberToken(user, 123)
|
|
1046
|
+
* console.log('Deleted tokens:', deletedCount)
|
|
1047
|
+
*/
|
|
372
1048
|
async deleteRemeberToken(user, identifier) {
|
|
373
1049
|
return (await this.getTokensProvider()).delete(user, identifier);
|
|
374
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Recycle a token for a user by the token identifier
|
|
1053
|
+
*
|
|
1054
|
+
* @param user - The user that owns the token
|
|
1055
|
+
* @param identifier - The token identifier to recycle
|
|
1056
|
+
* @param expiresIn - New expiration time
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* const newToken = await provider.recycleRememberToken(user, 123, '30d')
|
|
1060
|
+
* console.log('Recycled token:', newToken.value.release())
|
|
1061
|
+
*/
|
|
375
1062
|
async recycleRememberToken(user, identifier, expiresIn) {
|
|
376
1063
|
return (await this.getTokensProvider()).recycle(user, identifier, expiresIn);
|
|
377
1064
|
}
|
|
378
1065
|
};
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region modules/session_guard/define_config.ts
|
|
1068
|
+
/**
|
|
1069
|
+
* Configures session guard for authentication using HTTP sessions
|
|
1070
|
+
*
|
|
1071
|
+
* @param config - Configuration object containing the user provider and session options
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* const guard = sessionGuard({
|
|
1075
|
+
* useRememberMeTokens: false,
|
|
1076
|
+
* provider: sessionUserProvider({
|
|
1077
|
+
* model: () => import('#models/user')
|
|
1078
|
+
* })
|
|
1079
|
+
* })
|
|
1080
|
+
*/
|
|
379
1081
|
function sessionGuard(config) {
|
|
380
1082
|
return { async resolver(name, app) {
|
|
381
1083
|
const emitter = await app.container.make("emitter");
|
|
@@ -383,7 +1085,19 @@ function sessionGuard(config) {
|
|
|
383
1085
|
return (ctx) => new SessionGuard(name, ctx, config, emitter, provider);
|
|
384
1086
|
} };
|
|
385
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Configures user provider that uses Lucid models to authenticate
|
|
1090
|
+
* users using sessions
|
|
1091
|
+
*
|
|
1092
|
+
* @param config - Configuration options for the Lucid user provider
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* const userProvider = sessionUserProvider({
|
|
1096
|
+
* model: () => import('#models/user')
|
|
1097
|
+
* })
|
|
1098
|
+
*/
|
|
386
1099
|
function sessionUserProvider(config) {
|
|
387
1100
|
return new SessionLucidUserProvider(config);
|
|
388
1101
|
}
|
|
1102
|
+
//#endregion
|
|
389
1103
|
export { DbRememberMeTokensProvider, RememberMeToken, SessionGuard, SessionLucidUserProvider, sessionGuard, sessionUserProvider };
|