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.
- package/dist/database/DatabaseController.d.ts +2 -0
- package/dist/file/FileDevAdapter.d.ts +1 -0
- package/dist/graphql/pointerAndRelationFunction.d.ts +6 -0
- package/dist/index.js +3827 -3541
- package/dist/schema/Schema.d.ts +2 -2
- package/dist/server/generateCodegen.d.ts +10 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/utils/objectKeys.d.ts +1 -0
- package/package.json +7 -4
- package/dev/index.ts +0 -215
- package/dist/schema/resolvers/sendEmail.d.ts +0 -1
- 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
package/src/server/index.ts
DELETED
|
@@ -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 }
|
package/src/server/interface.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/crypto.test.ts
DELETED
|
@@ -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
|
-
})
|
package/src/utils/crypto.ts
DELETED
|
@@ -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
|
-
}
|