wabe 0.6.9 → 0.6.10

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.
Files changed (158) hide show
  1. package/README.md +138 -32
  2. package/bucket/b.txt +1 -0
  3. package/dev/index.ts +215 -0
  4. package/dist/authentication/Session.d.ts +4 -1
  5. package/dist/authentication/interface.d.ts +16 -0
  6. package/dist/email/interface.d.ts +1 -1
  7. package/dist/graphql/resolvers.d.ts +4 -2
  8. package/dist/hooks/index.d.ts +1 -0
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +8713 -8867
  11. package/dist/server/index.d.ts +4 -2
  12. package/dist/utils/crypto.d.ts +7 -0
  13. package/dist/utils/helper.d.ts +4 -1
  14. package/generated/schema.graphql +16 -14
  15. package/generated/wabe.ts +4 -4
  16. package/package.json +15 -15
  17. package/src/authentication/OTP.test.ts +69 -0
  18. package/src/authentication/OTP.ts +66 -0
  19. package/src/authentication/Session.test.ts +665 -0
  20. package/src/authentication/Session.ts +529 -0
  21. package/src/authentication/defaultAuthentication.ts +214 -0
  22. package/src/authentication/index.ts +3 -0
  23. package/src/authentication/interface.ts +157 -0
  24. package/src/authentication/oauth/GitHub.test.ts +105 -0
  25. package/src/authentication/oauth/GitHub.ts +133 -0
  26. package/src/authentication/oauth/Google.test.ts +105 -0
  27. package/src/authentication/oauth/Google.ts +110 -0
  28. package/src/authentication/oauth/Oauth2Client.test.ts +225 -0
  29. package/src/authentication/oauth/Oauth2Client.ts +140 -0
  30. package/src/authentication/oauth/index.ts +2 -0
  31. package/src/authentication/oauth/utils.test.ts +35 -0
  32. package/src/authentication/oauth/utils.ts +28 -0
  33. package/src/authentication/providers/EmailOTP.test.ts +138 -0
  34. package/src/authentication/providers/EmailOTP.ts +93 -0
  35. package/src/authentication/providers/EmailPassword.test.ts +187 -0
  36. package/src/authentication/providers/EmailPassword.ts +130 -0
  37. package/src/authentication/providers/EmailPasswordSRP.test.ts +206 -0
  38. package/src/authentication/providers/EmailPasswordSRP.ts +184 -0
  39. package/src/authentication/providers/GitHub.ts +30 -0
  40. package/src/authentication/providers/Google.ts +30 -0
  41. package/src/authentication/providers/OAuth.test.ts +185 -0
  42. package/src/authentication/providers/OAuth.ts +112 -0
  43. package/src/authentication/providers/PhonePassword.test.ts +187 -0
  44. package/src/authentication/providers/PhonePassword.ts +129 -0
  45. package/src/authentication/providers/QRCodeOTP.test.ts +79 -0
  46. package/src/authentication/providers/QRCodeOTP.ts +65 -0
  47. package/src/authentication/providers/index.ts +6 -0
  48. package/src/authentication/resolvers/refreshResolver.test.ts +37 -0
  49. package/src/authentication/resolvers/refreshResolver.ts +20 -0
  50. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
  51. package/src/authentication/resolvers/signInWithResolver.test.ts +307 -0
  52. package/src/authentication/resolvers/signInWithResolver.ts +102 -0
  53. package/src/authentication/resolvers/signOutResolver.test.ts +41 -0
  54. package/src/authentication/resolvers/signOutResolver.ts +22 -0
  55. package/src/authentication/resolvers/signUpWithResolver.test.ts +186 -0
  56. package/src/authentication/resolvers/signUpWithResolver.ts +69 -0
  57. package/src/authentication/resolvers/verifyChallenge.test.ts +136 -0
  58. package/src/authentication/resolvers/verifyChallenge.ts +69 -0
  59. package/src/authentication/roles.test.ts +59 -0
  60. package/src/authentication/roles.ts +40 -0
  61. package/src/authentication/utils.test.ts +99 -0
  62. package/src/authentication/utils.ts +43 -0
  63. package/src/cache/InMemoryCache.test.ts +62 -0
  64. package/src/cache/InMemoryCache.ts +45 -0
  65. package/src/cron/index.test.ts +17 -0
  66. package/src/cron/index.ts +46 -0
  67. package/src/database/DatabaseController.test.ts +625 -0
  68. package/src/database/DatabaseController.ts +983 -0
  69. package/src/database/index.test.ts +1230 -0
  70. package/src/database/index.ts +9 -0
  71. package/src/database/interface.ts +312 -0
  72. package/src/email/DevAdapter.ts +8 -0
  73. package/src/email/EmailController.test.ts +29 -0
  74. package/src/email/EmailController.ts +13 -0
  75. package/src/email/index.ts +2 -0
  76. package/src/email/interface.ts +36 -0
  77. package/src/email/templates/sendOtpCode.ts +120 -0
  78. package/src/file/FileController.ts +28 -0
  79. package/src/file/FileDevAdapter.ts +54 -0
  80. package/src/file/hookDeleteFile.ts +27 -0
  81. package/src/file/hookReadFile.ts +70 -0
  82. package/src/file/hookUploadFile.ts +53 -0
  83. package/src/file/index.test.ts +979 -0
  84. package/src/file/index.ts +2 -0
  85. package/src/file/interface.ts +42 -0
  86. package/src/graphql/GraphQLSchema.test.ts +4399 -0
  87. package/src/graphql/GraphQLSchema.ts +928 -0
  88. package/src/graphql/index.ts +2 -0
  89. package/src/graphql/parseGraphqlSchema.ts +94 -0
  90. package/src/graphql/parser.test.ts +217 -0
  91. package/src/graphql/parser.ts +566 -0
  92. package/src/graphql/pointerAndRelationFunction.ts +200 -0
  93. package/src/graphql/resolvers.ts +467 -0
  94. package/src/graphql/tests/aggregation.test.ts +1123 -0
  95. package/src/graphql/tests/e2e.test.ts +596 -0
  96. package/src/graphql/tests/scalars.test.ts +250 -0
  97. package/src/graphql/types.ts +219 -0
  98. package/src/hooks/HookObject.test.ts +122 -0
  99. package/src/hooks/HookObject.ts +168 -0
  100. package/src/hooks/authentication.ts +76 -0
  101. package/src/hooks/createUser.test.ts +77 -0
  102. package/src/hooks/createUser.ts +10 -0
  103. package/src/hooks/defaultFields.test.ts +187 -0
  104. package/src/hooks/defaultFields.ts +40 -0
  105. package/src/hooks/deleteSession.test.ts +181 -0
  106. package/src/hooks/deleteSession.ts +20 -0
  107. package/src/hooks/hashFieldHook.test.ts +163 -0
  108. package/src/hooks/hashFieldHook.ts +97 -0
  109. package/src/hooks/index.test.ts +207 -0
  110. package/src/hooks/index.ts +430 -0
  111. package/src/hooks/permissions.test.ts +424 -0
  112. package/src/hooks/permissions.ts +113 -0
  113. package/src/hooks/protected.test.ts +551 -0
  114. package/src/hooks/protected.ts +72 -0
  115. package/src/hooks/searchableFields.test.ts +166 -0
  116. package/src/hooks/searchableFields.ts +98 -0
  117. package/src/hooks/session.test.ts +138 -0
  118. package/src/hooks/session.ts +78 -0
  119. package/src/hooks/setEmail.test.ts +216 -0
  120. package/src/hooks/setEmail.ts +35 -0
  121. package/src/hooks/setupAcl.test.ts +589 -0
  122. package/src/hooks/setupAcl.ts +29 -0
  123. package/src/index.ts +9 -0
  124. package/src/schema/Schema.test.ts +484 -0
  125. package/src/schema/Schema.ts +795 -0
  126. package/src/schema/defaultResolvers.ts +94 -0
  127. package/src/schema/index.ts +1 -0
  128. package/src/schema/resolvers/meResolver.test.ts +62 -0
  129. package/src/schema/resolvers/meResolver.ts +14 -0
  130. package/src/schema/resolvers/newFile.ts +0 -0
  131. package/src/schema/resolvers/resetPassword.test.ts +345 -0
  132. package/src/schema/resolvers/resetPassword.ts +64 -0
  133. package/src/schema/resolvers/sendEmail.test.ts +118 -0
  134. package/src/schema/resolvers/sendEmail.ts +21 -0
  135. package/src/schema/resolvers/sendOtpCode.test.ts +153 -0
  136. package/src/schema/resolvers/sendOtpCode.ts +52 -0
  137. package/src/security.test.ts +3461 -0
  138. package/src/server/defaultSessionHandler.test.ts +66 -0
  139. package/src/server/defaultSessionHandler.ts +115 -0
  140. package/src/server/generateCodegen.ts +476 -0
  141. package/src/server/index.test.ts +552 -0
  142. package/src/server/index.ts +354 -0
  143. package/src/server/interface.ts +11 -0
  144. package/src/server/routes/authHandler.ts +187 -0
  145. package/src/server/routes/index.ts +40 -0
  146. package/src/utils/crypto.test.ts +41 -0
  147. package/src/utils/crypto.ts +121 -0
  148. package/src/utils/export.ts +13 -0
  149. package/src/utils/helper.ts +195 -0
  150. package/src/utils/index.test.ts +11 -0
  151. package/src/utils/index.ts +201 -0
  152. package/src/utils/preload.ts +8 -0
  153. package/src/utils/testHelper.ts +117 -0
  154. package/tsconfig.json +32 -0
  155. package/bunfig.toml +0 -4
  156. package/dist/ai/index.d.ts +0 -1
  157. package/dist/ai/interface.d.ts +0 -9
  158. /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
