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.
- package/README.md +138 -32
- package/bucket/b.txt +1 -0
- package/dev/index.ts +215 -0
- package/dist/authentication/Session.d.ts +4 -1
- package/dist/authentication/interface.d.ts +16 -0
- package/dist/email/interface.d.ts +1 -1
- package/dist/graphql/resolvers.d.ts +4 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +8713 -8867
- package/dist/server/index.d.ts +4 -2
- package/dist/utils/crypto.d.ts +7 -0
- package/dist/utils/helper.d.ts +4 -1
- package/generated/schema.graphql +16 -14
- package/generated/wabe.ts +4 -4
- package/package.json +15 -15
- package/src/authentication/OTP.test.ts +69 -0
- package/src/authentication/OTP.ts +66 -0
- package/src/authentication/Session.test.ts +665 -0
- package/src/authentication/Session.ts +529 -0
- package/src/authentication/defaultAuthentication.ts +214 -0
- package/src/authentication/index.ts +3 -0
- package/src/authentication/interface.ts +157 -0
- package/src/authentication/oauth/GitHub.test.ts +105 -0
- package/src/authentication/oauth/GitHub.ts +133 -0
- package/src/authentication/oauth/Google.test.ts +105 -0
- package/src/authentication/oauth/Google.ts +110 -0
- package/src/authentication/oauth/Oauth2Client.test.ts +225 -0
- package/src/authentication/oauth/Oauth2Client.ts +140 -0
- package/src/authentication/oauth/index.ts +2 -0
- package/src/authentication/oauth/utils.test.ts +35 -0
- package/src/authentication/oauth/utils.ts +28 -0
- package/src/authentication/providers/EmailOTP.test.ts +138 -0
- package/src/authentication/providers/EmailOTP.ts +93 -0
- package/src/authentication/providers/EmailPassword.test.ts +187 -0
- package/src/authentication/providers/EmailPassword.ts +130 -0
- package/src/authentication/providers/EmailPasswordSRP.test.ts +206 -0
- package/src/authentication/providers/EmailPasswordSRP.ts +184 -0
- package/src/authentication/providers/GitHub.ts +30 -0
- package/src/authentication/providers/Google.ts +30 -0
- package/src/authentication/providers/OAuth.test.ts +185 -0
- package/src/authentication/providers/OAuth.ts +112 -0
- package/src/authentication/providers/PhonePassword.test.ts +187 -0
- package/src/authentication/providers/PhonePassword.ts +129 -0
- package/src/authentication/providers/QRCodeOTP.test.ts +79 -0
- package/src/authentication/providers/QRCodeOTP.ts +65 -0
- package/src/authentication/providers/index.ts +6 -0
- package/src/authentication/resolvers/refreshResolver.test.ts +37 -0
- package/src/authentication/resolvers/refreshResolver.ts +20 -0
- package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
- package/src/authentication/resolvers/signInWithResolver.test.ts +307 -0
- package/src/authentication/resolvers/signInWithResolver.ts +102 -0
- package/src/authentication/resolvers/signOutResolver.test.ts +41 -0
- package/src/authentication/resolvers/signOutResolver.ts +22 -0
- package/src/authentication/resolvers/signUpWithResolver.test.ts +186 -0
- package/src/authentication/resolvers/signUpWithResolver.ts +69 -0
- package/src/authentication/resolvers/verifyChallenge.test.ts +136 -0
- package/src/authentication/resolvers/verifyChallenge.ts +69 -0
- package/src/authentication/roles.test.ts +59 -0
- package/src/authentication/roles.ts +40 -0
- package/src/authentication/utils.test.ts +99 -0
- package/src/authentication/utils.ts +43 -0
- package/src/cache/InMemoryCache.test.ts +62 -0
- package/src/cache/InMemoryCache.ts +45 -0
- package/src/cron/index.test.ts +17 -0
- package/src/cron/index.ts +46 -0
- package/src/database/DatabaseController.test.ts +625 -0
- package/src/database/DatabaseController.ts +983 -0
- package/src/database/index.test.ts +1230 -0
- package/src/database/index.ts +9 -0
- package/src/database/interface.ts +312 -0
- package/src/email/DevAdapter.ts +8 -0
- package/src/email/EmailController.test.ts +29 -0
- package/src/email/EmailController.ts +13 -0
- package/src/email/index.ts +2 -0
- package/src/email/interface.ts +36 -0
- package/src/email/templates/sendOtpCode.ts +120 -0
- package/src/file/FileController.ts +28 -0
- package/src/file/FileDevAdapter.ts +54 -0
- package/src/file/hookDeleteFile.ts +27 -0
- package/src/file/hookReadFile.ts +70 -0
- package/src/file/hookUploadFile.ts +53 -0
- package/src/file/index.test.ts +979 -0
- package/src/file/index.ts +2 -0
- package/src/file/interface.ts +42 -0
- package/src/graphql/GraphQLSchema.test.ts +4399 -0
- package/src/graphql/GraphQLSchema.ts +928 -0
- package/src/graphql/index.ts +2 -0
- package/src/graphql/parseGraphqlSchema.ts +94 -0
- package/src/graphql/parser.test.ts +217 -0
- package/src/graphql/parser.ts +566 -0
- package/src/graphql/pointerAndRelationFunction.ts +200 -0
- package/src/graphql/resolvers.ts +467 -0
- package/src/graphql/tests/aggregation.test.ts +1123 -0
- package/src/graphql/tests/e2e.test.ts +596 -0
- package/src/graphql/tests/scalars.test.ts +250 -0
- package/src/graphql/types.ts +219 -0
- package/src/hooks/HookObject.test.ts +122 -0
- package/src/hooks/HookObject.ts +168 -0
- package/src/hooks/authentication.ts +76 -0
- package/src/hooks/createUser.test.ts +77 -0
- package/src/hooks/createUser.ts +10 -0
- package/src/hooks/defaultFields.test.ts +187 -0
- package/src/hooks/defaultFields.ts +40 -0
- package/src/hooks/deleteSession.test.ts +181 -0
- package/src/hooks/deleteSession.ts +20 -0
- package/src/hooks/hashFieldHook.test.ts +163 -0
- package/src/hooks/hashFieldHook.ts +97 -0
- package/src/hooks/index.test.ts +207 -0
- package/src/hooks/index.ts +430 -0
- package/src/hooks/permissions.test.ts +424 -0
- package/src/hooks/permissions.ts +113 -0
- package/src/hooks/protected.test.ts +551 -0
- package/src/hooks/protected.ts +72 -0
- package/src/hooks/searchableFields.test.ts +166 -0
- package/src/hooks/searchableFields.ts +98 -0
- package/src/hooks/session.test.ts +138 -0
- package/src/hooks/session.ts +78 -0
- package/src/hooks/setEmail.test.ts +216 -0
- package/src/hooks/setEmail.ts +35 -0
- package/src/hooks/setupAcl.test.ts +589 -0
- package/src/hooks/setupAcl.ts +29 -0
- package/src/index.ts +9 -0
- package/src/schema/Schema.test.ts +484 -0
- package/src/schema/Schema.ts +795 -0
- package/src/schema/defaultResolvers.ts +94 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/resolvers/meResolver.test.ts +62 -0
- package/src/schema/resolvers/meResolver.ts +14 -0
- package/src/schema/resolvers/newFile.ts +0 -0
- package/src/schema/resolvers/resetPassword.test.ts +345 -0
- package/src/schema/resolvers/resetPassword.ts +64 -0
- package/src/schema/resolvers/sendEmail.test.ts +118 -0
- package/src/schema/resolvers/sendEmail.ts +21 -0
- package/src/schema/resolvers/sendOtpCode.test.ts +153 -0
- package/src/schema/resolvers/sendOtpCode.ts +52 -0
- package/src/security.test.ts +3461 -0
- package/src/server/defaultSessionHandler.test.ts +66 -0
- package/src/server/defaultSessionHandler.ts +115 -0
- package/src/server/generateCodegen.ts +476 -0
- package/src/server/index.test.ts +552 -0
- package/src/server/index.ts +354 -0
- package/src/server/interface.ts +11 -0
- package/src/server/routes/authHandler.ts +187 -0
- package/src/server/routes/index.ts +40 -0
- package/src/utils/crypto.test.ts +41 -0
- package/src/utils/crypto.ts +121 -0
- package/src/utils/export.ts +13 -0
- package/src/utils/helper.ts +195 -0
- package/src/utils/index.test.ts +11 -0
- package/src/utils/index.ts +201 -0
- package/src/utils/preload.ts +8 -0
- package/src/utils/testHelper.ts +117 -0
- package/tsconfig.json +32 -0
- package/bunfig.toml +0 -4
- package/dist/ai/index.d.ts +0 -1
- package/dist/ai/interface.d.ts +0 -9
- /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
package/dist/server/index.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/utils/crypto.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/helper.d.ts
CHANGED
|
@@ -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,
|
|
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;
|
package/generated/schema.graphql
CHANGED
|
@@ -425,9 +425,9 @@ input PostRelationInput {
|
|
|
425
425
|
type _Session {
|
|
426
426
|
id: ID!
|
|
427
427
|
user: User
|
|
428
|
-
|
|
428
|
+
accessTokenEncrypted: String!
|
|
429
429
|
accessTokenExpiresAt: Date!
|
|
430
|
-
|
|
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
|
-
|
|
457
|
+
accessTokenEncrypted: String!
|
|
458
458
|
accessTokenExpiresAt: Date!
|
|
459
|
-
|
|
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
|
-
|
|
493
|
+
accessTokenEncrypted: String
|
|
494
494
|
accessTokenExpiresAt: Date
|
|
495
|
-
|
|
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
|
-
|
|
940
|
+
accessTokenEncrypted: StringWhereInput
|
|
941
941
|
accessTokenExpiresAt: DateWhereInput
|
|
942
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1102
|
+
accessTokenEncrypted_ASC
|
|
1103
|
+
accessTokenEncrypted_DESC
|
|
1104
1104
|
accessTokenExpiresAt_ASC
|
|
1105
1105
|
accessTokenExpiresAt_DESC
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
-
|
|
1510
|
+
accessTokenEncrypted: String
|
|
1511
1511
|
accessTokenExpiresAt: Date
|
|
1512
|
-
|
|
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
|
-
|
|
119
|
+
accessTokenEncrypted: string,
|
|
120
120
|
accessTokenExpiresAt: string,
|
|
121
|
-
|
|
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
|
-
|
|
184
|
+
accessTokenEncrypted: string,
|
|
185
185
|
accessTokenExpiresAt: Date,
|
|
186
|
-
|
|
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.
|
|
4
|
-
"description": "Your backend in
|
|
5
|
-
"homepage": "https://wabe
|
|
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
|
|
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 .
|
|
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": "
|
|
39
|
-
"wobe": "1.1.
|
|
40
|
-
"wobe-graphql-yoga": "1.2.
|
|
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": "
|
|
48
|
-
"wabe-mongodb-launcher": "
|
|
49
|
-
"wabe-pluralize": "
|
|
50
|
-
"wabe-build": "
|
|
51
|
-
"wabe-mongodb": "
|
|
52
|
-
"wabe": "
|
|
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
|
+
}
|