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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthenticationEventsOptions,
|
|
3
|
+
OnVerifyChallengeOptions,
|
|
4
|
+
ProviderInterface,
|
|
5
|
+
SecondaryProviderInterface,
|
|
6
|
+
} from '../interface'
|
|
7
|
+
import { contextWithRoot } from '../../utils/export'
|
|
8
|
+
import type { DevWabeTypes } from '../../utils/helper'
|
|
9
|
+
import { createSRPServer, type Ephemeral, type Session } from 'js-srp6a'
|
|
10
|
+
|
|
11
|
+
// 🛡 Valeurs factices pour mitigation des timing attacks
|
|
12
|
+
const DUMMY_SALT = 'deadbeefdeadbeefdeadbeefdeadbeef'
|
|
13
|
+
const DUMMY_VERIFIER =
|
|
14
|
+
'94c8f9b69f44fa0453a8a65129a7865ea2d70b21e645cf185d6fd42a679e524c394d4f02bba2032b10517be8c80f0f58e94302cb57cce7ce1e0a21906b6d22020b84a473d8ef58ea1f53e5204f8b83f05dc334b781fda309ad7cb8fa5c91dc81f64c114b671688b22e0f693a9c97ad2f43e6f1954c83d73e81e3dc8a963b7cbce'
|
|
15
|
+
|
|
16
|
+
type EmailPasswordSRPInterface = {
|
|
17
|
+
clientPublic: string
|
|
18
|
+
email: string
|
|
19
|
+
salt?: string
|
|
20
|
+
verifier?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class EmailPasswordSRP
|
|
24
|
+
implements ProviderInterface<DevWabeTypes, EmailPasswordSRPInterface>
|
|
25
|
+
{
|
|
26
|
+
async onSignIn({
|
|
27
|
+
input,
|
|
28
|
+
context,
|
|
29
|
+
}: AuthenticationEventsOptions<DevWabeTypes, EmailPasswordSRPInterface>) {
|
|
30
|
+
const server = createSRPServer('SHA-256', 3072)
|
|
31
|
+
|
|
32
|
+
const users = await context.wabe.controllers.database.getObjects({
|
|
33
|
+
className: 'User',
|
|
34
|
+
context: contextWithRoot(context),
|
|
35
|
+
where: {
|
|
36
|
+
email: { equalTo: input.email },
|
|
37
|
+
},
|
|
38
|
+
select: {
|
|
39
|
+
authentication: true,
|
|
40
|
+
role: true,
|
|
41
|
+
secondFA: true,
|
|
42
|
+
email: true,
|
|
43
|
+
id: true,
|
|
44
|
+
provider: true,
|
|
45
|
+
isOauth: true,
|
|
46
|
+
createdAt: true,
|
|
47
|
+
updatedAt: true,
|
|
48
|
+
},
|
|
49
|
+
first: 1,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const user = users[0]
|
|
53
|
+
|
|
54
|
+
const salt = user?.authentication?.emailPasswordSRP?.salt ?? DUMMY_SALT
|
|
55
|
+
const verifier =
|
|
56
|
+
user?.authentication?.emailPasswordSRP?.verifier ?? DUMMY_VERIFIER
|
|
57
|
+
|
|
58
|
+
let ephemeral: Ephemeral
|
|
59
|
+
try {
|
|
60
|
+
ephemeral = await server.generateEphemeral(verifier)
|
|
61
|
+
} catch {
|
|
62
|
+
throw new Error('Invalid authentication credentials')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!user || !user?.id) {
|
|
66
|
+
// Simulation d'opération pour garder le même temps
|
|
67
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
68
|
+
throw new Error('Invalid authentication credentials')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await context.wabe.controllers.database.updateObject({
|
|
72
|
+
className: 'User',
|
|
73
|
+
context: contextWithRoot(context),
|
|
74
|
+
id: user.id,
|
|
75
|
+
data: {
|
|
76
|
+
authentication: {
|
|
77
|
+
emailPasswordSRP: {
|
|
78
|
+
...user.authentication?.emailPasswordSRP,
|
|
79
|
+
serverSecret: ephemeral.secret,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
select: {},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
return { srp: { salt, serverPublic: ephemeral.public }, user }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async onSignUp({
|
|
90
|
+
input,
|
|
91
|
+
context,
|
|
92
|
+
}: AuthenticationEventsOptions<DevWabeTypes, EmailPasswordSRPInterface>) {
|
|
93
|
+
const users = await context.wabe.controllers.database.count({
|
|
94
|
+
className: 'User',
|
|
95
|
+
where: {
|
|
96
|
+
email: { equalTo: input.email },
|
|
97
|
+
},
|
|
98
|
+
context: contextWithRoot(context),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
if (users > 0) throw new Error('Not authorized to create user')
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
authenticationDataToSave: {
|
|
105
|
+
salt: input.salt,
|
|
106
|
+
verifier: input.verifier,
|
|
107
|
+
email: input.email,
|
|
108
|
+
serverSecret: null,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface EmailPasswordSRPChallengeInterface {
|
|
115
|
+
email: string
|
|
116
|
+
clientPublic: string
|
|
117
|
+
clientSessionProof: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class EmailPasswordSRPChallenge
|
|
121
|
+
implements
|
|
122
|
+
SecondaryProviderInterface<DevWabeTypes, EmailPasswordSRPChallengeInterface>
|
|
123
|
+
{
|
|
124
|
+
async onVerifyChallenge({
|
|
125
|
+
context,
|
|
126
|
+
input,
|
|
127
|
+
}: OnVerifyChallengeOptions<
|
|
128
|
+
DevWabeTypes,
|
|
129
|
+
EmailPasswordSRPChallengeInterface
|
|
130
|
+
>) {
|
|
131
|
+
const server = createSRPServer('SHA-256', 3072)
|
|
132
|
+
|
|
133
|
+
const users = await context.wabe.controllers.database.getObjects({
|
|
134
|
+
className: 'User',
|
|
135
|
+
context: contextWithRoot(context),
|
|
136
|
+
where: {
|
|
137
|
+
authentication: {
|
|
138
|
+
emailPasswordSRP: {
|
|
139
|
+
email: { equalTo: input.email },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
select: {
|
|
144
|
+
id: true,
|
|
145
|
+
authentication: true,
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const user = users[0]
|
|
150
|
+
|
|
151
|
+
const salt = user?.authentication?.emailPasswordSRP?.salt ?? DUMMY_SALT
|
|
152
|
+
const verifier =
|
|
153
|
+
user?.authentication?.emailPasswordSRP?.verifier ?? DUMMY_VERIFIER
|
|
154
|
+
const serverSecret =
|
|
155
|
+
user?.authentication?.emailPasswordSRP?.serverSecret ?? 'deadbeef'
|
|
156
|
+
|
|
157
|
+
let serverSession: Session
|
|
158
|
+
try {
|
|
159
|
+
serverSession = await server.deriveSession(
|
|
160
|
+
serverSecret,
|
|
161
|
+
input.clientPublic,
|
|
162
|
+
salt,
|
|
163
|
+
'', // no username
|
|
164
|
+
verifier,
|
|
165
|
+
input.clientSessionProof,
|
|
166
|
+
)
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error('Invalid authentication credentials')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!user || !user?.id) {
|
|
172
|
+
// Simulation pour garder un timing constant
|
|
173
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
174
|
+
throw new Error('Invalid authentication credentials')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
userId: user.id,
|
|
179
|
+
srp: {
|
|
180
|
+
serverSessionProof: serverSession.proof,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 GitHubInterface = {
|
|
10
|
+
authorizationCode: string
|
|
11
|
+
codeVerifier: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class GitHub
|
|
15
|
+
implements ProviderInterface<DevWabeTypes, GitHubInterface>
|
|
16
|
+
{
|
|
17
|
+
name = 'github'
|
|
18
|
+
onSignIn(
|
|
19
|
+
options: AuthenticationEventsOptions<DevWabeTypes, GitHubInterface>,
|
|
20
|
+
) {
|
|
21
|
+
return oAuthAuthentication(AuthenticationProvider.GitHub)(options)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
onSignUp() {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'SignUp is not implemented for Oauth provider, you should use signIn instead.',
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
|
15
|
+
implements ProviderInterface<DevWabeTypes, GoogleInterface>
|
|
16
|
+
{
|
|
17
|
+
name = 'google'
|
|
18
|
+
onSignIn(
|
|
19
|
+
options: AuthenticationEventsOptions<DevWabeTypes, GoogleInterface>,
|
|
20
|
+
) {
|
|
21
|
+
return oAuthAuthentication(AuthenticationProvider.Google)(options)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
onSignUp() {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'SignUp is not implemented for Oauth provider, you should use signIn instead.',
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -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,112 @@
|
|
|
1
|
+
import type { WabeContext } from '../../server/interface'
|
|
2
|
+
import { contextWithRoot } from '../../utils/export'
|
|
3
|
+
import type { DevWabeTypes } from '../../utils/helper'
|
|
4
|
+
import {
|
|
5
|
+
type AuthenticationEventsOptions,
|
|
6
|
+
AuthenticationProvider,
|
|
7
|
+
} from '../interface'
|
|
8
|
+
import { Google } from '../oauth'
|
|
9
|
+
import { GitHub } from '../oauth/GitHub'
|
|
10
|
+
|
|
11
|
+
export type OAuthAuthenticationInterface = {
|
|
12
|
+
authorizationCode: string
|
|
13
|
+
codeVerifier: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const getProvider = (
|
|
17
|
+
context: WabeContext<DevWabeTypes>,
|
|
18
|
+
provider: AuthenticationProvider,
|
|
19
|
+
) => {
|
|
20
|
+
const config = context.wabe.config
|
|
21
|
+
|
|
22
|
+
switch (provider) {
|
|
23
|
+
case AuthenticationProvider.Google:
|
|
24
|
+
return new Google(config)
|
|
25
|
+
case AuthenticationProvider.GitHub:
|
|
26
|
+
return new GitHub(config)
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Provider ${provider} not found`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const oAuthAuthentication =
|
|
33
|
+
(oAuthProvider: AuthenticationProvider) =>
|
|
34
|
+
async ({
|
|
35
|
+
context,
|
|
36
|
+
input,
|
|
37
|
+
}: AuthenticationEventsOptions<
|
|
38
|
+
DevWabeTypes,
|
|
39
|
+
OAuthAuthenticationInterface
|
|
40
|
+
>) => {
|
|
41
|
+
const { authorizationCode, codeVerifier } = input
|
|
42
|
+
|
|
43
|
+
const provider = getProvider(context, oAuthProvider)
|
|
44
|
+
|
|
45
|
+
const { accessToken } = await provider.validateAuthorizationCode(
|
|
46
|
+
authorizationCode,
|
|
47
|
+
codeVerifier,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const userInfoToSave = await provider.getUserInfo(accessToken)
|
|
51
|
+
|
|
52
|
+
const user = await context.wabe.controllers.database.getObjects({
|
|
53
|
+
className: 'User',
|
|
54
|
+
where: {
|
|
55
|
+
authentication: {
|
|
56
|
+
[oAuthProvider]: {
|
|
57
|
+
email: { equalTo: userInfoToSave.email },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
context: contextWithRoot(context),
|
|
62
|
+
first: 1,
|
|
63
|
+
select: {
|
|
64
|
+
authentication: true,
|
|
65
|
+
role: true,
|
|
66
|
+
secondFA: true,
|
|
67
|
+
email: true,
|
|
68
|
+
id: true,
|
|
69
|
+
provider: true,
|
|
70
|
+
isOauth: true,
|
|
71
|
+
createdAt: true,
|
|
72
|
+
updatedAt: true,
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (user.length === 0) {
|
|
77
|
+
const createdUser = await context.wabe.controllers.database.createObject({
|
|
78
|
+
className: 'User',
|
|
79
|
+
data: {
|
|
80
|
+
provider: oAuthProvider,
|
|
81
|
+
isOauth: true,
|
|
82
|
+
authentication: {
|
|
83
|
+
[oAuthProvider]: userInfoToSave,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
context: contextWithRoot(context),
|
|
87
|
+
select: {
|
|
88
|
+
authentication: true,
|
|
89
|
+
role: true,
|
|
90
|
+
secondFA: true,
|
|
91
|
+
email: true,
|
|
92
|
+
id: true,
|
|
93
|
+
provider: true,
|
|
94
|
+
isOauth: true,
|
|
95
|
+
createdAt: true,
|
|
96
|
+
updatedAt: true,
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (!createdUser) throw new Error('User not found')
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
user: createdUser,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!user[0]) throw new Error('User not found')
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
user: user[0],
|
|
111
|
+
}
|
|
112
|
+
}
|