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.
Files changed (147) hide show
  1. package/package.json +4 -1
  2. package/dev/index.ts +0 -215
  3. package/generated/schema.graphql +0 -1945
  4. package/generated/wabe.ts +0 -448
  5. package/src/authentication/OTP.test.ts +0 -69
  6. package/src/authentication/OTP.ts +0 -64
  7. package/src/authentication/Session.test.ts +0 -629
  8. package/src/authentication/Session.ts +0 -517
  9. package/src/authentication/cookies.ts +0 -10
  10. package/src/authentication/defaultAuthentication.ts +0 -209
  11. package/src/authentication/index.ts +0 -4
  12. package/src/authentication/interface.ts +0 -177
  13. package/src/authentication/oauth/GitHub.test.ts +0 -91
  14. package/src/authentication/oauth/GitHub.ts +0 -121
  15. package/src/authentication/oauth/Google.test.ts +0 -91
  16. package/src/authentication/oauth/Google.ts +0 -101
  17. package/src/authentication/oauth/Oauth2Client.test.ts +0 -219
  18. package/src/authentication/oauth/Oauth2Client.ts +0 -135
  19. package/src/authentication/oauth/index.ts +0 -2
  20. package/src/authentication/oauth/utils.test.ts +0 -33
  21. package/src/authentication/oauth/utils.ts +0 -27
  22. package/src/authentication/providers/EmailOTP.test.ts +0 -127
  23. package/src/authentication/providers/EmailOTP.ts +0 -95
  24. package/src/authentication/providers/EmailPassword.test.ts +0 -263
  25. package/src/authentication/providers/EmailPassword.ts +0 -138
  26. package/src/authentication/providers/EmailPasswordSRP.test.ts +0 -208
  27. package/src/authentication/providers/EmailPasswordSRP.ts +0 -191
  28. package/src/authentication/providers/GitHub.ts +0 -24
  29. package/src/authentication/providers/Google.ts +0 -24
  30. package/src/authentication/providers/OAuth.test.ts +0 -185
  31. package/src/authentication/providers/OAuth.ts +0 -106
  32. package/src/authentication/providers/PhonePassword.test.ts +0 -221
  33. package/src/authentication/providers/PhonePassword.ts +0 -136
  34. package/src/authentication/providers/QRCodeOTP.test.ts +0 -77
  35. package/src/authentication/providers/QRCodeOTP.ts +0 -69
  36. package/src/authentication/providers/index.ts +0 -6
  37. package/src/authentication/resolvers/refreshResolver.test.ts +0 -30
  38. package/src/authentication/resolvers/refreshResolver.ts +0 -19
  39. package/src/authentication/resolvers/signInWithResolver.inte.test.ts +0 -59
  40. package/src/authentication/resolvers/signInWithResolver.test.ts +0 -306
  41. package/src/authentication/resolvers/signInWithResolver.ts +0 -106
  42. package/src/authentication/resolvers/signOutResolver.test.ts +0 -38
  43. package/src/authentication/resolvers/signOutResolver.ts +0 -18
  44. package/src/authentication/resolvers/signUpWithResolver.test.ts +0 -180
  45. package/src/authentication/resolvers/signUpWithResolver.ts +0 -68
  46. package/src/authentication/resolvers/verifyChallenge.test.ts +0 -230
  47. package/src/authentication/resolvers/verifyChallenge.ts +0 -78
  48. package/src/authentication/roles.test.ts +0 -49
  49. package/src/authentication/roles.ts +0 -40
  50. package/src/authentication/security.ts +0 -278
  51. package/src/authentication/utils.test.ts +0 -97
  52. package/src/authentication/utils.ts +0 -39
  53. package/src/cache/InMemoryCache.test.ts +0 -62
  54. package/src/cache/InMemoryCache.ts +0 -45
  55. package/src/cron/index.test.ts +0 -17
  56. package/src/cron/index.ts +0 -43
  57. package/src/database/DatabaseController.test.ts +0 -613
  58. package/src/database/DatabaseController.ts +0 -1415
  59. package/src/database/index.test.ts +0 -1551
  60. package/src/database/index.ts +0 -9
  61. package/src/database/interface.ts +0 -308
  62. package/src/email/DevAdapter.ts +0 -7
  63. package/src/email/EmailController.test.ts +0 -29
  64. package/src/email/EmailController.ts +0 -13
  65. package/src/email/index.ts +0 -2
  66. package/src/email/interface.ts +0 -36
  67. package/src/email/templates/sendOtpCode.ts +0 -120
  68. package/src/file/FileController.ts +0 -28
  69. package/src/file/FileDevAdapter.ts +0 -51
  70. package/src/file/hookDeleteFile.ts +0 -25
  71. package/src/file/hookReadFile.ts +0 -66
  72. package/src/file/hookUploadFile.ts +0 -52
  73. package/src/file/index.test.ts +0 -1031
  74. package/src/file/index.ts +0 -2
  75. package/src/file/interface.ts +0 -63
  76. package/src/file/security.ts +0 -156
  77. package/src/graphql/GraphQLSchema.test.ts +0 -5099
  78. package/src/graphql/GraphQLSchema.ts +0 -886
  79. package/src/graphql/index.ts +0 -2
  80. package/src/graphql/parseGraphqlSchema.ts +0 -85
  81. package/src/graphql/parser.test.ts +0 -203
  82. package/src/graphql/parser.ts +0 -707
  83. package/src/graphql/pointerAndRelationFunction.ts +0 -191
  84. package/src/graphql/resolvers.ts +0 -464
  85. package/src/graphql/tests/aggregation.test.ts +0 -1115
  86. package/src/graphql/tests/e2e.test.ts +0 -590
  87. package/src/graphql/tests/scalars.test.ts +0 -250
  88. package/src/graphql/types.ts +0 -227
  89. package/src/hooks/HookObject.test.ts +0 -122
  90. package/src/hooks/HookObject.ts +0 -165
  91. package/src/hooks/authentication.ts +0 -67
  92. package/src/hooks/createUser.test.ts +0 -77
  93. package/src/hooks/createUser.ts +0 -10
  94. package/src/hooks/defaultFields.test.ts +0 -176
  95. package/src/hooks/defaultFields.ts +0 -32
  96. package/src/hooks/deleteSession.test.ts +0 -181
  97. package/src/hooks/deleteSession.ts +0 -20
  98. package/src/hooks/hashFieldHook.test.ts +0 -152
  99. package/src/hooks/hashFieldHook.ts +0 -89
  100. package/src/hooks/index.test.ts +0 -258
  101. package/src/hooks/index.ts +0 -420
  102. package/src/hooks/permissions.test.ts +0 -412
  103. package/src/hooks/permissions.ts +0 -93
  104. package/src/hooks/protected.test.ts +0 -551
  105. package/src/hooks/protected.ts +0 -74
  106. package/src/hooks/searchableFields.test.ts +0 -147
  107. package/src/hooks/searchableFields.ts +0 -86
  108. package/src/hooks/session.test.ts +0 -134
  109. package/src/hooks/session.ts +0 -76
  110. package/src/hooks/setEmail.test.ts +0 -216
  111. package/src/hooks/setEmail.ts +0 -33
  112. package/src/hooks/setupAcl.test.ts +0 -618
  113. package/src/hooks/setupAcl.ts +0 -25
  114. package/src/hooks/virtualFields.test.ts +0 -228
  115. package/src/hooks/virtualFields.ts +0 -48
  116. package/src/index.ts +0 -9
  117. package/src/schema/Schema.test.ts +0 -482
  118. package/src/schema/Schema.ts +0 -839
  119. package/src/schema/defaultResolvers.ts +0 -93
  120. package/src/schema/index.ts +0 -1
  121. package/src/schema/resolvers/meResolver.test.ts +0 -62
  122. package/src/schema/resolvers/meResolver.ts +0 -10
  123. package/src/schema/resolvers/resetPassword.test.ts +0 -341
  124. package/src/schema/resolvers/resetPassword.ts +0 -63
  125. package/src/schema/resolvers/sendEmail.test.ts +0 -118
  126. package/src/schema/resolvers/sendEmail.ts +0 -21
  127. package/src/schema/resolvers/sendOtpCode.test.ts +0 -141
  128. package/src/schema/resolvers/sendOtpCode.ts +0 -52
  129. package/src/security.test.ts +0 -4136
  130. package/src/server/defaultSessionHandler.test.ts +0 -62
  131. package/src/server/defaultSessionHandler.ts +0 -104
  132. package/src/server/generateCodegen.ts +0 -433
  133. package/src/server/index.test.ts +0 -843
  134. package/src/server/index.ts +0 -336
  135. package/src/server/interface.ts +0 -11
  136. package/src/server/routes/authHandler.ts +0 -171
  137. package/src/server/routes/index.ts +0 -48
  138. package/src/utils/crypto.test.ts +0 -41
  139. package/src/utils/crypto.ts +0 -105
  140. package/src/utils/database.ts +0 -8
  141. package/src/utils/export.ts +0 -12
  142. package/src/utils/helper.ts +0 -204
  143. package/src/utils/index.test.ts +0 -11
  144. package/src/utils/index.ts +0 -196
  145. package/src/utils/preload.ts +0 -8
  146. package/src/utils/testHelper.ts +0 -124
  147. package/tsconfig.json +0 -32
