wabe 0.6.12 → 0.6.14

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 (156) hide show
  1. package/dist/database/DatabaseController.d.ts +2 -0
  2. package/dist/file/FileDevAdapter.d.ts +1 -0
  3. package/dist/graphql/pointerAndRelationFunction.d.ts +6 -0
  4. package/dist/index.js +3827 -3541
  5. package/dist/schema/Schema.d.ts +2 -2
  6. package/dist/server/generateCodegen.d.ts +10 -0
  7. package/dist/server/index.d.ts +2 -1
  8. package/dist/utils/objectKeys.d.ts +1 -0
  9. package/package.json +7 -4
  10. package/dev/index.ts +0 -215
  11. package/dist/schema/resolvers/sendEmail.d.ts +0 -1
  12. package/generated/schema.graphql +0 -1945
  13. package/generated/wabe.ts +0 -448
  14. package/src/authentication/OTP.test.ts +0 -69
  15. package/src/authentication/OTP.ts +0 -64
  16. package/src/authentication/Session.test.ts +0 -629
  17. package/src/authentication/Session.ts +0 -517
  18. package/src/authentication/cookies.ts +0 -10
  19. package/src/authentication/defaultAuthentication.ts +0 -209
  20. package/src/authentication/index.ts +0 -4
  21. package/src/authentication/interface.ts +0 -177
  22. package/src/authentication/oauth/GitHub.test.ts +0 -91
  23. package/src/authentication/oauth/GitHub.ts +0 -121
  24. package/src/authentication/oauth/Google.test.ts +0 -91
  25. package/src/authentication/oauth/Google.ts +0 -101
  26. package/src/authentication/oauth/Oauth2Client.test.ts +0 -219
  27. package/src/authentication/oauth/Oauth2Client.ts +0 -135
  28. package/src/authentication/oauth/index.ts +0 -2
  29. package/src/authentication/oauth/utils.test.ts +0 -33
  30. package/src/authentication/oauth/utils.ts +0 -27
  31. package/src/authentication/providers/EmailOTP.test.ts +0 -127
  32. package/src/authentication/providers/EmailOTP.ts +0 -95
  33. package/src/authentication/providers/EmailPassword.test.ts +0 -263
  34. package/src/authentication/providers/EmailPassword.ts +0 -138
  35. package/src/authentication/providers/EmailPasswordSRP.test.ts +0 -208
  36. package/src/authentication/providers/EmailPasswordSRP.ts +0 -191
  37. package/src/authentication/providers/GitHub.ts +0 -24
  38. package/src/authentication/providers/Google.ts +0 -24
  39. package/src/authentication/providers/OAuth.test.ts +0 -185
  40. package/src/authentication/providers/OAuth.ts +0 -106
  41. package/src/authentication/providers/PhonePassword.test.ts +0 -221
  42. package/src/authentication/providers/PhonePassword.ts +0 -136
  43. package/src/authentication/providers/QRCodeOTP.test.ts +0 -77
  44. package/src/authentication/providers/QRCodeOTP.ts +0 -69
  45. package/src/authentication/providers/index.ts +0 -6
  46. package/src/authentication/resolvers/refreshResolver.test.ts +0 -30
  47. package/src/authentication/resolvers/refreshResolver.ts +0 -19
  48. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +0 -59
  49. package/src/authentication/resolvers/signInWithResolver.test.ts +0 -306
  50. package/src/authentication/resolvers/signInWithResolver.ts +0 -106
  51. package/src/authentication/resolvers/signOutResolver.test.ts +0 -38
  52. package/src/authentication/resolvers/signOutResolver.ts +0 -18
  53. package/src/authentication/resolvers/signUpWithResolver.test.ts +0 -180
  54. package/src/authentication/resolvers/signUpWithResolver.ts +0 -68
  55. package/src/authentication/resolvers/verifyChallenge.test.ts +0 -230
  56. package/src/authentication/resolvers/verifyChallenge.ts +0 -78
  57. package/src/authentication/roles.test.ts +0 -49
  58. package/src/authentication/roles.ts +0 -40
  59. package/src/authentication/security.ts +0 -278
  60. package/src/authentication/utils.test.ts +0 -97
  61. package/src/authentication/utils.ts +0 -39
  62. package/src/cache/InMemoryCache.test.ts +0 -62
  63. package/src/cache/InMemoryCache.ts +0 -45
  64. package/src/cron/index.test.ts +0 -17
  65. package/src/cron/index.ts +0 -43
  66. package/src/database/DatabaseController.test.ts +0 -613
  67. package/src/database/DatabaseController.ts +0 -1415
  68. package/src/database/index.test.ts +0 -1551
  69. package/src/database/index.ts +0 -9
  70. package/src/database/interface.ts +0 -308
  71. package/src/email/DevAdapter.ts +0 -7
  72. package/src/email/EmailController.test.ts +0 -29
  73. package/src/email/EmailController.ts +0 -13
  74. package/src/email/index.ts +0 -2
  75. package/src/email/interface.ts +0 -36
  76. package/src/email/templates/sendOtpCode.ts +0 -120
  77. package/src/file/FileController.ts +0 -28
  78. package/src/file/FileDevAdapter.ts +0 -51
  79. package/src/file/hookDeleteFile.ts +0 -25
  80. package/src/file/hookReadFile.ts +0 -66
  81. package/src/file/hookUploadFile.ts +0 -52
  82. package/src/file/index.test.ts +0 -1031
  83. package/src/file/index.ts +0 -2
  84. package/src/file/interface.ts +0 -63
  85. package/src/file/security.ts +0 -156
  86. package/src/graphql/GraphQLSchema.test.ts +0 -5099
  87. package/src/graphql/GraphQLSchema.ts +0 -886
  88. package/src/graphql/index.ts +0 -2
  89. package/src/graphql/parseGraphqlSchema.ts +0 -85
  90. package/src/graphql/parser.test.ts +0 -203
  91. package/src/graphql/parser.ts +0 -707
  92. package/src/graphql/pointerAndRelationFunction.ts +0 -191
  93. package/src/graphql/resolvers.ts +0 -464
  94. package/src/graphql/tests/aggregation.test.ts +0 -1115
  95. package/src/graphql/tests/e2e.test.ts +0 -590
  96. package/src/graphql/tests/scalars.test.ts +0 -250
  97. package/src/graphql/types.ts +0 -227
  98. package/src/hooks/HookObject.test.ts +0 -122
  99. package/src/hooks/HookObject.ts +0 -165
  100. package/src/hooks/authentication.ts +0 -67
  101. package/src/hooks/createUser.test.ts +0 -77
  102. package/src/hooks/createUser.ts +0 -10
  103. package/src/hooks/defaultFields.test.ts +0 -176
  104. package/src/hooks/defaultFields.ts +0 -32
  105. package/src/hooks/deleteSession.test.ts +0 -181
  106. package/src/hooks/deleteSession.ts +0 -20
  107. package/src/hooks/hashFieldHook.test.ts +0 -152
  108. package/src/hooks/hashFieldHook.ts +0 -89
  109. package/src/hooks/index.test.ts +0 -258
  110. package/src/hooks/index.ts +0 -420
  111. package/src/hooks/permissions.test.ts +0 -412
  112. package/src/hooks/permissions.ts +0 -93
  113. package/src/hooks/protected.test.ts +0 -551
  114. package/src/hooks/protected.ts +0 -74
  115. package/src/hooks/searchableFields.test.ts +0 -147
  116. package/src/hooks/searchableFields.ts +0 -86
  117. package/src/hooks/session.test.ts +0 -134
  118. package/src/hooks/session.ts +0 -76
  119. package/src/hooks/setEmail.test.ts +0 -216
  120. package/src/hooks/setEmail.ts +0 -33
  121. package/src/hooks/setupAcl.test.ts +0 -618
  122. package/src/hooks/setupAcl.ts +0 -25
  123. package/src/hooks/virtualFields.test.ts +0 -228
  124. package/src/hooks/virtualFields.ts +0 -48
  125. package/src/index.ts +0 -9
  126. package/src/schema/Schema.test.ts +0 -482
  127. package/src/schema/Schema.ts +0 -839
  128. package/src/schema/defaultResolvers.ts +0 -93
  129. package/src/schema/index.ts +0 -1
  130. package/src/schema/resolvers/meResolver.test.ts +0 -62
  131. package/src/schema/resolvers/meResolver.ts +0 -10
  132. package/src/schema/resolvers/resetPassword.test.ts +0 -341
  133. package/src/schema/resolvers/resetPassword.ts +0 -63
  134. package/src/schema/resolvers/sendEmail.test.ts +0 -118
  135. package/src/schema/resolvers/sendEmail.ts +0 -21
  136. package/src/schema/resolvers/sendOtpCode.test.ts +0 -141
  137. package/src/schema/resolvers/sendOtpCode.ts +0 -52
  138. package/src/security.test.ts +0 -4136
  139. package/src/server/defaultSessionHandler.test.ts +0 -62
  140. package/src/server/defaultSessionHandler.ts +0 -104
  141. package/src/server/generateCodegen.ts +0 -433
  142. package/src/server/index.test.ts +0 -843
  143. package/src/server/index.ts +0 -336
  144. package/src/server/interface.ts +0 -11
  145. package/src/server/routes/authHandler.ts +0 -171
  146. package/src/server/routes/index.ts +0 -48
  147. package/src/utils/crypto.test.ts +0 -41
  148. package/src/utils/crypto.ts +0 -105
  149. package/src/utils/database.ts +0 -8
  150. package/src/utils/export.ts +0 -12
  151. package/src/utils/helper.ts +0 -204
  152. package/src/utils/index.test.ts +0 -11
  153. package/src/utils/index.ts +0 -196
  154. package/src/utils/preload.ts +0 -8
  155. package/src/utils/testHelper.ts +0 -124
  156. package/tsconfig.json +0 -32
