wabe 0.6.9 → 0.6.11
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 +156 -50
- 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/cron/index.d.ts +0 -1
- package/dist/database/DatabaseController.d.ts +41 -13
- package/dist/database/interface.d.ts +1 -0
- package/dist/email/DevAdapter.d.ts +0 -1
- package/dist/email/interface.d.ts +1 -1
- package/dist/graphql/resolvers.d.ts +4 -2
- package/dist/hooks/index.d.ts +8 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +32144 -32058
- package/dist/schema/Schema.d.ts +2 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/utils/crypto.d.ts +7 -0
- package/dist/utils/helper.d.ts +5 -1
- package/generated/schema.graphql +22 -14
- package/generated/wabe.ts +4 -4
- package/package.json +23 -23
- package/src/authentication/OTP.test.ts +69 -0
- package/src/authentication/OTP.ts +64 -0
- package/src/authentication/Session.test.ts +629 -0
- package/src/authentication/Session.ts +493 -0
- package/src/authentication/defaultAuthentication.ts +209 -0
- package/src/authentication/index.ts +3 -0
- package/src/authentication/interface.ts +155 -0
- package/src/authentication/oauth/GitHub.test.ts +91 -0
- package/src/authentication/oauth/GitHub.ts +121 -0
- package/src/authentication/oauth/Google.test.ts +91 -0
- package/src/authentication/oauth/Google.ts +101 -0
- package/src/authentication/oauth/Oauth2Client.test.ts +219 -0
- package/src/authentication/oauth/Oauth2Client.ts +135 -0
- package/src/authentication/oauth/index.ts +2 -0
- package/src/authentication/oauth/utils.test.ts +33 -0
- package/src/authentication/oauth/utils.ts +27 -0
- package/src/authentication/providers/EmailOTP.test.ts +127 -0
- package/src/authentication/providers/EmailOTP.ts +84 -0
- package/src/authentication/providers/EmailPassword.test.ts +176 -0
- package/src/authentication/providers/EmailPassword.ts +116 -0
- package/src/authentication/providers/EmailPasswordSRP.test.ts +208 -0
- package/src/authentication/providers/EmailPasswordSRP.ts +179 -0
- package/src/authentication/providers/GitHub.ts +24 -0
- package/src/authentication/providers/Google.ts +24 -0
- package/src/authentication/providers/OAuth.test.ts +185 -0
- package/src/authentication/providers/OAuth.ts +106 -0
- package/src/authentication/providers/PhonePassword.test.ts +176 -0
- package/src/authentication/providers/PhonePassword.ts +115 -0
- package/src/authentication/providers/QRCodeOTP.test.ts +77 -0
- package/src/authentication/providers/QRCodeOTP.ts +58 -0
- package/src/authentication/providers/index.ts +6 -0
- package/src/authentication/resolvers/refreshResolver.test.ts +30 -0
- package/src/authentication/resolvers/refreshResolver.ts +19 -0
- package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
- package/src/authentication/resolvers/signInWithResolver.test.ts +293 -0
- package/src/authentication/resolvers/signInWithResolver.ts +92 -0
- package/src/authentication/resolvers/signOutResolver.test.ts +38 -0
- package/src/authentication/resolvers/signOutResolver.ts +18 -0
- package/src/authentication/resolvers/signUpWithResolver.test.ts +180 -0
- package/src/authentication/resolvers/signUpWithResolver.ts +65 -0
- package/src/authentication/resolvers/verifyChallenge.test.ts +133 -0
- package/src/authentication/resolvers/verifyChallenge.ts +62 -0
- package/src/authentication/roles.test.ts +49 -0
- package/src/authentication/roles.ts +40 -0
- package/src/authentication/utils.test.ts +97 -0
- package/src/authentication/utils.ts +39 -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 +43 -0
- package/src/database/DatabaseController.test.ts +613 -0
- package/src/database/DatabaseController.ts +1007 -0
- package/src/database/index.test.ts +1372 -0
- package/src/database/index.ts +9 -0
- package/src/database/interface.ts +302 -0
- package/src/email/DevAdapter.ts +7 -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 +51 -0
- package/src/file/hookDeleteFile.ts +25 -0
- package/src/file/hookReadFile.ts +66 -0
- package/src/file/hookUploadFile.ts +50 -0
- package/src/file/index.test.ts +932 -0
- package/src/file/index.ts +2 -0
- package/src/file/interface.ts +39 -0
- package/src/graphql/GraphQLSchema.test.ts +4408 -0
- package/src/graphql/GraphQLSchema.ts +880 -0
- package/src/graphql/index.ts +2 -0
- package/src/graphql/parseGraphqlSchema.ts +85 -0
- package/src/graphql/parser.test.ts +203 -0
- package/src/graphql/parser.ts +542 -0
- package/src/graphql/pointerAndRelationFunction.ts +191 -0
- package/src/graphql/resolvers.ts +442 -0
- package/src/graphql/tests/aggregation.test.ts +1115 -0
- package/src/graphql/tests/e2e.test.ts +590 -0
- package/src/graphql/tests/scalars.test.ts +250 -0
- package/src/graphql/types.ts +227 -0
- package/src/hooks/HookObject.test.ts +122 -0
- package/src/hooks/HookObject.ts +165 -0
- package/src/hooks/authentication.ts +67 -0
- package/src/hooks/createUser.test.ts +77 -0
- package/src/hooks/createUser.ts +10 -0
- package/src/hooks/defaultFields.test.ts +176 -0
- package/src/hooks/defaultFields.ts +32 -0
- package/src/hooks/deleteSession.test.ts +181 -0
- package/src/hooks/deleteSession.ts +20 -0
- package/src/hooks/hashFieldHook.test.ts +152 -0
- package/src/hooks/hashFieldHook.ts +89 -0
- package/src/hooks/index.test.ts +258 -0
- package/src/hooks/index.ts +414 -0
- package/src/hooks/permissions.test.ts +412 -0
- package/src/hooks/permissions.ts +93 -0
- package/src/hooks/protected.test.ts +551 -0
- package/src/hooks/protected.ts +60 -0
- package/src/hooks/searchableFields.test.ts +147 -0
- package/src/hooks/searchableFields.ts +86 -0
- package/src/hooks/session.test.ts +134 -0
- package/src/hooks/session.ts +76 -0
- package/src/hooks/setEmail.test.ts +216 -0
- package/src/hooks/setEmail.ts +33 -0
- package/src/hooks/setupAcl.test.ts +618 -0
- package/src/hooks/setupAcl.ts +25 -0
- package/src/index.ts +9 -0
- package/src/schema/Schema.test.ts +482 -0
- package/src/schema/Schema.ts +757 -0
- package/src/schema/defaultResolvers.ts +93 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/resolvers/meResolver.test.ts +62 -0
- package/src/schema/resolvers/meResolver.ts +10 -0
- package/src/schema/resolvers/resetPassword.test.ts +341 -0
- package/src/schema/resolvers/resetPassword.ts +63 -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 +141 -0
- package/src/schema/resolvers/sendOtpCode.ts +52 -0
- package/src/security.test.ts +3434 -0
- package/src/server/defaultSessionHandler.test.ts +62 -0
- package/src/server/defaultSessionHandler.ts +105 -0
- package/src/server/generateCodegen.ts +433 -0
- package/src/server/index.test.ts +532 -0
- package/src/server/index.ts +334 -0
- package/src/server/interface.ts +11 -0
- package/src/server/routes/authHandler.ts +169 -0
- package/src/server/routes/index.ts +39 -0
- package/src/utils/crypto.test.ts +41 -0
- package/src/utils/crypto.ts +105 -0
- package/src/utils/export.ts +11 -0
- package/src/utils/helper.ts +204 -0
- package/src/utils/index.test.ts +11 -0
- package/src/utils/index.ts +189 -0
- package/src/utils/preload.ts +8 -0
- package/src/utils/testHelper.ts +116 -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/schema/Schema.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WabeConfig, WabeTypes } from "../server";
|
|
2
2
|
import type { HookObject } from "../hooks/HookObject";
|
|
3
|
+
export declare const defaultPrivateFields: unknown;
|
|
3
4
|
export type WabePrimaryTypes = "String" | "Int" | "Float" | "Boolean" | "Email" | "Phone" | "Date" | "File" | "Hash";
|
|
4
5
|
export type WabeCustomTypes = "Array" | "Object";
|
|
5
6
|
export type WabeRelationTypes = "Pointer" | "Relation";
|
|
@@ -54,7 +55,7 @@ type TypeFieldFile = {
|
|
|
54
55
|
type: "File";
|
|
55
56
|
};
|
|
56
57
|
type TypeFieldCustomScalars<T extends WabeTypes> = {
|
|
57
|
-
type: T["scalars"];
|
|
58
|
+
type: T["scalars"] extends "" ? never : T["scalars"];
|
|
58
59
|
required?: boolean;
|
|
59
60
|
defaultValue?: any;
|
|
60
61
|
};
|
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
|
@@ -7,10 +7,14 @@ export interface DevWabeTypes extends WabeTypes {
|
|
|
7
7
|
enums: WabeSchemaEnums;
|
|
8
8
|
where: WabeSchemaWhereTypes;
|
|
9
9
|
}
|
|
10
|
+
export declare const selectFieldsWithoutPrivateFields: <T extends Record<string, any>>(select?: T) => T;
|
|
10
11
|
export declare const firstLetterUpperCase: (str: string) => string;
|
|
11
12
|
export declare const getGraphqlClient: (port: number) => GraphQLClient;
|
|
12
13
|
export declare const getAnonymousClient: (port: number) => GraphQLClient;
|
|
13
|
-
export declare const getUserClient: (port: number,
|
|
14
|
+
export declare const getUserClient: (port: number, options: {
|
|
15
|
+
accessToken?: string;
|
|
16
|
+
csrfToken?: string;
|
|
17
|
+
}) => GraphQLClient;
|
|
14
18
|
export declare const getAdminUserClient: (port: number, wabe: Wabe<DevWabeTypes>, { email, password }: {
|
|
15
19
|
email: string;
|
|
16
20
|
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
|
|
@@ -766,6 +766,7 @@ input StringWhereInput {
|
|
|
766
766
|
notEqualTo: String
|
|
767
767
|
in: [String]
|
|
768
768
|
notIn: [String]
|
|
769
|
+
exists: Boolean
|
|
769
770
|
}
|
|
770
771
|
|
|
771
772
|
input IntWhereInput {
|
|
@@ -777,6 +778,7 @@ input IntWhereInput {
|
|
|
777
778
|
greaterThanOrEqualTo: Int
|
|
778
779
|
in: [Int]
|
|
779
780
|
notIn: [Int]
|
|
781
|
+
exists: Boolean
|
|
780
782
|
}
|
|
781
783
|
|
|
782
784
|
input EmailWhereInput {
|
|
@@ -784,6 +786,7 @@ input EmailWhereInput {
|
|
|
784
786
|
notEqualTo: Email
|
|
785
787
|
in: [Email]
|
|
786
788
|
notIn: [Email]
|
|
789
|
+
exists: Boolean
|
|
787
790
|
}
|
|
788
791
|
|
|
789
792
|
input UserACLObjectWhereInput {
|
|
@@ -806,6 +809,7 @@ input BooleanWhereInput {
|
|
|
806
809
|
notEqualTo: Boolean
|
|
807
810
|
in: [Boolean]
|
|
808
811
|
notIn: [Boolean]
|
|
812
|
+
exists: Boolean
|
|
809
813
|
}
|
|
810
814
|
|
|
811
815
|
input UserACLObjectRolesACLWhereInput {
|
|
@@ -825,6 +829,7 @@ input DateWhereInput {
|
|
|
825
829
|
lessThanOrEqualTo: Date
|
|
826
830
|
greaterThan: Date
|
|
827
831
|
greaterThanOrEqualTo: Date
|
|
832
|
+
exists: Boolean
|
|
828
833
|
}
|
|
829
834
|
|
|
830
835
|
input SearchWhereInput {
|
|
@@ -865,6 +870,7 @@ input PhoneWhereInput {
|
|
|
865
870
|
notEqualTo: Phone
|
|
866
871
|
in: [Phone]
|
|
867
872
|
notIn: [Phone]
|
|
873
|
+
exists: Boolean
|
|
868
874
|
}
|
|
869
875
|
|
|
870
876
|
input UserAuthenticationEmailPasswordWhereInput {
|
|
@@ -892,6 +898,7 @@ input UserAuthenticationGithubWhereInput {
|
|
|
892
898
|
input AnyWhereInput {
|
|
893
899
|
equalTo: Any
|
|
894
900
|
notEqualTo: Any
|
|
901
|
+
exists: Boolean
|
|
895
902
|
}
|
|
896
903
|
|
|
897
904
|
"""
|
|
@@ -937,9 +944,9 @@ input RoleACLObjectRolesACLWhereInput {
|
|
|
937
944
|
input _SessionWhereInput {
|
|
938
945
|
id: IdWhereInput
|
|
939
946
|
user: UserWhereInput
|
|
940
|
-
|
|
947
|
+
accessTokenEncrypted: StringWhereInput
|
|
941
948
|
accessTokenExpiresAt: DateWhereInput
|
|
942
|
-
|
|
949
|
+
refreshTokenEncrypted: StringWhereInput
|
|
943
950
|
refreshTokenExpiresAt: DateWhereInput
|
|
944
951
|
acl: _SessionACLObjectWhereInput
|
|
945
952
|
createdAt: DateWhereInput
|
|
@@ -1050,6 +1057,7 @@ input ArrayWhereInput {
|
|
|
1050
1057
|
notEqualTo: Any
|
|
1051
1058
|
contains: Any
|
|
1052
1059
|
notContains: Any
|
|
1060
|
+
exists: Boolean
|
|
1053
1061
|
}
|
|
1054
1062
|
|
|
1055
1063
|
input PostACLObjectWhereInput {
|
|
@@ -1099,12 +1107,12 @@ enum PostOrder {
|
|
|
1099
1107
|
enum _SessionOrder {
|
|
1100
1108
|
user_ASC
|
|
1101
1109
|
user_DESC
|
|
1102
|
-
|
|
1103
|
-
|
|
1110
|
+
accessTokenEncrypted_ASC
|
|
1111
|
+
accessTokenEncrypted_DESC
|
|
1104
1112
|
accessTokenExpiresAt_ASC
|
|
1105
1113
|
accessTokenExpiresAt_DESC
|
|
1106
|
-
|
|
1107
|
-
|
|
1114
|
+
refreshTokenEncrypted_ASC
|
|
1115
|
+
refreshTokenEncrypted_DESC
|
|
1108
1116
|
refreshTokenExpiresAt_ASC
|
|
1109
1117
|
refreshTokenExpiresAt_DESC
|
|
1110
1118
|
acl_ASC
|
|
@@ -1507,9 +1515,9 @@ input Update_SessionInput {
|
|
|
1507
1515
|
|
|
1508
1516
|
input _SessionUpdateFieldsInput {
|
|
1509
1517
|
user: UserPointerInput
|
|
1510
|
-
|
|
1518
|
+
accessTokenEncrypted: String
|
|
1511
1519
|
accessTokenExpiresAt: Date
|
|
1512
|
-
|
|
1520
|
+
refreshTokenEncrypted: String
|
|
1513
1521
|
refreshTokenExpiresAt: Date
|
|
1514
1522
|
acl: _SessionACLObjectUpdateFieldsInput
|
|
1515
1523
|
createdAt: Date
|
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,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wabe",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Your backend in
|
|
5
|
-
"homepage": "https://wabe.dev",
|
|
6
|
-
"author": {
|
|
7
|
-
"name": "coratgerl",
|
|
8
|
-
"url": "https://github.com/coratgerl"
|
|
9
|
-
},
|
|
10
|
-
"license": "Apache-2.0",
|
|
3
|
+
"version": "0.6.11",
|
|
4
|
+
"description": "Your backend without vendor lock-in in Typescript",
|
|
11
5
|
"keywords": [
|
|
6
|
+
"baas",
|
|
12
7
|
"backend",
|
|
13
|
-
"wabe",
|
|
14
8
|
"graphql",
|
|
15
|
-
"
|
|
9
|
+
"wabe"
|
|
16
10
|
],
|
|
11
|
+
"homepage": "https://palixir.github.io/wabe/",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "coratgerl",
|
|
15
|
+
"url": "https://github.com/coratgerl"
|
|
16
|
+
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
19
|
"url": "git+https://github.com/palixir/wabe.git"
|
|
@@ -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": "
|
|
25
|
+
"lint": "oxlint .",
|
|
26
26
|
"ci": "bun generate:codegen && bun lint $(pwd) && bun check && bun test src",
|
|
27
|
-
"format": "
|
|
27
|
+
"format": "oxfmt --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.15",
|
|
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
|
-
"graphql-request": "6.1.0",
|
|
46
45
|
"get-port": "7.1.0",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"wabe
|
|
50
|
-
"wabe-build": "
|
|
51
|
-
"wabe-mongodb": "
|
|
52
|
-
"wabe": "
|
|
46
|
+
"graphql-request": "6.1.0",
|
|
47
|
+
"uuid": "13.0.0",
|
|
48
|
+
"wabe": "0.6.11",
|
|
49
|
+
"wabe-build": "0.5.0",
|
|
50
|
+
"wabe-mongodb": "0.5.3",
|
|
51
|
+
"wabe-mongodb-launcher": "0.5.2",
|
|
52
|
+
"wabe-pluralize": "0.0.1"
|
|
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,64 @@
|
|
|
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').update(`${this.secret}:${userId}`).digest()
|
|
21
|
+
|
|
22
|
+
return base32Encode(hash, 'RFC4648', { padding: false })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
generate(userId: string): string {
|
|
26
|
+
const secret = this.deriveSecret(userId)
|
|
27
|
+
|
|
28
|
+
return this.internalTotp.generate(secret)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
verify(otp: string, userId: string): boolean {
|
|
32
|
+
const secret = this.deriveSecret(userId)
|
|
33
|
+
|
|
34
|
+
return this.internalTotp.verify({ secret, token: otp })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
authenticatorGenerate(userId: string): string {
|
|
38
|
+
const secret = this.deriveSecret(userId)
|
|
39
|
+
return authenticator.generate(secret)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
authenticatorVerify(otp: string, userId: string): boolean {
|
|
43
|
+
const secret = this.deriveSecret(userId)
|
|
44
|
+
|
|
45
|
+
return authenticator.verify({
|
|
46
|
+
secret,
|
|
47
|
+
token: otp,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
generateKeyuri({
|
|
52
|
+
userId,
|
|
53
|
+
emailOrUsername,
|
|
54
|
+
applicationName,
|
|
55
|
+
}: {
|
|
56
|
+
userId: string
|
|
57
|
+
emailOrUsername: string
|
|
58
|
+
applicationName: string
|
|
59
|
+
}): string {
|
|
60
|
+
const secret = this.deriveSecret(userId)
|
|
61
|
+
|
|
62
|
+
return authenticator.keyuri(emailOrUsername, applicationName, secret)
|
|
63
|
+
}
|
|
64
|
+
}
|