wabe 0.6.9 → 0.6.10
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/README.md +138 -32
- package/bucket/b.txt +1 -0
- package/dev/index.ts +215 -0
- package/dist/authentication/Session.d.ts +4 -1
- package/dist/authentication/interface.d.ts +16 -0
- package/dist/email/interface.d.ts +1 -1
- package/dist/graphql/resolvers.d.ts +4 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +8713 -8867
- package/dist/server/index.d.ts +4 -2
- package/dist/utils/crypto.d.ts +7 -0
- package/dist/utils/helper.d.ts +4 -1
- package/generated/schema.graphql +16 -14
- package/generated/wabe.ts +4 -4
- package/package.json +15 -15
- package/src/authentication/OTP.test.ts +69 -0
- package/src/authentication/OTP.ts +66 -0
- package/src/authentication/Session.test.ts +665 -0
- package/src/authentication/Session.ts +529 -0
- package/src/authentication/defaultAuthentication.ts +214 -0
- package/src/authentication/index.ts +3 -0
- package/src/authentication/interface.ts +157 -0
- package/src/authentication/oauth/GitHub.test.ts +105 -0
- package/src/authentication/oauth/GitHub.ts +133 -0
- package/src/authentication/oauth/Google.test.ts +105 -0
- package/src/authentication/oauth/Google.ts +110 -0
- package/src/authentication/oauth/Oauth2Client.test.ts +225 -0
- package/src/authentication/oauth/Oauth2Client.ts +140 -0
- package/src/authentication/oauth/index.ts +2 -0
- package/src/authentication/oauth/utils.test.ts +35 -0
- package/src/authentication/oauth/utils.ts +28 -0
- package/src/authentication/providers/EmailOTP.test.ts +138 -0
- package/src/authentication/providers/EmailOTP.ts +93 -0
- package/src/authentication/providers/EmailPassword.test.ts +187 -0
- package/src/authentication/providers/EmailPassword.ts +130 -0
- package/src/authentication/providers/EmailPasswordSRP.test.ts +206 -0
- package/src/authentication/providers/EmailPasswordSRP.ts +184 -0
- package/src/authentication/providers/GitHub.ts +30 -0
- package/src/authentication/providers/Google.ts +30 -0
- package/src/authentication/providers/OAuth.test.ts +185 -0
- package/src/authentication/providers/OAuth.ts +112 -0
- package/src/authentication/providers/PhonePassword.test.ts +187 -0
- package/src/authentication/providers/PhonePassword.ts +129 -0
- package/src/authentication/providers/QRCodeOTP.test.ts +79 -0
- package/src/authentication/providers/QRCodeOTP.ts +65 -0
- package/src/authentication/providers/index.ts +6 -0
- package/src/authentication/resolvers/refreshResolver.test.ts +37 -0
- package/src/authentication/resolvers/refreshResolver.ts +20 -0
- package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
- package/src/authentication/resolvers/signInWithResolver.test.ts +307 -0
- package/src/authentication/resolvers/signInWithResolver.ts +102 -0
- package/src/authentication/resolvers/signOutResolver.test.ts +41 -0
- package/src/authentication/resolvers/signOutResolver.ts +22 -0
- package/src/authentication/resolvers/signUpWithResolver.test.ts +186 -0
- package/src/authentication/resolvers/signUpWithResolver.ts +69 -0
- package/src/authentication/resolvers/verifyChallenge.test.ts +136 -0
- package/src/authentication/resolvers/verifyChallenge.ts +69 -0
- package/src/authentication/roles.test.ts +59 -0
- package/src/authentication/roles.ts +40 -0
- package/src/authentication/utils.test.ts +99 -0
- package/src/authentication/utils.ts +43 -0
- package/src/cache/InMemoryCache.test.ts +62 -0
- package/src/cache/InMemoryCache.ts +45 -0
- package/src/cron/index.test.ts +17 -0
- package/src/cron/index.ts +46 -0
- package/src/database/DatabaseController.test.ts +625 -0
- package/src/database/DatabaseController.ts +983 -0
- package/src/database/index.test.ts +1230 -0
- package/src/database/index.ts +9 -0
- package/src/database/interface.ts +312 -0
- package/src/email/DevAdapter.ts +8 -0
- package/src/email/EmailController.test.ts +29 -0
- package/src/email/EmailController.ts +13 -0
- package/src/email/index.ts +2 -0
- package/src/email/interface.ts +36 -0
- package/src/email/templates/sendOtpCode.ts +120 -0
- package/src/file/FileController.ts +28 -0
- package/src/file/FileDevAdapter.ts +54 -0
- package/src/file/hookDeleteFile.ts +27 -0
- package/src/file/hookReadFile.ts +70 -0
- package/src/file/hookUploadFile.ts +53 -0
- package/src/file/index.test.ts +979 -0
- package/src/file/index.ts +2 -0
- package/src/file/interface.ts +42 -0
- package/src/graphql/GraphQLSchema.test.ts +4399 -0
- package/src/graphql/GraphQLSchema.ts +928 -0
- package/src/graphql/index.ts +2 -0
- package/src/graphql/parseGraphqlSchema.ts +94 -0
- package/src/graphql/parser.test.ts +217 -0
- package/src/graphql/parser.ts +566 -0
- package/src/graphql/pointerAndRelationFunction.ts +200 -0
- package/src/graphql/resolvers.ts +467 -0
- package/src/graphql/tests/aggregation.test.ts +1123 -0
- package/src/graphql/tests/e2e.test.ts +596 -0
- package/src/graphql/tests/scalars.test.ts +250 -0
- package/src/graphql/types.ts +219 -0
- package/src/hooks/HookObject.test.ts +122 -0
- package/src/hooks/HookObject.ts +168 -0
- package/src/hooks/authentication.ts +76 -0
- package/src/hooks/createUser.test.ts +77 -0
- package/src/hooks/createUser.ts +10 -0
- package/src/hooks/defaultFields.test.ts +187 -0
- package/src/hooks/defaultFields.ts +40 -0
- package/src/hooks/deleteSession.test.ts +181 -0
- package/src/hooks/deleteSession.ts +20 -0
- package/src/hooks/hashFieldHook.test.ts +163 -0
- package/src/hooks/hashFieldHook.ts +97 -0
- package/src/hooks/index.test.ts +207 -0
- package/src/hooks/index.ts +430 -0
- package/src/hooks/permissions.test.ts +424 -0
- package/src/hooks/permissions.ts +113 -0
- package/src/hooks/protected.test.ts +551 -0
- package/src/hooks/protected.ts +72 -0
- package/src/hooks/searchableFields.test.ts +166 -0
- package/src/hooks/searchableFields.ts +98 -0
- package/src/hooks/session.test.ts +138 -0
- package/src/hooks/session.ts +78 -0
- package/src/hooks/setEmail.test.ts +216 -0
- package/src/hooks/setEmail.ts +35 -0
- package/src/hooks/setupAcl.test.ts +589 -0
- package/src/hooks/setupAcl.ts +29 -0
- package/src/index.ts +9 -0
- package/src/schema/Schema.test.ts +484 -0
- package/src/schema/Schema.ts +795 -0
- package/src/schema/defaultResolvers.ts +94 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/resolvers/meResolver.test.ts +62 -0
- package/src/schema/resolvers/meResolver.ts +14 -0
- package/src/schema/resolvers/newFile.ts +0 -0
- package/src/schema/resolvers/resetPassword.test.ts +345 -0
- package/src/schema/resolvers/resetPassword.ts +64 -0
- package/src/schema/resolvers/sendEmail.test.ts +118 -0
- package/src/schema/resolvers/sendEmail.ts +21 -0
- package/src/schema/resolvers/sendOtpCode.test.ts +153 -0
- package/src/schema/resolvers/sendOtpCode.ts +52 -0
- package/src/security.test.ts +3461 -0
- package/src/server/defaultSessionHandler.test.ts +66 -0
- package/src/server/defaultSessionHandler.ts +115 -0
- package/src/server/generateCodegen.ts +476 -0
- package/src/server/index.test.ts +552 -0
- package/src/server/index.ts +354 -0
- package/src/server/interface.ts +11 -0
- package/src/server/routes/authHandler.ts +187 -0
- package/src/server/routes/index.ts +40 -0
- package/src/utils/crypto.test.ts +41 -0
- package/src/utils/crypto.ts +121 -0
- package/src/utils/export.ts +13 -0
- package/src/utils/helper.ts +195 -0
- package/src/utils/index.test.ts +11 -0
- package/src/utils/index.ts +201 -0
- package/src/utils/preload.ts +8 -0
- package/src/utils/testHelper.ts +117 -0
- package/tsconfig.json +32 -0
- package/bunfig.toml +0 -4
- package/dist/ai/index.d.ts +0 -1
- package/dist/ai/interface.d.ts +0 -9
- /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
randomBytes,
|
|
3
|
+
createCipheriv,
|
|
4
|
+
createDecipheriv,
|
|
5
|
+
createHmac,
|
|
6
|
+
} from 'node:crypto'
|
|
7
|
+
import { promisify } from 'node:util'
|
|
8
|
+
|
|
9
|
+
const params = {
|
|
10
|
+
parallelism: 1,
|
|
11
|
+
tagLength: 64,
|
|
12
|
+
memory: 65536,
|
|
13
|
+
passes: 2,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Hash a string with Argon2id and PHC format
|
|
18
|
+
* @return : Returns the PHC format of the hashed text
|
|
19
|
+
*/
|
|
20
|
+
export const hashArgon2 = async (text: string) => {
|
|
21
|
+
if (process.versions.bun)
|
|
22
|
+
return Bun.password.hash(text, { algorithm: 'argon2id' })
|
|
23
|
+
|
|
24
|
+
// Node support
|
|
25
|
+
const argon2 = promisify(require('node:crypto').argon2)
|
|
26
|
+
|
|
27
|
+
const nonce = randomBytes(16)
|
|
28
|
+
|
|
29
|
+
const result = await argon2('argon2id', {
|
|
30
|
+
message: text,
|
|
31
|
+
nonce,
|
|
32
|
+
...params,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return `$argon2id$v=19$m=${params.memory},t=${params.passes},p=${params.parallelism}$${nonce.toString('base64').replace(/=+$/, '')}$${result.toString('base64').replace(/=+$/, '')}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Verify if a hash matchs with a string
|
|
40
|
+
* @return : Returns true if the password matchs with the hash, false otherwise
|
|
41
|
+
*/
|
|
42
|
+
export const verifyArgon2 = async (password: string, hash: string) => {
|
|
43
|
+
if (process.versions.bun)
|
|
44
|
+
return Bun.password.verify(password, hash, 'argon2id')
|
|
45
|
+
|
|
46
|
+
// Node support
|
|
47
|
+
const [, algorithm, , paramString, nonceHex, storedHashHex] = hash.split('$')
|
|
48
|
+
|
|
49
|
+
const kvPairs = paramString?.split(',')
|
|
50
|
+
const parsedParams = Object.fromEntries(
|
|
51
|
+
kvPairs?.map((pair) => {
|
|
52
|
+
const [key, value] = pair.split('=')
|
|
53
|
+
return [key, Number.parseInt(value || '', 10)]
|
|
54
|
+
}) || [],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const memory = parsedParams.m
|
|
58
|
+
const passes = parsedParams.t
|
|
59
|
+
const parallelism = parsedParams.p
|
|
60
|
+
|
|
61
|
+
const newDerived = await promisify(require('node:crypto'))(algorithm, {
|
|
62
|
+
nonce: Buffer.from(nonceHex || '', 'base64'),
|
|
63
|
+
parallelism,
|
|
64
|
+
tagLength: 64,
|
|
65
|
+
memory,
|
|
66
|
+
passes,
|
|
67
|
+
message: password,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const isMatch = crypto.timingSafeEqual(
|
|
71
|
+
Buffer.from(newDerived),
|
|
72
|
+
Buffer.from(storedHashHex || '', 'base64'),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return isMatch
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const isArgon2Hash = (value: string): boolean =>
|
|
79
|
+
typeof value === 'string' && value.startsWith('$argon2')
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Deterministic AES-256-GCM encryption for tokens.
|
|
83
|
+
* IV is derived via HMAC-SHA256(key, token) to allow equality checks without storing plaintext.
|
|
84
|
+
* Caller must provide a strong 32-byte key (already derived/hashed).
|
|
85
|
+
*/
|
|
86
|
+
export const encryptDeterministicToken = (
|
|
87
|
+
token: string,
|
|
88
|
+
key: Buffer,
|
|
89
|
+
): string => {
|
|
90
|
+
const iv = createHmac('sha256', key).update(token).digest().subarray(0, 12)
|
|
91
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv)
|
|
92
|
+
const encrypted = Buffer.concat([
|
|
93
|
+
cipher.update(token, 'utf8'),
|
|
94
|
+
cipher.final(),
|
|
95
|
+
])
|
|
96
|
+
const tag = cipher.getAuthTag()
|
|
97
|
+
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const decryptDeterministicToken = (
|
|
101
|
+
encryptedToken: string | undefined,
|
|
102
|
+
key: Buffer,
|
|
103
|
+
): string | null => {
|
|
104
|
+
if (!encryptedToken) return null
|
|
105
|
+
const [ivHex, tagHex, valueHex] = encryptedToken.split(':')
|
|
106
|
+
if (!ivHex || !tagHex || !valueHex) return null
|
|
107
|
+
try {
|
|
108
|
+
const iv = Buffer.from(ivHex, 'hex')
|
|
109
|
+
const tag = Buffer.from(tagHex, 'hex')
|
|
110
|
+
const encryptedValue = Buffer.from(valueHex, 'hex')
|
|
111
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv)
|
|
112
|
+
decipher.setAuthTag(tag)
|
|
113
|
+
const decrypted = Buffer.concat([
|
|
114
|
+
decipher.update(encryptedValue),
|
|
115
|
+
decipher.final(),
|
|
116
|
+
])
|
|
117
|
+
return decrypted.toString('utf8')
|
|
118
|
+
} catch {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WabeContext } from '../server/interface'
|
|
2
|
+
|
|
3
|
+
export const contextWithRoot = (
|
|
4
|
+
context: WabeContext<any>,
|
|
5
|
+
): WabeContext<any> => ({
|
|
6
|
+
...context,
|
|
7
|
+
isRoot: true,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const notEmpty = <T>(value: T | null | undefined): value is T =>
|
|
11
|
+
value !== null && value !== undefined
|
|
12
|
+
|
|
13
|
+
export * from './crypto'
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { gql, GraphQLClient } from 'graphql-request'
|
|
2
|
+
import {
|
|
3
|
+
RoleEnum,
|
|
4
|
+
type WabeSchemaWhereTypes,
|
|
5
|
+
type WabeSchemaEnums,
|
|
6
|
+
type WabeSchemaScalars,
|
|
7
|
+
type WabeSchemaTypes,
|
|
8
|
+
} from '../../generated/wabe'
|
|
9
|
+
import type { Wabe, WabeTypes } from '../server'
|
|
10
|
+
|
|
11
|
+
export interface DevWabeTypes extends WabeTypes {
|
|
12
|
+
types: WabeSchemaTypes
|
|
13
|
+
scalars: WabeSchemaScalars
|
|
14
|
+
enums: WabeSchemaEnums
|
|
15
|
+
where: WabeSchemaWhereTypes
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const firstLetterUpperCase = (str: string): string =>
|
|
19
|
+
str.charAt(0).toUpperCase() + str.slice(1)
|
|
20
|
+
|
|
21
|
+
export const getGraphqlClient = (port: number): GraphQLClient => {
|
|
22
|
+
const client = new GraphQLClient(`http://127.0.0.1:${port}/graphql`, {
|
|
23
|
+
headers: {
|
|
24
|
+
'Wabe-Root-Key': 'dev',
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return { ...client, request: client.request<any> } as GraphQLClient
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const getAnonymousClient = (port: number): GraphQLClient => {
|
|
32
|
+
const client = new GraphQLClient(`http://127.0.0.1:${port}/graphql`)
|
|
33
|
+
|
|
34
|
+
return { ...client, request: client.request<any> } as GraphQLClient
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const getUserClient = (
|
|
38
|
+
port: number,
|
|
39
|
+
options: {
|
|
40
|
+
accessToken?: string
|
|
41
|
+
csrfToken?: string
|
|
42
|
+
},
|
|
43
|
+
): GraphQLClient => {
|
|
44
|
+
const client = new GraphQLClient(`http://127.0.0.1:${port}/graphql`, {
|
|
45
|
+
headers: {
|
|
46
|
+
'Wabe-Access-Token': options.accessToken || '',
|
|
47
|
+
'Wabe-Csrf-Token': options.csrfToken || '',
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return { ...client, request: client.request<any> } as GraphQLClient
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const getAdminUserClient = async (
|
|
55
|
+
port: number,
|
|
56
|
+
wabe: Wabe<DevWabeTypes>,
|
|
57
|
+
{ email, password }: { email: string; password: string },
|
|
58
|
+
): Promise<GraphQLClient> => {
|
|
59
|
+
const roles = await wabe.controllers.database.getObjects({
|
|
60
|
+
className: 'Role',
|
|
61
|
+
context: {
|
|
62
|
+
isRoot: true,
|
|
63
|
+
wabe,
|
|
64
|
+
} as any,
|
|
65
|
+
select: { id: true },
|
|
66
|
+
where: {
|
|
67
|
+
name: { equalTo: RoleEnum.Admin },
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const adminRoleId = roles[0]?.id
|
|
72
|
+
|
|
73
|
+
const res = await getGraphqlClient(port).request<any>(
|
|
74
|
+
gql`
|
|
75
|
+
mutation signUpWith($input: SignUpWithInput!) {
|
|
76
|
+
signUpWith(input: $input) {
|
|
77
|
+
id
|
|
78
|
+
accessToken
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`,
|
|
82
|
+
{
|
|
83
|
+
input: {
|
|
84
|
+
authentication: {
|
|
85
|
+
emailPassword: {
|
|
86
|
+
email,
|
|
87
|
+
password,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const accessToken = res.signUpWith.accessToken
|
|
95
|
+
const userId = res.signUpWith.id
|
|
96
|
+
|
|
97
|
+
await wabe.controllers.database.updateObjects({
|
|
98
|
+
className: 'User',
|
|
99
|
+
context: {
|
|
100
|
+
isRoot: true,
|
|
101
|
+
wabe,
|
|
102
|
+
} as any,
|
|
103
|
+
data: {
|
|
104
|
+
role: adminRoleId,
|
|
105
|
+
},
|
|
106
|
+
select: {},
|
|
107
|
+
where: {
|
|
108
|
+
id: { equalTo: userId },
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const client = new GraphQLClient(`http://127.0.0.1:${port}/graphql`, {
|
|
113
|
+
headers: {
|
|
114
|
+
'Wabe-Access-Token': accessToken,
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return { ...client, request: client.request<any> } as GraphQLClient
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const createUserAndUpdateRole = async ({
|
|
122
|
+
anonymousClient,
|
|
123
|
+
rootClient,
|
|
124
|
+
roleName,
|
|
125
|
+
port,
|
|
126
|
+
email,
|
|
127
|
+
}: {
|
|
128
|
+
port: number
|
|
129
|
+
anonymousClient: GraphQLClient
|
|
130
|
+
rootClient: GraphQLClient
|
|
131
|
+
roleName: string
|
|
132
|
+
email?: string
|
|
133
|
+
}) => {
|
|
134
|
+
const random = Math.random().toString(36).substring(2)
|
|
135
|
+
|
|
136
|
+
const res = await anonymousClient.request<any>(
|
|
137
|
+
gql`
|
|
138
|
+
mutation signUpWith($input: SignUpWithInput!) {
|
|
139
|
+
signUpWith(input: $input) {
|
|
140
|
+
id
|
|
141
|
+
accessToken
|
|
142
|
+
refreshToken
|
|
143
|
+
csrfToken
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`,
|
|
147
|
+
{
|
|
148
|
+
input: {
|
|
149
|
+
authentication: {
|
|
150
|
+
emailPassword: {
|
|
151
|
+
email: email || `email${random}@test.fr`,
|
|
152
|
+
password: 'password',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const resOfRoles = await rootClient.request<any>(gql`
|
|
160
|
+
query getRoles {
|
|
161
|
+
roles(where: {name: {equalTo: "${roleName}"}}) {
|
|
162
|
+
edges {
|
|
163
|
+
node {
|
|
164
|
+
id
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
`)
|
|
170
|
+
|
|
171
|
+
const roleId = resOfRoles.roles.edges[0].node.id
|
|
172
|
+
|
|
173
|
+
await rootClient.request<any>(gql`
|
|
174
|
+
mutation updateUser {
|
|
175
|
+
updateUser(input: {id: "${res.signUpWith.id}", fields: {role: {link: "${roleId}"}}}) {
|
|
176
|
+
user {
|
|
177
|
+
id
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
`)
|
|
182
|
+
|
|
183
|
+
const userClient = getUserClient(port, {
|
|
184
|
+
accessToken: res.signUpWith.accessToken,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
userClient,
|
|
189
|
+
roleId,
|
|
190
|
+
userId: res.signUpWith.id,
|
|
191
|
+
refreshToken: res.signUpWith.refreshToken,
|
|
192
|
+
accessToken: res.signUpWith.accessToken,
|
|
193
|
+
csrfToken: res.signUpWith.csrfToken,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { firstLetterInLowerCase } from '.'
|
|
3
|
+
|
|
4
|
+
describe('utils', () => {
|
|
5
|
+
it('should put the first letter in lowercase', () => {
|
|
6
|
+
expect(firstLetterInLowerCase('Hello')).toEqual('hello')
|
|
7
|
+
expect(firstLetterInLowerCase('User')).toEqual('user')
|
|
8
|
+
expect(firstLetterInLowerCase('USer')).toEqual('uSer')
|
|
9
|
+
expect(firstLetterInLowerCase('99 User')).toEqual('99 user')
|
|
10
|
+
})
|
|
11
|
+
})
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { ClassInterface } from '../schema'
|
|
2
|
+
import type { WabeTypes, WabeConfig, WabeContext } from '../server'
|
|
3
|
+
|
|
4
|
+
export const contextWithoutGraphQLCall = (
|
|
5
|
+
context: WabeContext<any>,
|
|
6
|
+
): WabeContext<any> => ({
|
|
7
|
+
...context,
|
|
8
|
+
isGraphQLCall: false,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const RFC4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
12
|
+
const RFC4648_HEX = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
|
13
|
+
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
|
14
|
+
|
|
15
|
+
type Base32Variant = 'RFC3548' | 'RFC4648' | 'RFC4648-HEX' | 'Crockford'
|
|
16
|
+
|
|
17
|
+
interface Base32Options {
|
|
18
|
+
padding?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert supported input types to Uint8Array.
|
|
23
|
+
*/
|
|
24
|
+
export const toUint8Array = (
|
|
25
|
+
data: string | ArrayBuffer | Uint8Array | Buffer,
|
|
26
|
+
): Uint8Array => {
|
|
27
|
+
if (data instanceof Uint8Array) return data
|
|
28
|
+
|
|
29
|
+
if (typeof data === 'string') {
|
|
30
|
+
const encoder = new TextEncoder()
|
|
31
|
+
return encoder.encode(data)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data)
|
|
35
|
+
|
|
36
|
+
throw new TypeError('Unsupported data type for base32 encoding')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Encode binary data to base32 using specified variant.
|
|
41
|
+
* Base on https://github.com/LinusU/base32-encode/blob/master/index.js
|
|
42
|
+
*/
|
|
43
|
+
export const base32Encode = (
|
|
44
|
+
data: string | ArrayBuffer | Uint8Array | Buffer,
|
|
45
|
+
variant: Base32Variant,
|
|
46
|
+
options: Base32Options = {},
|
|
47
|
+
): string => {
|
|
48
|
+
let alphabet: string
|
|
49
|
+
let defaultPadding: boolean
|
|
50
|
+
|
|
51
|
+
switch (variant) {
|
|
52
|
+
case 'RFC3548':
|
|
53
|
+
case 'RFC4648':
|
|
54
|
+
alphabet = RFC4648
|
|
55
|
+
defaultPadding = true
|
|
56
|
+
break
|
|
57
|
+
case 'RFC4648-HEX':
|
|
58
|
+
alphabet = RFC4648_HEX
|
|
59
|
+
defaultPadding = true
|
|
60
|
+
break
|
|
61
|
+
case 'Crockford':
|
|
62
|
+
alphabet = CROCKFORD
|
|
63
|
+
defaultPadding = false
|
|
64
|
+
break
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Unknown base32 variant: ${variant}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const padding =
|
|
70
|
+
options.padding !== undefined ? options.padding : defaultPadding
|
|
71
|
+
const view = toUint8Array(data)
|
|
72
|
+
|
|
73
|
+
let bits = 0
|
|
74
|
+
let value = 0
|
|
75
|
+
let output = ''
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < view.length; i++) {
|
|
78
|
+
// @ts-expect-error
|
|
79
|
+
value = (value << 8) | view[i]
|
|
80
|
+
bits += 8
|
|
81
|
+
|
|
82
|
+
while (bits >= 5) {
|
|
83
|
+
output += alphabet[(value >>> (bits - 5)) & 31]
|
|
84
|
+
bits -= 5
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (bits > 0) {
|
|
89
|
+
output += alphabet[(value << (5 - bits)) & 31]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (padding) {
|
|
93
|
+
while (output.length % 8 !== 0) {
|
|
94
|
+
output += '='
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return output
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const getNewObjectAfterUpdateNestedProperty = (
|
|
102
|
+
obj: any,
|
|
103
|
+
path: string,
|
|
104
|
+
value: any,
|
|
105
|
+
) => {
|
|
106
|
+
const keys = path.split('.')
|
|
107
|
+
let current = { ...obj }
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
110
|
+
const key = keys[i]
|
|
111
|
+
|
|
112
|
+
if (!key) continue
|
|
113
|
+
|
|
114
|
+
if (current[key] === undefined) {
|
|
115
|
+
current[key] = {}
|
|
116
|
+
}
|
|
117
|
+
current = current[key]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// @ts-expect-error
|
|
121
|
+
current[keys[keys.length - 1]] = value
|
|
122
|
+
return obj
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const getNestedProperty = (obj: any, path: string) => {
|
|
126
|
+
return path.split('.').reduce((acc, part) => acc?.[part], obj)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const firstLetterInUpperCase = (str: string) => {
|
|
130
|
+
const indexOfFirstLetter = str.search(/[a-z]/i)
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
str.slice(0, indexOfFirstLetter) +
|
|
134
|
+
str[indexOfFirstLetter]?.toUpperCase() +
|
|
135
|
+
str.slice(indexOfFirstLetter + 1)
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const firstLetterInLowerCase = (str: string) => {
|
|
140
|
+
const indexOfFirstLetter = str.search(/[a-z]/i)
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
str.slice(0, indexOfFirstLetter) +
|
|
144
|
+
str[indexOfFirstLetter]?.toLowerCase() +
|
|
145
|
+
str.slice(indexOfFirstLetter + 1)
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const getClassFromClassName = <T extends WabeTypes>(
|
|
150
|
+
className: string,
|
|
151
|
+
config: WabeConfig<any>,
|
|
152
|
+
): ClassInterface<T> => {
|
|
153
|
+
const classInSchema = config.schema?.classes?.find(
|
|
154
|
+
(schemaClass) => schemaClass.name === className,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if (!classInSchema) throw new Error('Class not found in schema')
|
|
158
|
+
|
|
159
|
+
return classInSchema
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// TODO: Put this in wobe
|
|
163
|
+
export const getCookieInRequestHeaders = (
|
|
164
|
+
cookieName: string,
|
|
165
|
+
headers: Headers,
|
|
166
|
+
) => {
|
|
167
|
+
const cookies = headers.get('Cookie')
|
|
168
|
+
|
|
169
|
+
if (!cookies) return
|
|
170
|
+
|
|
171
|
+
const cookie = cookies.split(';').find((c) => c.includes(cookieName))
|
|
172
|
+
|
|
173
|
+
if (!cookie) return
|
|
174
|
+
|
|
175
|
+
return cookie.split('=')[1]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* This apply the following transformations on string:
|
|
180
|
+
* - lowercase
|
|
181
|
+
* - normalize with NFD
|
|
182
|
+
* - remove diacritics and accents characters
|
|
183
|
+
* - replace matching abbreviation with long version (if disableAbbreviations is not set)
|
|
184
|
+
* - replace 2 or more spaces by one
|
|
185
|
+
* - replace all non alpha characters by a space
|
|
186
|
+
* - trim
|
|
187
|
+
*/
|
|
188
|
+
export const tokenize = (value: string) => {
|
|
189
|
+
const tmpValue = value
|
|
190
|
+
.toLowerCase()
|
|
191
|
+
.normalize('NFD')
|
|
192
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
193
|
+
.replace(/\s\s+/g, ' ')
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
tmpValue
|
|
197
|
+
// Replace all non alpha
|
|
198
|
+
.replace(/[\W_]+/g, ' ')
|
|
199
|
+
.trim()
|
|
200
|
+
)
|
|
201
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid'
|
|
2
|
+
import { type ClassInterface, EmailDevAdapter, FileDevAdapter } from '..'
|
|
3
|
+
import { Wabe } from '../server'
|
|
4
|
+
import type { DevWabeTypes } from './helper'
|
|
5
|
+
import getPort from 'get-port'
|
|
6
|
+
|
|
7
|
+
export const getDatabaseAdapter = async (databaseName: string) => {
|
|
8
|
+
const mongodbAdapter = await import('wabe-mongodb')
|
|
9
|
+
|
|
10
|
+
return new mongodbAdapter.MongoAdapter<DevWabeTypes>({
|
|
11
|
+
// For postgres
|
|
12
|
+
// databaseUrl: 'postgresql://wabe:wabe@localhost:5432',
|
|
13
|
+
databaseUrl: 'mongodb://localhost:27045',
|
|
14
|
+
databaseName,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const setupTests = async (
|
|
19
|
+
additionalClasses: ClassInterface<any>[] = [],
|
|
20
|
+
options: {
|
|
21
|
+
isProduction?: boolean
|
|
22
|
+
disableCSRFProtection?: boolean
|
|
23
|
+
} = {},
|
|
24
|
+
) => {
|
|
25
|
+
const databaseId = uuid()
|
|
26
|
+
|
|
27
|
+
const port = await getPort()
|
|
28
|
+
|
|
29
|
+
const wabe = new Wabe<DevWabeTypes>({
|
|
30
|
+
isProduction: !!options.isProduction,
|
|
31
|
+
rootKey: 'dev',
|
|
32
|
+
database: {
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
adapter: await getDatabaseAdapter(databaseId),
|
|
35
|
+
},
|
|
36
|
+
security: {
|
|
37
|
+
// To make test easier keep default value to true
|
|
38
|
+
disableCSRFProtection: options.disableCSRFProtection ?? true,
|
|
39
|
+
},
|
|
40
|
+
authentication: {
|
|
41
|
+
roles: ['Client', 'Client2', 'Client3', 'Admin'],
|
|
42
|
+
session: {
|
|
43
|
+
jwtSecret: 'dev',
|
|
44
|
+
cookieSession: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
port,
|
|
48
|
+
email: {
|
|
49
|
+
adapter: new EmailDevAdapter(),
|
|
50
|
+
mainEmail: 'main.email@wabe.com',
|
|
51
|
+
},
|
|
52
|
+
file: {
|
|
53
|
+
adapter: new FileDevAdapter(),
|
|
54
|
+
// 12 hours of cache
|
|
55
|
+
urlCacheInSeconds: 3600 * 12,
|
|
56
|
+
},
|
|
57
|
+
schema: {
|
|
58
|
+
classes: [
|
|
59
|
+
...additionalClasses,
|
|
60
|
+
{
|
|
61
|
+
name: 'User',
|
|
62
|
+
fields: {
|
|
63
|
+
name: { type: 'String' },
|
|
64
|
+
age: { type: 'Int' },
|
|
65
|
+
isAdmin: { type: 'Boolean', defaultValue: false },
|
|
66
|
+
floatValue: { type: 'Float' },
|
|
67
|
+
birthDate: { type: 'Date' },
|
|
68
|
+
arrayValue: {
|
|
69
|
+
type: 'Array',
|
|
70
|
+
typeValue: 'String',
|
|
71
|
+
},
|
|
72
|
+
test: { type: 'TestScalar' },
|
|
73
|
+
},
|
|
74
|
+
searchableFields: ['email'],
|
|
75
|
+
permissions: {
|
|
76
|
+
create: {
|
|
77
|
+
requireAuthentication: false,
|
|
78
|
+
},
|
|
79
|
+
delete: {
|
|
80
|
+
requireAuthentication: true,
|
|
81
|
+
},
|
|
82
|
+
update: {
|
|
83
|
+
requireAuthentication: false,
|
|
84
|
+
},
|
|
85
|
+
read: {
|
|
86
|
+
requireAuthentication: false,
|
|
87
|
+
},
|
|
88
|
+
acl: async (hookObject) => {
|
|
89
|
+
await hookObject.addACL('users', {
|
|
90
|
+
userId: hookObject.object?.id,
|
|
91
|
+
read: true,
|
|
92
|
+
write: true,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await hookObject.addACL('roles', null)
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
scalars: [
|
|
101
|
+
{
|
|
102
|
+
name: 'TestScalar',
|
|
103
|
+
description: 'Test scalar',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
await wabe.start()
|
|
110
|
+
|
|
111
|
+
return { wabe, port }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const closeTests = async (wabe: Wabe<DevWabeTypes>) => {
|
|
115
|
+
await wabe.controllers.database.adapter?.close()
|
|
116
|
+
await wabe.close()
|
|
117
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": false,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
28
|
+
"baseUrl": "."
|
|
29
|
+
},
|
|
30
|
+
"exclude": ["node_modules", "dist", "generated"],
|
|
31
|
+
"include": ["src/**/*.ts", "dev/**/*.ts"]
|
|
32
|
+
}
|
package/bunfig.toml
DELETED
package/dist/ai/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./interface";
|