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
@@ -11,7 +11,6 @@ import type { Context, CorsOptions, RateLimitOptions } from "wobe";
11
11
  import type { WabeContext } from "./interface";
12
12
  import type { EmailConfig } from "../email";
13
13
  import { EmailController } from "../email/EmailController";
14
- import type { AIConfig } from "../ai";
15
14
  import { FileController } from "../file/FileController";
16
15
  import type { CronConfig } from "../cron";
17
16
  import type { FileConfig } from "../file";
@@ -19,9 +18,13 @@ type SecurityConfig = {
19
18
  corsOptions?: CorsOptions;
20
19
  rateLimit?: RateLimitOptions;
21
20
  hideSensitiveErrorMessage?: boolean;
21
+ disableCSRFProtection?: boolean;
22
+ allowIntrospectionInProduction?: boolean;
23
+ maxGraphqlDepth?: number;
22
24
  };
23
25
  export * from "./interface";
24
26
  export * from "./routes";
27
+ export declare const defaultRoles: unknown;
25
28
  export interface WabeConfig<T extends WabeTypes> {
26
29
  port: number;
27
30
  isProduction: boolean;
@@ -41,7 +44,6 @@ export interface WabeConfig<T extends WabeTypes> {
41
44
  rootKey: string;
42
45
  hooks?: Hook<T, any>[];
43
46
  email?: EmailConfig;
44
- ai?: AIConfig;
45
47
  file?: FileConfig<T>;
46
48
  crons?: CronConfig<T>;
47
49
  }
@@ -9,3 +9,10 @@ export declare const hashArgon2: unknown;
9
9
  */
10
10
  export declare const verifyArgon2: unknown;
11
11
  export declare const isArgon2Hash: (value: string) => boolean;
12
+ /**
13
+ * Deterministic AES-256-GCM encryption for tokens.
14
+ * IV is derived via HMAC-SHA256(key, token) to allow equality checks without storing plaintext.
15
+ * Caller must provide a strong 32-byte key (already derived/hashed).
16
+ */
17
+ export declare const encryptDeterministicToken: (token: string, key: Buffer) => string;
18
+ export declare const decryptDeterministicToken: (encryptedToken: string | undefined, key: Buffer) => string | null;
@@ -10,7 +10,10 @@ export interface DevWabeTypes extends WabeTypes {
10
10
  export declare const firstLetterUpperCase: (str: string) => string;
11
11
  export declare const getGraphqlClient: (port: number) => GraphQLClient;
12
12
  export declare const getAnonymousClient: (port: number) => GraphQLClient;
13
- export declare const getUserClient: (port: number, accessToken: string) => GraphQLClient;
13
+ export declare const getUserClient: (port: number, options: {
14
+ accessToken?: string;
15
+ csrfToken?: string;
16
+ }) => GraphQLClient;
14
17
  export declare const getAdminUserClient: (port: number, wabe: Wabe<DevWabeTypes>, { email, password }: {
15
18
  email: string;
16
19
  password: string;
@@ -425,9 +425,9 @@ input PostRelationInput {
425
425
  type _Session {
426
426
  id: ID!
427
427
  user: User
428
- accessToken: String!
428
+ accessTokenEncrypted: String!
429
429
  accessTokenExpiresAt: Date!
430
- refreshToken: String
430
+ refreshTokenEncrypted: String!
431
431
  refreshTokenExpiresAt: Date!
432
432
  acl: _SessionACLObject
433
433
  createdAt: Date
@@ -454,9 +454,9 @@ type _SessionACLObjectRolesACL {
454
454
 
455
455
  input _SessionInput {
456
456
  user: UserPointerInput
457
- accessToken: String!
457
+ accessTokenEncrypted: String!
458
458
  accessTokenExpiresAt: Date!
459
- refreshToken: String
459
+ refreshTokenEncrypted: String!
460
460
  refreshTokenExpiresAt: Date!
461
461
  acl: _SessionACLObjectInput
462
462
  createdAt: Date
@@ -490,9 +490,9 @@ input _SessionPointerInput {
490
490
 
491
491
  input _SessionCreateFieldsInput {
492
492
  user: UserPointerInput
493
- accessToken: String
493
+ accessTokenEncrypted: String
494
494
  accessTokenExpiresAt: Date
495
- refreshToken: String
495
+ refreshTokenEncrypted: String
496
496
  refreshTokenExpiresAt: Date
497
497
  acl: _SessionACLObjectCreateFieldsInput
498
498
  createdAt: Date
@@ -937,9 +937,9 @@ input RoleACLObjectRolesACLWhereInput {
937
937
  input _SessionWhereInput {
938
938
  id: IdWhereInput
939
939
  user: UserWhereInput
940
- accessToken: StringWhereInput
940
+ accessTokenEncrypted: StringWhereInput
941
941
  accessTokenExpiresAt: DateWhereInput
942
- refreshToken: StringWhereInput
942
+ refreshTokenEncrypted: StringWhereInput
943
943
  refreshTokenExpiresAt: DateWhereInput
944
944
  acl: _SessionACLObjectWhereInput
945
945
  createdAt: DateWhereInput
@@ -1099,12 +1099,12 @@ enum PostOrder {
1099
1099
  enum _SessionOrder {
1100
1100
  user_ASC
1101
1101
  user_DESC
1102
- accessToken_ASC
1103
- accessToken_DESC
1102
+ accessTokenEncrypted_ASC
1103
+ accessTokenEncrypted_DESC
1104
1104
  accessTokenExpiresAt_ASC
1105
1105
  accessTokenExpiresAt_DESC
1106
- refreshToken_ASC
1107
- refreshToken_DESC
1106
+ refreshTokenEncrypted_ASC
1107
+ refreshTokenEncrypted_DESC
1108
1108
  refreshTokenExpiresAt_ASC
1109
1109
  refreshTokenExpiresAt_DESC
1110
1110
  acl_ASC
@@ -1507,9 +1507,9 @@ input Update_SessionInput {
1507
1507
 
1508
1508
  input _SessionUpdateFieldsInput {
1509
1509
  user: UserPointerInput
1510
- accessToken: String
1510
+ accessTokenEncrypted: String
1511
1511
  accessTokenExpiresAt: Date
1512
- refreshToken: String
1512
+ refreshTokenEncrypted: String
1513
1513
  refreshTokenExpiresAt: Date
1514
1514
  acl: _SessionACLObjectUpdateFieldsInput
1515
1515
  createdAt: Date
@@ -1746,6 +1746,7 @@ type SignInWithOutput {
1746
1746
  user: User
1747
1747
  accessToken: String
1748
1748
  refreshToken: String
1749
+ csrfToken: String
1749
1750
  srp: SignInWithOutputSRPOutputSignInWith
1750
1751
  }
1751
1752
 
@@ -1797,6 +1798,7 @@ type SignUpWithOutput {
1797
1798
  id: String
1798
1799
  accessToken: String!
1799
1800
  refreshToken: String!
1801
+ csrfToken: String
1800
1802
  }
1801
1803
 
1802
1804
  input SignUpWithInput {
package/generated/wabe.ts CHANGED
@@ -116,9 +116,9 @@ export type Post = {
116
116
  export type _Session = {
117
117
  id: string,
118
118
  user: User,
119
- accessToken: string,
119
+ accessTokenEncrypted: string,
120
120
  accessTokenExpiresAt: string,
121
- refreshToken?: string,
121
+ refreshTokenEncrypted: string,
122
122
  refreshTokenExpiresAt: string,
123
123
  acl?: ACLObject,
124
124
  createdAt?: string,
@@ -181,9 +181,9 @@ export type WherePost = {
181
181
  export type Where_Session = {
182
182
  id: string,
183
183
  user: User,
184
- accessToken: string,
184
+ accessTokenEncrypted: string,
185
185
  accessTokenExpiresAt: Date,
186
- refreshToken?: string,
186
+ refreshTokenEncrypted: string,
187
187
  refreshTokenExpiresAt: Date,
188
188
  acl?: ACLObject,
189
189
  createdAt?: Date,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "wabe",
3
- "version": "0.6.9",
4
- "description": "Your backend in minutes not days",
5
- "homepage": "https://wabe.dev",
3
+ "version": "0.6.10",
4
+ "description": "Your backend without vendor lock-in in Typescript",
5
+ "homepage": "https://palixir.github.io/wabe/",
6
6
  "author": {
7
7
  "name": "coratgerl",
8
8
  "url": "https://github.com/coratgerl"
@@ -22,33 +22,33 @@
22
22
  "scripts": {
23
23
  "build": "bun --filter wabe-build build:package $(pwd)",
24
24
  "check": "tsc --project $(pwd)/tsconfig.json",
25
- "lint": "biome lint . --no-errors-on-unmatched --config-path=../../",
25
+ "lint": "biome lint . --no-errors-on-unmatched",
26
26
  "ci": "bun generate:codegen && bun lint $(pwd) && bun check && bun test src",
27
- "format": "biome format --write . --config-path=../../",
27
+ "format": "biome format --write .",
28
28
  "dev": "bun run --watch dev/index.ts",
29
29
  "generate:codegen": "touch generated/wabe.ts && CODEGEN=true bun dev/index.ts"
30
30
  },
31
31
  "dependencies": {
32
- "@graphql-yoga/plugin-disable-introspection": "2.10.9",
33
32
  "croner": "9.0.0",
33
+ "graphql": "16.12.0",
34
34
  "js-srp6a": "1.0.2",
35
35
  "jsonwebtoken": "9.0.2",
36
36
  "libphonenumber-js": "1.11.18",
37
37
  "otplib": "12.0.1",
38
- "p-retry": "6.2.1",
39
- "wobe": "1.1.10",
40
- "wobe-graphql-yoga": "1.2.6"
38
+ "p-retry": "7.1.0",
39
+ "wobe": "1.1.14",
40
+ "wobe-graphql-yoga": "1.2.9"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/jsonwebtoken": "9.0.6",
44
44
  "@types/uuid": "9.0.6",
45
45
  "graphql-request": "6.1.0",
46
46
  "get-port": "7.1.0",
47
- "uuid": "10.0.0",
48
- "wabe-mongodb-launcher": "workspace:*",
49
- "wabe-pluralize": "workspace:*",
50
- "wabe-build": "workspace:*",
51
- "wabe-mongodb": "workspace:*",
52
- "wabe": "workspace:*"
47
+ "uuid": "13.0.0",
48
+ "wabe-mongodb-launcher": "0.5.2",
49
+ "wabe-pluralize": "0.0.1",
50
+ "wabe-build": "0.5.0",
51
+ "wabe-mongodb": "0.5.2",
52
+ "wabe": "0.6.9"
53
53
  }
54
54
  }
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { OTP } from './OTP'
3
+
4
+ describe('OTP', () => {
5
+ it('should generate a valid OTP code', () => {
6
+ const otp = new OTP('rootKey')
7
+
8
+ const otpValue = otp.generate('userId')
9
+
10
+ expect(otpValue.length).toBe(6)
11
+ })
12
+
13
+ it('should verify a valid OTP code', () => {
14
+ const otp = new OTP('rootKey')
15
+
16
+ const otpValue = otp.generate('userId')
17
+
18
+ expect(otpValue.length).toBe(6)
19
+
20
+ expect(otp.verify(otpValue, 'userId')).toBe(true)
21
+ })
22
+
23
+ it('should not verify an invalid OTP code', () => {
24
+ const otp = new OTP('rootKey')
25
+
26
+ const otpValue = otp.generate('userId')
27
+
28
+ expect(otpValue.length).toBe(6)
29
+
30
+ expect(otp.verify('invalidOtp', 'userId')).toBe(false)
31
+
32
+ const otpValue2 = otp.generate('invalidUserId')
33
+
34
+ expect(otpValue2.length).toBe(6)
35
+
36
+ expect(otp.verify(otpValue2, 'userId')).toBe(false)
37
+ })
38
+
39
+ it('should not verify an invalid OTP code (more than 5 minutes)', () => {
40
+ // Directly test the timeout is flaky we only test that the correct value is passed to totp
41
+ const otp = new OTP('rootKey')
42
+
43
+ expect(otp.internalTotp.options.window).toEqual([1, 0])
44
+ })
45
+
46
+ it('should generate a valid keyuri', () => {
47
+ const otp = new OTP('rootKey')
48
+
49
+ const keyuri = otp.generateKeyuri({
50
+ userId: 'userId',
51
+ emailOrUsername: 'email@test.fr',
52
+ applicationName: 'Wabe',
53
+ })
54
+
55
+ expect(keyuri).toBe(
56
+ 'otpauth://totp/Wabe:email%40test.fr?secret=O54OZDANWM2YFHJKJMMVMQSV7DUMUZFT3BWE4Z5NOQCAATGGHKYA&period=30&digits=6&algorithm=SHA1&issuer=Wabe',
57
+ )
58
+ })
59
+
60
+ it('should verify an OTP generated from authenticator', () => {
61
+ const otp = new OTP('rootKey')
62
+
63
+ const code = otp.authenticatorGenerate('userId')
64
+
65
+ const isValid = otp.authenticatorVerify(code, 'userId')
66
+
67
+ expect(isValid).toBe(true)
68
+ })
69
+ })
@@ -0,0 +1,66 @@
1
+ import { totp, authenticator } from 'otplib'
2
+ import type { TOTP } from 'otplib/core'
3
+ import { createHash } from 'node:crypto'
4
+ import { base32Encode } from 'src/utils'
5
+
6
+ const ONE_WINDOW = 1
7
+
8
+ export class OTP {
9
+ private secret: string
10
+ public internalTotp: TOTP
11
+
12
+ constructor(rootKey: string) {
13
+ this.secret = rootKey
14
+ this.internalTotp = totp.clone({
15
+ window: [ONE_WINDOW, 0],
16
+ })
17
+ }
18
+
19
+ deriveSecret(userId: string): string {
20
+ const hash = createHash('sha256')
21
+ .update(`${this.secret}:${userId}`)
22
+ .digest()
23
+
24
+ return base32Encode(hash, 'RFC4648', { padding: false })
25
+ }
26
+
27
+ generate(userId: string): string {
28
+ const secret = this.deriveSecret(userId)
29
+
30
+ return this.internalTotp.generate(secret)
31
+ }
32
+
33
+ verify(otp: string, userId: string): boolean {
34
+ const secret = this.deriveSecret(userId)
35
+
36
+ return this.internalTotp.verify({ secret, token: otp })
37
+ }
38
+
39
+ authenticatorGenerate(userId: string): string {
40
+ const secret = this.deriveSecret(userId)
41
+ return authenticator.generate(secret)
42
+ }
43
+
44
+ authenticatorVerify(otp: string, userId: string): boolean {
45
+ const secret = this.deriveSecret(userId)
46
+
47
+ return authenticator.verify({
48
+ secret,
49
+ token: otp,
50
+ })
51
+ }
52
+
53
+ generateKeyuri({
54
+ userId,
55
+ emailOrUsername,
56
+ applicationName,
57
+ }: {
58
+ userId: string
59
+ emailOrUsername: string
60
+ applicationName: string
61
+ }): string {
62
+ const secret = this.deriveSecret(userId)
63
+
64
+ return authenticator.keyuri(emailOrUsername, applicationName, secret)
65
+ }
66
+ }