wabe 0.6.12 → 0.6.13
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/package.json +4 -1
- package/dev/index.ts +0 -215
- package/generated/schema.graphql +0 -1945
- package/generated/wabe.ts +0 -448
- package/src/authentication/OTP.test.ts +0 -69
- package/src/authentication/OTP.ts +0 -64
- package/src/authentication/Session.test.ts +0 -629
- package/src/authentication/Session.ts +0 -517
- package/src/authentication/cookies.ts +0 -10
- package/src/authentication/defaultAuthentication.ts +0 -209
- package/src/authentication/index.ts +0 -4
- package/src/authentication/interface.ts +0 -177
- package/src/authentication/oauth/GitHub.test.ts +0 -91
- package/src/authentication/oauth/GitHub.ts +0 -121
- package/src/authentication/oauth/Google.test.ts +0 -91
- package/src/authentication/oauth/Google.ts +0 -101
- package/src/authentication/oauth/Oauth2Client.test.ts +0 -219
- package/src/authentication/oauth/Oauth2Client.ts +0 -135
- package/src/authentication/oauth/index.ts +0 -2
- package/src/authentication/oauth/utils.test.ts +0 -33
- package/src/authentication/oauth/utils.ts +0 -27
- package/src/authentication/providers/EmailOTP.test.ts +0 -127
- package/src/authentication/providers/EmailOTP.ts +0 -95
- package/src/authentication/providers/EmailPassword.test.ts +0 -263
- package/src/authentication/providers/EmailPassword.ts +0 -138
- package/src/authentication/providers/EmailPasswordSRP.test.ts +0 -208
- package/src/authentication/providers/EmailPasswordSRP.ts +0 -191
- package/src/authentication/providers/GitHub.ts +0 -24
- package/src/authentication/providers/Google.ts +0 -24
- package/src/authentication/providers/OAuth.test.ts +0 -185
- package/src/authentication/providers/OAuth.ts +0 -106
- package/src/authentication/providers/PhonePassword.test.ts +0 -221
- package/src/authentication/providers/PhonePassword.ts +0 -136
- package/src/authentication/providers/QRCodeOTP.test.ts +0 -77
- package/src/authentication/providers/QRCodeOTP.ts +0 -69
- package/src/authentication/providers/index.ts +0 -6
- package/src/authentication/resolvers/refreshResolver.test.ts +0 -30
- package/src/authentication/resolvers/refreshResolver.ts +0 -19
- package/src/authentication/resolvers/signInWithResolver.inte.test.ts +0 -59
- package/src/authentication/resolvers/signInWithResolver.test.ts +0 -306
- package/src/authentication/resolvers/signInWithResolver.ts +0 -106
- package/src/authentication/resolvers/signOutResolver.test.ts +0 -38
- package/src/authentication/resolvers/signOutResolver.ts +0 -18
- package/src/authentication/resolvers/signUpWithResolver.test.ts +0 -180
- package/src/authentication/resolvers/signUpWithResolver.ts +0 -68
- package/src/authentication/resolvers/verifyChallenge.test.ts +0 -230
- package/src/authentication/resolvers/verifyChallenge.ts +0 -78
- package/src/authentication/roles.test.ts +0 -49
- package/src/authentication/roles.ts +0 -40
- package/src/authentication/security.ts +0 -278
- package/src/authentication/utils.test.ts +0 -97
- package/src/authentication/utils.ts +0 -39
- package/src/cache/InMemoryCache.test.ts +0 -62
- package/src/cache/InMemoryCache.ts +0 -45
- package/src/cron/index.test.ts +0 -17
- package/src/cron/index.ts +0 -43
- package/src/database/DatabaseController.test.ts +0 -613
- package/src/database/DatabaseController.ts +0 -1415
- package/src/database/index.test.ts +0 -1551
- package/src/database/index.ts +0 -9
- package/src/database/interface.ts +0 -308
- package/src/email/DevAdapter.ts +0 -7
- package/src/email/EmailController.test.ts +0 -29
- package/src/email/EmailController.ts +0 -13
- package/src/email/index.ts +0 -2
- package/src/email/interface.ts +0 -36
- package/src/email/templates/sendOtpCode.ts +0 -120
- package/src/file/FileController.ts +0 -28
- package/src/file/FileDevAdapter.ts +0 -51
- package/src/file/hookDeleteFile.ts +0 -25
- package/src/file/hookReadFile.ts +0 -66
- package/src/file/hookUploadFile.ts +0 -52
- package/src/file/index.test.ts +0 -1031
- package/src/file/index.ts +0 -2
- package/src/file/interface.ts +0 -63
- package/src/file/security.ts +0 -156
- package/src/graphql/GraphQLSchema.test.ts +0 -5099
- package/src/graphql/GraphQLSchema.ts +0 -886
- package/src/graphql/index.ts +0 -2
- package/src/graphql/parseGraphqlSchema.ts +0 -85
- package/src/graphql/parser.test.ts +0 -203
- package/src/graphql/parser.ts +0 -707
- package/src/graphql/pointerAndRelationFunction.ts +0 -191
- package/src/graphql/resolvers.ts +0 -464
- package/src/graphql/tests/aggregation.test.ts +0 -1115
- package/src/graphql/tests/e2e.test.ts +0 -590
- package/src/graphql/tests/scalars.test.ts +0 -250
- package/src/graphql/types.ts +0 -227
- package/src/hooks/HookObject.test.ts +0 -122
- package/src/hooks/HookObject.ts +0 -165
- package/src/hooks/authentication.ts +0 -67
- package/src/hooks/createUser.test.ts +0 -77
- package/src/hooks/createUser.ts +0 -10
- package/src/hooks/defaultFields.test.ts +0 -176
- package/src/hooks/defaultFields.ts +0 -32
- package/src/hooks/deleteSession.test.ts +0 -181
- package/src/hooks/deleteSession.ts +0 -20
- package/src/hooks/hashFieldHook.test.ts +0 -152
- package/src/hooks/hashFieldHook.ts +0 -89
- package/src/hooks/index.test.ts +0 -258
- package/src/hooks/index.ts +0 -420
- package/src/hooks/permissions.test.ts +0 -412
- package/src/hooks/permissions.ts +0 -93
- package/src/hooks/protected.test.ts +0 -551
- package/src/hooks/protected.ts +0 -74
- package/src/hooks/searchableFields.test.ts +0 -147
- package/src/hooks/searchableFields.ts +0 -86
- package/src/hooks/session.test.ts +0 -134
- package/src/hooks/session.ts +0 -76
- package/src/hooks/setEmail.test.ts +0 -216
- package/src/hooks/setEmail.ts +0 -33
- package/src/hooks/setupAcl.test.ts +0 -618
- package/src/hooks/setupAcl.ts +0 -25
- package/src/hooks/virtualFields.test.ts +0 -228
- package/src/hooks/virtualFields.ts +0 -48
- package/src/index.ts +0 -9
- package/src/schema/Schema.test.ts +0 -482
- package/src/schema/Schema.ts +0 -839
- package/src/schema/defaultResolvers.ts +0 -93
- package/src/schema/index.ts +0 -1
- package/src/schema/resolvers/meResolver.test.ts +0 -62
- package/src/schema/resolvers/meResolver.ts +0 -10
- package/src/schema/resolvers/resetPassword.test.ts +0 -341
- package/src/schema/resolvers/resetPassword.ts +0 -63
- package/src/schema/resolvers/sendEmail.test.ts +0 -118
- package/src/schema/resolvers/sendEmail.ts +0 -21
- package/src/schema/resolvers/sendOtpCode.test.ts +0 -141
- package/src/schema/resolvers/sendOtpCode.ts +0 -52
- package/src/security.test.ts +0 -4136
- package/src/server/defaultSessionHandler.test.ts +0 -62
- package/src/server/defaultSessionHandler.ts +0 -104
- package/src/server/generateCodegen.ts +0 -433
- package/src/server/index.test.ts +0 -843
- package/src/server/index.ts +0 -336
- package/src/server/interface.ts +0 -11
- package/src/server/routes/authHandler.ts +0 -171
- package/src/server/routes/index.ts +0 -48
- package/src/utils/crypto.test.ts +0 -41
- package/src/utils/crypto.ts +0 -105
- package/src/utils/database.ts +0 -8
- package/src/utils/export.ts +0 -12
- package/src/utils/helper.ts +0 -204
- package/src/utils/index.test.ts +0 -11
- package/src/utils/index.ts +0 -196
- package/src/utils/preload.ts +0 -8
- package/src/utils/testHelper.ts +0 -124
- package/tsconfig.json +0 -32
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, beforeEach, mock, spyOn } from 'bun:test'
|
|
2
|
-
import { verifyChallengeResolver } from './verifyChallenge'
|
|
3
|
-
import type { WabeContext } from '../../server/interface'
|
|
4
|
-
import { Session } from '../Session'
|
|
5
|
-
import { createMfaChallenge } from '../security'
|
|
6
|
-
|
|
7
|
-
describe('verifyChallenge', () => {
|
|
8
|
-
const mockOnVerifyChallenge = mock(() => Promise.resolve(true))
|
|
9
|
-
let pendingChallenges: Array<{ token: string; provider: string; expiresAt: Date }> = []
|
|
10
|
-
const mockGetObject = mock(() => Promise.resolve({ pendingChallenges }))
|
|
11
|
-
const mockUpdateObject = mock((options: any) => {
|
|
12
|
-
pendingChallenges = options?.data?.pendingChallenges || []
|
|
13
|
-
return Promise.resolve({})
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
const context: WabeContext<any> = {
|
|
17
|
-
sessionId: 'sessionId',
|
|
18
|
-
user: {
|
|
19
|
-
id: 'userId',
|
|
20
|
-
} as any,
|
|
21
|
-
wabe: {
|
|
22
|
-
controllers: {
|
|
23
|
-
database: {
|
|
24
|
-
getObject: mockGetObject,
|
|
25
|
-
updateObject: mockUpdateObject,
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
config: {
|
|
29
|
-
authentication: {
|
|
30
|
-
customAuthenticationMethods: [
|
|
31
|
-
{
|
|
32
|
-
name: 'fakeOtp',
|
|
33
|
-
input: {
|
|
34
|
-
code: {
|
|
35
|
-
type: 'String',
|
|
36
|
-
required: true,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
provider: {
|
|
40
|
-
onVerifyChallenge: mockOnVerifyChallenge,
|
|
41
|
-
onSendChallenge: () => Promise.resolve(),
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
} as any
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
mockOnVerifyChallenge.mockClear()
|
|
52
|
-
mockGetObject.mockClear()
|
|
53
|
-
mockUpdateObject.mockClear()
|
|
54
|
-
pendingChallenges = []
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('should throw an error if no one factor is provided', () => {
|
|
58
|
-
expect(
|
|
59
|
-
verifyChallengeResolver(
|
|
60
|
-
undefined,
|
|
61
|
-
{
|
|
62
|
-
input: {},
|
|
63
|
-
},
|
|
64
|
-
context,
|
|
65
|
-
),
|
|
66
|
-
).rejects.toThrow('One factor is required')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('should throw an error if more than one factor is provided', () => {
|
|
70
|
-
expect(
|
|
71
|
-
verifyChallengeResolver(
|
|
72
|
-
undefined,
|
|
73
|
-
{
|
|
74
|
-
input: {
|
|
75
|
-
secondFA: {
|
|
76
|
-
// @ts-expect-error
|
|
77
|
-
factor1: {},
|
|
78
|
-
factor2: {},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
context,
|
|
83
|
-
),
|
|
84
|
-
).rejects.toThrow('Only one factor is allowed')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should throw an error if the onVerifyChallenge failed', () => {
|
|
88
|
-
mockOnVerifyChallenge.mockResolvedValue(false as never)
|
|
89
|
-
|
|
90
|
-
expect(
|
|
91
|
-
verifyChallengeResolver(
|
|
92
|
-
undefined,
|
|
93
|
-
{
|
|
94
|
-
input: {
|
|
95
|
-
secondFA: {
|
|
96
|
-
// @ts-expect-error
|
|
97
|
-
fakeOtp: {
|
|
98
|
-
code: '123456',
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
context,
|
|
104
|
-
),
|
|
105
|
-
).rejects.toThrow('Invalid challenge')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should return userId if the verifyChallenge is correct', async () => {
|
|
109
|
-
const spyCreateSession = spyOn(Session.prototype, 'create').mockResolvedValue({
|
|
110
|
-
accessToken: 'accessToken',
|
|
111
|
-
refreshToken: 'refreshToken',
|
|
112
|
-
sessionId: 'sessionId',
|
|
113
|
-
csrfToken: 'csrfToken',
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
mockOnVerifyChallenge.mockResolvedValue({ userId: 'userId' } as never)
|
|
117
|
-
|
|
118
|
-
expect(
|
|
119
|
-
await verifyChallengeResolver(
|
|
120
|
-
undefined,
|
|
121
|
-
{
|
|
122
|
-
input: {
|
|
123
|
-
secondFA: {
|
|
124
|
-
// @ts-expect-error
|
|
125
|
-
fakeOtp: {
|
|
126
|
-
code: '123456',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
context,
|
|
132
|
-
),
|
|
133
|
-
).toEqual({
|
|
134
|
-
accessToken: 'accessToken',
|
|
135
|
-
srp: undefined,
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
expect(mockOnVerifyChallenge).toHaveBeenCalledTimes(1)
|
|
139
|
-
expect(mockOnVerifyChallenge).toHaveBeenCalledWith({
|
|
140
|
-
input: { code: '123456' },
|
|
141
|
-
context: expect.any(Object),
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
expect(spyCreateSession).toHaveBeenCalledTimes(1)
|
|
145
|
-
expect(spyCreateSession).toHaveBeenCalledWith('userId', context)
|
|
146
|
-
|
|
147
|
-
spyCreateSession.mockRestore()
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('should require challenge token in production', () => {
|
|
151
|
-
mockOnVerifyChallenge.mockResolvedValue({ userId: 'userId' } as never)
|
|
152
|
-
|
|
153
|
-
const productionContext = {
|
|
154
|
-
...context,
|
|
155
|
-
wabe: {
|
|
156
|
-
...context.wabe,
|
|
157
|
-
config: {
|
|
158
|
-
...context.wabe.config,
|
|
159
|
-
isProduction: true,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
} as WabeContext<any>
|
|
163
|
-
|
|
164
|
-
expect(
|
|
165
|
-
verifyChallengeResolver(
|
|
166
|
-
undefined,
|
|
167
|
-
{
|
|
168
|
-
input: {
|
|
169
|
-
secondFA: {
|
|
170
|
-
// @ts-expect-error
|
|
171
|
-
fakeOtp: {
|
|
172
|
-
code: '123456',
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
productionContext,
|
|
178
|
-
),
|
|
179
|
-
).rejects.toThrow('Invalid challenge')
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('should validate challenge token in production', async () => {
|
|
183
|
-
const spyCreateSession = spyOn(Session.prototype, 'create').mockResolvedValue({
|
|
184
|
-
accessToken: 'accessToken',
|
|
185
|
-
refreshToken: 'refreshToken',
|
|
186
|
-
sessionId: 'sessionId',
|
|
187
|
-
csrfToken: 'csrfToken',
|
|
188
|
-
})
|
|
189
|
-
mockOnVerifyChallenge.mockResolvedValue({ userId: 'userId' } as never)
|
|
190
|
-
|
|
191
|
-
const productionContext = {
|
|
192
|
-
...context,
|
|
193
|
-
wabe: {
|
|
194
|
-
...context.wabe,
|
|
195
|
-
config: {
|
|
196
|
-
...context.wabe.config,
|
|
197
|
-
isProduction: true,
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
} as WabeContext<any>
|
|
201
|
-
|
|
202
|
-
const challengeToken = await createMfaChallenge(productionContext, {
|
|
203
|
-
userId: 'userId',
|
|
204
|
-
provider: 'fakeOtp',
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
expect(
|
|
208
|
-
await verifyChallengeResolver(
|
|
209
|
-
undefined,
|
|
210
|
-
{
|
|
211
|
-
input: {
|
|
212
|
-
challengeToken,
|
|
213
|
-
secondFA: {
|
|
214
|
-
// @ts-expect-error
|
|
215
|
-
fakeOtp: {
|
|
216
|
-
code: '123456',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
productionContext,
|
|
222
|
-
),
|
|
223
|
-
).toEqual({
|
|
224
|
-
accessToken: 'accessToken',
|
|
225
|
-
srp: undefined,
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
spyCreateSession.mockRestore()
|
|
229
|
-
})
|
|
230
|
-
})
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { VerifyChallengeInput } from '../../../generated/wabe'
|
|
2
|
-
import type { WabeContext } from '../../server/interface'
|
|
3
|
-
import type { DevWabeTypes } from '../../utils/helper'
|
|
4
|
-
import { getSessionCookieSameSite } from '../cookies'
|
|
5
|
-
import { consumeMfaChallenge, shouldRequireMfaChallenge } from '../security'
|
|
6
|
-
import { Session } from '../Session'
|
|
7
|
-
import type { SecondaryProviderInterface } from '../interface'
|
|
8
|
-
import { getAuthenticationMethod } from '../utils'
|
|
9
|
-
|
|
10
|
-
export const verifyChallengeResolver = async (
|
|
11
|
-
_: any,
|
|
12
|
-
{
|
|
13
|
-
input,
|
|
14
|
-
}: {
|
|
15
|
-
input: VerifyChallengeInput
|
|
16
|
-
},
|
|
17
|
-
context: WabeContext<DevWabeTypes>,
|
|
18
|
-
) => {
|
|
19
|
-
if (!input.secondFA) throw new Error('One factor is required')
|
|
20
|
-
|
|
21
|
-
const listOfFactor = Object.keys(input.secondFA)
|
|
22
|
-
|
|
23
|
-
if (listOfFactor.length > 1) throw new Error('Only one factor is allowed')
|
|
24
|
-
|
|
25
|
-
const { provider, name } = getAuthenticationMethod<any, SecondaryProviderInterface<DevWabeTypes>>(
|
|
26
|
-
listOfFactor,
|
|
27
|
-
context,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
const result = await provider.onVerifyChallenge({
|
|
31
|
-
context,
|
|
32
|
-
// @ts-expect-error
|
|
33
|
-
input: input.secondFA[name],
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
if (!result?.userId) throw new Error('Invalid challenge')
|
|
37
|
-
|
|
38
|
-
if (shouldRequireMfaChallenge(context)) {
|
|
39
|
-
const challengeToken = input.challengeToken
|
|
40
|
-
if (!challengeToken) throw new Error('Invalid challenge')
|
|
41
|
-
|
|
42
|
-
const isValidChallenge = await consumeMfaChallenge(context, {
|
|
43
|
-
challengeToken,
|
|
44
|
-
userId: result.userId,
|
|
45
|
-
provider: name,
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
if (!isValidChallenge) throw new Error('Invalid challenge')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const session = new Session<DevWabeTypes>()
|
|
52
|
-
|
|
53
|
-
const { accessToken, refreshToken } = await session.create(result.userId, context)
|
|
54
|
-
|
|
55
|
-
if (context.wabe.config.authentication?.session?.cookieSession) {
|
|
56
|
-
const accessTokenExpiresAt = session.getAccessTokenExpireAt(context.wabe.config)
|
|
57
|
-
const refreshTokenExpiresAt = session.getRefreshTokenExpireAt(context.wabe.config)
|
|
58
|
-
const sameSite = getSessionCookieSameSite(context.wabe.config)
|
|
59
|
-
|
|
60
|
-
context.response?.setCookie('refreshToken', refreshToken, {
|
|
61
|
-
httpOnly: true,
|
|
62
|
-
path: '/',
|
|
63
|
-
sameSite,
|
|
64
|
-
secure: true,
|
|
65
|
-
expires: refreshTokenExpiresAt,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
context.response?.setCookie('accessToken', accessToken, {
|
|
69
|
-
httpOnly: true,
|
|
70
|
-
path: '/',
|
|
71
|
-
sameSite,
|
|
72
|
-
secure: true,
|
|
73
|
-
expires: accessTokenExpiresAt,
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { accessToken, srp: result.srp }
|
|
78
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, it, expect } from 'bun:test'
|
|
2
|
-
import type { Wabe } from '../server'
|
|
3
|
-
import type { DevWabeTypes } from '../utils/helper'
|
|
4
|
-
import { initializeRoles } from './roles'
|
|
5
|
-
import { setupTests, closeTests } from '../utils/testHelper'
|
|
6
|
-
|
|
7
|
-
describe('roles', () => {
|
|
8
|
-
let wabe: Wabe<DevWabeTypes>
|
|
9
|
-
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
const setup = await setupTests()
|
|
12
|
-
wabe = setup.wabe
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await closeTests(wabe)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should create all roles', async () => {
|
|
20
|
-
await wabe.controllers.database.clearDatabase()
|
|
21
|
-
|
|
22
|
-
await initializeRoles(wabe)
|
|
23
|
-
|
|
24
|
-
const res = await wabe.controllers.database.getObjects({
|
|
25
|
-
className: 'Role',
|
|
26
|
-
context: { isRoot: true, wabe: wabe },
|
|
27
|
-
select: { name: true },
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
expect(res.length).toEqual(4)
|
|
31
|
-
expect(res.map((role) => role?.name)).toEqual(['Client', 'Client2', 'Client3', 'Admin'])
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should not create all roles if there already exist', async () => {
|
|
35
|
-
await wabe.controllers.database.clearDatabase()
|
|
36
|
-
|
|
37
|
-
await initializeRoles(wabe)
|
|
38
|
-
await initializeRoles(wabe)
|
|
39
|
-
|
|
40
|
-
const res = await wabe.controllers.database.getObjects({
|
|
41
|
-
className: 'Role',
|
|
42
|
-
context: { isRoot: true, wabe: wabe },
|
|
43
|
-
select: { name: true },
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
expect(res.length).toEqual(4)
|
|
47
|
-
expect(res.map((role) => role?.name)).toEqual(['Client', 'Client2', 'Client3', 'Admin'])
|
|
48
|
-
})
|
|
49
|
-
})
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { notEmpty, type Wabe } from '..'
|
|
2
|
-
import type { DevWabeTypes } from '../utils/helper'
|
|
3
|
-
|
|
4
|
-
export const initializeRoles = async (wabe: Wabe<DevWabeTypes>) => {
|
|
5
|
-
const roles = wabe.config?.authentication?.roles || []
|
|
6
|
-
|
|
7
|
-
if (roles.length === 0) return
|
|
8
|
-
|
|
9
|
-
const res = await wabe.controllers.database.getObjects({
|
|
10
|
-
className: 'Role',
|
|
11
|
-
context: {
|
|
12
|
-
isRoot: true,
|
|
13
|
-
wabe,
|
|
14
|
-
},
|
|
15
|
-
select: { name: true },
|
|
16
|
-
where: {
|
|
17
|
-
name: {
|
|
18
|
-
in: roles,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const alreadyCreatedRoles = res.map((role) => role?.name).filter(notEmpty)
|
|
24
|
-
|
|
25
|
-
const objectsToCreate = roles
|
|
26
|
-
.filter((role) => !alreadyCreatedRoles.includes(role))
|
|
27
|
-
.map((role) => ({ name: role }))
|
|
28
|
-
|
|
29
|
-
if (objectsToCreate.length === 0) return
|
|
30
|
-
|
|
31
|
-
await wabe.controllers.database.createObjects({
|
|
32
|
-
className: 'Role',
|
|
33
|
-
context: {
|
|
34
|
-
isRoot: true,
|
|
35
|
-
wabe,
|
|
36
|
-
},
|
|
37
|
-
data: objectsToCreate,
|
|
38
|
-
select: {},
|
|
39
|
-
})
|
|
40
|
-
}
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto'
|
|
2
|
-
import type { WabeContext } from '../server/interface'
|
|
3
|
-
import type { WabeTypes } from '../server'
|
|
4
|
-
import { contextWithRoot, getDatabaseController } from '../utils/export'
|
|
5
|
-
import { DevWabeTypes } from 'src/utils/helper'
|
|
6
|
-
|
|
7
|
-
type RateLimitScope = 'signIn' | 'signUp' | 'verifyChallenge'
|
|
8
|
-
|
|
9
|
-
type RateLimitOptions = {
|
|
10
|
-
enabled: boolean
|
|
11
|
-
maxAttempts: number
|
|
12
|
-
windowMs: number
|
|
13
|
-
blockDurationMs: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type RateLimitState = {
|
|
17
|
-
attempts: number
|
|
18
|
-
windowStartedAt: number
|
|
19
|
-
blockedUntil: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type PendingChallenge = {
|
|
23
|
-
token: string
|
|
24
|
-
provider: string
|
|
25
|
-
expiresAt: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const DEFAULT_SIGN_IN_RATE_LIMIT = {
|
|
29
|
-
maxAttempts: 10,
|
|
30
|
-
windowMs: 10 * 60 * 1000,
|
|
31
|
-
blockDurationMs: 15 * 60 * 1000,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const DEFAULT_SIGN_UP_RATE_LIMIT = {
|
|
35
|
-
maxAttempts: 10,
|
|
36
|
-
windowMs: 10 * 60 * 1000,
|
|
37
|
-
blockDurationMs: 15 * 60 * 1000,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const DEFAULT_VERIFY_CHALLENGE_RATE_LIMIT = {
|
|
41
|
-
maxAttempts: 10,
|
|
42
|
-
windowMs: 10 * 60 * 1000,
|
|
43
|
-
blockDurationMs: 15 * 60 * 1000,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const DEFAULT_MFA_CHALLENGE_TTL_MS = 5 * 60 * 1000
|
|
47
|
-
|
|
48
|
-
const rateLimitStorage = new Map<string, RateLimitState>()
|
|
49
|
-
|
|
50
|
-
const getRateLimitOptions = <T extends WabeTypes>(
|
|
51
|
-
context: WabeContext<T>,
|
|
52
|
-
scope: RateLimitScope,
|
|
53
|
-
): RateLimitOptions => {
|
|
54
|
-
const wabeConfig = context.wabe.config
|
|
55
|
-
const securityConfig = wabeConfig?.authentication?.security
|
|
56
|
-
const scopeConfigMap = {
|
|
57
|
-
signIn: securityConfig?.signInRateLimit,
|
|
58
|
-
signUp: securityConfig?.signUpRateLimit,
|
|
59
|
-
verifyChallenge: securityConfig?.verifyChallengeRateLimit,
|
|
60
|
-
}
|
|
61
|
-
const defaultsMap = {
|
|
62
|
-
signIn: DEFAULT_SIGN_IN_RATE_LIMIT,
|
|
63
|
-
signUp: DEFAULT_SIGN_UP_RATE_LIMIT,
|
|
64
|
-
verifyChallenge: DEFAULT_VERIFY_CHALLENGE_RATE_LIMIT,
|
|
65
|
-
}
|
|
66
|
-
const scopeConfig = scopeConfigMap[scope]
|
|
67
|
-
const defaults = defaultsMap[scope]
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
enabled: scopeConfig?.enabled ?? !!wabeConfig?.isProduction,
|
|
71
|
-
maxAttempts: scopeConfig?.maxAttempts ?? defaults.maxAttempts,
|
|
72
|
-
windowMs: scopeConfig?.windowMs ?? defaults.windowMs,
|
|
73
|
-
blockDurationMs: scopeConfig?.blockDurationMs ?? defaults.blockDurationMs,
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const getRateLimitKey = (scope: RateLimitScope, key: string) =>
|
|
78
|
-
`${scope}:${key.trim().toLowerCase()}`
|
|
79
|
-
|
|
80
|
-
export const isRateLimited = <T extends WabeTypes>(
|
|
81
|
-
context: WabeContext<T>,
|
|
82
|
-
scope: RateLimitScope,
|
|
83
|
-
key: string,
|
|
84
|
-
): boolean => {
|
|
85
|
-
const options = getRateLimitOptions(context, scope)
|
|
86
|
-
|
|
87
|
-
if (!options.enabled) return false
|
|
88
|
-
|
|
89
|
-
const now = Date.now()
|
|
90
|
-
const storageKey = getRateLimitKey(scope, key)
|
|
91
|
-
const state = rateLimitStorage.get(storageKey)
|
|
92
|
-
|
|
93
|
-
if (!state) return false
|
|
94
|
-
|
|
95
|
-
if (state.blockedUntil <= now) {
|
|
96
|
-
if (state.windowStartedAt + options.windowMs <= now) {
|
|
97
|
-
rateLimitStorage.delete(storageKey)
|
|
98
|
-
}
|
|
99
|
-
return false
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return true
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export const registerRateLimitFailure = <T extends WabeTypes>(
|
|
106
|
-
context: WabeContext<T>,
|
|
107
|
-
scope: RateLimitScope,
|
|
108
|
-
key: string,
|
|
109
|
-
) => {
|
|
110
|
-
const options = getRateLimitOptions(context, scope)
|
|
111
|
-
|
|
112
|
-
if (!options.enabled) return
|
|
113
|
-
|
|
114
|
-
const now = Date.now()
|
|
115
|
-
const storageKey = getRateLimitKey(scope, key)
|
|
116
|
-
const currentState = rateLimitStorage.get(storageKey)
|
|
117
|
-
const hasExpiredBlock =
|
|
118
|
-
!!currentState && currentState.blockedUntil > 0 && currentState.blockedUntil <= now
|
|
119
|
-
const shouldResetWindow =
|
|
120
|
-
!currentState || currentState.windowStartedAt + options.windowMs <= now || hasExpiredBlock
|
|
121
|
-
|
|
122
|
-
const state: RateLimitState = shouldResetWindow
|
|
123
|
-
? {
|
|
124
|
-
attempts: 0,
|
|
125
|
-
windowStartedAt: now,
|
|
126
|
-
blockedUntil: 0,
|
|
127
|
-
}
|
|
128
|
-
: currentState
|
|
129
|
-
|
|
130
|
-
state.attempts += 1
|
|
131
|
-
|
|
132
|
-
if (state.attempts >= options.maxAttempts) {
|
|
133
|
-
state.attempts = 0
|
|
134
|
-
state.windowStartedAt = now
|
|
135
|
-
state.blockedUntil = now + options.blockDurationMs
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
rateLimitStorage.set(storageKey, state)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export const clearRateLimit = <T extends WabeTypes>(
|
|
142
|
-
context: WabeContext<T>,
|
|
143
|
-
scope: RateLimitScope,
|
|
144
|
-
key: string,
|
|
145
|
-
) => {
|
|
146
|
-
const options = getRateLimitOptions(context, scope)
|
|
147
|
-
|
|
148
|
-
if (!options.enabled) return
|
|
149
|
-
|
|
150
|
-
rateLimitStorage.delete(getRateLimitKey(scope, key))
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const getMfaChallengeTTL = <T extends WabeTypes>(context: WabeContext<T>) =>
|
|
154
|
-
context.wabe.config?.authentication?.security?.mfaChallengeTtlMs || DEFAULT_MFA_CHALLENGE_TTL_MS
|
|
155
|
-
|
|
156
|
-
const parsePendingChallenges = (input: unknown): PendingChallenge[] => {
|
|
157
|
-
if (!Array.isArray(input)) return []
|
|
158
|
-
|
|
159
|
-
return input.reduce((acc, item) => {
|
|
160
|
-
if (!item || typeof item !== 'object') return acc
|
|
161
|
-
const challenge = item as Record<string, unknown>
|
|
162
|
-
const token = typeof challenge.token === 'string' ? challenge.token : null
|
|
163
|
-
const provider = typeof challenge.provider === 'string' ? challenge.provider : null
|
|
164
|
-
const expiresAtRaw = challenge.expiresAt
|
|
165
|
-
const expiresAt = new Date(expiresAtRaw as string | Date).getTime()
|
|
166
|
-
|
|
167
|
-
if (!token || !provider || Number.isNaN(expiresAt)) return acc
|
|
168
|
-
|
|
169
|
-
acc.push({
|
|
170
|
-
token,
|
|
171
|
-
provider: provider.toLowerCase(),
|
|
172
|
-
expiresAt,
|
|
173
|
-
})
|
|
174
|
-
return acc
|
|
175
|
-
}, [] as PendingChallenge[])
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const pruneExpiredChallenges = (challenges: PendingChallenge[]) => {
|
|
179
|
-
const now = Date.now()
|
|
180
|
-
return challenges.filter((challenge) => challenge.expiresAt > now)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const getUserPendingChallenges = async (
|
|
184
|
-
context: WabeContext<DevWabeTypes>,
|
|
185
|
-
userId: string,
|
|
186
|
-
): Promise<PendingChallenge[] | null> => {
|
|
187
|
-
try {
|
|
188
|
-
const user = await getDatabaseController(context).getObject({
|
|
189
|
-
className: 'User',
|
|
190
|
-
id: userId,
|
|
191
|
-
context: contextWithRoot(context),
|
|
192
|
-
select: {
|
|
193
|
-
pendingChallenges: true,
|
|
194
|
-
},
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
return parsePendingChallenges(user?.pendingChallenges)
|
|
198
|
-
} catch {
|
|
199
|
-
return null
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const saveUserPendingChallenges = async (
|
|
204
|
-
context: WabeContext<DevWabeTypes>,
|
|
205
|
-
userId: string,
|
|
206
|
-
challenges: PendingChallenge[],
|
|
207
|
-
) =>
|
|
208
|
-
getDatabaseController(context).updateObject({
|
|
209
|
-
className: 'User',
|
|
210
|
-
id: userId,
|
|
211
|
-
context: contextWithRoot(context),
|
|
212
|
-
data: {
|
|
213
|
-
pendingChallenges: challenges.map((challenge) => ({
|
|
214
|
-
token: challenge.token,
|
|
215
|
-
provider: challenge.provider,
|
|
216
|
-
expiresAt: new Date(challenge.expiresAt),
|
|
217
|
-
})),
|
|
218
|
-
},
|
|
219
|
-
select: {},
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
export const createMfaChallenge = async (
|
|
223
|
-
context: WabeContext<DevWabeTypes>,
|
|
224
|
-
{ userId, provider }: { userId: string; provider: string },
|
|
225
|
-
): Promise<string> => {
|
|
226
|
-
const token = crypto.randomUUID()
|
|
227
|
-
const expiresAt = Date.now() + getMfaChallengeTTL(context)
|
|
228
|
-
|
|
229
|
-
const currentChallenges = (await getUserPendingChallenges(context, userId)) || []
|
|
230
|
-
const nextChallenges = [
|
|
231
|
-
...pruneExpiredChallenges(currentChallenges),
|
|
232
|
-
{
|
|
233
|
-
token,
|
|
234
|
-
provider: provider.toLowerCase(),
|
|
235
|
-
expiresAt,
|
|
236
|
-
},
|
|
237
|
-
]
|
|
238
|
-
|
|
239
|
-
await saveUserPendingChallenges(context, userId, nextChallenges)
|
|
240
|
-
|
|
241
|
-
return token
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export const consumeMfaChallenge = async (
|
|
245
|
-
context: WabeContext<DevWabeTypes>,
|
|
246
|
-
{
|
|
247
|
-
challengeToken,
|
|
248
|
-
userId,
|
|
249
|
-
provider,
|
|
250
|
-
}: {
|
|
251
|
-
challengeToken: string
|
|
252
|
-
userId: string
|
|
253
|
-
provider: string
|
|
254
|
-
},
|
|
255
|
-
): Promise<boolean> => {
|
|
256
|
-
const currentChallenges = await getUserPendingChallenges(context, userId)
|
|
257
|
-
if (!currentChallenges) return false
|
|
258
|
-
|
|
259
|
-
const activeChallenges = pruneExpiredChallenges(currentChallenges)
|
|
260
|
-
const normalizedProvider = provider.toLowerCase()
|
|
261
|
-
const isValid = activeChallenges.some(
|
|
262
|
-
(challenge) => challenge.token === challengeToken && challenge.provider === normalizedProvider,
|
|
263
|
-
)
|
|
264
|
-
const remainingChallenges = activeChallenges.filter(
|
|
265
|
-
(challenge) =>
|
|
266
|
-
!(challenge.token === challengeToken && challenge.provider === normalizedProvider),
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
if (remainingChallenges.length !== currentChallenges.length || isValid) {
|
|
270
|
-
await saveUserPendingChallenges(context, userId, remainingChallenges)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return isValid
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export const shouldRequireMfaChallenge = <T extends WabeTypes>(context: WabeContext<T>) =>
|
|
277
|
-
context.wabe.config?.authentication?.security?.requireMfaChallengeInProduction ??
|
|
278
|
-
!!context.wabe.config?.isProduction
|