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
|
@@ -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
|
+
})
|