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.
Files changed (162) hide show
  1. package/README.md +156 -50
  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/cron/index.d.ts +0 -1
  7. package/dist/database/DatabaseController.d.ts +41 -13
  8. package/dist/database/interface.d.ts +1 -0
  9. package/dist/email/DevAdapter.d.ts +0 -1
  10. package/dist/email/interface.d.ts +1 -1
  11. package/dist/graphql/resolvers.d.ts +4 -2
  12. package/dist/hooks/index.d.ts +8 -2
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.js +32144 -32058
  15. package/dist/schema/Schema.d.ts +2 -1
  16. package/dist/server/index.d.ts +4 -2
  17. package/dist/utils/crypto.d.ts +7 -0
  18. package/dist/utils/helper.d.ts +5 -1
  19. package/generated/schema.graphql +22 -14
  20. package/generated/wabe.ts +4 -4
  21. package/package.json +23 -23
  22. package/src/authentication/OTP.test.ts +69 -0
  23. package/src/authentication/OTP.ts +64 -0
  24. package/src/authentication/Session.test.ts +629 -0
  25. package/src/authentication/Session.ts +493 -0
  26. package/src/authentication/defaultAuthentication.ts +209 -0
  27. package/src/authentication/index.ts +3 -0
  28. package/src/authentication/interface.ts +155 -0
  29. package/src/authentication/oauth/GitHub.test.ts +91 -0
  30. package/src/authentication/oauth/GitHub.ts +121 -0
  31. package/src/authentication/oauth/Google.test.ts +91 -0
  32. package/src/authentication/oauth/Google.ts +101 -0
  33. package/src/authentication/oauth/Oauth2Client.test.ts +219 -0
  34. package/src/authentication/oauth/Oauth2Client.ts +135 -0
  35. package/src/authentication/oauth/index.ts +2 -0
  36. package/src/authentication/oauth/utils.test.ts +33 -0
  37. package/src/authentication/oauth/utils.ts +27 -0
  38. package/src/authentication/providers/EmailOTP.test.ts +127 -0
  39. package/src/authentication/providers/EmailOTP.ts +84 -0
  40. package/src/authentication/providers/EmailPassword.test.ts +176 -0
  41. package/src/authentication/providers/EmailPassword.ts +116 -0
  42. package/src/authentication/providers/EmailPasswordSRP.test.ts +208 -0
  43. package/src/authentication/providers/EmailPasswordSRP.ts +179 -0
  44. package/src/authentication/providers/GitHub.ts +24 -0
  45. package/src/authentication/providers/Google.ts +24 -0
  46. package/src/authentication/providers/OAuth.test.ts +185 -0
  47. package/src/authentication/providers/OAuth.ts +106 -0
  48. package/src/authentication/providers/PhonePassword.test.ts +176 -0
  49. package/src/authentication/providers/PhonePassword.ts +115 -0
  50. package/src/authentication/providers/QRCodeOTP.test.ts +77 -0
  51. package/src/authentication/providers/QRCodeOTP.ts +58 -0
  52. package/src/authentication/providers/index.ts +6 -0
  53. package/src/authentication/resolvers/refreshResolver.test.ts +30 -0
  54. package/src/authentication/resolvers/refreshResolver.ts +19 -0
  55. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
  56. package/src/authentication/resolvers/signInWithResolver.test.ts +293 -0
  57. package/src/authentication/resolvers/signInWithResolver.ts +92 -0
  58. package/src/authentication/resolvers/signOutResolver.test.ts +38 -0
  59. package/src/authentication/resolvers/signOutResolver.ts +18 -0
  60. package/src/authentication/resolvers/signUpWithResolver.test.ts +180 -0
  61. package/src/authentication/resolvers/signUpWithResolver.ts +65 -0
  62. package/src/authentication/resolvers/verifyChallenge.test.ts +133 -0
  63. package/src/authentication/resolvers/verifyChallenge.ts +62 -0
  64. package/src/authentication/roles.test.ts +49 -0
  65. package/src/authentication/roles.ts +40 -0
  66. package/src/authentication/utils.test.ts +97 -0
  67. package/src/authentication/utils.ts +39 -0
  68. package/src/cache/InMemoryCache.test.ts +62 -0
  69. package/src/cache/InMemoryCache.ts +45 -0
  70. package/src/cron/index.test.ts +17 -0
  71. package/src/cron/index.ts +43 -0
  72. package/src/database/DatabaseController.test.ts +613 -0
  73. package/src/database/DatabaseController.ts +1007 -0
  74. package/src/database/index.test.ts +1372 -0
  75. package/src/database/index.ts +9 -0
  76. package/src/database/interface.ts +302 -0
  77. package/src/email/DevAdapter.ts +7 -0
  78. package/src/email/EmailController.test.ts +29 -0
  79. package/src/email/EmailController.ts +13 -0
  80. package/src/email/index.ts +2 -0
  81. package/src/email/interface.ts +36 -0
  82. package/src/email/templates/sendOtpCode.ts +120 -0
  83. package/src/file/FileController.ts +28 -0
  84. package/src/file/FileDevAdapter.ts +51 -0
  85. package/src/file/hookDeleteFile.ts +25 -0
  86. package/src/file/hookReadFile.ts +66 -0
  87. package/src/file/hookUploadFile.ts +50 -0
  88. package/src/file/index.test.ts +932 -0
  89. package/src/file/index.ts +2 -0
  90. package/src/file/interface.ts +39 -0
  91. package/src/graphql/GraphQLSchema.test.ts +4408 -0
  92. package/src/graphql/GraphQLSchema.ts +880 -0
  93. package/src/graphql/index.ts +2 -0
  94. package/src/graphql/parseGraphqlSchema.ts +85 -0
  95. package/src/graphql/parser.test.ts +203 -0
  96. package/src/graphql/parser.ts +542 -0
  97. package/src/graphql/pointerAndRelationFunction.ts +191 -0
  98. package/src/graphql/resolvers.ts +442 -0
  99. package/src/graphql/tests/aggregation.test.ts +1115 -0
  100. package/src/graphql/tests/e2e.test.ts +590 -0
  101. package/src/graphql/tests/scalars.test.ts +250 -0
  102. package/src/graphql/types.ts +227 -0
  103. package/src/hooks/HookObject.test.ts +122 -0
  104. package/src/hooks/HookObject.ts +165 -0
  105. package/src/hooks/authentication.ts +67 -0
  106. package/src/hooks/createUser.test.ts +77 -0
  107. package/src/hooks/createUser.ts +10 -0
  108. package/src/hooks/defaultFields.test.ts +176 -0
  109. package/src/hooks/defaultFields.ts +32 -0
  110. package/src/hooks/deleteSession.test.ts +181 -0
  111. package/src/hooks/deleteSession.ts +20 -0
  112. package/src/hooks/hashFieldHook.test.ts +152 -0
  113. package/src/hooks/hashFieldHook.ts +89 -0
  114. package/src/hooks/index.test.ts +258 -0
  115. package/src/hooks/index.ts +414 -0
  116. package/src/hooks/permissions.test.ts +412 -0
  117. package/src/hooks/permissions.ts +93 -0
  118. package/src/hooks/protected.test.ts +551 -0
  119. package/src/hooks/protected.ts +60 -0
  120. package/src/hooks/searchableFields.test.ts +147 -0
  121. package/src/hooks/searchableFields.ts +86 -0
  122. package/src/hooks/session.test.ts +134 -0
  123. package/src/hooks/session.ts +76 -0
  124. package/src/hooks/setEmail.test.ts +216 -0
  125. package/src/hooks/setEmail.ts +33 -0
  126. package/src/hooks/setupAcl.test.ts +618 -0
  127. package/src/hooks/setupAcl.ts +25 -0
  128. package/src/index.ts +9 -0
  129. package/src/schema/Schema.test.ts +482 -0
  130. package/src/schema/Schema.ts +757 -0
  131. package/src/schema/defaultResolvers.ts +93 -0
  132. package/src/schema/index.ts +1 -0
  133. package/src/schema/resolvers/meResolver.test.ts +62 -0
  134. package/src/schema/resolvers/meResolver.ts +10 -0
  135. package/src/schema/resolvers/resetPassword.test.ts +341 -0
  136. package/src/schema/resolvers/resetPassword.ts +63 -0
  137. package/src/schema/resolvers/sendEmail.test.ts +118 -0
  138. package/src/schema/resolvers/sendEmail.ts +21 -0
  139. package/src/schema/resolvers/sendOtpCode.test.ts +141 -0
  140. package/src/schema/resolvers/sendOtpCode.ts +52 -0
  141. package/src/security.test.ts +3434 -0
  142. package/src/server/defaultSessionHandler.test.ts +62 -0
  143. package/src/server/defaultSessionHandler.ts +105 -0
  144. package/src/server/generateCodegen.ts +433 -0
  145. package/src/server/index.test.ts +532 -0
  146. package/src/server/index.ts +334 -0
  147. package/src/server/interface.ts +11 -0
  148. package/src/server/routes/authHandler.ts +169 -0
  149. package/src/server/routes/index.ts +39 -0
  150. package/src/utils/crypto.test.ts +41 -0
  151. package/src/utils/crypto.ts +105 -0
  152. package/src/utils/export.ts +11 -0
  153. package/src/utils/helper.ts +204 -0
  154. package/src/utils/index.test.ts +11 -0
  155. package/src/utils/index.ts +189 -0
  156. package/src/utils/preload.ts +8 -0
  157. package/src/utils/testHelper.ts +116 -0
  158. package/tsconfig.json +32 -0
  159. package/bunfig.toml +0 -4
  160. package/dist/ai/index.d.ts +0 -1
  161. package/dist/ai/interface.d.ts +0 -9
  162. /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