@@ -1,101 +0,0 @@
1
- import { OAuth2Client } from '.'
2
- import type { WabeConfig } from '../../server'
3
- import type { OAuth2ProviderWithPKCE, Tokens } from './utils'
4
-
5
- const authorizeEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth'
6
- const tokenEndpoint = 'https://oauth2.googleapis.com/token'
7
-
8
- interface AuthorizationCodeResponseBody {
9
- access_token: string
10
- refresh_token?: string
11
- expires_in: number
12
- id_token: string
13
- }
14
-
15
- interface RefreshTokenResponseBody {
16
- access_token: string
17
- expires_in: number
18
- }
19
-
20
- export class Google implements OAuth2ProviderWithPKCE {
21
- private client: OAuth2Client
22
- private clientSecret: string
23
-
24
- constructor(config: WabeConfig<any>) {
25
- const googleConfig = config.authentication?.providers?.google
26
-
27
- if (!googleConfig) throw new Error('Google config not found')
28
-
29
- const baseUrl = `http${config.isProduction ? 's' : ''}://${config.authentication?.backDomain || '127.0.0.1:' + config.port || 3001}`
30
-
31
- const redirectURI = `${baseUrl}/auth/oauth/callback`
32
-
33
- this.client = new OAuth2Client(
34
- googleConfig.clientId,
35
- authorizeEndpoint,
36
- tokenEndpoint,
37
- redirectURI,
38
- )
39
-
40
- this.clientSecret = googleConfig.clientSecret
41
- }
42
-
43
- createAuthorizationURL(
44
- state: string,
45
- codeVerifier: string,
46
- options?: {
47
- scopes?: string[]
48
- },
49
- ): URL {
50
- const scopes = options?.scopes ?? []
51
- const url = this.client.createAuthorizationURL({
52
- state,
53
- codeVerifier,
54
- scopes: [...scopes, 'openid'],
55
- })
56
-
57
- url.searchParams.set('access_type', 'offline')
58
- url.searchParams.set('prompt', 'select_account')
59
-
60
- return url
61
- }
62
-
63
- async validateAuthorizationCode(code: string, codeVerifier: string): Promise<Tokens> {
64
- const { access_token, expires_in, refresh_token, id_token } =
65
- await this.client.validateAuthorizationCode<AuthorizationCodeResponseBody>(code, {
66
- authenticateWith: 'request_body',
67
- credentials: this.clientSecret,
68
- codeVerifier,
69
- })
70
-
71
- return {
72
- accessToken: access_token,
73
- refreshToken: refresh_token,
74
- accessTokenExpiresAt: new Date(Date.now() + expires_in * 1000),
75
- idToken: id_token,
76
- }
77
- }
78
-
79
- async refreshAccessToken(refreshToken: string): Promise<Tokens> {
80
- const { access_token, expires_in } =
81
- await this.client.refreshAccessToken<RefreshTokenResponseBody>(refreshToken, {
82
- authenticateWith: 'request_body',
83
- credentials: this.clientSecret,
84
- })
85
-
86
- return {
87
- accessToken: access_token,
88
- accessTokenExpiresAt: new Date(Date.now() + expires_in * 1000),
89
- }
90
- }
91
-
92
- async getUserInfo(accessToken: string) {
93
- const userInfo = await fetch(
94
- `https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${accessToken}`,
95
- )
96
-
97
- const { email, verified_email } = await userInfo.json()
98
-
99
- return { email, verifiedEmail: verified_email }
100
- }
101
- }
@@ -1,219 +0,0 @@
1
- import { describe, expect, it, spyOn, mock, afterAll } from 'bun:test'
2
- import { fail } from 'node:assert'
3
- import { OAuth2Client } from './Oauth2Client'
4
- import { base64URLencode } from './utils'
5
-
6
- const mockFetch = mock(() => {})
7
-
8
- const originalFetch = global.fetch
9
-
10
- // @ts-expect-error
11
- global.fetch = mockFetch
12
-
13
- describe('Oauth2Client', () => {
14
- const oauthClient = new OAuth2Client(
15
- 'clientId',
16
- 'https://authorizationEndpoint',
17
- 'https://tokenEndpoint',
18
- 'https://redirectURI',
19
- )
20
-
21
- afterAll(() => {
22
- global.fetch = originalFetch
23
- })
24
-
25
- it('should create authorization URl', () => {
26
- const authorizationURL = oauthClient.createAuthorizationURL()
27
-
28
- expect(authorizationURL.toString()).toEqual(
29
- 'https://authorizationendpoint/?response_type=code&client_id=clientId&redirect_uri=https%3A%2F%2FredirectURI',
30
- )
31
-
32
- const authorizationURLWithState = oauthClient.createAuthorizationURL({
33
- state: 'state',
34
- })
35
-
36
- expect(authorizationURLWithState.toString()).toEqual(
37
- 'https://authorizationendpoint/?response_type=code&client_id=clientId&state=state&redirect_uri=https%3A%2F%2FredirectURI',
38
- )
39
-
40
- const authorizationURLWithScopes = oauthClient.createAuthorizationURL({
41
- scopes: ['scope1', 'scope2'],
42
- })
43
-
44
- expect(authorizationURLWithScopes.toString()).toEqual(
45
- 'https://authorizationendpoint/?response_type=code&client_id=clientId&scope=scope1+scope2&redirect_uri=https%3A%2F%2FredirectURI',
46
- )
47
-
48
- const authorizationURLWithCodeVerifier = oauthClient.createAuthorizationURL({
49
- codeVerifier: 'codeVerifier',
50
- })
51
-
52
- const codeChallenge = base64URLencode('codeVerifier')
53
-
54
- expect(authorizationURLWithCodeVerifier.toString()).toEqual(
55
- `https://authorizationendpoint/?response_type=code&client_id=clientId&redirect_uri=https%3A%2F%2FredirectURI&code_challenge_method=S256&code_challenge=${codeChallenge.replace(
56
- '/',
57
- '%2F',
58
- )}`,
59
- )
60
- })
61
-
62
- it('should validate authorization code', async () => {
63
- const spySendTokenRequest = spyOn(
64
- OAuth2Client.prototype,
65
- '_sendTokenRequest',
66
- ).mockResolvedValue({} as any)
67
-
68
- await oauthClient.validateAuthorizationCode('code')
69
-
70
- const expectedBody = new URLSearchParams()
71
- expectedBody.set('code', 'code')
72
- expectedBody.set('client_id', 'clientId')
73
- expectedBody.set('grant_type', 'authorization_code')
74
- expectedBody.set('redirect_uri', 'https://redirectURI')
75
-
76
- expect(spySendTokenRequest).toHaveBeenCalledTimes(1)
77
- const body = spySendTokenRequest.mock.calls[0]?.[0]
78
- const options = spySendTokenRequest.mock.calls[0]?.[1]
79
- expect(body?.toString()).toEqual(expectedBody.toString())
80
- expect(options).toBeUndefined()
81
-
82
- await oauthClient.validateAuthorizationCode('code', {
83
- codeVerifier: 'codeVerifier',
84
- authenticateWith: 'http_basic_auth',
85
- credentials: 'credentials',
86
- })
87
-
88
- const expectedBody2 = new URLSearchParams()
89
- expectedBody2.set('code', 'code')
90
- expectedBody2.set('client_id', 'clientId')
91
- expectedBody2.set('grant_type', 'authorization_code')
92
- expectedBody2.set('redirect_uri', 'https://redirectURI')
93
- expectedBody2.set('code_verifier', 'codeVerifier')
94
-
95
- expect(spySendTokenRequest).toHaveBeenCalledTimes(2)
96
- const body2 = spySendTokenRequest.mock.calls[1]?.[0]
97
- const options2 = spySendTokenRequest.mock.calls[1]?.[1]
98
- expect(body2?.toString()).toEqual(expectedBody2.toString())
99
- expect(options2).toEqual({
100
- codeVerifier: 'codeVerifier',
101
- authenticateWith: 'http_basic_auth',
102
- credentials: 'credentials',
103
- } as any)
104
-
105
- spySendTokenRequest.mockRestore()
106
- })
107
-
108
- it('should refresh access token', async () => {
109
- const spySendTokenRequest = spyOn(
110
- OAuth2Client.prototype,
111
- '_sendTokenRequest',
112
- ).mockResolvedValue({} as any)
113
-
114
- await oauthClient.refreshAccessToken('refreshToken')
115
-
116
- const expectedBody = new URLSearchParams()
117
- expectedBody.set('refresh_token', 'refreshToken')
118
- expectedBody.set('client_id', 'clientId')
119
- expectedBody.set('grant_type', 'refresh_token')
120
-
121
- expect(spySendTokenRequest).toHaveBeenCalledTimes(1)
122
- const body = spySendTokenRequest.mock.calls[0]?.[0]
123
- const options = spySendTokenRequest.mock.calls[0]?.[1]
124
- expect(body?.toString()).toEqual(expectedBody.toString())
125
- expect(options).toBeUndefined()
126
-
127
- await oauthClient.refreshAccessToken('refreshToken', {
128
- authenticateWith: 'http_basic_auth',
129
- credentials: 'credentials',
130
- })
131
-
132
- const expectedBody2 = new URLSearchParams()
133
- expectedBody2.set('refresh_token', 'refreshToken')
134
- expectedBody2.set('client_id', 'clientId')
135
- expectedBody2.set('grant_type', 'refresh_token')
136
-
137
- expect(spySendTokenRequest).toHaveBeenCalledTimes(2)
138
- const body2 = spySendTokenRequest.mock.calls[1]?.[0]
139
- const options2 = spySendTokenRequest.mock.calls[1]?.[1]
140
- expect(body2?.toString()).toEqual(expectedBody2.toString())
141
- expect(options2).toEqual({
142
- authenticateWith: 'http_basic_auth',
143
- credentials: 'credentials',
144
- })
145
-
146
- spySendTokenRequest.mockRestore()
147
- })
148
-
149
- it('should send token request', async () => {
150
- mockFetch.mockResolvedValue({
151
- json: () => Promise.resolve({ access_token: 'access_token' }),
152
- ok: true,
153
- status: 200,
154
- } as never)
155
-
156
- await oauthClient._sendTokenRequest(new URLSearchParams(), {
157
- authenticateWith: 'http_basic_auth',
158
- credentials: 'credentials',
159
- })
160
-
161
- const encodeCredentials = btoa('clientId:credentials')
162
-
163
- expect(mockFetch).toHaveBeenCalledTimes(1)
164
- // @ts-expect-error
165
- const receivedRequest = mockFetch.mock.calls[0][0] as any
166
-
167
- if (!receivedRequest) fail()
168
- expect(receivedRequest.url).toEqual('https://tokenendpoint/')
169
- expect(receivedRequest.method).toEqual('POST')
170
- expect(receivedRequest.headers.get('content-type')).toEqual('application/x-www-form-urlencoded')
171
- expect(receivedRequest.headers.get('accept')).toEqual('application/json')
172
- expect(receivedRequest.headers.get('user-agent')).toEqual('wabe')
173
- expect(receivedRequest.headers.get('authorization')).toEqual(`Basic ${encodeCredentials}`)
174
-
175
- mockFetch.mockRestore()
176
- })
177
-
178
- it('should throw an error if the result of the request is not valid', () => {
179
- mockFetch.mockResolvedValue({
180
- json: () => Promise.resolve({}),
181
- ok: false,
182
- } as never)
183
-
184
- expect(
185
- oauthClient._sendTokenRequest(new URLSearchParams(), {
186
- authenticateWith: 'http_basic_auth',
187
- credentials: 'credentials',
188
- }),
189
- ).rejects.toThrow('Error in token request')
190
-
191
- mockFetch.mockResolvedValue({
192
- json: () => Promise.resolve({}),
193
- ok: true,
194
- status: 400,
195
- } as never)
196
-
197
- expect(
198
- oauthClient._sendTokenRequest(new URLSearchParams(), {
199
- authenticateWith: 'http_basic_auth',
200
- credentials: 'credentials',
201
- }),
202
- ).rejects.toThrow('Error in token request')
203
-
204
- mockFetch.mockResolvedValue({
205
- json: () => Promise.resolve({}),
206
- ok: true,
207
- status: 200,
208
- } as never)
209
-
210
- expect(
211
- oauthClient._sendTokenRequest(new URLSearchParams(), {
212
- authenticateWith: 'http_basic_auth',
213
- credentials: 'credentials',
214
- }),
215
- ).rejects.toThrow('Error in token request')
216
-
217
- mockFetch.mockRestore()
218
- })
219
- })
@@ -1,135 +0,0 @@
1
- // Code inspired by Oslo : https://github.com/pilcrowOnPaper/oslo/blob/main/src/oauth2/index.ts
2
-
3
- import { base64URLencode } from './utils'
4
-
5
- export interface TokenResponseBody {
6
- access_token: string
7
- token_type?: string
8
- expires_in?: number
9
- refresh_token?: string
10
- scope?: string
11
- }
12
-
13
- export class OAuth2Client {
14
- public clientId: string
15
-
16
- private authorizeEndpoint: string
17
- private tokenEndpoint: string
18
- private redirectURI: string
19
-
20
- constructor(
21
- clientId: string,
22
- authorizeEndpoint: string,
23
- tokenEndpoint: string,
24
- redirectURI: string,
25
- ) {
26
- this.clientId = clientId
27
- this.authorizeEndpoint = authorizeEndpoint
28
- this.tokenEndpoint = tokenEndpoint
29
- this.redirectURI = redirectURI
30
- }
31
-
32
- createAuthorizationURL(options?: {
33
- state?: string
34
- codeVerifier?: string
35
- scopes?: string[]
36
- }): URL {
37
- const scopes = Array.from(new Set(options?.scopes || [])) // remove duplicates
38
- const authorizationUrl = new URL(this.authorizeEndpoint)
39
- authorizationUrl.searchParams.set('response_type', 'code')
40
- authorizationUrl.searchParams.set('client_id', this.clientId)
41
-
42
- if (options?.state !== undefined) authorizationUrl.searchParams.set('state', options.state)
43
-
44
- if (scopes.length > 0) authorizationUrl.searchParams.set('scope', scopes.join(' '))
45
-
46
- if (this.redirectURI !== null)
47
- authorizationUrl.searchParams.set('redirect_uri', this.redirectURI)
48
-
49
- if (options?.codeVerifier !== undefined) {
50
- const codeChallenge = base64URLencode(options.codeVerifier)
51
-
52
- authorizationUrl.searchParams.set('code_challenge_method', 'S256')
53
- authorizationUrl.searchParams.set('code_challenge', codeChallenge)
54
- }
55
-
56
- return authorizationUrl
57
- }
58
-
59
- validateAuthorizationCode<_TokenResponseBody extends TokenResponseBody>(
60
- authorizationCode: string,
61
- options?: {
62
- codeVerifier?: string
63
- credentials?: string
64
- authenticateWith?: 'http_basic_auth' | 'request_body'
65
- },
66
- ): Promise<_TokenResponseBody> {
67
- const body = new URLSearchParams()
68
- body.set('code', authorizationCode)
69
- body.set('client_id', this.clientId)
70
- body.set('grant_type', 'authorization_code')
71
-
72
- if (this.redirectURI !== null) body.set('redirect_uri', this.redirectURI)
73
-
74
- if (options?.codeVerifier !== undefined) body.set('code_verifier', options.codeVerifier)
75
-
76
- return this._sendTokenRequest<_TokenResponseBody>(body, options)
77
- }
78
-
79
- async refreshAccessToken<_TokenResponseBody extends TokenResponseBody>(
80
- refreshToken: string,
81
- options?: {
82
- credentials?: string
83
- authenticateWith?: 'http_basic_auth' | 'request_body'
84
- scopes?: string[]
85
- },
86
- ): Promise<_TokenResponseBody> {
87
- const body = new URLSearchParams()
88
- body.set('refresh_token', refreshToken)
89
- body.set('client_id', this.clientId)
90
- body.set('grant_type', 'refresh_token')
91
-
92
- const scopes = Array.from(new Set(options?.scopes ?? [])) // remove duplicates
93
- if (scopes.length > 0) body.set('scope', scopes.join(' '))
94
-
95
- return await this._sendTokenRequest<_TokenResponseBody>(body, options)
96
- }
97
-
98
- async _sendTokenRequest<_TokenResponseBody extends TokenResponseBody>(
99
- body: URLSearchParams,
100
- options?: {
101
- credentials?: string
102
- authenticateWith?: 'http_basic_auth' | 'request_body'
103
- },
104
- ): Promise<_TokenResponseBody> {
105
- const headers = new Headers()
106
- headers.set('Content-Type', 'application/x-www-form-urlencoded')
107
- headers.set('Accept', 'application/json')
108
- headers.set('User-Agent', 'wabe')
109
-
110
- if (options?.credentials !== undefined) {
111
- const authenticateWith = options?.authenticateWith || 'http_basic_auth'
112
- if (authenticateWith === 'http_basic_auth') {
113
- const encodedCredentials = btoa(`${this.clientId}:${options.credentials}`)
114
- headers.set('Authorization', `Basic ${encodedCredentials}`)
115
- } else {
116
- body.set('client_secret', options.credentials)
117
- }
118
- }
119
-
120
- const request = new Request(this.tokenEndpoint, {
121
- method: 'POST',
122
- headers,
123
- body,
124
- })
125
-
126
- const response = await fetch(request)
127
- const result: _TokenResponseBody = await response.json()
128
-
129
- // providers are allowed to return non-400 status code for errors
130
- if (!('access_token' in result) || response.status !== 200 || !response.ok)
131
- throw new Error('Error in token request')
132
-
133
- return result
134
- }
135
- }
@@ -1,2 +0,0 @@
1
- export * from './Oauth2Client'
2
- export * from './Google'
@@ -1,33 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { base64URLencode, generateRandomValues } from './utils'
3
-
4
- describe('Oauth utils', () => {
5
- it('should encode url with base64', () => {
6
- const content = 'test'
7
-
8
- // Keep Bun. here to be sure the compatibility between node and Bun implem
9
- const hasher = new Bun.CryptoHasher('sha256')
10
- hasher.update(new TextEncoder().encode(content))
11
- const resultWithPadding = hasher.digest('base64')
12
-
13
- const result = base64URLencode(content)
14
-
15
- expect(resultWithPadding).toBe('n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=')
16
- expect(result).toBe('n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg')
17
- })
18
-
19
- // Real use case check with oauth simulator
20
- it('should encode correctly with base64', () => {
21
- const content = 'bIaNJCsNzrZE7QEzYjwbl0fa1CyzF49moM6Ua4H0d5cG-l7d'
22
- const result = base64URLencode(content)
23
-
24
- expect(result).toBe('1pdL2CLvBbNBnrfBZeNYlFzpedMhUTbgyhn0CnWVYoc')
25
- })
26
-
27
- it('should generate random values for code_verifier or state', () => {
28
- const randomValue = generateRandomValues()
29
-
30
- // Google recommends an entropy between 43 and 128 characters for the code_verifier
31
- expect(randomValue.length).toEqual(80)
32
- })
33
- })
@@ -1,27 +0,0 @@
1
- import crypto from 'node:crypto'
2
-
3
- export interface Tokens {
4
- accessToken: string
5
- refreshToken?: string | null
6
- accessTokenExpiresAt?: Date
7
- refreshTokenExpiresAt?: Date | null
8
- idToken?: string
9
- }
10
-
11
- export interface OAuth2ProviderWithPKCE {
12
- createAuthorizationURL(state: string, codeVerifier: string): URL
13
- validateAuthorizationCode(code: string, codeVerifier: string): Promise<Tokens>
14
- refreshAccessToken?(refreshToken: string): Promise<Tokens>
15
- }
16
-
17
- // https://datatracker.ietf.org/doc/html/rfc7636#appendix-A
18
- export const base64URLencode = (content: string) => {
19
- const hasher = crypto.createHash('sha256').update(content)
20
-
21
- const result = hasher.digest('base64')
22
-
23
- // @ts-expect-error
24
- return result.split('=')[0].replaceAll('+', '-').replaceAll('/', '_')
25
- }
26
-
27
- export const generateRandomValues = () => crypto.randomBytes(60).toString('base64url')
@@ -1,127 +0,0 @@
1
- import { describe, expect, it, spyOn, beforeAll, afterAll, afterEach } from 'bun:test'
2
- import { EmailOTP } from './EmailOTP'
3
- import type { DevWabeTypes } from '../../utils/helper'
4
- import { setupTests, closeTests } from '../../utils/testHelper'
5
- import { EmailDevAdapter, type Wabe } from '../..'
6
- import * as sendOtpCodeTemplate from '../../email/templates/sendOtpCode'
7
- import { OTP } from '../OTP'
8
-
9
- describe('EmailOTPProvider', () => {
10
- const spyEmailSend = spyOn(EmailDevAdapter.prototype, 'send')
11
- const spySendOtpCodeTemplate = spyOn(sendOtpCodeTemplate, 'sendOtpCodeTemplate')
12
-
13
- let wabe: Wabe<DevWabeTypes>
14
-
15
- beforeAll(async () => {
16
- const setup = await setupTests()
17
- wabe = setup.wabe
18
- })
19
-
20
- afterAll(async () => {
21
- await closeTests(wabe)
22
- })
23
-
24
- afterEach(async () => {
25
- spyEmailSend.mockClear()
26
- spySendOtpCodeTemplate.mockClear()
27
-
28
- await wabe.controllers.database.clearDatabase()
29
- })
30
-
31
- it("should send an OTP code to the user's email", async () => {
32
- const createdUser = await wabe.controllers.database.createObject({
33
- className: 'User',
34
- context: {
35
- wabe,
36
- isRoot: true,
37
- },
38
- data: {
39
- email: 'email@test.fr',
40
- },
41
- select: {
42
- id: true,
43
- email: true,
44
- },
45
- })
46
-
47
- if (!createdUser) throw new Error('User not created')
48
-
49
- const emailOTP = new EmailOTP()
50
-
51
- await emailOTP.onSendChallenge({
52
- context: {
53
- wabe,
54
- isRoot: false,
55
- },
56
- user: createdUser,
57
- })
58
-
59
- expect(spyEmailSend).toHaveBeenCalledTimes(1)
60
- expect(spyEmailSend).toHaveBeenCalledWith(
61
- expect.objectContaining({
62
- from: 'main.email@wabe.com',
63
- to: ['email@test.fr'],
64
- subject: 'Your OTP code',
65
- }),
66
- )
67
-
68
- const otp = spySendOtpCodeTemplate.mock.calls[0]?.[0]
69
-
70
- expect(spySendOtpCodeTemplate).toHaveBeenCalledTimes(1)
71
- expect(otp?.length).toBe(6)
72
- })
73
-
74
- it('should return the userId if the OTP code is valid', async () => {
75
- const createdUser = await wabe.controllers.database.createObject({
76
- className: 'User',
77
- context: {
78
- wabe,
79
- isRoot: true,
80
- },
81
- data: {
82
- email: 'email@test.fr',
83
- },
84
- select: {
85
- id: true,
86
- },
87
- })
88
-
89
- if (!createdUser) throw new Error('User not created')
90
-
91
- const otp = new OTP(wabe.config.rootKey).generate(createdUser.id)
92
-
93
- const emailOTP = new EmailOTP()
94
-
95
- expect(
96
- await emailOTP.onVerifyChallenge({
97
- context: {
98
- wabe,
99
- isRoot: false,
100
- },
101
- input: {
102
- email: 'email@test.fr',
103
- otp,
104
- },
105
- }),
106
- ).toEqual({
107
- userId: createdUser.id,
108
- })
109
- })
110
-
111
- it("should return null if the user doesn't exist", async () => {
112
- const emailOTP = new EmailOTP()
113
-
114
- expect(
115
- await emailOTP.onVerifyChallenge({
116
- context: {
117
- wabe,
118
- isRoot: false,
119
- },
120
- input: {
121
- email: 'email@test.fr',
122
- otp: '123456',
123
- },
124
- }),
125
- ).toEqual(null)
126
- })
127
- })