@@ -1,209 +0,0 @@
1
- import type { WabeTypes } from '..'
2
- import type { CustomAuthenticationMethods, ProviderInterface } from './interface'
3
- import { GitHub, QRCodeOTP } from './providers'
4
- import { Google } from './providers'
5
- import { EmailOTP } from './providers/EmailOTP'
6
- import { EmailPassword } from './providers/EmailPassword'
7
- import { EmailPasswordSRPChallenge, EmailPasswordSRP } from './providers/EmailPasswordSRP'
8
- import { PhonePassword } from './providers/PhonePassword'
9
-
10
- export const defaultAuthenticationMethods = <T extends WabeTypes>(): CustomAuthenticationMethods<
11
- T,
12
- ProviderInterface<T>
13
- >[] => [
14
- {
15
- name: 'emailPasswordSRPChallenge',
16
- input: {
17
- email: {
18
- type: 'Email',
19
- required: true,
20
- },
21
- clientPublic: {
22
- type: 'String',
23
- required: true,
24
- },
25
- clientSessionProof: {
26
- type: 'String',
27
- required: true,
28
- },
29
- },
30
- // @ts-expect-error
31
- provider: new EmailPasswordSRPChallenge(),
32
- isSecondaryFactor: true,
33
- },
34
- {
35
- name: 'emailPasswordSRP',
36
- input: {
37
- email: {
38
- type: 'Email',
39
- required: true,
40
- },
41
- clientPublic: {
42
- type: 'String',
43
- },
44
- salt: {
45
- type: 'String',
46
- },
47
- verifier: {
48
- type: 'String',
49
- },
50
- },
51
- dataToStore: {
52
- email: {
53
- type: 'Email',
54
- required: true,
55
- },
56
- salt: {
57
- type: 'String',
58
- required: true,
59
- },
60
- verifier: {
61
- type: 'String',
62
- required: true,
63
- },
64
- serverSecret: {
65
- type: 'String',
66
- },
67
- },
68
- // @ts-expect-error
69
- provider: new EmailPasswordSRP(),
70
- },
71
- {
72
- name: 'emailOTP',
73
- input: {
74
- email: {
75
- type: 'Email',
76
- required: true,
77
- },
78
- otp: {
79
- type: 'String',
80
- required: true,
81
- },
82
- },
83
- // @ts-expect-error
84
- provider: new EmailOTP(),
85
- isSecondaryFactor: true,
86
- },
87
- {
88
- name: 'qrCodeOTP',
89
- input: {
90
- email: {
91
- type: 'Email',
92
- required: true,
93
- },
94
- otp: {
95
- type: 'String',
96
- required: true,
97
- },
98
- },
99
- // @ts-expect-error
100
- provider: new QRCodeOTP(),
101
- isSecondaryFactor: true,
102
- },
103
- {
104
- name: 'phonePassword',
105
- input: {
106
- phone: {
107
- type: 'Phone',
108
- required: true,
109
- },
110
- password: {
111
- type: 'Hash',
112
- required: true,
113
- },
114
- },
115
- dataToStore: {
116
- phone: {
117
- type: 'Phone',
118
- required: true,
119
- },
120
- password: {
121
- type: 'Hash',
122
- required: true,
123
- },
124
- },
125
- // @ts-expect-error
126
- provider: new PhonePassword(),
127
- },
128
- {
129
- name: 'emailPassword',
130
- input: {
131
- email: {
132
- type: 'Email',
133
- required: true,
134
- },
135
- password: {
136
- type: 'Hash',
137
- required: true,
138
- },
139
- },
140
- dataToStore: {
141
- email: {
142
- type: 'Email',
143
- required: true,
144
- },
145
- password: {
146
- type: 'Hash',
147
- required: true,
148
- },
149
- },
150
- // @ts-expect-error
151
- provider: new EmailPassword(),
152
- },
153
- {
154
- name: 'google',
155
- input: {
156
- authorizationCode: {
157
- type: 'String',
158
- required: true,
159
- },
160
- codeVerifier: {
161
- type: 'String',
162
- required: true,
163
- },
164
- },
165
- dataToStore: {
166
- email: {
167
- type: 'Email',
168
- required: true,
169
- },
170
- verifiedEmail: {
171
- type: 'Boolean',
172
- required: true,
173
- },
174
- },
175
- // There is no signUp method for Google provider
176
- // @ts-expect-error
177
- provider: new Google(),
178
- },
179
- {
180
- name: 'github',
181
- input: {
182
- authorizationCode: {
183
- type: 'String',
184
- required: true,
185
- },
186
- codeVerifier: {
187
- type: 'String',
188
- required: true,
189
- },
190
- },
191
- dataToStore: {
192
- email: {
193
- type: 'Email',
194
- required: true,
195
- },
196
- avatarUrl: {
197
- type: 'String',
198
- required: true,
199
- },
200
- username: {
201
- type: 'String',
202
- required: true,
203
- },
204
- },
205
- // There is no signUp method for Google provider
206
- // @ts-expect-error
207
- provider: new GitHub(),
208
- },
209
- ]
@@ -1,4 +0,0 @@
1
- export * from './interface'
2
- export * from './oauth'
3
- export * from './OTP'
4
- export * from './Session'
@@ -1,177 +0,0 @@
1
- import type { User } from '../../generated/wabe'
2
- import type { WabeContext } from '../server/interface'
3
- import type { SchemaFields } from '../schema'
4
- import type { WabeTypes, WobeCustomContext } from '../server'
5
- import type { SelectType } from '../database/interface'
6
-
7
- export enum ProviderEnum {
8
- google = 'google',
9
- github = 'github',
10
- }
11
-
12
- export interface ProviderConfig {
13
- clientId: string
14
- clientSecret: string
15
- }
16
-
17
- export type AuthenticationEventsOptions<T extends WabeTypes, K> = {
18
- context: WabeContext<T>
19
- input: K
20
- }
21
-
22
- export type AuthenticationEventsOptionsWithUserId<
23
- T extends WabeTypes,
24
- K,
25
- > = AuthenticationEventsOptions<T, K> & {
26
- userId: string
27
- }
28
-
29
- export type OnSendChallengeOptions<T extends WabeTypes> = {
30
- context: WabeContext<T>
31
- user: T['types']['User']
32
- }
33
-
34
- export type OnVerifyChallengeOptions<T extends WabeTypes, K> = {
35
- context: WabeContext<T>
36
- input: K
37
- }
38
-
39
- export type ProviderInterface<T extends WabeTypes, K = any> = {
40
- onSignIn: (options: AuthenticationEventsOptions<T, K>) => Promise<{
41
- user: Partial<User>
42
- srp?: {
43
- salt: string
44
- serverPublic: string
45
- }
46
- }>
47
- onSignUp: (
48
- options: AuthenticationEventsOptions<T, K>,
49
- ) => Promise<{ authenticationDataToSave: any }>
50
- onUpdateAuthenticationData?: (
51
- options: AuthenticationEventsOptionsWithUserId<T, K>,
52
- ) => Promise<{ authenticationDataToSave: any }>
53
- }
54
-
55
- export type SecondaryProviderInterface<T extends WabeTypes, K = any> = {
56
- onSendChallenge?: (options: OnSendChallengeOptions<T>) => Promise<void> | void
57
- onVerifyChallenge: (options: OnVerifyChallengeOptions<T, K>) =>
58
- | Promise<{
59
- userId: string
60
- srp?: { serverSessionProof: string }
61
- } | null>
62
- | ({ userId: string; srp?: { serverSessionProof: string } } | null)
63
- }
64
-
65
- export type CustomAuthenticationMethods<
66
- T extends WabeTypes,
67
- U = ProviderInterface<T> | SecondaryProviderInterface<T>,
68
- K = SchemaFields<T>,
69
- W = SchemaFields<T>,
70
- > = {
71
- name: string
72
- input: K
73
- dataToStore?: W
74
- provider: U
75
- isSecondaryFactor?: boolean
76
- }
77
-
78
- export type RoleConfig = Array<string>
79
-
80
- export interface SessionConfig<T extends WabeTypes> {
81
- /**
82
- * The time in milliseconds that the access token will expire
83
- */
84
- accessTokenExpiresInMs?: number
85
- /**
86
- * The time in milliseconds that the refresh token will expire
87
- */
88
- refreshTokenExpiresInMs?: number
89
- /**
90
- * Set to true to automatically store the session tokens in cookies
91
- */
92
- cookieSession?: boolean
93
- /**
94
- * The JWT secret used to sign the session tokens
95
- */
96
- jwtSecret: string
97
- /**
98
- * Optional audience to embed and verify in JWTs
99
- */
100
- jwtAudience?: string
101
- /**
102
- * Optional issuer to embed and verify in JWTs
103
- */
104
- jwtIssuer?: string
105
- /**
106
- * Secret dedicated to CSRF token HMAC (defaults to jwtSecret)
107
- */
108
- csrfSecret?: string
109
- /**
110
- * Secret used to encrypt session tokens at rest (defaults to jwtSecret)
111
- */
112
- tokenSecret?: string
113
- /**
114
- * A selection of fields to include in the JWT token in the "user" fields
115
- */
116
- jwtTokenFields?: SelectType<T, 'User', keyof T['types']['User']>
117
- }
118
-
119
- export interface AuthenticationRateLimitConfig {
120
- /**
121
- * Enable this rate limiter. Enabled by default in production.
122
- */
123
- enabled?: boolean
124
- maxAttempts?: number
125
- windowMs?: number
126
- blockDurationMs?: number
127
- }
128
-
129
- export interface AuthenticationSecurityConfig {
130
- signInRateLimit?: AuthenticationRateLimitConfig
131
- signUpRateLimit?: AuthenticationRateLimitConfig
132
- verifyChallengeRateLimit?: AuthenticationRateLimitConfig
133
- mfaChallengeTtlMs?: number
134
- /**
135
- * Require a valid challenge token during verifyChallenge in production.
136
- */
137
- requireMfaChallengeInProduction?: boolean
138
- }
139
-
140
- export interface AuthenticationConfig<T extends WabeTypes> {
141
- session?: SessionConfig<T>
142
- roles?: RoleConfig
143
- successRedirectPath?: string
144
- failureRedirectPath?: string
145
- frontDomain?: string
146
- backDomain?: string
147
- providers?: Partial<Record<ProviderEnum, ProviderConfig>>
148
- customAuthenticationMethods?: CustomAuthenticationMethods<T>[]
149
- sessionHandler?: (context: WobeCustomContext<T>) => void | Promise<void>
150
- disableSignUp?: boolean
151
- security?: AuthenticationSecurityConfig
152
- }
153
-
154
- export interface CreateTokenFromAuthorizationCodeOptions {
155
- code: string
156
- }
157
-
158
- export interface refreshTokenOptions {
159
- refreshToken: string
160
- }
161
-
162
- export interface Provider {
163
- createTokenFromAuthorizationCode(options: CreateTokenFromAuthorizationCodeOptions): Promise<void>
164
- refreshToken(options: refreshTokenOptions): Promise<void>
165
- }
166
-
167
- export enum AuthenticationProvider {
168
- GitHub = 'github',
169
- Google = 'google',
170
- EmailPassword = 'emailPassword',
171
- PhonePassword = 'phonePassword',
172
- }
173
-
174
- export enum SecondaryFactor {
175
- EmailOTP = 'emailOTP',
176
- QRCodeOTP = 'qrcodeOTP',
177
- }
@@ -1,91 +0,0 @@
1
- import { describe, expect, it, spyOn } from 'bun:test'
2
- import { GitHub } from './GitHub'
3
- import { OAuth2Client } from './Oauth2Client'
4
-
5
- describe('GitHub oauth', () => {
6
- const config = {
7
- port: 3001,
8
- authentication: {
9
- backDomain: 'api.wabe.dev',
10
- providers: {
11
- github: {
12
- clientId: 'clientId',
13
- clientSecret: 'clientSecret',
14
- },
15
- },
16
- },
17
- } as any
18
-
19
- const githubOauth = new GitHub(config)
20
-
21
- it('should create authorization url', () => {
22
- const spyOauth2ClientCreateAuthorizationUrl = spyOn(
23
- OAuth2Client.prototype,
24
- 'createAuthorizationURL',
25
- ).mockReturnValue(new URL('https://url') as never)
26
-
27
- const authorizationUrl = githubOauth.createAuthorizationURL('state', 'codeVerifier')
28
-
29
- expect(authorizationUrl.toString()).toBe(
30
- 'https://url/?access_type=offline&prompt=select_account',
31
- )
32
- expect(spyOauth2ClientCreateAuthorizationUrl).toHaveBeenCalledTimes(1)
33
- expect(spyOauth2ClientCreateAuthorizationUrl).toHaveBeenCalledWith({
34
- state: 'state',
35
- codeVerifier: 'codeVerifier',
36
- scopes: ['read:user', 'user:email'],
37
- })
38
-
39
- spyOauth2ClientCreateAuthorizationUrl.mockRestore()
40
- })
41
-
42
- it('should validate authorization code', async () => {
43
- const spyOauth2ClientValidateAuthorizationCode = spyOn(
44
- OAuth2Client.prototype,
45
- 'validateAuthorizationCode',
46
- ).mockResolvedValue({
47
- access_token: 'access_token',
48
- refresh_token: 'refresh_token',
49
- expires_in: 3600,
50
- })
51
-
52
- const res = await githubOauth.validateAuthorizationCode('code', 'codeVerifier')
53
-
54
- expect(spyOauth2ClientValidateAuthorizationCode).toHaveBeenCalledTimes(1)
55
- expect(spyOauth2ClientValidateAuthorizationCode).toHaveBeenCalledWith('code', {
56
- authenticateWith: 'request_body',
57
- credentials: 'clientSecret',
58
- codeVerifier: 'codeVerifier',
59
- })
60
-
61
- // +100 to avoid flaky
62
- expect((res.accessTokenExpiresAt?.getTime() || 0) + 100).toBeGreaterThanOrEqual(
63
- Date.now() + 3600 * 1000,
64
- )
65
-
66
- spyOauth2ClientValidateAuthorizationCode.mockRestore()
67
- })
68
-
69
- it('should refresh access token', async () => {
70
- const spyOauth2ClientRefreshAccessToken = spyOn(
71
- OAuth2Client.prototype,
72
- 'refreshAccessToken',
73
- ).mockResolvedValue({
74
- access_token: 'access_token',
75
- expires_in: 3600,
76
- })
77
-
78
- const res = await githubOauth.refreshAccessToken('refresh_token')
79
-
80
- expect(spyOauth2ClientRefreshAccessToken).toHaveBeenCalledTimes(1)
81
- expect(spyOauth2ClientRefreshAccessToken).toHaveBeenCalledWith('refresh_token', {
82
- authenticateWith: 'request_body',
83
- credentials: 'clientSecret',
84
- })
85
-
86
- expect(res.accessToken).toBe('access_token')
87
- expect(res.accessTokenExpiresAt?.getTime()).toBeGreaterThanOrEqual(Date.now() + 3600 * 1000)
88
-
89
- spyOauth2ClientRefreshAccessToken.mockRestore()
90
- })
91
- })
@@ -1,121 +0,0 @@
1
- import { OAuth2Client } from '.'
2
- import type { WabeConfig } from '../../server'
3
- import type { OAuth2ProviderWithPKCE, Tokens } from './utils'
4
-
5
- const authorizeEndpoint = 'https://github.com/login/oauth/authorize'
6
- const tokenEndpoint = 'https://github.com/login/oauth/access_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 GitHub implements OAuth2ProviderWithPKCE {
21
- private client: OAuth2Client
22
- private clientSecret: string
23
-
24
- constructor(config: WabeConfig<any>) {
25
- const githubConfig = config.authentication?.providers?.github
26
-
27
- if (!githubConfig) throw new Error('GitHub 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
- githubConfig.clientId,
35
- authorizeEndpoint,
36
- tokenEndpoint,
37
- redirectURI,
38
- )
39
-
40
- this.clientSecret = githubConfig.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, 'read:user', 'user:email'],
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
- codeVerifier,
68
- credentials: this.clientSecret,
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 userInfoResponse = await fetch('https://api.github.com/user', {
94
- headers: {
95
- Authorization: `Bearer ${accessToken}`,
96
- Accept: 'application/vnd.github.v3+json',
97
- },
98
- })
99
-
100
- const userEmailResponse = await fetch('https://api.github.com/user/emails', {
101
- headers: {
102
- Authorization: `Bearer ${accessToken}`,
103
- Accept: 'application/vnd.github.v3+json',
104
- },
105
- })
106
-
107
- if (!userInfoResponse.ok || !userEmailResponse.ok)
108
- throw new Error('Failed to fetch user information from GitHub')
109
-
110
- const userInfo = await userInfoResponse.json()
111
- const userEmails = await userEmailResponse.json()
112
-
113
- const primaryEmail = userEmails.find((email: any) => email.primary)?.email
114
-
115
- return {
116
- email: primaryEmail || null,
117
- username: userInfo.login,
118
- avatarUrl: userInfo.avatar_url,
119
- }
120
- }
121
- }
@@ -1,91 +0,0 @@
1
- import { describe, expect, it, spyOn } from 'bun:test'
2
- import { Google } from './Google'
3
- import { OAuth2Client } from './Oauth2Client'
4
-
5
- describe('Google oauth', () => {
6
- const config = {
7
- port: 3001,
8
- authentication: {
9
- backDomain: 'api.wabe.com',
10
- providers: {
11
- google: {
12
- clientId: 'clientId',
13
- clientSecret: 'clientSecret',
14
- },
15
- },
16
- },
17
- } as any
18
-
19
- const googleOauth = new Google(config)
20
-
21
- it('should create authorization url', () => {
22
- const spyOauth2ClientCreateAuthorizationUrl = spyOn(
23
- OAuth2Client.prototype,
24
- 'createAuthorizationURL',
25
- ).mockReturnValue(new URL('https://url') as never)
26
-
27
- const authorizationUrl = googleOauth.createAuthorizationURL('state', 'codeVerifier')
28
-
29
- expect(authorizationUrl.toString()).toBe(
30
- 'https://url/?access_type=offline&prompt=select_account',
31
- )
32
- expect(spyOauth2ClientCreateAuthorizationUrl).toHaveBeenCalledTimes(1)
33
- expect(spyOauth2ClientCreateAuthorizationUrl).toHaveBeenCalledWith({
34
- state: 'state',
35
- codeVerifier: 'codeVerifier',
36
- scopes: ['openid'],
37
- })
38
-
39
- spyOauth2ClientCreateAuthorizationUrl.mockRestore()
40
- })
41
-
42
- it('should validate authorization code', async () => {
43
- const spyOauth2ClientValidateAuthorizationCode = spyOn(
44
- OAuth2Client.prototype,
45
- 'validateAuthorizationCode',
46
- ).mockResolvedValue({
47
- access_token: 'access_token',
48
- refresh_token: 'refresh_token',
49
- expires_in: 3600,
50
- })
51
-
52
- const res = await googleOauth.validateAuthorizationCode('code', 'codeVerifier')
53
-
54
- expect(spyOauth2ClientValidateAuthorizationCode).toHaveBeenCalledTimes(1)
55
- expect(spyOauth2ClientValidateAuthorizationCode).toHaveBeenCalledWith('code', {
56
- authenticateWith: 'request_body',
57
- credentials: 'clientSecret',
58
- codeVerifier: 'codeVerifier',
59
- })
60
-
61
- // +100 to avoid flaky
62
- expect((res.accessTokenExpiresAt?.getTime() || 0) + 100).toBeGreaterThanOrEqual(
63
- Date.now() + 3600 * 1000,
64
- )
65
-
66
- spyOauth2ClientValidateAuthorizationCode.mockRestore()
67
- })
68
-
69
- it('should refresh access token', async () => {
70
- const spyOauth2ClientRefreshAccessToken = spyOn(
71
- OAuth2Client.prototype,
72
- 'refreshAccessToken',
73
- ).mockResolvedValue({
74
- access_token: 'access_token',
75
- expires_in: 3600,
76
- })
77
-
78
- const res = await googleOauth.refreshAccessToken('refresh_token')
79
-
80
- expect(spyOauth2ClientRefreshAccessToken).toHaveBeenCalledTimes(1)
81
- expect(spyOauth2ClientRefreshAccessToken).toHaveBeenCalledWith('refresh_token', {
82
- authenticateWith: 'request_body',
83
- credentials: 'clientSecret',
84
- })
85
-
86
- expect(res.accessToken).toBe('access_token')
87
- expect(res.accessTokenExpiresAt?.getTime()).toBeGreaterThanOrEqual(Date.now() + 3600 * 1000)
88
-
89
- spyOauth2ClientRefreshAccessToken.mockRestore()
90
- })
91
- })