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,336 +0,0 @@
1
- import type { DatabaseConfig } from '../database'
2
- import { DatabaseController } from '../database/DatabaseController'
3
- import { type EnumInterface, Schema, type SchemaInterface } from '../schema/Schema'
4
- import { GraphQLObjectType, GraphQLSchema, NoSchemaIntrospectionCustomRule } from 'graphql'
5
- import { GraphQLSchema as WabeGraphQLSchema } from '../graphql'
6
- import type { AuthenticationConfig } from '../authentication/interface'
7
- import { type WabeRoute, defaultRoutes } from './routes'
8
- import { type Hook, getDefaultHooks } from '../hooks'
9
- import { generateCodegen } from './generateCodegen'
10
- import { defaultAuthenticationMethods } from '../authentication/defaultAuthentication'
11
- import { Wobe, cors, rateLimit } from 'wobe'
12
- import type { Context, CorsOptions, RateLimitOptions } from 'wobe'
13
- import type { WabeContext } from './interface'
14
- import { initializeRoles } from '../authentication/roles'
15
- import type { EmailConfig } from '../email'
16
- import { EmailController } from '../email/EmailController'
17
- import { FileController } from '../file/FileController'
18
- import { defaultSessionHandler } from './defaultSessionHandler'
19
- import type { CronConfig } from '../cron'
20
- import type { FileConfig } from '../file'
21
- import { WobeGraphqlYogaPlugin } from 'wobe-graphql-yoga'
22
-
23
- type SecurityConfig = {
24
- corsOptions?: CorsOptions
25
- rateLimit?: RateLimitOptions
26
- hideSensitiveErrorMessage?: boolean
27
- disableCSRFProtection?: boolean
28
- disableGraphQLDashboard?: boolean
29
- disableIntrospection?: boolean
30
- maxGraphqlDepth?: number
31
- }
32
-
33
- export * from './interface'
34
- export * from './routes'
35
-
36
- export const defaultRoles = ['DashboardAdmin']
37
-
38
- export interface WabeConfig<T extends WabeTypes> {
39
- port: number
40
- isProduction: boolean
41
- hostname?: string
42
- security?: SecurityConfig
43
- schema?: SchemaInterface<T>
44
- graphqlSchema?: GraphQLSchema
45
- database: DatabaseConfig<T>
46
- codegen?:
47
- | {
48
- enabled: true
49
- path: string
50
- }
51
- | { enabled?: false }
52
- authentication?: AuthenticationConfig<T>
53
- routes?: WabeRoute[]
54
- rootKey: string
55
- hooks?: Hook<T, any>[]
56
- email?: EmailConfig
57
- file?: FileConfig<T>
58
- crons?: CronConfig<T>
59
- }
60
-
61
- export type WabeTypes = {
62
- types: Record<any, any>
63
- where: Record<any, any>
64
- scalars: string
65
- enums: Record<any, any>
66
- }
67
-
68
- export type WobeCustomContext<T extends WabeTypes> = Context & {
69
- wabe: WabeContext<T>
70
- }
71
-
72
- type WabeControllers<T extends WabeTypes> = {
73
- database: DatabaseController<T>
74
- email?: EmailController
75
- file?: FileController
76
- }
77
-
78
- export class Wabe<T extends WabeTypes> {
79
- public server: Wobe<WobeCustomContext<T>>
80
-
81
- public config: WabeConfig<T>
82
- public controllers: WabeControllers<T>
83
-
84
- constructor({
85
- isProduction,
86
- port,
87
- hostname,
88
- security,
89
- schema,
90
- database,
91
- authentication,
92
- rootKey,
93
- codegen,
94
- hooks,
95
- file,
96
- email,
97
- routes,
98
- crons,
99
- }: WabeConfig<T>) {
100
- this.config = {
101
- isProduction,
102
- port,
103
- hostname,
104
- security,
105
- schema,
106
- database,
107
- codegen,
108
- authentication,
109
- rootKey,
110
- hooks,
111
- email,
112
- routes,
113
- file,
114
- crons,
115
- }
116
-
117
- this.server = new Wobe<WobeCustomContext<T>>({ hostname }).get('/health', (context) => {
118
- context.res.status = 200
119
- context.res.send('OK')
120
- })
121
-
122
- this.controllers = {
123
- database: new DatabaseController<T>(database.adapter),
124
- email: email?.adapter ? new EmailController(email.adapter) : undefined,
125
- file: file?.adapter ? new FileController(file.adapter, this) : undefined,
126
- }
127
-
128
- this.loadCrons()
129
- this.loadAuthenticationMethods()
130
- this.loadRoleEnum()
131
- this.loadRoutes()
132
- this.loadHooks()
133
- }
134
-
135
- loadCrons() {
136
- if (!this.config.crons) return
137
-
138
- const crons = this.config.crons.map((cron) => ({
139
- ...cron,
140
- job: cron.cron(this),
141
- }))
142
-
143
- this.config.crons = crons
144
- }
145
-
146
- loadRoleEnum() {
147
- const roles = [...defaultRoles, ...(this.config.authentication?.roles || [])]
148
-
149
- const roleEnum: EnumInterface = {
150
- name: 'RoleEnum',
151
- values: roles.reduce(
152
- (acc, currentRole) => {
153
- acc[currentRole] = currentRole
154
- return acc
155
- },
156
- {} as Record<string, any>,
157
- ),
158
- }
159
-
160
- this.config.schema = {
161
- ...this.config.schema,
162
- enums: [...(this.config.schema?.enums || []), roleEnum],
163
- }
164
- }
165
-
166
- loadAuthenticationMethods() {
167
- this.config.authentication = {
168
- ...this.config.authentication,
169
- customAuthenticationMethods: [
170
- ...defaultAuthenticationMethods<T>(),
171
- ...(this.config.authentication?.customAuthenticationMethods || []),
172
- ],
173
- }
174
- }
175
-
176
- loadHooks() {
177
- if (this.config.hooks?.find((hook) => hook.priority <= 0))
178
- throw new Error('Hook priority <= 0 is reserved for internal uses')
179
-
180
- this.config.hooks = [...getDefaultHooks(), ...(this.config.hooks || [])]
181
- }
182
-
183
- loadRoutes() {
184
- const enableBucketRoute = !this.config.isProduction
185
-
186
- const wabeRoutes = [
187
- ...defaultRoutes({
188
- devDirectory: this.config.file?.devDirectory || `${__dirname}/../../bucket`,
189
- enableBucketRoute,
190
- }),
191
- ...(this.config.routes || []),
192
- ]
193
-
194
- wabeRoutes.forEach((route) => {
195
- const { method } = route
196
-
197
- switch (method) {
198
- case 'GET':
199
- this.server.get(route.path, route.handler)
200
- break
201
- case 'POST':
202
- this.server.post(route.path, route.handler)
203
- break
204
- case 'PUT':
205
- this.server.put(route.path, route.handler)
206
- break
207
- case 'DELETE':
208
- this.server.delete(route.path, route.handler)
209
- break
210
- default:
211
- throw new Error('Invalid method for default route')
212
- }
213
- })
214
- }
215
-
216
- async start() {
217
- if (!this.config.rootKey || this.config.rootKey.length === 0)
218
- throw new Error('rootKey cannot be empty')
219
-
220
- if (this.config.authentication?.session && !this.config.authentication.session.jwtSecret)
221
- throw new Error('Authentication session requires jwt secret')
222
-
223
- const wabeSchema = new Schema(this.config)
224
-
225
- this.config.schema = wabeSchema.schema
226
-
227
- await this.controllers.database.initializeDatabase(wabeSchema.schema)
228
-
229
- const graphqlSchema = new WabeGraphQLSchema(wabeSchema)
230
-
231
- const types = graphqlSchema.createSchema()
232
-
233
- this.config.graphqlSchema = new GraphQLSchema({
234
- query: new GraphQLObjectType({
235
- name: 'Query',
236
- fields: types.queries,
237
- }),
238
- mutation: new GraphQLObjectType({
239
- name: 'Mutation',
240
- fields: types.mutations,
241
- }),
242
- types: [...types.scalars, ...types.enums, ...types.objects],
243
- })
244
-
245
- if (
246
- !this.config.isProduction &&
247
- process.env.NODE_ENV !== 'test' &&
248
- this.config.codegen &&
249
- this.config.codegen.enabled &&
250
- this.config.codegen.path.length > 0
251
- ) {
252
- await generateCodegen({
253
- path: this.config.codegen.path,
254
- schema: wabeSchema.schema,
255
- graphqlSchema: this.config.graphqlSchema,
256
- })
257
-
258
- // If we just want codegen we exit before server created.
259
- // Not the best solution but useful to avoid multiple source of truth
260
- if (process.env.CODEGEN) process.exit(0)
261
- }
262
-
263
- this.server.options(
264
- '/*',
265
- (ctx) => {
266
- return ctx.res.send('OK')
267
- },
268
- cors(this.config.security?.corsOptions),
269
- )
270
-
271
- const rateLimitOptions = this.config.security?.rateLimit
272
-
273
- if (rateLimitOptions) this.server.beforeHandler(rateLimit(rateLimitOptions))
274
-
275
- this.server.beforeHandler(cors(this.config.security?.corsOptions))
276
-
277
- // Set the wabe context
278
- this.server.beforeHandler(
279
- // @ts-expect-error
280
- this.config.authentication?.sessionHandler ||
281
- // @ts-expect-error
282
- defaultSessionHandler(this),
283
- )
284
-
285
- const maxDepth = this.config.security?.maxGraphqlDepth ?? 50
286
-
287
- await this.server.usePlugin(
288
- WobeGraphqlYogaPlugin({
289
- schema: this.config.graphqlSchema,
290
- allowGetRequests: !(this.config.security?.disableGraphQLDashboard ?? false),
291
- maskedErrors: this.config.security?.hideSensitiveErrorMessage || this.config.isProduction,
292
- allowIntrospection: !(this.config.security?.disableIntrospection ?? false),
293
- maxDepth,
294
- allowMultipleOperations: true,
295
- graphqlEndpoint: '/graphql',
296
- plugins: [
297
- (() => {
298
- const introspectionDisabled = new WeakSet<Request>()
299
- return {
300
- onRequestParse: ({ request }: { request: Request }) => {
301
- if (!(this.config.security?.disableIntrospection ?? false)) return
302
- introspectionDisabled.add(request)
303
- },
304
- onValidate: ({
305
- addValidationRule,
306
- context,
307
- }: {
308
- addValidationRule: (rule: unknown) => void
309
- context: { request: Request }
310
- }) => {
311
- if (introspectionDisabled.has(context.request)) {
312
- addValidationRule(NoSchemaIntrospectionCustomRule)
313
- }
314
- },
315
- }
316
- })(),
317
- ],
318
- context: async (ctx): Promise<WabeContext<T>> => ctx.wabe,
319
- }),
320
- )
321
-
322
- this.server.listen(this.config.port, ({ port }) => {
323
- if (!process.env.TEST) console.log(`Server is running on port ${port}`)
324
- })
325
-
326
- // @ts-expect-error
327
- await initializeRoles(this)
328
- }
329
-
330
- async close() {
331
- await this.controllers.database.close()
332
- await this.server.stop()
333
- }
334
- }
335
-
336
- export { generateCodegen }
@@ -1,11 +0,0 @@
1
- import type { WobeResponse } from 'wobe'
2
- import type { Wabe, WabeTypes } from '.'
3
-
4
- export interface WabeContext<T extends WabeTypes> {
5
- response?: WobeResponse
6
- user?: T['types']['User'] | null
7
- sessionId?: string | null
8
- isRoot: boolean
9
- wabe: Wabe<T>
10
- isGraphQLCall?: boolean
11
- }
@@ -1,171 +0,0 @@
1
- import type { Context } from 'wobe'
2
- import type { WabeContext } from '../interface'
3
- import { ProviderEnum } from '../../authentication/interface'
4
- import { getGraphqlClient } from '../../utils/helper'
5
- import { gql } from 'graphql-request'
6
- import { Google } from '../../authentication/oauth'
7
- import { getSessionCookieSameSite } from '../../authentication/cookies'
8
- import { generateRandomValues } from '../../authentication/oauth/utils'
9
- import { GitHub } from '../../authentication/oauth/GitHub'
10
-
11
- /*
12
- - Generate code verifier (back)
13
- - Sent post request to a route on back with code verifier in url (back)
14
- - Generate code challenge (back)
15
- - Redirect the user to google auth page with code challenge (back -> front)
16
- - User sign in with google (front)
17
- - The user is redirected to the route with the code (front -> back)
18
- - Get the code from the url (back)
19
- - Validate and sign in with google provider (back)
20
- */
21
-
22
- // https://www.rfc-editor.org/rfc/rfc7636#section-4.4 not precise the storage of codeVerifier
23
- export const oauthHandlerCallback = async (context: Context, wabeContext: WabeContext<any>) => {
24
- try {
25
- const state = decodeURIComponent(context.query.state || '')
26
- const code = decodeURIComponent(context.query.code || '')
27
-
28
- const stateInCookie = context.getCookie('state')
29
-
30
- if (state !== stateInCookie) throw new Error('Authentication failed')
31
-
32
- const codeVerifier = context.getCookie('code_verifier')
33
- const provider = context.getCookie('provider')
34
-
35
- const { signInWith } = await getGraphqlClient(wabeContext.wabe.config.port).request<any>(
36
- gql`
37
- mutation signInWith(
38
- $authorizationCode: String!
39
- $codeVerifier: String!
40
- ) {
41
- signInWith(
42
- input: {
43
- authentication: {
44
- ${provider}: {
45
- authorizationCode: $authorizationCode
46
- codeVerifier: $codeVerifier
47
- }
48
- }
49
- }
50
- ){
51
- accessToken
52
- refreshToken
53
- }
54
- }
55
- `,
56
- {
57
- authorizationCode: code,
58
- codeVerifier,
59
- },
60
- )
61
-
62
- const { accessToken, refreshToken } = signInWith
63
-
64
- const isCookieSession = !!wabeContext.wabe.config.authentication?.session?.cookieSession
65
- const sameSite = getSessionCookieSameSite(wabeContext.wabe.config)
66
-
67
- context.res.setCookie('accessToken', accessToken, {
68
- // If cookie session we put httpOnly to true, otherwise the front will need to get it
69
- // So we keep it to false
70
- httpOnly: isCookieSession,
71
- path: '/',
72
- maxAge:
73
- (wabeContext.wabe.config.authentication?.session?.accessTokenExpiresInMs ||
74
- 60 * 15 * 1000) / 1000, // 15 minutes in seconds
75
- sameSite,
76
- secure: true,
77
- })
78
-
79
- context.res.setCookie('refreshToken', refreshToken, {
80
- // If cookie session we put httpOnly to true, otherwise the front will need to get it
81
- // So we keep it to false
82
- httpOnly: isCookieSession,
83
- path: '/',
84
- maxAge:
85
- (wabeContext.wabe.config.authentication?.session?.accessTokenExpiresInMs ||
86
- 60 * 15 * 1000) / 1000, // 15 minutes in seconds
87
- sameSite,
88
- secure: true,
89
- })
90
-
91
- context.redirect(wabeContext.wabe.config.authentication?.successRedirectPath || '/')
92
- } catch {
93
- context.redirect(wabeContext.wabe.config.authentication?.failureRedirectPath || '/')
94
- }
95
- }
96
-
97
- export const authHandler = (
98
- context: Context,
99
- wabeContext: WabeContext<any>,
100
- provider: ProviderEnum,
101
- ) => {
102
- if (!wabeContext.wabe.config) throw new Error('Wabe config not found')
103
-
104
- context.res.setCookie('provider', provider, {
105
- httpOnly: true,
106
- path: '/',
107
- maxAge: 60 * 5, // 5 minutes
108
- secure: true,
109
- })
110
-
111
- switch (provider) {
112
- case ProviderEnum.google: {
113
- const googleOauth = new Google(wabeContext.wabe.config)
114
-
115
- const state = generateRandomValues()
116
- const codeVerifier = generateRandomValues()
117
-
118
- context.res.setCookie('code_verifier', codeVerifier, {
119
- httpOnly: true,
120
- path: '/',
121
- maxAge: 60 * 5, // 5 minutes
122
- secure: true,
123
- })
124
-
125
- context.res.setCookie('state', state, {
126
- httpOnly: true,
127
- path: '/',
128
- maxAge: 60 * 5, // 5 minutes
129
- secure: true,
130
- })
131
-
132
- const authorizationURL = googleOauth.createAuthorizationURL(state, codeVerifier, {
133
- scopes: ['email'],
134
- })
135
-
136
- context.redirect(authorizationURL.toString())
137
-
138
- break
139
- }
140
- case ProviderEnum.github: {
141
- const githubOauth = new GitHub(wabeContext.wabe.config)
142
-
143
- const state = generateRandomValues()
144
- const codeVerifier = generateRandomValues()
145
-
146
- context.res.setCookie('code_verifier', codeVerifier, {
147
- httpOnly: true,
148
- path: '/',
149
- maxAge: 60 * 5, // 5 minutes
150
- secure: true,
151
- })
152
-
153
- context.res.setCookie('state', state, {
154
- httpOnly: true,
155
- path: '/',
156
- maxAge: 60 * 5, // 5 minutes
157
- secure: true,
158
- })
159
-
160
- const authorizationURL = githubOauth.createAuthorizationURL(state, codeVerifier, {
161
- scopes: ['email'],
162
- })
163
-
164
- context.redirect(authorizationURL.toString())
165
-
166
- break
167
- }
168
- default:
169
- break
170
- }
171
- }
@@ -1,48 +0,0 @@
1
- import { type WobeHandler, uploadDirectory } from 'wobe'
2
- import type { ProviderEnum } from '../../authentication/interface'
3
- import { authHandler, oauthHandlerCallback } from './authHandler'
4
- import type { WobeCustomContext } from '..'
5
-
6
- export interface WabeRoute {
7
- method: 'GET' | 'POST' | 'PUT' | 'DELETE'
8
- path: string
9
- handler: WobeHandler<WobeCustomContext<any>>
10
- }
11
-
12
- export const defaultRoutes = ({
13
- devDirectory,
14
- enableBucketRoute,
15
- }: {
16
- devDirectory: string
17
- enableBucketRoute: boolean
18
- }): WabeRoute[] => {
19
- const routes: WabeRoute[] = [
20
- {
21
- method: 'GET',
22
- path: '/auth/oauth',
23
- handler: (context) => {
24
- const provider = context.query.provider
25
-
26
- if (!provider) throw new Error('Authentication failed, provider not found')
27
-
28
- // TODO: Maybe check if the value is in the enum
29
- return authHandler(context, context.wabe, provider as ProviderEnum)
30
- },
31
- },
32
- {
33
- method: 'GET',
34
- path: '/auth/oauth/callback',
35
- handler: (context) => oauthHandlerCallback(context, context.wabe),
36
- },
37
- ]
38
-
39
- if (enableBucketRoute) {
40
- routes.push({
41
- method: 'GET',
42
- path: '/bucket/:filename',
43
- handler: uploadDirectory({ directory: devDirectory }),
44
- })
45
- }
46
-
47
- return routes
48
- }
@@ -1,41 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { encryptDeterministicToken, decryptDeterministicToken } from './crypto'
3
-
4
- const key = Buffer.alloc(32, 1) // deterministic test key
5
-
6
- describe('crypto deterministic token helpers', () => {
7
- it('should encrypt and decrypt deterministically with same key', () => {
8
- const token = 'my-token'
9
- const encrypted = encryptDeterministicToken(token, key)
10
- const decrypted = decryptDeterministicToken(encrypted, key)
11
-
12
- expect(decrypted).toBe(token)
13
- })
14
-
15
- it('should produce the same ciphertext for the same token/key', () => {
16
- const token = 'stable'
17
- const enc1 = encryptDeterministicToken(token, key)
18
- const enc2 = encryptDeterministicToken(token, key)
19
-
20
- expect(enc1).toBe(enc2)
21
- })
22
-
23
- it('should produce different ciphertexts for different tokens', () => {
24
- const enc1 = encryptDeterministicToken('a', key)
25
- const enc2 = encryptDeterministicToken('b', key)
26
-
27
- expect(enc1).not.toBe(enc2)
28
- })
29
-
30
- it('should return null when decrypting with the wrong key', () => {
31
- const token = 'secret'
32
- const encrypted = encryptDeterministicToken(token, key)
33
- const wrongKey = Buffer.alloc(32, 2)
34
-
35
- expect(decryptDeterministicToken(encrypted, wrongKey)).toBeNull()
36
- })
37
-
38
- it('should return null on malformed ciphertext', () => {
39
- expect(decryptDeterministicToken('bad:format', key)).toBeNull()
40
- })
41
- })
@@ -1,105 +0,0 @@
1
- import { randomBytes, createCipheriv, createDecipheriv, createHmac } from 'node:crypto'
2
- import { promisify } from 'node:util'
3
-
4
- const params = {
5
- parallelism: 1,
6
- tagLength: 64,
7
- memory: 65536,
8
- passes: 2,
9
- }
10
-
11
- /*
12
- * Hash a string with Argon2id and PHC format
13
- * @return : Returns the PHC format of the hashed text
14
- */
15
- export const hashArgon2 = async (text: string) => {
16
- if (process.versions.bun) return Bun.password.hash(text, { algorithm: 'argon2id' })
17
-
18
- // Node support
19
- const argon2 = promisify(require('node:crypto').argon2)
20
-
21
- const nonce = randomBytes(16)
22
-
23
- const result = await argon2('argon2id', {
24
- message: text,
25
- nonce,
26
- ...params,
27
- })
28
-
29
- return `$argon2id$v=19$m=${params.memory},t=${params.passes},p=${params.parallelism}$${nonce.toString('base64').replace(/=+$/, '')}$${result.toString('base64').replace(/=+$/, '')}`
30
- }
31
-
32
- /*
33
- * Verify if a hash matchs with a string
34
- * @return : Returns true if the password matchs with the hash, false otherwise
35
- */
36
- export const verifyArgon2 = async (password: string, hash: string) => {
37
- if (process.versions.bun) return Bun.password.verify(password, hash, 'argon2id')
38
-
39
- // Node support
40
- const [, algorithm, , paramString, nonceHex, storedHashHex] = hash.split('$')
41
-
42
- const kvPairs = paramString?.split(',')
43
- const parsedParams = Object.fromEntries(
44
- kvPairs?.map((pair) => {
45
- const [key, value] = pair.split('=')
46
- return [key, Number.parseInt(value || '', 10)]
47
- }) || [],
48
- )
49
-
50
- const memory = parsedParams.m
51
- const passes = parsedParams.t
52
- const parallelism = parsedParams.p
53
-
54
- const newDerived = await promisify(require('node:crypto'))(algorithm, {
55
- nonce: Buffer.from(nonceHex || '', 'base64'),
56
- parallelism,
57
- tagLength: 64,
58
- memory,
59
- passes,
60
- message: password,
61
- })
62
-
63
- const isMatch = crypto.timingSafeEqual(
64
- Buffer.from(newDerived),
65
- Buffer.from(storedHashHex || '', 'base64'),
66
- )
67
-
68
- return isMatch
69
- }
70
-
71
- export const isArgon2Hash = (value: string): boolean =>
72
- typeof value === 'string' && value.startsWith('$argon2')
73
-
74
- /**
75
- * Deterministic AES-256-GCM encryption for tokens.
76
- * IV is derived via HMAC-SHA256(key, token) to allow equality checks without storing plaintext.
77
- * Caller must provide a strong 32-byte key (already derived/hashed).
78
- */
79
- export const encryptDeterministicToken = (token: string, key: Buffer): string => {
80
- const iv = createHmac('sha256', key).update(token).digest().subarray(0, 12)
81
- const cipher = createCipheriv('aes-256-gcm', key, iv)
82
- const encrypted = Buffer.concat([cipher.update(token, 'utf8'), cipher.final()])
83
- const tag = cipher.getAuthTag()
84
- return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`
85
- }
86
-
87
- export const decryptDeterministicToken = (
88
- encryptedToken: string | undefined,
89
- key: Buffer,
90
- ): string | null => {
91
- if (!encryptedToken) return null
92
- const [ivHex, tagHex, valueHex] = encryptedToken.split(':')
93
- if (!ivHex || !tagHex || !valueHex) return null
94
- try {
95
- const iv = Buffer.from(ivHex, 'hex')
96
- const tag = Buffer.from(tagHex, 'hex')
97
- const encryptedValue = Buffer.from(valueHex, 'hex')
98
- const decipher = createDecipheriv('aes-256-gcm', key, iv)
99
- decipher.setAuthTag(tag)
100
- const decrypted = Buffer.concat([decipher.update(encryptedValue), decipher.final()])
101
- return decrypted.toString('utf8')
102
- } catch {
103
- return null
104
- }
105
- }