@@ -0,0 +1,24 @@
1
+ import type { DevWabeTypes } from '../../utils/helper'
2
+ import {
3
+ AuthenticationProvider,
4
+ type AuthenticationEventsOptions,
5
+ type ProviderInterface,
6
+ } from '../interface'
7
+ import { oAuthAuthentication } from './OAuth'
8
+
9
+ type GoogleInterface = {
10
+ authorizationCode: string
11
+ codeVerifier: string
12
+ }
13
+
14
+ export class Google implements ProviderInterface<DevWabeTypes, GoogleInterface> {
15
+ name = 'google'
16
+ onSignIn(options: AuthenticationEventsOptions<DevWabeTypes, GoogleInterface>) {
17
+ return oAuthAuthentication(AuthenticationProvider.Google)(options)
18
+ }
19
+
20
+ // @ts-expect-error
21
+ onSignUp() {
22
+ throw new Error('SignUp is not implemented for Oauth provider, you should use signIn instead.')
23
+ }
24
+ }
@@ -0,0 +1,185 @@
1
+ import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test'
2
+ import { GitHub } from './GitHub'
3
+ import * as OAuth from './OAuth'
4
+ import { AuthenticationProvider } from '../interface'
5
+
6
+ // Use GitHub test as use case
7
+ describe('OAuth', () => {
8
+ const mockGetObjects = mock(() => Promise.resolve([]))
9
+ const mockCount = mock(() => Promise.resolve(0)) as any
10
+ const mockCreateObject = mock(() => Promise.resolve({ id: 'userId' })) as any
11
+
12
+ const mockGetUserInfo = mock().mockResolvedValue({
13
+ email: 'email@test.fr',
14
+ avatarUrl: 'avatarUrl',
15
+ username: 'username',
16
+ })
17
+
18
+ const mockValidateAuthorizationCode = mock().mockResolvedValue({
19
+ accessToken: 'accessToken',
20
+ refreshToken: 'refreshToken',
21
+ accessTokenExpiresAt: new Date(0),
22
+ })
23
+
24
+ spyOn(OAuth, 'getProvider').mockReturnValue({
25
+ validateAuthorizationCode: mockValidateAuthorizationCode,
26
+ getUserInfo: mockGetUserInfo,
27
+ } as never)
28
+
29
+ const context = {
30
+ wabe: {
31
+ controllers: {
32
+ database: {
33
+ getObjects: mockGetObjects,
34
+ createObject: mockCreateObject,
35
+ count: mockCount,
36
+ },
37
+ },
38
+ config: {
39
+ authentication: {
40
+ providers: {
41
+ github: {
42
+ clientId: 'clientId',
43
+ clientSecret: 'clientSecret',
44
+ },
45
+ },
46
+ },
47
+ },
48
+ },
49
+ } as any
50
+
51
+ afterEach(() => {
52
+ mockGetObjects.mockClear()
53
+ mockCreateObject.mockClear()
54
+ mockCount.mockClear()
55
+ mockValidateAuthorizationCode.mockClear()
56
+ mockGetUserInfo.mockClear()
57
+ })
58
+
59
+ it('should sign up with GitHub Provider if there is no user found', async () => {
60
+ const github = new GitHub()
61
+
62
+ await github.onSignIn({
63
+ context,
64
+ input: {
65
+ authorizationCode: 'authorizationCode',
66
+ codeVerifier: 'codeVerifier',
67
+ },
68
+ })
69
+
70
+ expect(mockValidateAuthorizationCode).toHaveBeenCalledTimes(1)
71
+ expect(mockGetUserInfo).toHaveBeenCalledTimes(1)
72
+
73
+ expect(mockGetObjects).toHaveBeenCalledTimes(1)
74
+ expect(mockGetObjects).toHaveBeenCalledWith({
75
+ className: 'User',
76
+ where: {
77
+ authentication: {
78
+ github: {
79
+ email: { equalTo: 'email@test.fr' },
80
+ },
81
+ },
82
+ },
83
+ first: 1,
84
+ context: expect.any(Object),
85
+ select: {
86
+ authentication: true,
87
+ role: true,
88
+ secondFA: true,
89
+ email: true,
90
+ id: true,
91
+ provider: true,
92
+ isOauth: true,
93
+ createdAt: true,
94
+ updatedAt: true,
95
+ },
96
+ })
97
+
98
+ expect(mockCreateObject).toHaveBeenCalledTimes(1)
99
+ expect(mockCreateObject).toHaveBeenCalledWith({
100
+ className: 'User',
101
+ data: {
102
+ provider: AuthenticationProvider.GitHub,
103
+ isOauth: true,
104
+ authentication: {
105
+ github: {
106
+ email: 'email@test.fr',
107
+ username: 'username',
108
+ avatarUrl: 'avatarUrl',
109
+ },
110
+ },
111
+ },
112
+ context: expect.any(Object),
113
+ select: {
114
+ authentication: true,
115
+ role: true,
116
+ secondFA: true,
117
+ email: true,
118
+ id: true,
119
+ provider: true,
120
+ isOauth: true,
121
+ createdAt: true,
122
+ updatedAt: true,
123
+ },
124
+ })
125
+ })
126
+
127
+ it('should sign in with GitHub Provider if there is no user found', async () => {
128
+ mockGetObjects.mockResolvedValue([
129
+ {
130
+ id: 'userId',
131
+ authentication: {
132
+ github: {
133
+ email: 'email@test.fr',
134
+ verifiedEmail: true,
135
+ idToken: 'idToken',
136
+ },
137
+ },
138
+ provider: AuthenticationProvider.Google,
139
+ isOauth: true,
140
+ } as any,
141
+ ] as never)
142
+
143
+ const github = new GitHub()
144
+
145
+ await github.onSignIn({
146
+ context,
147
+ input: {
148
+ authorizationCode: 'authorizationCode',
149
+ codeVerifier: 'codeVerifier',
150
+ },
151
+ })
152
+
153
+ expect(mockValidateAuthorizationCode).toHaveBeenCalledTimes(1)
154
+ expect(mockGetUserInfo).toHaveBeenCalledTimes(1)
155
+
156
+ expect(mockGetObjects).toHaveBeenCalledTimes(1)
157
+ expect(mockGetObjects).toHaveBeenCalledWith({
158
+ className: 'User',
159
+ where: {
160
+ authentication: {
161
+ github: {
162
+ email: { equalTo: 'email@test.fr' },
163
+ },
164
+ },
165
+ },
166
+ first: 1,
167
+ context: expect.any(Object),
168
+ select: {
169
+ authentication: true,
170
+ role: true,
171
+ secondFA: true,
172
+ email: true,
173
+ id: true,
174
+ provider: true,
175
+ isOauth: true,
176
+ createdAt: true,
177
+ updatedAt: true,
178
+ },
179
+ })
180
+
181
+ expect(mockCreateObject).toHaveBeenCalledTimes(0)
182
+
183
+ mockValidateAuthorizationCode.mockRestore()
184
+ })
185
+ })
@@ -0,0 +1,106 @@
1
+ import type { WabeContext } from '../../server/interface'
2
+ import { contextWithRoot } from '../../utils/export'
3
+ import type { DevWabeTypes } from '../../utils/helper'
4
+ import { type AuthenticationEventsOptions, AuthenticationProvider } from '../interface'
5
+ import { Google } from '../oauth'
6
+ import { GitHub } from '../oauth/GitHub'
7
+
8
+ export type OAuthAuthenticationInterface = {
9
+ authorizationCode: string
10
+ codeVerifier: string
11
+ }
12
+
13
+ export const getProvider = (
14
+ context: WabeContext<DevWabeTypes>,
15
+ provider: AuthenticationProvider,
16
+ ) => {
17
+ const config = context.wabe.config
18
+
19
+ switch (provider) {
20
+ case AuthenticationProvider.Google:
21
+ return new Google(config)
22
+ case AuthenticationProvider.GitHub:
23
+ return new GitHub(config)
24
+ default:
25
+ throw new Error(`Provider ${provider} not found`)
26
+ }
27
+ }
28
+
29
+ export const oAuthAuthentication =
30
+ (oAuthProvider: AuthenticationProvider) =>
31
+ async ({
32
+ context,
33
+ input,
34
+ }: AuthenticationEventsOptions<DevWabeTypes, OAuthAuthenticationInterface>) => {
35
+ const { authorizationCode, codeVerifier } = input
36
+
37
+ const provider = getProvider(context, oAuthProvider)
38
+
39
+ const { accessToken } = await provider.validateAuthorizationCode(
40
+ authorizationCode,
41
+ codeVerifier,
42
+ )
43
+
44
+ const userInfoToSave = await provider.getUserInfo(accessToken)
45
+
46
+ const user = await context.wabe.controllers.database.getObjects({
47
+ className: 'User',
48
+ where: {
49
+ authentication: {
50
+ [oAuthProvider]: {
51
+ email: { equalTo: userInfoToSave.email },
52
+ },
53
+ },
54
+ },
55
+ context: contextWithRoot(context),
56
+ first: 1,
57
+ select: {
58
+ authentication: true,
59
+ role: true,
60
+ secondFA: true,
61
+ email: true,
62
+ id: true,
63
+ provider: true,
64
+ isOauth: true,
65
+ createdAt: true,
66
+ updatedAt: true,
67
+ },
68
+ })
69
+
70
+ if (user.length === 0) {
71
+ const createdUser = await context.wabe.controllers.database.createObject({
72
+ className: 'User',
73
+ data: {
74
+ provider: oAuthProvider,
75
+ isOauth: true,
76
+ authentication: {
77
+ [oAuthProvider]: userInfoToSave,
78
+ },
79
+ },
80
+ context: contextWithRoot(context),
81
+ select: {
82
+ authentication: true,
83
+ role: true,
84
+ secondFA: true,
85
+ email: true,
86
+ id: true,
87
+ provider: true,
88
+ isOauth: true,
89
+ createdAt: true,
90
+ updatedAt: true,
91
+ },
92
+ })
93
+
94
+ if (!createdUser) throw new Error('User not found')
95
+
96
+ return {
97
+ user: createdUser,
98
+ }
99
+ }
100
+
101
+ if (!user[0]) throw new Error('User not found')
102
+
103
+ return {
104
+ user: user[0],
105
+ }
106
+ }
@@ -0,0 +1,176 @@
1
+ import { describe, expect, it, mock, spyOn, afterEach, afterAll } from 'bun:test'
2
+ import * as crypto from '../../utils/crypto'
3
+
4
+ import { PhonePassword } from './PhonePassword'
5
+
6
+ describe('Phone password', () => {
7
+ const mockGetObjects = mock(() => Promise.resolve([]))
8
+ const mockCount = mock(() => Promise.resolve(0)) as any
9
+ const mockCreateObject = mock(() => Promise.resolve({ id: 'userId' })) as any
10
+
11
+ const spyArgonPasswordVerify = spyOn(crypto, 'verifyArgon2')
12
+ const spyBunPasswordHash = spyOn(crypto, 'hashArgon2')
13
+
14
+ const controllers = {
15
+ controllers: {
16
+ database: {
17
+ getObjects: mockGetObjects,
18
+ createObject: mockCreateObject,
19
+ count: mockCount,
20
+ },
21
+ },
22
+ } as any
23
+
24
+ afterEach(() => {
25
+ mockGetObjects.mockClear()
26
+ mockCreateObject.mockClear()
27
+ spyArgonPasswordVerify.mockClear()
28
+ spyBunPasswordHash.mockClear()
29
+ })
30
+
31
+ afterAll(() => {
32
+ spyArgonPasswordVerify.mockRestore()
33
+ spyBunPasswordHash.mockRestore()
34
+ })
35
+
36
+ const phonePassword = new PhonePassword()
37
+
38
+ it('should signUp with phone password', async () => {
39
+ spyBunPasswordHash.mockResolvedValueOnce('$argon2id$hashedPassword')
40
+
41
+ const {
42
+ authenticationDataToSave: { phone },
43
+ } = await phonePassword.onSignUp({
44
+ context: { wabe: controllers } as any,
45
+ input: { phone: 'phone@test.fr', password: 'password' },
46
+ })
47
+
48
+ expect(phone).toBe('phone@test.fr')
49
+ })
50
+
51
+ it('should signIn with phone password', async () => {
52
+ mockGetObjects.mockResolvedValue([
53
+ {
54
+ id: 'userId',
55
+ authentication: {
56
+ phonePassword: {
57
+ phone: 'phone@test.fr',
58
+ password: 'hashedPassword',
59
+ },
60
+ },
61
+ } as never,
62
+ ])
63
+
64
+ spyArgonPasswordVerify.mockResolvedValueOnce(true)
65
+
66
+ const { user } = await phonePassword.onSignIn({
67
+ context: { wabe: controllers } as any,
68
+ input: { phone: 'phone@test.fr', password: 'password' },
69
+ })
70
+
71
+ expect(user).toEqual({
72
+ id: 'userId',
73
+ authentication: {
74
+ phonePassword: {
75
+ phone: 'phone@test.fr',
76
+ password: 'hashedPassword',
77
+ },
78
+ },
79
+ })
80
+
81
+ expect(spyArgonPasswordVerify).toHaveBeenCalledTimes(1)
82
+ expect(spyArgonPasswordVerify).toHaveBeenCalledWith('password', 'hashedPassword')
83
+ })
84
+
85
+ it('should not signIn with phone password if password is undefined', () => {
86
+ spyArgonPasswordVerify.mockResolvedValueOnce(false)
87
+
88
+ expect(
89
+ phonePassword.onSignIn({
90
+ context: { wabe: controllers } as any,
91
+ // @ts-expect-error
92
+ input: { phone: 'phone@test.fr' },
93
+ }),
94
+ ).rejects.toThrow('Invalid authentication credentials')
95
+ })
96
+
97
+ it('should not signIn with phone password if there is no user found', () => {
98
+ mockGetObjects.mockResolvedValue([])
99
+
100
+ expect(
101
+ phonePassword.onSignIn({
102
+ context: { wabe: controllers } as any,
103
+ input: {
104
+ phone: 'invalidEmail@test.fr',
105
+ password: 'password',
106
+ },
107
+ }),
108
+ ).rejects.toThrow('Invalid authentication credentials')
109
+
110
+ expect(spyArgonPasswordVerify).toHaveBeenCalledTimes(1)
111
+ })
112
+
113
+ it('should not signIn with phone password if there is phone is invalid', () => {
114
+ mockGetObjects.mockResolvedValue([
115
+ {
116
+ authentication: {
117
+ phonePassword: {
118
+ password: 'hashedPassword',
119
+ },
120
+ },
121
+ } as never,
122
+ ])
123
+
124
+ spyArgonPasswordVerify.mockResolvedValueOnce(true)
125
+
126
+ expect(
127
+ phonePassword.onSignIn({
128
+ context: { wabe: controllers } as any,
129
+ input: {
130
+ phone: 'invalidEmail@test.fr',
131
+ password: 'password',
132
+ },
133
+ }),
134
+ ).rejects.toThrow('Invalid authentication credentials')
135
+
136
+ expect(spyArgonPasswordVerify).toHaveBeenCalledTimes(1)
137
+ })
138
+
139
+ it('should not update authentication data if there is no user found', () => {
140
+ mockGetObjects.mockResolvedValue([])
141
+
142
+ spyArgonPasswordVerify.mockResolvedValueOnce(true)
143
+
144
+ expect(
145
+ phonePassword.onUpdateAuthenticationData?.({
146
+ context: { wabe: controllers } as any,
147
+ input: {
148
+ phone: 'phone@test.fr',
149
+ password: 'password',
150
+ },
151
+ userId: 'userId',
152
+ }),
153
+ ).rejects.toThrow('User not found')
154
+ })
155
+
156
+ it('should update authentication data if the userId match with an user', async () => {
157
+ mockGetObjects.mockResolvedValue([
158
+ {
159
+ id: 'id',
160
+ },
161
+ ] as any)
162
+
163
+ spyBunPasswordHash.mockResolvedValueOnce('$argon2id$hashedPassword')
164
+
165
+ const res = await phonePassword.onUpdateAuthenticationData?.({
166
+ context: { wabe: controllers } as any,
167
+ input: {
168
+ phone: 'phone@test.fr',
169
+ password: 'password',
170
+ },
171
+ userId: 'userId',
172
+ })
173
+
174
+ expect(res.authenticationDataToSave.phone).toBe('phone@test.fr')
175
+ })
176
+ })
@@ -0,0 +1,115 @@
1
+ import type {
2
+ AuthenticationEventsOptions,
3
+ AuthenticationEventsOptionsWithUserId,
4
+ ProviderInterface,
5
+ } from '../interface'
6
+ import { contextWithRoot, verifyArgon2 } from '../../utils/export'
7
+ import type { DevWabeTypes } from '../../utils/helper'
8
+
9
+ const DUMMY_PASSWORD_HASH =
10
+ '$argon2id$v=19$m=65536,t=2,p=1$wHZB9xRS/Mbo7L3SL9e935Ag5K+T2EuT/XgB8akwZgo$SPf8EZ4T1HYkuIll4v2hSzNCH7woX3VrZJo3yWg5u8U'
11
+
12
+ type PhonePasswordInterface = {
13
+ password: string
14
+ phone: string
15
+ otp?: string
16
+ }
17
+
18
+ export class PhonePassword implements ProviderInterface<DevWabeTypes, PhonePasswordInterface> {
19
+ async onSignIn({
20
+ input,
21
+ context,
22
+ }: AuthenticationEventsOptions<DevWabeTypes, PhonePasswordInterface>) {
23
+ const users = await context.wabe.controllers.database.getObjects({
24
+ className: 'User',
25
+ where: {
26
+ authentication: {
27
+ phonePassword: {
28
+ phone: { equalTo: input.phone },
29
+ },
30
+ },
31
+ },
32
+ context: contextWithRoot(context),
33
+ select: {
34
+ authentication: true,
35
+ role: true,
36
+ secondFA: true,
37
+ email: true,
38
+ id: true,
39
+ provider: true,
40
+ isOauth: true,
41
+ createdAt: true,
42
+ updatedAt: true,
43
+ },
44
+ first: 1,
45
+ })
46
+
47
+ const user = users[0]
48
+ const userDatabasePassword = user?.authentication?.phonePassword?.password
49
+
50
+ const passwordHashToCheck = userDatabasePassword ?? DUMMY_PASSWORD_HASH
51
+
52
+ const isPasswordEquals = await verifyArgon2(input.password, passwordHashToCheck)
53
+
54
+ if (!user || !isPasswordEquals || input.phone !== user.authentication?.phonePassword?.phone)
55
+ throw new Error('Invalid authentication credentials')
56
+
57
+ return {
58
+ user,
59
+ }
60
+ }
61
+
62
+ async onSignUp({
63
+ input,
64
+ context,
65
+ }: AuthenticationEventsOptions<DevWabeTypes, PhonePasswordInterface>) {
66
+ const users = await context.wabe.controllers.database.count({
67
+ className: 'User',
68
+ where: {
69
+ authentication: {
70
+ phonePassword: {
71
+ phone: { equalTo: input.phone },
72
+ },
73
+ },
74
+ },
75
+ context: contextWithRoot(context),
76
+ })
77
+
78
+ if (users > 0) throw new Error('Not authorized to create user')
79
+
80
+ return {
81
+ authenticationDataToSave: {
82
+ phone: input.phone,
83
+ password: input.password,
84
+ },
85
+ }
86
+ }
87
+
88
+ async onUpdateAuthenticationData({
89
+ userId,
90
+ input,
91
+ context,
92
+ }: AuthenticationEventsOptionsWithUserId<DevWabeTypes, PhonePasswordInterface>) {
93
+ const users = await context.wabe.controllers.database.getObjects({
94
+ className: 'User',
95
+ where: {
96
+ id: {
97
+ equalTo: userId,
98
+ },
99
+ },
100
+ context,
101
+ select: { authentication: true },
102
+ })
103
+
104
+ if (users.length === 0) throw new Error('User not found')
105
+
106
+ const user = users[0]
107
+
108
+ return {
109
+ authenticationDataToSave: {
110
+ phone: input.phone ?? user?.authentication?.phonePassword?.phone,
111
+ password: input.password ? input.password : user?.authentication?.phonePassword?.password,
112
+ },
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it, beforeAll, afterAll, afterEach } from 'bun:test'
2
+ import type { DevWabeTypes } from '../../utils/helper'
3
+ import { setupTests, closeTests } from '../../utils/testHelper'
4
+ import type { Wabe } from '../..'
5
+ import { OTP } from '../OTP'
6
+ import { QRCodeOTP } from './QRCodeOTP'
7
+
8
+ describe('QRCodeOTPProvider', () => {
9
+ let wabe: Wabe<DevWabeTypes>
10
+
11
+ beforeAll(async () => {
12
+ const setup = await setupTests()
13
+ wabe = setup.wabe
14
+ })
15
+
16
+ afterAll(async () => {
17
+ await closeTests(wabe)
18
+ })
19
+
20
+ afterEach(async () => {
21
+ await wabe.controllers.database.clearDatabase()
22
+ })
23
+
24
+ it('should return the userId if the OTP code is valid', async () => {
25
+ const createdUser = await wabe.controllers.database.createObject({
26
+ className: 'User',
27
+ context: {
28
+ wabe,
29
+ isRoot: true,
30
+ },
31
+ data: {
32
+ email: 'email@test.fr',
33
+ },
34
+ select: {
35
+ id: true,
36
+ },
37
+ })
38
+
39
+ if (!createdUser) throw new Error('User not created')
40
+
41
+ const otp = new OTP(wabe.config.rootKey).authenticatorGenerate(createdUser.id)
42
+
43
+ const qrCodeOTP = new QRCodeOTP()
44
+
45
+ expect(
46
+ await qrCodeOTP.onVerifyChallenge({
47
+ context: {
48
+ wabe,
49
+ isRoot: false,
50
+ },
51
+ input: {
52
+ email: 'email@test.fr',
53
+ otp,
54
+ },
55
+ }),
56
+ ).toEqual({
57
+ userId: createdUser.id,
58
+ })
59
+ })
60
+
61
+ it("should return null if the user doesn't exist", async () => {
62
+ const qrCodeOTP = new QRCodeOTP()
63
+
64
+ expect(
65
+ await qrCodeOTP.onVerifyChallenge({
66
+ context: {
67
+ wabe,
68
+ isRoot: false,
69
+ },
70
+ input: {
71
+ email: 'email@test.fr',
72
+ otp: '123456',
73
+ },
74
+ }),
75
+ ).toEqual(null)
76
+ })
77
+ })