@rimelight/auth 0.0.1
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 +19 -0
- package/package.json +61 -0
- package/src/client/index.ts +3 -0
- package/src/index.ts +6 -0
- package/src/permissions/index.ts +27 -0
- package/src/plugins/construction-guest/index.ts +107 -0
- package/src/plugins/discriminator-tag/index.ts +55 -0
- package/src/plugins/reserved-usernames/index.ts +198 -0
- package/src/plugins/synthetic-user/index.ts +50 -0
- package/src/schema/drizzle/index.ts +363 -0
- package/src/server/index.ts +148 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Rimelight Entertainment Workspace
|
|
2
|
+
|
|
3
|
+
## Structure
|
|
4
|
+
|
|
5
|
+
### Apps (`/packages`)
|
|
6
|
+
|
|
7
|
+
- **`rimelight.com`**: The main company website.
|
|
8
|
+
- **`starter.rimelight.com`**: Our standardized starter template for Astro websites.
|
|
9
|
+
|
|
10
|
+
### Packages (`/packages`)
|
|
11
|
+
|
|
12
|
+
- **`@rimelight/auth`**: Authentication and authorization utilities for the Rimelight ecosystem.
|
|
13
|
+
- **`@rimelight/cli`**: The command line interface for managing Rimelight projects.
|
|
14
|
+
- **`@rimelight/cms`**: Enterprise content management, block rendering, and wiki engine.
|
|
15
|
+
- **`@rimelight/docs`**: Documentation components and utilities.
|
|
16
|
+
- **`@rimelight/i18n`**: Internationalization and localization tools.
|
|
17
|
+
- **`@rimelight/security`**: Astro security integration (CSP, SRI, and more).
|
|
18
|
+
- **`@rimelight/seo`**: SEO utilities including sitemap, robots, and meta components.
|
|
19
|
+
- **`@rimelight/ui`**: Our component library used in all our web projects.
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rimelight/auth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Rimelight Entertainment's Authentication Package",
|
|
6
|
+
"homepage": "https://rimelight.com/docs",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Rimelight Entertainment"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./src/index.ts",
|
|
24
|
+
"./server": "./src/server/index.ts",
|
|
25
|
+
"./client": "./src/client/index.ts",
|
|
26
|
+
"./permissions": "./src/permissions/index.ts",
|
|
27
|
+
"./schema/drizzle": "./src/schema/drizzle/index.ts",
|
|
28
|
+
"./plugins/reserved-usernames": "./src/plugins/reserved-usernames/index.ts",
|
|
29
|
+
"./plugins/discriminator-tag": "./src/plugins/discriminator-tag/index.ts",
|
|
30
|
+
"./plugins/synthetic-user": "./src/plugins/synthetic-user/index.ts",
|
|
31
|
+
"./plugins/construction-guest": "./src/plugins/construction-guest/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"check": "pnpm audit --audit-level=moderate && vp check --fix"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@better-auth/drizzle-adapter": "1.6.30",
|
|
41
|
+
"better-auth": "1.6.30",
|
|
42
|
+
"drizzle-orm": "0.45.2"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@rimelight/config": "workspace:*"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"better-auth": ">=1.0.0",
|
|
49
|
+
"drizzle-orm": ">=0.30.0",
|
|
50
|
+
"hono": ">=4.12.34"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"hono": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=26.7.0"
|
|
59
|
+
},
|
|
60
|
+
"packageManager": "pnpm@11.22.0"
|
|
61
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./server/index"
|
|
2
|
+
export * from "./permissions/index"
|
|
3
|
+
export * from "./plugins/reserved-usernames/index"
|
|
4
|
+
export * from "./plugins/synthetic-user/index"
|
|
5
|
+
export * from "./plugins/construction-guest/index"
|
|
6
|
+
export * from "./plugins/discriminator-tag/index"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createAccessControl } from "better-auth/plugins/access"
|
|
2
|
+
import {
|
|
3
|
+
defaultStatements,
|
|
4
|
+
ownerAc,
|
|
5
|
+
adminAc,
|
|
6
|
+
memberAc
|
|
7
|
+
} from "better-auth/plugins/organization/access"
|
|
8
|
+
|
|
9
|
+
export { createAccessControl, defaultStatements, ownerAc, adminAc, memberAc }
|
|
10
|
+
|
|
11
|
+
export interface CreatePermissionsOptions<TStatements extends Record<string, readonly string[]>> {
|
|
12
|
+
statements: TStatements
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createPermissions<const TStatements extends Record<string, readonly string[]>>(
|
|
16
|
+
statements: TStatements
|
|
17
|
+
) {
|
|
18
|
+
const mergedStatements = {
|
|
19
|
+
...defaultStatements,
|
|
20
|
+
...statements
|
|
21
|
+
}
|
|
22
|
+
const ac = createAccessControl(mergedStatements)
|
|
23
|
+
return {
|
|
24
|
+
ac,
|
|
25
|
+
statements: mergedStatements
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getCookie, setCookie } from "hono/cookie"
|
|
2
|
+
|
|
3
|
+
export const CONSTRUCTION_GUEST_COOKIE = "rimelight-construction-guest"
|
|
4
|
+
|
|
5
|
+
export type GuestEnv = {
|
|
6
|
+
CONSTRUCTION_GUEST_EMAIL?: string
|
|
7
|
+
CONSTRUCTION_GUEST_PASSWORD?: string
|
|
8
|
+
CONSTRUCTION_GUEST_SECRET?: string
|
|
9
|
+
BETTER_AUTH_SECRET?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getConfig = (env?: GuestEnv) => ({
|
|
13
|
+
email: env?.CONSTRUCTION_GUEST_EMAIL ?? process.env.CONSTRUCTION_GUEST_EMAIL,
|
|
14
|
+
password: env?.CONSTRUCTION_GUEST_PASSWORD ?? process.env.CONSTRUCTION_GUEST_PASSWORD,
|
|
15
|
+
secret:
|
|
16
|
+
env?.CONSTRUCTION_GUEST_SECRET ??
|
|
17
|
+
process.env.CONSTRUCTION_GUEST_SECRET ??
|
|
18
|
+
env?.BETTER_AUTH_SECRET ??
|
|
19
|
+
process.env.BETTER_AUTH_SECRET
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const encode = (value: Uint8Array) =>
|
|
23
|
+
btoa(String.fromCharCode(...value))
|
|
24
|
+
.replace(/\+/g, "-")
|
|
25
|
+
.replace(/\//g, "_")
|
|
26
|
+
.replace(/=+$/, "")
|
|
27
|
+
|
|
28
|
+
const decode = (value: string) => {
|
|
29
|
+
const padded = value
|
|
30
|
+
.replace(/-/g, "+")
|
|
31
|
+
.replace(/_/g, "/")
|
|
32
|
+
.padEnd(Math.ceil(value.length / 4) * 4, "=")
|
|
33
|
+
return Uint8Array.from(atob(padded), (char) => char.charCodeAt(0))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sign = async (payload: string, secret: string) => {
|
|
37
|
+
const key = await crypto.subtle.importKey(
|
|
38
|
+
"raw",
|
|
39
|
+
new TextEncoder().encode(secret),
|
|
40
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
41
|
+
false,
|
|
42
|
+
["sign", "verify"]
|
|
43
|
+
)
|
|
44
|
+
return {
|
|
45
|
+
key,
|
|
46
|
+
signature: await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const isConstructionGuest = async (c: any) => {
|
|
51
|
+
const token = getCookie(c, CONSTRUCTION_GUEST_COOKIE)
|
|
52
|
+
const secret = getConfig(c.env).secret
|
|
53
|
+
if (!token || !secret) return false
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const [encodedPayload, encodedSignature] = token.split(".")
|
|
57
|
+
if (!encodedPayload || !encodedSignature) return false
|
|
58
|
+
const payload = new TextDecoder().decode(decode(encodedPayload))
|
|
59
|
+
const { key } = await sign(payload, secret)
|
|
60
|
+
const valid = await crypto.subtle.verify(
|
|
61
|
+
"HMAC",
|
|
62
|
+
key,
|
|
63
|
+
decode(encodedSignature),
|
|
64
|
+
new TextEncoder().encode(payload)
|
|
65
|
+
)
|
|
66
|
+
return valid && JSON.parse(payload).expiresAt > Date.now()
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const signInConstructionGuest = async (
|
|
73
|
+
c: any,
|
|
74
|
+
email: string,
|
|
75
|
+
password: string,
|
|
76
|
+
rememberMe = true
|
|
77
|
+
) => {
|
|
78
|
+
const config = getConfig(c.env)
|
|
79
|
+
if (
|
|
80
|
+
!config.email ||
|
|
81
|
+
!config.password ||
|
|
82
|
+
!config.secret ||
|
|
83
|
+
email !== config.email ||
|
|
84
|
+
password !== config.password
|
|
85
|
+
) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const payload = JSON.stringify({
|
|
90
|
+
email: config.email,
|
|
91
|
+
expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 7
|
|
92
|
+
})
|
|
93
|
+
const { signature } = await sign(payload, config.secret)
|
|
94
|
+
setCookie(
|
|
95
|
+
c,
|
|
96
|
+
CONSTRUCTION_GUEST_COOKIE,
|
|
97
|
+
encode(new TextEncoder().encode(payload)) + "." + encode(new Uint8Array(signature)),
|
|
98
|
+
{
|
|
99
|
+
httpOnly: true,
|
|
100
|
+
secure: true,
|
|
101
|
+
sameSite: "Lax",
|
|
102
|
+
path: "/",
|
|
103
|
+
...(rememberMe ? { maxAge: 60 * 60 * 24 * 7 } : {})
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { APIError } from "better-auth/api"
|
|
2
|
+
import { normalizeUsername, RESTRICTED_SET } from "../reserved-usernames"
|
|
3
|
+
import { eq, and, count } from "drizzle-orm"
|
|
4
|
+
|
|
5
|
+
export interface TagHookOptions {
|
|
6
|
+
userTable?: any
|
|
7
|
+
restrictedSet?: Set<string>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createTagAndUsernameHook(db: any, userTable: any, options?: TagHookOptions) {
|
|
11
|
+
const restricted = options?.restrictedSet ?? RESTRICTED_SET
|
|
12
|
+
|
|
13
|
+
return async (userData: any, _ctx: any) => {
|
|
14
|
+
// 1. Normalize the username and check against the restricted set
|
|
15
|
+
const normalizedInput = normalizeUsername(userData.name)
|
|
16
|
+
if (restricted.has(normalizedInput)) {
|
|
17
|
+
throw new APIError("BAD_REQUEST", {
|
|
18
|
+
message: "This username is reserved for official use."
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Generate a unique 4-digit discriminator tag
|
|
23
|
+
let uniqueTag = "0000"
|
|
24
|
+
const tryFindTag = async (attemptsLeft: number): Promise<string> => {
|
|
25
|
+
if (attemptsLeft <= 0) return "0000"
|
|
26
|
+
const newTag = Math.floor(Math.random() * 10000)
|
|
27
|
+
.toString()
|
|
28
|
+
.padStart(4, "0")
|
|
29
|
+
|
|
30
|
+
const result = await db
|
|
31
|
+
.select({ count: count() })
|
|
32
|
+
.from(userTable)
|
|
33
|
+
.where(and(eq(userTable.name, userData.name), eq(userTable.tag, newTag)))
|
|
34
|
+
|
|
35
|
+
const tagCount = result[0]?.count ?? 0
|
|
36
|
+
if (tagCount === 0) return newTag
|
|
37
|
+
return tryFindTag(attemptsLeft - 1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
uniqueTag = await tryFindTag(50)
|
|
41
|
+
|
|
42
|
+
if (uniqueTag === "0000") {
|
|
43
|
+
throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
44
|
+
message: "Failed to generate a unique tag for this username. Please try again."
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
data: {
|
|
50
|
+
...userData,
|
|
51
|
+
tag: uniqueTag
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const HOMOGLYPH_MAP: Record<string, string> = {
|
|
2
|
+
"0": "o",
|
|
3
|
+
"1": "i",
|
|
4
|
+
"3": "e",
|
|
5
|
+
"4": "a",
|
|
6
|
+
"5": "s",
|
|
7
|
+
"7": "t",
|
|
8
|
+
"8": "b",
|
|
9
|
+
"9": "g",
|
|
10
|
+
"@": "a",
|
|
11
|
+
"!": "i",
|
|
12
|
+
"$": "s",
|
|
13
|
+
"|": "i"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const normalizeUsername = (input: string): string => {
|
|
17
|
+
return input
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.split("")
|
|
20
|
+
.map((char) => HOMOGLYPH_MAP[char] || char)
|
|
21
|
+
.join("")
|
|
22
|
+
.replace(/[^a-z0-9]/g, "")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const STANDARD_RESTRICTED_GROUPS = {
|
|
26
|
+
STAFF_ROLES: [
|
|
27
|
+
"admin",
|
|
28
|
+
"administrator",
|
|
29
|
+
"moderator",
|
|
30
|
+
"mod",
|
|
31
|
+
"staff",
|
|
32
|
+
"support",
|
|
33
|
+
"help",
|
|
34
|
+
"official",
|
|
35
|
+
"verified",
|
|
36
|
+
"system",
|
|
37
|
+
"root",
|
|
38
|
+
"bot",
|
|
39
|
+
"security",
|
|
40
|
+
"community",
|
|
41
|
+
"manager",
|
|
42
|
+
"dev",
|
|
43
|
+
"developer",
|
|
44
|
+
"designer",
|
|
45
|
+
"gamemaster",
|
|
46
|
+
"gm",
|
|
47
|
+
"assistant",
|
|
48
|
+
"coordinator",
|
|
49
|
+
"representative",
|
|
50
|
+
"agent",
|
|
51
|
+
"supervisor",
|
|
52
|
+
"executive",
|
|
53
|
+
"ambassador",
|
|
54
|
+
"expert",
|
|
55
|
+
"specialist",
|
|
56
|
+
"advocate",
|
|
57
|
+
"internal",
|
|
58
|
+
"employee",
|
|
59
|
+
"associate",
|
|
60
|
+
"webmaster",
|
|
61
|
+
"sysop",
|
|
62
|
+
"operator",
|
|
63
|
+
"host",
|
|
64
|
+
"referee",
|
|
65
|
+
"council",
|
|
66
|
+
"ceo",
|
|
67
|
+
"founder",
|
|
68
|
+
"owner"
|
|
69
|
+
],
|
|
70
|
+
LEGAL_FINANCIAL: [
|
|
71
|
+
"checkout",
|
|
72
|
+
"subscribe",
|
|
73
|
+
"subscription",
|
|
74
|
+
"premium",
|
|
75
|
+
"vip",
|
|
76
|
+
"store",
|
|
77
|
+
"shop",
|
|
78
|
+
"marketplace",
|
|
79
|
+
"wallet",
|
|
80
|
+
"refund",
|
|
81
|
+
"invoice",
|
|
82
|
+
"payout",
|
|
83
|
+
"rewards",
|
|
84
|
+
"prize",
|
|
85
|
+
"giveaway",
|
|
86
|
+
"claims",
|
|
87
|
+
"verification",
|
|
88
|
+
"billing",
|
|
89
|
+
"payment",
|
|
90
|
+
"sales",
|
|
91
|
+
"marketing",
|
|
92
|
+
"legal",
|
|
93
|
+
"compliance",
|
|
94
|
+
"privacy",
|
|
95
|
+
"tos",
|
|
96
|
+
"terms",
|
|
97
|
+
"copyright",
|
|
98
|
+
"trademark",
|
|
99
|
+
"dmca",
|
|
100
|
+
"abuse",
|
|
101
|
+
"report"
|
|
102
|
+
],
|
|
103
|
+
TECHNICAL: [
|
|
104
|
+
"null",
|
|
105
|
+
"undefined",
|
|
106
|
+
"nan",
|
|
107
|
+
"none",
|
|
108
|
+
"everyone",
|
|
109
|
+
"all",
|
|
110
|
+
"guest",
|
|
111
|
+
"user",
|
|
112
|
+
"test",
|
|
113
|
+
"tester",
|
|
114
|
+
"account",
|
|
115
|
+
"api",
|
|
116
|
+
"webhook",
|
|
117
|
+
"index",
|
|
118
|
+
"config",
|
|
119
|
+
"settings",
|
|
120
|
+
"profile",
|
|
121
|
+
"auth",
|
|
122
|
+
"login",
|
|
123
|
+
"signup",
|
|
124
|
+
"signin",
|
|
125
|
+
"logout",
|
|
126
|
+
"signout",
|
|
127
|
+
"localhost",
|
|
128
|
+
"ftp",
|
|
129
|
+
"smtp",
|
|
130
|
+
"pop3",
|
|
131
|
+
"imap",
|
|
132
|
+
"dns",
|
|
133
|
+
"proxy",
|
|
134
|
+
"cdn",
|
|
135
|
+
"static",
|
|
136
|
+
"assets",
|
|
137
|
+
"media",
|
|
138
|
+
"upload",
|
|
139
|
+
"download",
|
|
140
|
+
"docs",
|
|
141
|
+
"manual",
|
|
142
|
+
"guide",
|
|
143
|
+
"tutorial",
|
|
144
|
+
"error",
|
|
145
|
+
"404",
|
|
146
|
+
"500",
|
|
147
|
+
"maintenance",
|
|
148
|
+
"update",
|
|
149
|
+
"patch",
|
|
150
|
+
"changelog",
|
|
151
|
+
"status",
|
|
152
|
+
"db",
|
|
153
|
+
"database",
|
|
154
|
+
"sql",
|
|
155
|
+
"query",
|
|
156
|
+
"health",
|
|
157
|
+
"ping",
|
|
158
|
+
"metrics",
|
|
159
|
+
"logs",
|
|
160
|
+
"no-reply",
|
|
161
|
+
"noreply",
|
|
162
|
+
"donotreply",
|
|
163
|
+
"security-alert",
|
|
164
|
+
"mail",
|
|
165
|
+
"email",
|
|
166
|
+
"postmaster"
|
|
167
|
+
],
|
|
168
|
+
LEETSPEAK: [
|
|
169
|
+
"4dmin",
|
|
170
|
+
"@dmin",
|
|
171
|
+
"st4ff",
|
|
172
|
+
"m0d",
|
|
173
|
+
"m0derator",
|
|
174
|
+
"4dm1n",
|
|
175
|
+
"4dmin1strator",
|
|
176
|
+
"m0d3rator",
|
|
177
|
+
"st4f",
|
|
178
|
+
"5upport",
|
|
179
|
+
"0fficial",
|
|
180
|
+
"v3rified",
|
|
181
|
+
"5ystem",
|
|
182
|
+
"r00t",
|
|
183
|
+
"b0t",
|
|
184
|
+
"s3curity",
|
|
185
|
+
"d3v",
|
|
186
|
+
"d3veloper",
|
|
187
|
+
"@dministrator",
|
|
188
|
+
"adm1n",
|
|
189
|
+
"adm1nistrator"
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const createRestrictedUsernameSet = (customBlacklist: string[] = []): Set<string> => {
|
|
194
|
+
const base = Object.values(STANDARD_RESTRICTED_GROUPS).flat().concat(customBlacklist)
|
|
195
|
+
return new Set(base.map((name) => normalizeUsername(name)))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const RESTRICTED_SET = createRestrictedUsernameSet()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface CoreFields {
|
|
2
|
+
name: string
|
|
3
|
+
email: string
|
|
4
|
+
emailVerified: boolean | null
|
|
5
|
+
image: string | null
|
|
6
|
+
createdAt: Date
|
|
7
|
+
updatedAt: Date
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SyntheticUserData {
|
|
11
|
+
coreFields: CoreFields
|
|
12
|
+
additionalFields: Record<string, unknown>
|
|
13
|
+
id: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createSyntheticUser({ coreFields, additionalFields, id }: SyntheticUserData) {
|
|
17
|
+
return {
|
|
18
|
+
...coreFields,
|
|
19
|
+
role: "user",
|
|
20
|
+
banned: false,
|
|
21
|
+
banReason: null,
|
|
22
|
+
banExpires: null,
|
|
23
|
+
tag: typeof additionalFields.tag === "string" ? additionalFields.tag : "0000",
|
|
24
|
+
firstName: typeof additionalFields.firstName === "string" ? additionalFields.firstName : "",
|
|
25
|
+
lastName: typeof additionalFields.lastName === "string" ? additionalFields.lastName : "",
|
|
26
|
+
availability:
|
|
27
|
+
typeof additionalFields.availability === "string"
|
|
28
|
+
? additionalFields.availability
|
|
29
|
+
: "available",
|
|
30
|
+
status: typeof additionalFields.status === "string" ? additionalFields.status : "",
|
|
31
|
+
publicKey:
|
|
32
|
+
typeof additionalFields.publicKey === "string" ? additionalFields.publicKey : undefined,
|
|
33
|
+
id
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createSyntheticUserWrapper(userData: any) {
|
|
38
|
+
return createSyntheticUser({
|
|
39
|
+
coreFields: {
|
|
40
|
+
name: userData.name,
|
|
41
|
+
email: userData.email,
|
|
42
|
+
emailVerified: userData.emailVerified,
|
|
43
|
+
image: userData.image,
|
|
44
|
+
createdAt: userData.createdAt,
|
|
45
|
+
updatedAt: userData.createdAt
|
|
46
|
+
},
|
|
47
|
+
additionalFields: userData,
|
|
48
|
+
id: crypto.randomUUID()
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { relations, sql } from "drizzle-orm"
|
|
2
|
+
import {
|
|
3
|
+
type AnyPgColumn,
|
|
4
|
+
pgTable,
|
|
5
|
+
text,
|
|
6
|
+
uniqueIndex,
|
|
7
|
+
index,
|
|
8
|
+
boolean,
|
|
9
|
+
timestamp,
|
|
10
|
+
integer
|
|
11
|
+
} from "drizzle-orm/pg-core"
|
|
12
|
+
|
|
13
|
+
export type UserAvailability = "available" | "busy" | "away" | "offline" | (string & {})
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Core Auth Tables
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export const user = pgTable(
|
|
20
|
+
"user",
|
|
21
|
+
{
|
|
22
|
+
id: text("id")
|
|
23
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
24
|
+
.notNull()
|
|
25
|
+
.primaryKey(),
|
|
26
|
+
name: text("name").notNull(),
|
|
27
|
+
tag: text("tag").notNull().default("0000"),
|
|
28
|
+
email: text("email").notNull().unique(),
|
|
29
|
+
emailVerified: boolean("email_verified").default(false).notNull(),
|
|
30
|
+
image: text("image"),
|
|
31
|
+
firstName: text("first_name").notNull(),
|
|
32
|
+
lastName: text("last_name").notNull(),
|
|
33
|
+
availability: text("availability").$type<UserAvailability>().notNull().default("available"),
|
|
34
|
+
status: text("status"),
|
|
35
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
36
|
+
createdAt: timestamp("created_at")
|
|
37
|
+
.$defaultFn(() => new Date())
|
|
38
|
+
.notNull(),
|
|
39
|
+
deletedAt: timestamp("deleted_at"),
|
|
40
|
+
role: text("role"),
|
|
41
|
+
banned: boolean("banned").default(false),
|
|
42
|
+
banReason: text("ban_reason"),
|
|
43
|
+
banExpires: timestamp("ban_expires"),
|
|
44
|
+
publicKey: text("public_key"),
|
|
45
|
+
encryptedPrivateKey: text("encrypted_private_key"),
|
|
46
|
+
derivationSalt: text("derivation_salt")
|
|
47
|
+
},
|
|
48
|
+
(table) => [uniqueIndex("user_name_tag_unique").on(table.name, table.tag)]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
export const session = pgTable(
|
|
52
|
+
"session",
|
|
53
|
+
{
|
|
54
|
+
id: text("id")
|
|
55
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
56
|
+
.notNull()
|
|
57
|
+
.primaryKey(),
|
|
58
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
59
|
+
token: text("token").notNull().unique(),
|
|
60
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
61
|
+
createdAt: timestamp("created_at")
|
|
62
|
+
.$defaultFn(() => new Date())
|
|
63
|
+
.notNull(),
|
|
64
|
+
deletedAt: timestamp("deleted_at"),
|
|
65
|
+
ipAddress: text("ip_address"),
|
|
66
|
+
userAgent: text("user_agent"),
|
|
67
|
+
userId: text("user_id")
|
|
68
|
+
.notNull()
|
|
69
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
70
|
+
impersonatedBy: text("impersonated_by"),
|
|
71
|
+
activeOrganizationId: text("active_organization_id"),
|
|
72
|
+
activeTeamId: text("active_team_id")
|
|
73
|
+
},
|
|
74
|
+
(table) => [index("session_userId_idx").on(table.userId)]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
export const account = pgTable(
|
|
78
|
+
"account",
|
|
79
|
+
{
|
|
80
|
+
id: text("id")
|
|
81
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
82
|
+
.notNull()
|
|
83
|
+
.primaryKey(),
|
|
84
|
+
accountId: text("account_id").notNull(),
|
|
85
|
+
providerId: text("provider_id").notNull(),
|
|
86
|
+
userId: text("user_id")
|
|
87
|
+
.notNull()
|
|
88
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
89
|
+
accessToken: text("access_token"),
|
|
90
|
+
refreshToken: text("refresh_token"),
|
|
91
|
+
idToken: text("id_token"),
|
|
92
|
+
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
|
93
|
+
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
|
94
|
+
scope: text("scope"),
|
|
95
|
+
password: text("password"),
|
|
96
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
97
|
+
createdAt: timestamp("created_at")
|
|
98
|
+
.$defaultFn(() => new Date())
|
|
99
|
+
.notNull(),
|
|
100
|
+
deletedAt: timestamp("deleted_at")
|
|
101
|
+
},
|
|
102
|
+
(table) => [index("account_userId_idx").on(table.userId)]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
export const verification = pgTable(
|
|
106
|
+
"verification",
|
|
107
|
+
{
|
|
108
|
+
id: text("id")
|
|
109
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
110
|
+
.notNull()
|
|
111
|
+
.primaryKey(),
|
|
112
|
+
identifier: text("identifier").notNull(),
|
|
113
|
+
value: text("value").notNull(),
|
|
114
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
115
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
116
|
+
createdAt: timestamp("created_at")
|
|
117
|
+
.$defaultFn(() => new Date())
|
|
118
|
+
.notNull(),
|
|
119
|
+
deletedAt: timestamp("deleted_at")
|
|
120
|
+
},
|
|
121
|
+
(table) => [index("verification_identifier_idx").on(table.identifier)]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
export const rateLimit = pgTable("rate_limit", {
|
|
125
|
+
id: text("id")
|
|
126
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
127
|
+
.notNull()
|
|
128
|
+
.primaryKey(),
|
|
129
|
+
key: text("key"),
|
|
130
|
+
count: integer("count"),
|
|
131
|
+
lastRequest: integer("last_request")
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Organization Tables
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
export const organization = pgTable("organization", {
|
|
139
|
+
id: text("id")
|
|
140
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
141
|
+
.notNull()
|
|
142
|
+
.primaryKey(),
|
|
143
|
+
name: text("name").notNull(),
|
|
144
|
+
slug: text("slug").notNull().unique(),
|
|
145
|
+
logo: text("logo"),
|
|
146
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
147
|
+
createdAt: timestamp("created_at")
|
|
148
|
+
.$defaultFn(() => new Date())
|
|
149
|
+
.notNull(),
|
|
150
|
+
deletedAt: timestamp("deleted_at"),
|
|
151
|
+
metadata: text("metadata")
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
export const member = pgTable(
|
|
155
|
+
"member",
|
|
156
|
+
{
|
|
157
|
+
id: text("id")
|
|
158
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
159
|
+
.notNull()
|
|
160
|
+
.primaryKey(),
|
|
161
|
+
organizationId: text("organization_id")
|
|
162
|
+
.notNull()
|
|
163
|
+
.references(() => organization.id, { onDelete: "cascade" }),
|
|
164
|
+
userId: text("user_id")
|
|
165
|
+
.notNull()
|
|
166
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
167
|
+
role: text("role").default("member").notNull(),
|
|
168
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
169
|
+
createdAt: timestamp("created_at")
|
|
170
|
+
.$defaultFn(() => new Date())
|
|
171
|
+
.notNull(),
|
|
172
|
+
deletedAt: timestamp("deleted_at")
|
|
173
|
+
},
|
|
174
|
+
(table) => [
|
|
175
|
+
index("member_organizationId_idx").on(table.organizationId),
|
|
176
|
+
index("member_userId_idx").on(table.userId)
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
export const invitation = pgTable(
|
|
181
|
+
"invitation",
|
|
182
|
+
{
|
|
183
|
+
id: text("id")
|
|
184
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
185
|
+
.notNull()
|
|
186
|
+
.primaryKey(),
|
|
187
|
+
organizationId: text("organization_id")
|
|
188
|
+
.notNull()
|
|
189
|
+
.references(() => organization.id, { onDelete: "cascade" }),
|
|
190
|
+
email: text("email").notNull(),
|
|
191
|
+
role: text("role"),
|
|
192
|
+
status: text("status").default("pending").notNull(),
|
|
193
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
194
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
195
|
+
createdAt: timestamp("created_at")
|
|
196
|
+
.$defaultFn(() => new Date())
|
|
197
|
+
.notNull(),
|
|
198
|
+
deletedAt: timestamp("deleted_at"),
|
|
199
|
+
inviterId: text("inviter_id")
|
|
200
|
+
.notNull()
|
|
201
|
+
.references(() => user.id, { onDelete: "cascade" })
|
|
202
|
+
},
|
|
203
|
+
(table) => [
|
|
204
|
+
index("invitation_organizationId_idx").on(table.organizationId),
|
|
205
|
+
index("invitation_email_idx").on(table.email)
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
export const team = pgTable(
|
|
210
|
+
"team",
|
|
211
|
+
{
|
|
212
|
+
id: text("id")
|
|
213
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
214
|
+
.notNull()
|
|
215
|
+
.primaryKey(),
|
|
216
|
+
name: text("name").notNull(),
|
|
217
|
+
organizationId: text("organization_id")
|
|
218
|
+
.notNull()
|
|
219
|
+
.references(() => organization.id, { onDelete: "cascade" }),
|
|
220
|
+
parentId: text("parent_id").references((): AnyPgColumn => team.id, {
|
|
221
|
+
onDelete: "cascade"
|
|
222
|
+
}),
|
|
223
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
224
|
+
createdAt: timestamp("created_at")
|
|
225
|
+
.$defaultFn(() => new Date())
|
|
226
|
+
.notNull(),
|
|
227
|
+
deletedAt: timestamp("deleted_at"),
|
|
228
|
+
metadata: text("metadata")
|
|
229
|
+
},
|
|
230
|
+
(table) => [index("team_organizationId_idx").on(table.organizationId)]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
export const teamMember = pgTable("team_member", {
|
|
234
|
+
id: text("id")
|
|
235
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
236
|
+
.notNull()
|
|
237
|
+
.primaryKey(),
|
|
238
|
+
teamId: text("team_id")
|
|
239
|
+
.notNull()
|
|
240
|
+
.references(() => team.id, { onDelete: "cascade" }),
|
|
241
|
+
userId: text("user_id")
|
|
242
|
+
.notNull()
|
|
243
|
+
.references(() => user.id, { onDelete: "cascade" }),
|
|
244
|
+
role: text("role").notNull(),
|
|
245
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
246
|
+
createdAt: timestamp("created_at")
|
|
247
|
+
.$defaultFn(() => new Date())
|
|
248
|
+
.notNull(),
|
|
249
|
+
deletedAt: timestamp("deleted_at")
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// Relations
|
|
254
|
+
// ============================================================================
|
|
255
|
+
|
|
256
|
+
export const userRelations = relations(user, ({ many }) => ({
|
|
257
|
+
sessions: many(session),
|
|
258
|
+
accounts: many(account),
|
|
259
|
+
members: many(member),
|
|
260
|
+
invitations: many(invitation),
|
|
261
|
+
teamMembers: many(teamMember)
|
|
262
|
+
}))
|
|
263
|
+
|
|
264
|
+
export const sessionRelations = relations(session, ({ one }) => ({
|
|
265
|
+
user: one(user, {
|
|
266
|
+
fields: [session.userId],
|
|
267
|
+
references: [user.id]
|
|
268
|
+
})
|
|
269
|
+
}))
|
|
270
|
+
|
|
271
|
+
export const accountRelations = relations(account, ({ one }) => ({
|
|
272
|
+
user: one(user, {
|
|
273
|
+
fields: [account.userId],
|
|
274
|
+
references: [user.id]
|
|
275
|
+
})
|
|
276
|
+
}))
|
|
277
|
+
|
|
278
|
+
export const organizationRelations = relations(organization, ({ many }) => ({
|
|
279
|
+
members: many(member),
|
|
280
|
+
invitations: many(invitation),
|
|
281
|
+
teams: many(team)
|
|
282
|
+
}))
|
|
283
|
+
|
|
284
|
+
export const memberRelations = relations(member, ({ one }) => ({
|
|
285
|
+
organization: one(organization, {
|
|
286
|
+
fields: [member.organizationId],
|
|
287
|
+
references: [organization.id]
|
|
288
|
+
}),
|
|
289
|
+
user: one(user, {
|
|
290
|
+
fields: [member.userId],
|
|
291
|
+
references: [user.id]
|
|
292
|
+
})
|
|
293
|
+
}))
|
|
294
|
+
|
|
295
|
+
export const invitationRelations = relations(invitation, ({ one }) => ({
|
|
296
|
+
organization: one(organization, {
|
|
297
|
+
fields: [invitation.organizationId],
|
|
298
|
+
references: [organization.id]
|
|
299
|
+
}),
|
|
300
|
+
user: one(user, {
|
|
301
|
+
fields: [invitation.inviterId],
|
|
302
|
+
references: [user.id]
|
|
303
|
+
})
|
|
304
|
+
}))
|
|
305
|
+
|
|
306
|
+
export const teamRelations = relations(team, ({ one, many }) => ({
|
|
307
|
+
organization: one(organization, {
|
|
308
|
+
fields: [team.organizationId],
|
|
309
|
+
references: [organization.id]
|
|
310
|
+
}),
|
|
311
|
+
parentTeam: one(team, {
|
|
312
|
+
fields: [team.parentId],
|
|
313
|
+
references: [team.id],
|
|
314
|
+
relationName: "subteams"
|
|
315
|
+
}),
|
|
316
|
+
subteams: many(team, { relationName: "subteams" }),
|
|
317
|
+
members: many(teamMember)
|
|
318
|
+
}))
|
|
319
|
+
|
|
320
|
+
export const teamMemberRelations = relations(teamMember, ({ one }) => ({
|
|
321
|
+
team: one(team, {
|
|
322
|
+
fields: [teamMember.teamId],
|
|
323
|
+
references: [team.id]
|
|
324
|
+
}),
|
|
325
|
+
user: one(user, {
|
|
326
|
+
fields: [teamMember.userId],
|
|
327
|
+
references: [user.id]
|
|
328
|
+
})
|
|
329
|
+
}))
|
|
330
|
+
|
|
331
|
+
// ============================================================================
|
|
332
|
+
// Roles & Permissions Tables
|
|
333
|
+
// ============================================================================
|
|
334
|
+
|
|
335
|
+
export const role = pgTable("role", {
|
|
336
|
+
id: text("id")
|
|
337
|
+
.$defaultFn(() => crypto.randomUUID())
|
|
338
|
+
.notNull()
|
|
339
|
+
.primaryKey(),
|
|
340
|
+
name: text("name").notNull().unique(),
|
|
341
|
+
displayName: text("display_name").notNull(),
|
|
342
|
+
description: text("description"),
|
|
343
|
+
permissions: text("permissions")
|
|
344
|
+
.$type<string[]>()
|
|
345
|
+
.notNull()
|
|
346
|
+
.default(sql`'[]'`),
|
|
347
|
+
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
|
348
|
+
createdAt: timestamp("created_at")
|
|
349
|
+
.$defaultFn(() => new Date())
|
|
350
|
+
.notNull(),
|
|
351
|
+
deletedAt: timestamp("deleted_at")
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
export type User = typeof user.$inferSelect
|
|
355
|
+
export type Session = typeof session.$inferSelect
|
|
356
|
+
export type Account = typeof account.$inferSelect
|
|
357
|
+
export type Verification = typeof verification.$inferSelect
|
|
358
|
+
export type Organization = typeof organization.$inferSelect
|
|
359
|
+
export type Member = typeof member.$inferSelect
|
|
360
|
+
export type Invitation = typeof invitation.$inferSelect
|
|
361
|
+
export type Team = typeof team.$inferSelect
|
|
362
|
+
export type TeamMember = typeof teamMember.$inferSelect
|
|
363
|
+
export type Role = typeof role.$inferSelect
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { BetterAuthOptions } from "better-auth"
|
|
2
|
+
import { betterAuth } from "better-auth"
|
|
3
|
+
import { createSyntheticUserWrapper } from "../plugins/synthetic-user"
|
|
4
|
+
import { createTagAndUsernameHook } from "../plugins/discriminator-tag"
|
|
5
|
+
|
|
6
|
+
export interface RimelightAuthFactoryOptions {
|
|
7
|
+
database: BetterAuthOptions["database"]
|
|
8
|
+
db?: any
|
|
9
|
+
userTable?: any
|
|
10
|
+
emailHandlers?: {
|
|
11
|
+
sendVerificationEmail?: (data: any, request?: Request) => Promise<void>
|
|
12
|
+
sendPasswordResetEmail?: (data: any, request?: Request) => Promise<void>
|
|
13
|
+
sendExistingUserSignUpNotification?: (existingUser: any) => Promise<void>
|
|
14
|
+
}
|
|
15
|
+
additionalUserFields?: Record<string, any>
|
|
16
|
+
plugins?: BetterAuthOptions["plugins"]
|
|
17
|
+
statements?: any
|
|
18
|
+
advanced?: BetterAuthOptions["advanced"]
|
|
19
|
+
session?: BetterAuthOptions["session"]
|
|
20
|
+
rateLimit?: BetterAuthOptions["rateLimit"]
|
|
21
|
+
customDatabaseHooks?: BetterAuthOptions["databaseHooks"]
|
|
22
|
+
enableTagGenerator?: boolean
|
|
23
|
+
restrictedUsernameBlacklist?: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createRimelightAuth(options: RimelightAuthFactoryOptions) {
|
|
27
|
+
const enableTag = options.enableTagGenerator ?? (options.db && options.userTable)
|
|
28
|
+
|
|
29
|
+
const defaultUserHooks =
|
|
30
|
+
enableTag && options.db && options.userTable
|
|
31
|
+
? {
|
|
32
|
+
create: {
|
|
33
|
+
before: createTagAndUsernameHook(options.db, options.userTable)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
: undefined
|
|
37
|
+
|
|
38
|
+
const emailAndPasswordConfig: BetterAuthOptions["emailAndPassword"] = {
|
|
39
|
+
enabled: true,
|
|
40
|
+
autoSignIn: false,
|
|
41
|
+
requireEmailVerification: true,
|
|
42
|
+
minPasswordLength: 8,
|
|
43
|
+
maxPasswordLength: 128,
|
|
44
|
+
customSyntheticUser: createSyntheticUserWrapper
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (options.emailHandlers?.sendPasswordResetEmail) {
|
|
48
|
+
emailAndPasswordConfig.sendResetPassword = options.emailHandlers.sendPasswordResetEmail
|
|
49
|
+
}
|
|
50
|
+
if (options.emailHandlers?.sendExistingUserSignUpNotification) {
|
|
51
|
+
const handler = options.emailHandlers.sendExistingUserSignUpNotification
|
|
52
|
+
emailAndPasswordConfig.onExistingUserSignUp = async (existingUser: any) => {
|
|
53
|
+
await handler(existingUser)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const emailVerificationConfig: BetterAuthOptions["emailVerification"] = {
|
|
58
|
+
autoSignInAfterVerification: true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (options.emailHandlers?.sendVerificationEmail) {
|
|
62
|
+
emailVerificationConfig.sendVerificationEmail = options.emailHandlers.sendVerificationEmail
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return betterAuth({
|
|
66
|
+
database: options.database,
|
|
67
|
+
emailAndPassword: emailAndPasswordConfig,
|
|
68
|
+
emailVerification: emailVerificationConfig,
|
|
69
|
+
user: {
|
|
70
|
+
changeEmail: {
|
|
71
|
+
enabled: true
|
|
72
|
+
},
|
|
73
|
+
deleteUser: {
|
|
74
|
+
enabled: true
|
|
75
|
+
},
|
|
76
|
+
additionalFields: {
|
|
77
|
+
tag: {
|
|
78
|
+
type: "string",
|
|
79
|
+
required: false,
|
|
80
|
+
input: false
|
|
81
|
+
},
|
|
82
|
+
firstName: {
|
|
83
|
+
type: "string",
|
|
84
|
+
required: true,
|
|
85
|
+
default: "",
|
|
86
|
+
input: true
|
|
87
|
+
},
|
|
88
|
+
lastName: {
|
|
89
|
+
type: "string",
|
|
90
|
+
required: true,
|
|
91
|
+
default: "",
|
|
92
|
+
input: true
|
|
93
|
+
},
|
|
94
|
+
role: {
|
|
95
|
+
type: "string",
|
|
96
|
+
required: false,
|
|
97
|
+
default: "user",
|
|
98
|
+
input: false
|
|
99
|
+
},
|
|
100
|
+
availability: {
|
|
101
|
+
type: "string",
|
|
102
|
+
required: false,
|
|
103
|
+
default: "available"
|
|
104
|
+
},
|
|
105
|
+
status: {
|
|
106
|
+
type: "string",
|
|
107
|
+
required: false,
|
|
108
|
+
default: ""
|
|
109
|
+
},
|
|
110
|
+
publicKey: {
|
|
111
|
+
type: "string",
|
|
112
|
+
required: false,
|
|
113
|
+
input: true
|
|
114
|
+
},
|
|
115
|
+
...options.additionalUserFields
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
session: {
|
|
119
|
+
expiresIn: 60 * 60 * 24 * 7,
|
|
120
|
+
updateAge: 60 * 60 * 24,
|
|
121
|
+
freshAge: 60 * 15,
|
|
122
|
+
cookieCache: {
|
|
123
|
+
enabled: true,
|
|
124
|
+
maxAge: 60 * 5
|
|
125
|
+
},
|
|
126
|
+
...options.session
|
|
127
|
+
},
|
|
128
|
+
rateLimit: {
|
|
129
|
+
window: 10,
|
|
130
|
+
max: 100,
|
|
131
|
+
storage: "database",
|
|
132
|
+
modelName: "rateLimit",
|
|
133
|
+
...options.rateLimit
|
|
134
|
+
},
|
|
135
|
+
plugins: options.plugins ?? [],
|
|
136
|
+
auth: options.statements ? { statements: options.statements } : undefined,
|
|
137
|
+
advanced: {
|
|
138
|
+
useSecureCookies: process.env.NODE_ENV === "production",
|
|
139
|
+
cookiePrefix: "auth",
|
|
140
|
+
database: {
|
|
141
|
+
generateId: () => crypto.randomUUID()
|
|
142
|
+
},
|
|
143
|
+
...options.advanced
|
|
144
|
+
},
|
|
145
|
+
databaseHooks:
|
|
146
|
+
options.customDatabaseHooks ?? (defaultUserHooks ? { user: defaultUserHooks } : undefined)
|
|
147
|
+
})
|
|
148
|
+
}
|