@@ -0,0 +1,529 @@
1
+ import jwt, { verify, type SignOptions } from 'jsonwebtoken'
2
+ import crypto from 'node:crypto'
3
+ import type { WabeContext } from '../server/interface'
4
+ import type { User } from '../../generated/wabe'
5
+ import type { WabeConfig } from '../server'
6
+ import { contextWithRoot } from '../utils/export'
7
+ import type { DevWabeTypes } from '../utils/helper'
8
+ import {
9
+ encryptDeterministicToken,
10
+ decryptDeterministicToken,
11
+ } from '../utils/crypto'
12
+
13
+ const getJwtSecret = (context: WabeContext<DevWabeTypes>): string => {
14
+ const secret = context.wabe.config.authentication?.session?.jwtSecret
15
+ if (!secret) throw new Error('Authentication session requires jwtSecret')
16
+ return secret
17
+ }
18
+
19
+ const safeVerify = (
20
+ token: string,
21
+ secret: string,
22
+ options: Pick<SignOptions, 'audience' | 'issuer'> = {},
23
+ ) => {
24
+ try {
25
+ return !!verify(token, secret, options)
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ const getTokenSecret = (context: WabeContext<DevWabeTypes>): string =>
32
+ context.wabe.config.authentication?.session?.tokenSecret ??
33
+ getJwtSecret(context)
34
+
35
+ const getTokenEncryptionKey = (context: WabeContext<DevWabeTypes>) =>
36
+ crypto.createHash('sha256').update(getTokenSecret(context)).digest()
37
+
38
+ const getJwtVerifyOptions = (context: WabeContext<DevWabeTypes>) => {
39
+ const opts: Pick<SignOptions, 'audience' | 'issuer'> = {}
40
+ const audience = context.wabe.config.authentication?.session?.jwtAudience
41
+ const issuer = context.wabe.config.authentication?.session?.jwtIssuer
42
+ if (audience) opts.audience = audience
43
+ if (issuer) opts.issuer = issuer
44
+ return opts
45
+ }
46
+
47
+ export class Session {
48
+ private accessToken: string | undefined = undefined
49
+ private refreshToken: string | undefined = undefined
50
+
51
+ getAccessTokenExpireAt(config: WabeConfig<DevWabeTypes>) {
52
+ const customExpiresInMs =
53
+ config?.authentication?.session?.accessTokenExpiresInMs
54
+
55
+ if (!customExpiresInMs) return new Date(Date.now() + 1000 * 60 * 15) // 15 minutes in ms
56
+
57
+ return new Date(Date.now() + customExpiresInMs)
58
+ }
59
+
60
+ _getRefreshTokenExpiresInMs(config: WabeConfig<DevWabeTypes>) {
61
+ const customExpiresInMs =
62
+ config?.authentication?.session?.refreshTokenExpiresInMs
63
+
64
+ if (!customExpiresInMs) return 1000 * 60 * 60 * 24 * 7 // 7 days in ms
65
+
66
+ return customExpiresInMs
67
+ }
68
+
69
+ getRefreshTokenExpireAt(config: WabeConfig<DevWabeTypes>) {
70
+ const expiresInMs = this._getRefreshTokenExpiresInMs(config)
71
+
72
+ return new Date(Date.now() + expiresInMs)
73
+ }
74
+
75
+ async meFromAccessToken(
76
+ { accessToken, csrfToken }: { accessToken: string; csrfToken: string },
77
+ context: WabeContext<DevWabeTypes>,
78
+ ): Promise<{
79
+ sessionId: string | null
80
+ user: User | null
81
+ accessToken: string | null
82
+ refreshToken?: string | null
83
+ }> {
84
+ const verifyOptions = getJwtVerifyOptions(context)
85
+ if (!safeVerify(accessToken, getJwtSecret(context), verifyOptions)) {
86
+ return {
87
+ sessionId: null,
88
+ user: null,
89
+ accessToken: null,
90
+ refreshToken: null,
91
+ }
92
+ }
93
+
94
+ const encryptedAccessToken = encryptDeterministicToken(
95
+ accessToken,
96
+ getTokenEncryptionKey(context),
97
+ )
98
+
99
+ const sessions = await context.wabe.controllers.database.getObjects({
100
+ className: '_Session',
101
+ where: {
102
+ accessTokenEncrypted: { equalTo: encryptedAccessToken },
103
+ OR: [
104
+ {
105
+ accessTokenExpiresAt: {
106
+ greaterThanOrEqualTo: new Date(),
107
+ },
108
+ },
109
+ {
110
+ refreshTokenExpiresAt: {
111
+ greaterThanOrEqualTo: new Date(),
112
+ },
113
+ },
114
+ ],
115
+ },
116
+ select: {
117
+ id: true,
118
+ user: true,
119
+ accessTokenExpiresAt: true,
120
+ refreshTokenExpiresAt: true,
121
+ refreshTokenEncrypted: true,
122
+ },
123
+ first: 1,
124
+ context,
125
+ })
126
+
127
+ if (sessions.length === 0)
128
+ return {
129
+ sessionId: null,
130
+ user: null,
131
+ accessToken: null,
132
+ refreshToken: null,
133
+ }
134
+
135
+ const session = sessions[0]
136
+
137
+ if (!session || !session?.user)
138
+ return {
139
+ sessionId: null,
140
+ user: null,
141
+ accessToken: null,
142
+ refreshToken: null,
143
+ }
144
+
145
+ // CSRF check only for cookie-based sessions (enabled by default unless explicitly disabled)
146
+ if (
147
+ context.wabe.config.authentication?.session?.cookieSession &&
148
+ context.wabe.config.security?.disableCSRFProtection !== true
149
+ ) {
150
+ const [receivedHmacHex, receivedRandomValue] = csrfToken.split('.')
151
+
152
+ if (!receivedHmacHex || !receivedRandomValue)
153
+ return {
154
+ sessionId: null,
155
+ user: null,
156
+ accessToken: null,
157
+ refreshToken: null,
158
+ }
159
+
160
+ const currentSessionId = session.id
161
+
162
+ const message = `${currentSessionId.length}!${currentSessionId}!${receivedRandomValue?.length}!${receivedRandomValue}`
163
+
164
+ const csrfSecret =
165
+ context.wabe.config.authentication?.session?.csrfSecret ||
166
+ getJwtSecret(context)
167
+
168
+ const expectedHmac = crypto
169
+ .createHmac('sha256', csrfSecret)
170
+ .update(message)
171
+ .digest('hex')
172
+
173
+ const isValid = crypto.timingSafeEqual(
174
+ Buffer.from(receivedHmacHex || '', 'hex'),
175
+ Buffer.from(expectedHmac, 'hex'),
176
+ )
177
+
178
+ if (!isValid)
179
+ return {
180
+ sessionId: null,
181
+ user: null,
182
+ accessToken: null,
183
+ refreshToken: null,
184
+ }
185
+ }
186
+
187
+ // User check
188
+
189
+ const user = session.user
190
+
191
+ const userWithRole = await context.wabe.controllers.database.getObject({
192
+ className: 'User',
193
+ select: {
194
+ role: true,
195
+ },
196
+ context,
197
+ id: user.id,
198
+ })
199
+
200
+ // If access token is expired and refresh token is not expired
201
+ if (
202
+ new Date(session.accessTokenExpiresAt) < new Date() &&
203
+ new Date(session.refreshTokenExpiresAt) >= new Date() &&
204
+ session.refreshTokenEncrypted
205
+ ) {
206
+ const decryptedRefreshToken = decryptDeterministicToken(
207
+ session.refreshTokenEncrypted as string,
208
+ getTokenEncryptionKey(context),
209
+ )
210
+
211
+ if (!decryptedRefreshToken)
212
+ return {
213
+ sessionId: null,
214
+ user: null,
215
+ accessToken: null,
216
+ refreshToken: null,
217
+ }
218
+
219
+ const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
220
+ await this.refresh(accessToken, decryptedRefreshToken, context)
221
+
222
+ return {
223
+ sessionId: session.id,
224
+ user: {
225
+ ...user,
226
+ role: userWithRole?.role,
227
+ },
228
+ accessToken: newAccessToken,
229
+ refreshToken: newRefreshToken,
230
+ }
231
+ }
232
+
233
+ return {
234
+ sessionId: session.id,
235
+ user: {
236
+ ...user,
237
+ role: userWithRole?.role,
238
+ },
239
+ accessToken,
240
+ refreshToken: decryptDeterministicToken(
241
+ session.refreshTokenEncrypted as string,
242
+ getTokenEncryptionKey(context),
243
+ ),
244
+ }
245
+ }
246
+
247
+ async create(userId: string, context: WabeContext<DevWabeTypes>) {
248
+ const jwtTokenFields =
249
+ context.wabe.config.authentication?.session?.jwtTokenFields
250
+
251
+ const nowSeconds = Math.floor(Date.now() / 1000)
252
+
253
+ const result = jwtTokenFields
254
+ ? await context.wabe.controllers.database.getObject({
255
+ className: 'User',
256
+ select: jwtTokenFields,
257
+ context,
258
+ id: userId,
259
+ })
260
+ : undefined
261
+
262
+ const secretKey = getJwtSecret(context)
263
+
264
+ const signOptions: SignOptions = { jwtid: crypto.randomUUID() }
265
+ const audience = context.wabe.config.authentication?.session?.jwtAudience
266
+ const issuer = context.wabe.config.authentication?.session?.jwtIssuer
267
+ if (audience) signOptions.audience = audience
268
+ if (issuer) signOptions.issuer = issuer
269
+
270
+ this.accessToken = jwt.sign(
271
+ {
272
+ userId,
273
+ user: result,
274
+ iat: nowSeconds,
275
+ exp: Math.floor(
276
+ this.getAccessTokenExpireAt(context.wabe.config).getTime() / 1000,
277
+ ),
278
+ },
279
+ secretKey,
280
+ signOptions,
281
+ )
282
+
283
+ this.refreshToken = jwt.sign(
284
+ {
285
+ userId,
286
+ user: result,
287
+ iat: nowSeconds,
288
+ exp: Math.floor(
289
+ this.getRefreshTokenExpireAt(context.wabe.config).getTime() / 1000,
290
+ ),
291
+ },
292
+ secretKey,
293
+ signOptions,
294
+ )
295
+
296
+ const accessTokenEncrypted = encryptDeterministicToken(
297
+ this.accessToken,
298
+ getTokenEncryptionKey(context),
299
+ )
300
+ const refreshTokenEncrypted = encryptDeterministicToken(
301
+ this.refreshToken,
302
+ getTokenEncryptionKey(context),
303
+ )
304
+
305
+ const res = await context.wabe.controllers.database.createObject({
306
+ className: '_Session',
307
+ context: contextWithRoot(context),
308
+ data: {
309
+ accessTokenEncrypted,
310
+ accessTokenExpiresAt: this.getAccessTokenExpireAt(context.wabe.config),
311
+ refreshTokenEncrypted,
312
+ refreshTokenExpiresAt: this.getRefreshTokenExpireAt(
313
+ context.wabe.config,
314
+ ),
315
+ user: userId,
316
+ },
317
+ select: { id: true },
318
+ })
319
+
320
+ if (!res) throw new Error('Session not created')
321
+
322
+ const sessionId = res.id
323
+ const randomValue = crypto.randomBytes(16).toString('hex')
324
+ const message = `${sessionId.length}!${sessionId}!${randomValue.length}!${randomValue}`
325
+
326
+ const csrfSecret =
327
+ context.wabe.config.authentication?.session?.csrfSecret || secretKey
328
+
329
+ const hmac = crypto
330
+ .createHmac('sha256', csrfSecret)
331
+ .update(message)
332
+ .digest('hex')
333
+
334
+ const csrfToken = `${hmac}.${randomValue}`
335
+
336
+ return {
337
+ accessToken: this.accessToken,
338
+ refreshToken: this.refreshToken,
339
+ csrfToken,
340
+ sessionId: res.id,
341
+ }
342
+ }
343
+
344
+ async refresh(
345
+ accessToken: string,
346
+ refreshToken: string,
347
+ context: WabeContext<DevWabeTypes>,
348
+ ) {
349
+ const secretKey = getJwtSecret(context)
350
+
351
+ const verifyOptions = getJwtVerifyOptions(context)
352
+
353
+ if (!safeVerify(accessToken, secretKey, verifyOptions))
354
+ return {
355
+ accessToken: null,
356
+ refreshToken: null,
357
+ }
358
+
359
+ if (!safeVerify(refreshToken, secretKey, verifyOptions))
360
+ return {
361
+ accessToken: null,
362
+ refreshToken: null,
363
+ }
364
+
365
+ const accessTokenEncrypted = encryptDeterministicToken(
366
+ accessToken,
367
+ getTokenEncryptionKey(context),
368
+ )
369
+ const incomingRefreshTokenEncrypted = encryptDeterministicToken(
370
+ refreshToken,
371
+ getTokenEncryptionKey(context),
372
+ )
373
+
374
+ const session = await context.wabe.controllers.database.getObjects({
375
+ className: '_Session',
376
+ where: {
377
+ accessTokenEncrypted: { equalTo: accessTokenEncrypted },
378
+ refreshTokenEncrypted: { equalTo: incomingRefreshTokenEncrypted },
379
+ },
380
+ select: {
381
+ id: true,
382
+ user: {
383
+ id: true,
384
+ role: {
385
+ id: true,
386
+ name: true,
387
+ },
388
+ },
389
+ refreshTokenEncrypted: true,
390
+ refreshTokenExpiresAt: true,
391
+ },
392
+ context: contextWithRoot(context),
393
+ })
394
+
395
+ if (!session.length)
396
+ return {
397
+ accessToken: null,
398
+ refreshToken: null,
399
+ }
400
+
401
+ if (!session[0]) throw new Error('Session not found')
402
+
403
+ const {
404
+ refreshTokenExpiresAt,
405
+ user,
406
+ refreshTokenEncrypted: storedRefreshTokenEncrypted,
407
+ id,
408
+ } = session[0]
409
+
410
+ if (new Date(refreshTokenExpiresAt) < new Date(Date.now()))
411
+ throw new Error('Refresh token expired')
412
+
413
+ const decryptedRefreshToken =
414
+ decryptDeterministicToken(
415
+ storedRefreshTokenEncrypted,
416
+ getTokenEncryptionKey(context),
417
+ ) || refreshToken
418
+
419
+ if (!decryptedRefreshToken || decryptedRefreshToken !== refreshToken)
420
+ throw new Error('Invalid refresh token')
421
+
422
+ // Always rotate tokens on refresh
423
+ const userId = user?.id
424
+
425
+ if (!userId)
426
+ return {
427
+ accessToken: null,
428
+ refreshToken: null,
429
+ }
430
+
431
+ const jwtTokenFields =
432
+ context.wabe.config.authentication?.session?.jwtTokenFields
433
+
434
+ const result = jwtTokenFields
435
+ ? await context.wabe.controllers.database.getObject({
436
+ className: 'User',
437
+ select: jwtTokenFields,
438
+ context,
439
+ id: userId,
440
+ })
441
+ : undefined
442
+
443
+ const nowSeconds = Math.floor(Date.now() / 1000)
444
+
445
+ const signOptions: SignOptions = { jwtid: crypto.randomUUID() }
446
+ const audience = context.wabe.config.authentication?.session?.jwtAudience
447
+ const issuer = context.wabe.config.authentication?.session?.jwtIssuer
448
+ if (audience) signOptions.audience = audience
449
+ if (issuer) signOptions.issuer = issuer
450
+
451
+ const newAccessToken = jwt.sign(
452
+ {
453
+ userId,
454
+ user: result,
455
+ iat: nowSeconds,
456
+ exp: Math.floor(
457
+ this.getAccessTokenExpireAt(context.wabe.config).getTime() / 1000,
458
+ ),
459
+ },
460
+ secretKey,
461
+ signOptions,
462
+ )
463
+
464
+ const newRefreshToken = jwt.sign(
465
+ {
466
+ userId,
467
+ user: result,
468
+ iat: nowSeconds,
469
+ exp: Math.floor(
470
+ this.getRefreshTokenExpireAt(context.wabe.config).getTime() / 1000,
471
+ ),
472
+ },
473
+ secretKey,
474
+ signOptions,
475
+ )
476
+
477
+ const newAccessTokenEncrypted = encryptDeterministicToken(
478
+ newAccessToken,
479
+ getTokenEncryptionKey(context),
480
+ )
481
+ const newRefreshTokenEncrypted = encryptDeterministicToken(
482
+ newRefreshToken,
483
+ getTokenEncryptionKey(context),
484
+ )
485
+
486
+ await context.wabe.controllers.database.updateObject({
487
+ className: '_Session',
488
+ context: contextWithRoot(context),
489
+ id,
490
+ data: {
491
+ accessTokenEncrypted: newAccessTokenEncrypted,
492
+ accessTokenExpiresAt: this.getAccessTokenExpireAt(context.wabe.config),
493
+ refreshTokenEncrypted: newRefreshTokenEncrypted,
494
+ refreshTokenExpiresAt: this.getRefreshTokenExpireAt(
495
+ context.wabe.config,
496
+ ),
497
+ },
498
+ select: {},
499
+ })
500
+
501
+ return {
502
+ accessToken: newAccessToken,
503
+ refreshToken: newRefreshToken,
504
+ }
505
+ }
506
+
507
+ async delete(context: WabeContext<DevWabeTypes>) {
508
+ if (!context.sessionId) return
509
+
510
+ await context.wabe.controllers.database.deleteObject({
511
+ className: '_Session',
512
+ context: contextWithRoot(context),
513
+ id: context.sessionId,
514
+ select: {},
515
+ })
516
+ }
517
+
518
+ _isRefreshTokenExpired(
519
+ userRefreshTokenExpiresAt: Date,
520
+ refreshTokenAgeInMs: number,
521
+ ) {
522
+ const refreshTokenEmittedAt =
523
+ userRefreshTokenExpiresAt.getTime() - refreshTokenAgeInMs
524
+ const numberOfMsSinceRefreshTokenEmitted =
525
+ Date.now() - refreshTokenEmittedAt
526
+
527
+ return numberOfMsSinceRefreshTokenEmitted >= 0.75 * refreshTokenAgeInMs
528
+ }
529
+ }