@dbx-tools/auth 0.6.91
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 +44 -0
- package/index.ts +9 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +7 -0
- package/lib/src/auth.d.ts +43 -0
- package/lib/src/auth.js +218 -0
- package/lib/src/storage.d.ts +31 -0
- package/lib/src/storage.js +95 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +69 -0
- package/src/auth.ts +266 -0
- package/src/storage.ts +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# `@dbx-tools/auth`
|
|
2
|
+
|
|
3
|
+
Passwordless authentication runtime built on Better Auth, email OTP, passkeys,
|
|
4
|
+
and caller-provided identity policy and delivery.
|
|
5
|
+
|
|
6
|
+
Key features:
|
|
7
|
+
|
|
8
|
+
- Better Auth ownership of users, sessions, OTP lifecycle, rate limits, and
|
|
9
|
+
passkey credentials;
|
|
10
|
+
- session-required passkey enrollment and discoverable passkey login;
|
|
11
|
+
- caller-provided `authorizeIdentity`, email sender, secret, origin, and
|
|
12
|
+
database;
|
|
13
|
+
- native AppKit Lakebase pool or local SQLite storage;
|
|
14
|
+
- programmatic Better Auth migrations under advisory or file locks.
|
|
15
|
+
|
|
16
|
+
## Relationship To Native AppKit
|
|
17
|
+
|
|
18
|
+
Use the Databricks Apps front door and AppKit execution context when traffic
|
|
19
|
+
arrives through the platform. Use this package for a public tunnel or another
|
|
20
|
+
route that bypasses that identity-aware proxy and therefore needs its own
|
|
21
|
+
passwordless session. A passkey session proves the configured identity only; it
|
|
22
|
+
does not mint a Databricks OBO access token.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { auth, storage } from "@dbx-tools/auth";
|
|
26
|
+
|
|
27
|
+
const database = await storage.createAuthStorage({ storage: "sqlite" });
|
|
28
|
+
const runtime = await auth.createPasswordlessAuth({
|
|
29
|
+
storage: database,
|
|
30
|
+
baseURL: "http://localhost:8000",
|
|
31
|
+
appName: "My app",
|
|
32
|
+
secret: process.env.AUTH_SECRET!,
|
|
33
|
+
sessionTtlSeconds: 2_592_000,
|
|
34
|
+
codeTtlSeconds: 600,
|
|
35
|
+
maxAttempts: 5,
|
|
36
|
+
authorizeIdentity: (email) => email.endsWith("@example.com"),
|
|
37
|
+
sendCode: async (email, code) => sendEmail(email, code),
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Modules
|
|
42
|
+
|
|
43
|
+
- `auth` - Better Auth runtime and compatibility routes;
|
|
44
|
+
- `storage` - Lakebase/SQLite selection and migration locking.
|
package/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_IDENTIFIER = "@dbx-tools/auth";
|
|
6
|
+
export * as auth from "./src/auth.ts";
|
|
7
|
+
export * as storage from "./src/storage.ts";
|
|
8
|
+
export type { AuthorizeIdentity, AuthEmailOptions, PasswordlessAuthOptions, PasswordlessAuthRuntime } from "./src/auth.ts";
|
|
9
|
+
export type { AuthStorageMode, AuthStorageConfig, ResolvedAuthStorageConfig, AuthDatabase, AuthStorage } from "./src/storage.ts";
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const PACKAGE_IDENTIFIER = "@dbx-tools/auth";
|
|
2
|
+
export * as auth from "./src/auth.ts";
|
|
3
|
+
export * as storage from "./src/storage.ts";
|
|
4
|
+
export type { AuthorizeIdentity, AuthEmailOptions, PasswordlessAuthOptions, PasswordlessAuthRuntime } from "./src/auth.ts";
|
|
5
|
+
export type { AuthStorageMode, AuthStorageConfig, ResolvedAuthStorageConfig, AuthDatabase, AuthStorage } from "./src/storage.ts";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export const PACKAGE_IDENTIFIER = "@dbx-tools/auth";
|
|
5
|
+
export * as auth from "./src/auth.js";
|
|
6
|
+
export * as storage from "./src/storage.js";
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxpQkFBaUIsQ0FBQztBQUNwRCxPQUFPLEtBQUssSUFBSSxNQUFNLGVBQWUsQ0FBQztBQUN0QyxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0IGNvbnN0IFBBQ0tBR0VfSURFTlRJRklFUiA9IFwiQGRieC10b29scy9hdXRoXCI7XG5leHBvcnQgKiBhcyBhdXRoIGZyb20gXCIuL3NyYy9hdXRoLnRzXCI7XG5leHBvcnQgKiBhcyBzdG9yYWdlIGZyb20gXCIuL3NyYy9zdG9yYWdlLnRzXCI7XG5leHBvcnQgdHlwZSB7IEF1dGhvcml6ZUlkZW50aXR5LCBBdXRoRW1haWxPcHRpb25zLCBQYXNzd29yZGxlc3NBdXRoT3B0aW9ucywgUGFzc3dvcmRsZXNzQXV0aFJ1bnRpbWUgfSBmcm9tIFwiLi9zcmMvYXV0aC50c1wiO1xuZXhwb3J0IHR5cGUgeyBBdXRoU3RvcmFnZU1vZGUsIEF1dGhTdG9yYWdlQ29uZmlnLCBSZXNvbHZlZEF1dGhTdG9yYWdlQ29uZmlnLCBBdXRoRGF0YWJhc2UsIEF1dGhTdG9yYWdlIH0gZnJvbSBcIi4vc3JjL3N0b3JhZ2UudHNcIjtcbiJdfQ==
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth passwordless runtime with email OTP and passkeys.
|
|
3
|
+
*
|
|
4
|
+
* Callers provide authorization, delivery, storage, origin, and secret. Better
|
|
5
|
+
* Auth owns users, OTP verification records, sessions, rate limits, passkey
|
|
6
|
+
* credentials, and their native HTTP routes.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { type AuthStatus } from "@dbx-tools/shared-auth";
|
|
11
|
+
import type { AuthStorage } from "./storage.ts";
|
|
12
|
+
export type AuthorizeIdentity = (email: string) => boolean | Promise<boolean>;
|
|
13
|
+
export interface AuthEmailOptions {
|
|
14
|
+
subject: string;
|
|
15
|
+
brandName: string;
|
|
16
|
+
message: string;
|
|
17
|
+
codeTtlSeconds: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PasswordlessAuthOptions {
|
|
20
|
+
storage: AuthStorage;
|
|
21
|
+
baseURL: string;
|
|
22
|
+
basePath?: string;
|
|
23
|
+
appName: string;
|
|
24
|
+
secret: string;
|
|
25
|
+
sessionCookieName?: string;
|
|
26
|
+
sessionTtlSeconds: number;
|
|
27
|
+
sessionCutoffMs?: number;
|
|
28
|
+
codeTtlSeconds: number;
|
|
29
|
+
maxAttempts: number;
|
|
30
|
+
authorizeIdentity: AuthorizeIdentity;
|
|
31
|
+
sendCode(email: string, code: string, options: AuthEmailOptions): Promise<void>;
|
|
32
|
+
subject?: string;
|
|
33
|
+
message?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PasswordlessAuthRuntime {
|
|
36
|
+
readonly basePath: string;
|
|
37
|
+
readonly passkeysEnabled: boolean;
|
|
38
|
+
handler(request: Request): Promise<Response>;
|
|
39
|
+
session(headers: Headers): Promise<string | undefined>;
|
|
40
|
+
status(headers: Headers): Promise<AuthStatus>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export declare function createPasswordlessAuth(config: PasswordlessAuthOptions): Promise<PasswordlessAuthRuntime>;
|
package/lib/src/auth.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth passwordless runtime with email OTP and passkeys.
|
|
3
|
+
*
|
|
4
|
+
* Callers provide authorization, delivery, storage, origin, and secret. Better
|
|
5
|
+
* Auth owns users, OTP verification records, sessions, rate limits, passkey
|
|
6
|
+
* credentials, and their native HTTP routes.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { passkey } from "@better-auth/passkey";
|
|
11
|
+
import { authRequestSchema, authVerifySchema, SESSION_COOKIE_NAME, } from "@dbx-tools/shared-auth";
|
|
12
|
+
import { log } from "@dbx-tools/shared-core";
|
|
13
|
+
import { APIError, betterAuth } from "better-auth";
|
|
14
|
+
import { emailOTP } from "better-auth/plugins";
|
|
15
|
+
import { migrateAuth } from "./storage.js";
|
|
16
|
+
const logger = log.logger("auth");
|
|
17
|
+
export async function createPasswordlessAuth(config) {
|
|
18
|
+
const origin = new URL(config.baseURL).origin;
|
|
19
|
+
const rpID = new URL(origin).hostname;
|
|
20
|
+
const basePath = config.basePath ?? "/api/auth";
|
|
21
|
+
const emailOptions = {
|
|
22
|
+
subject: config.subject ?? "Your verification code",
|
|
23
|
+
brandName: config.appName,
|
|
24
|
+
message: config.message ?? "Your verification code is:",
|
|
25
|
+
codeTtlSeconds: config.codeTtlSeconds,
|
|
26
|
+
};
|
|
27
|
+
const options = {
|
|
28
|
+
appName: config.appName,
|
|
29
|
+
baseURL: origin,
|
|
30
|
+
basePath,
|
|
31
|
+
database: config.storage.database,
|
|
32
|
+
secret: config.secret,
|
|
33
|
+
trustedOrigins: [origin],
|
|
34
|
+
session: {
|
|
35
|
+
expiresIn: config.sessionTtlSeconds,
|
|
36
|
+
updateAge: Math.min(24 * 60 * 60, config.sessionTtlSeconds),
|
|
37
|
+
},
|
|
38
|
+
verification: {
|
|
39
|
+
storeIdentifier: "hashed",
|
|
40
|
+
storeInDatabase: true,
|
|
41
|
+
},
|
|
42
|
+
rateLimit: {
|
|
43
|
+
enabled: true,
|
|
44
|
+
window: 15 * 60,
|
|
45
|
+
max: 10,
|
|
46
|
+
customRules: {
|
|
47
|
+
"/email-otp/send-verification-otp": { window: 15 * 60, max: 5 },
|
|
48
|
+
"/sign-in/email-otp": { window: 15 * 60, max: 10 },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
advanced: {
|
|
52
|
+
useSecureCookies: origin.startsWith("https://"),
|
|
53
|
+
ipAddress: {
|
|
54
|
+
ipAddressHeaders: ["x-real-ip"],
|
|
55
|
+
disableIpTracking: false,
|
|
56
|
+
},
|
|
57
|
+
cookies: {
|
|
58
|
+
session_token: {
|
|
59
|
+
name: config.sessionCookieName ?? SESSION_COOKIE_NAME,
|
|
60
|
+
attributes: {
|
|
61
|
+
httpOnly: true,
|
|
62
|
+
sameSite: "lax",
|
|
63
|
+
secure: origin.startsWith("https://"),
|
|
64
|
+
path: "/",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
databaseHooks: {
|
|
70
|
+
user: {
|
|
71
|
+
create: {
|
|
72
|
+
before: async (user) => {
|
|
73
|
+
if (!(await config.authorizeIdentity(normalizeEmail(user.email)))) {
|
|
74
|
+
throw new APIError("FORBIDDEN", { message: "Identity is not authorized" });
|
|
75
|
+
}
|
|
76
|
+
return { data: user };
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
plugins: [
|
|
82
|
+
emailOTP({
|
|
83
|
+
otpLength: 6,
|
|
84
|
+
expiresIn: config.codeTtlSeconds,
|
|
85
|
+
allowedAttempts: config.maxAttempts,
|
|
86
|
+
storeOTP: "hashed",
|
|
87
|
+
async sendVerificationOTP({ email, otp, type }) {
|
|
88
|
+
if (type !== "sign-in")
|
|
89
|
+
return;
|
|
90
|
+
const address = normalizeEmail(email);
|
|
91
|
+
if (!(await config.authorizeIdentity(address)))
|
|
92
|
+
return;
|
|
93
|
+
void config.sendCode(address, otp, emailOptions).catch((error) => {
|
|
94
|
+
logger.error("verification email failed", { email: address, error });
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
passkey({
|
|
99
|
+
rpID,
|
|
100
|
+
rpName: config.appName,
|
|
101
|
+
origin,
|
|
102
|
+
authenticatorSelection: {
|
|
103
|
+
residentKey: "preferred",
|
|
104
|
+
userVerification: "preferred",
|
|
105
|
+
},
|
|
106
|
+
registration: { requireSession: true },
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
await migrateAuth(options, config.storage);
|
|
111
|
+
const auth = betterAuth(options);
|
|
112
|
+
const session = async (headers) => {
|
|
113
|
+
const current = await auth.api.getSession({ headers });
|
|
114
|
+
const email = normalizeEmail(current?.user.email);
|
|
115
|
+
const createdAt = current?.session.createdAt;
|
|
116
|
+
const createdAtMs = createdAt ? new Date(createdAt).getTime() : Number.NaN;
|
|
117
|
+
if (config.sessionCutoffMs && !(createdAtMs >= config.sessionCutoffMs))
|
|
118
|
+
return undefined;
|
|
119
|
+
if (!email || !(await config.authorizeIdentity(email)))
|
|
120
|
+
return undefined;
|
|
121
|
+
return email;
|
|
122
|
+
};
|
|
123
|
+
const handleCompatibilityRoute = async (request) => {
|
|
124
|
+
const path = new URL(request.url).pathname;
|
|
125
|
+
if (path === `${basePath}/status`) {
|
|
126
|
+
const email = await session(request.headers);
|
|
127
|
+
return jsonResponse({
|
|
128
|
+
authenticated: Boolean(email),
|
|
129
|
+
...(email ? { email } : {}),
|
|
130
|
+
enabled: true,
|
|
131
|
+
passkeysEnabled: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (path === `${basePath}/request` && request.method === "POST") {
|
|
135
|
+
const parsed = authRequestSchema.safeParse(await requestJson(request));
|
|
136
|
+
if (parsed.success) {
|
|
137
|
+
await auth.api
|
|
138
|
+
.sendVerificationOTP({
|
|
139
|
+
body: { email: normalizeEmail(parsed.data.email), type: "sign-in" },
|
|
140
|
+
headers: request.headers,
|
|
141
|
+
})
|
|
142
|
+
.catch(() => undefined);
|
|
143
|
+
}
|
|
144
|
+
return jsonResponse({ ok: true });
|
|
145
|
+
}
|
|
146
|
+
if (path === `${basePath}/verify` && request.method === "POST") {
|
|
147
|
+
const parsed = authVerifySchema.safeParse(await requestJson(request));
|
|
148
|
+
if (!parsed.success)
|
|
149
|
+
return jsonResponse({ ok: false });
|
|
150
|
+
try {
|
|
151
|
+
const response = await auth.api.signInEmailOTP({
|
|
152
|
+
body: {
|
|
153
|
+
email: normalizeEmail(parsed.data.email),
|
|
154
|
+
otp: parsed.data.code,
|
|
155
|
+
name: displayName(parsed.data.email),
|
|
156
|
+
},
|
|
157
|
+
headers: request.headers,
|
|
158
|
+
asResponse: true,
|
|
159
|
+
});
|
|
160
|
+
return compatibilityResponse(response, { ok: response.ok });
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return jsonResponse({ ok: false });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (path === `${basePath}/logout` && request.method === "POST") {
|
|
167
|
+
const response = await auth.api.signOut({
|
|
168
|
+
headers: request.headers,
|
|
169
|
+
asResponse: true,
|
|
170
|
+
});
|
|
171
|
+
return compatibilityResponse(response, { ok: response.ok });
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
basePath,
|
|
177
|
+
passkeysEnabled: true,
|
|
178
|
+
handler: async (request) => (await handleCompatibilityRoute(request)) ?? auth.handler(request),
|
|
179
|
+
session,
|
|
180
|
+
status: async (headers) => {
|
|
181
|
+
const email = await session(headers);
|
|
182
|
+
return {
|
|
183
|
+
authenticated: Boolean(email),
|
|
184
|
+
...(email ? { email } : {}),
|
|
185
|
+
enabled: true,
|
|
186
|
+
passkeysEnabled: true,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
close: () => config.storage.close(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function normalizeEmail(email) {
|
|
193
|
+
return email?.trim().toLowerCase() ?? "";
|
|
194
|
+
}
|
|
195
|
+
function displayName(email) {
|
|
196
|
+
return normalizeEmail(email).split("@")[0] || "User";
|
|
197
|
+
}
|
|
198
|
+
async function requestJson(request) {
|
|
199
|
+
try {
|
|
200
|
+
return await request.json();
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function jsonResponse(body, status = 200) {
|
|
207
|
+
return new Response(JSON.stringify(body), {
|
|
208
|
+
status,
|
|
209
|
+
headers: { "content-type": "application/json" },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function compatibilityResponse(response, body) {
|
|
213
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
214
|
+
for (const cookie of response.headers.getSetCookie())
|
|
215
|
+
headers.append("set-cookie", cookie);
|
|
216
|
+
return new Response(JSON.stringify(body), { status: 200, headers });
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hdXRoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQy9DLE9BQU8sRUFDTCxpQkFBaUIsRUFDakIsZ0JBQWdCLEVBRWhCLG1CQUFtQixHQUNwQixNQUFNLHdCQUF3QixDQUFDO0FBQ2hDLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBMEIsTUFBTSxhQUFhLENBQUM7QUFDM0UsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRy9DLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFM0MsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztBQXFDbEMsTUFBTSxDQUFDLEtBQUssVUFBVSxzQkFBc0IsQ0FDMUMsTUFBK0I7SUFFL0IsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUM5QyxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUM7SUFDdEMsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLFFBQVEsSUFBSSxXQUFXLENBQUM7SUFDaEQsTUFBTSxZQUFZLEdBQXFCO1FBQ3JDLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxJQUFJLHdCQUF3QjtRQUNuRCxTQUFTLEVBQUUsTUFBTSxDQUFDLE9BQU87UUFDekIsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPLElBQUksNEJBQTRCO1FBQ3ZELGNBQWMsRUFBRSxNQUFNLENBQUMsY0FBYztLQUN0QyxDQUFDO0lBRUYsTUFBTSxPQUFPLEdBQUc7UUFD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QVcsRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUMzQyxDQUFDO0FBRUQsU0FBUyxXQUFXLENBQUMsS0FBYTtJQUNoQyxPQUFPLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksTUFBTSxDQUFDO0FBQ3ZELENBQUM7QUFFRCxLQUFLLFVBQVUsV0FBVyxDQUFDLE9BQWdCO0lBQ3pDLElBQUksQ0FBQztRQUNILE9BQU8sTUFBTSxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDOUIsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDO0FBRUQsU0FBUyxZQUFZLENBQUMsSUFBYSxFQUFFLE1BQU0sR0FBRyxHQUFHO0lBQy9DLE9BQU8sSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUN4QyxNQUFNO1FBQ04sT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFO0tBQ2hELENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxTQUFTLHFCQUFxQixDQUFDLFFBQWtCLEVBQUUsSUFBYTtJQUM5RCxNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxFQUFFLGNBQWMsRUFBRSxrQkFBa0IsRUFBRSxDQUFDLENBQUM7SUFDcEUsS0FBSyxNQUFNLE1BQU0sSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRTtRQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQzNGLE9BQU8sSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQztBQUN0RSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBCZXR0ZXIgQXV0aCBwYXNzd29yZGxlc3MgcnVudGltZSB3aXRoIGVtYWlsIE9UUCBhbmQgcGFzc2tleXMuXG4gKlxuICogQ2FsbGVycyBwcm92aWRlIGF1dGhvcml6YXRpb24sIGRlbGl2ZXJ5LCBzdG9yYWdlLCBvcmlnaW4sIGFuZCBzZWNyZXQuIEJldHRlclxuICogQXV0aCBvd25zIHVzZXJzLCBPVFAgdmVyaWZpY2F0aW9uIHJlY29yZHMsIHNlc3Npb25zLCByYXRlIGxpbWl0cywgcGFzc2tleVxuICogY3JlZGVudGlhbHMsIGFuZCB0aGVpciBuYXRpdmUgSFRUUCByb3V0ZXMuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHBhc3NrZXkgfSBmcm9tIFwiQGJldHRlci1hdXRoL3Bhc3NrZXlcIjtcbmltcG9ydCB7XG4gIGF1dGhSZXF1ZXN0U2NoZW1hLFxuICBhdXRoVmVyaWZ5U2NoZW1hLFxuICB0eXBlIEF1dGhTdGF0dXMsXG4gIFNFU1NJT05fQ09PS0lFX05BTUUsXG59IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1hdXRoXCI7XG5pbXBvcnQgeyBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgQVBJRXJyb3IsIGJldHRlckF1dGgsIHR5cGUgQmV0dGVyQXV0aE9wdGlvbnMgfSBmcm9tIFwiYmV0dGVyLWF1dGhcIjtcbmltcG9ydCB7IGVtYWlsT1RQIH0gZnJvbSBcImJldHRlci1hdXRoL3BsdWdpbnNcIjtcblxuaW1wb3J0IHR5cGUgeyBBdXRoU3RvcmFnZSB9IGZyb20gXCIuL3N0b3JhZ2UudHNcIjtcbmltcG9ydCB7IG1pZ3JhdGVBdXRoIH0gZnJvbSBcIi4vc3RvcmFnZS50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwiYXV0aFwiKTtcblxuZXhwb3J0IHR5cGUgQXV0aG9yaXplSWRlbnRpdHkgPSAoZW1haWw6IHN0cmluZykgPT4gYm9vbGVhbiB8IFByb21pc2U8Ym9vbGVhbj47XG5cbmV4cG9ydCBpbnRlcmZhY2UgQXV0aEVtYWlsT3B0aW9ucyB7XG4gIHN1YmplY3Q6IHN0cmluZztcbiAgYnJhbmROYW1lOiBzdHJpbmc7XG4gIG1lc3NhZ2U6IHN0cmluZztcbiAgY29kZVR0bFNlY29uZHM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBQYXNzd29yZGxlc3NBdXRoT3B0aW9ucyB7XG4gIHN0b3JhZ2U6IEF1dGhTdG9yYWdlO1xuICBiYXNlVVJMOiBzdHJpbmc7XG4gIGJhc2VQYXRoPzogc3RyaW5nO1xuICBhcHBOYW1lOiBzdHJpbmc7XG4gIHNlY3JldDogc3RyaW5nO1xuICBzZXNzaW9uQ29va2llTmFtZT86IHN0cmluZztcbiAgc2Vzc2lvblR0bFNlY29uZHM6IG51bWJlcjtcbiAgc2Vzc2lvbkN1dG9mZk1zPzogbnVtYmVyO1xuICBjb2RlVHRsU2Vjb25kczogbnVtYmVyO1xuICBtYXhBdHRlbXB0czogbnVtYmVyO1xuICBhdXRob3JpemVJZGVudGl0eTogQXV0aG9yaXplSWRlbnRpdHk7XG4gIHNlbmRDb2RlKGVtYWlsOiBzdHJpbmcsIGNvZGU6IHN0cmluZywgb3B0aW9uczogQXV0aEVtYWlsT3B0aW9ucyk6IFByb21pc2U8dm9pZD47XG4gIHN1YmplY3Q/OiBzdHJpbmc7XG4gIG1lc3NhZ2U/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGFzc3dvcmRsZXNzQXV0aFJ1bnRpbWUge1xuICByZWFkb25seSBiYXNlUGF0aDogc3RyaW5nO1xuICByZWFkb25seSBwYXNza2V5c0VuYWJsZWQ6IGJvb2xlYW47XG4gIGhhbmRsZXIocmVxdWVzdDogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+O1xuICBzZXNzaW9uKGhlYWRlcnM6IEhlYWRlcnMpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD47XG4gIHN0YXR1cyhoZWFkZXJzOiBIZWFkZXJzKTogUHJvbWlzZTxBdXRoU3RhdHVzPjtcbiAgY2xvc2UoKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNyZWF0ZVBhc3N3b3JkbGVzc0F1dGgoXG4gIGNvbmZpZzogUGFzc3dvcmRsZXNzQXV0aE9wdGlvbnMsXG4pOiBQcm9taXNlPFBhc3N3b3JkbGVzc0F1dGhSdW50aW1lPiB7XG4gIGNvbnN0IG9yaWdpbiA9IG5ldyBVUkwoY29uZmlnLmJhc2VVUkwpLm9yaWdpbjtcbiAgY29uc3QgcnBJRCA9IG5ldyBVUkwob3JpZ2luKS5ob3N0bmFtZTtcbiAgY29uc3QgYmFzZVBhdGggPSBjb25maWcuYmFzZVBhdGggPz8gXCIvYXBpL2F1dGhcIjtcbiAgY29uc3QgZW1haWxPcHRpb25zOiBBdXRoRW1haWxPcHRpb25zID0ge1xuICAgIHN1YmplY3Q6IGNvbmZpZy5zdWJqZWN0ID8/IFwiWW91ciB2ZXJpZmljYXRpb24gY29kZVwiLFxuICAgIGJyYW5kTmFtZTogY29uZmlnLmFwcE5hbWUsXG4gICAgbWVzc2FnZTogY29uZmlnLm1lc3NhZ2UgPz8gXCJZb3VyIHZlcmlmaWNhdGlvbiBjb2RlIGlzOlwiLFxuICAgIGNvZGVUdGxTZWNvbmRzOiBjb25maWcuY29kZVR0bFNlY29uZHMsXG4gIH07XG5cbiAgY29uc3Qgb3B0aW9ucyA9IHtcbiAgICBhcHBOYW1lOiBjb25maWcuYXBwTmFtZSxcbiAgICBiYXNlVVJMOiBvcmlnaW4sXG4gICAgYmFzZVBhdGgsXG4gICAgZGF0YWJhc2U6IGNvbmZpZy5zdG9yYWdlLmRhdGFiYXNlLFxuICAgIHNlY3JldDogY29uZmlnLnNlY3JldCxcbiAgICB0cnVzdGVkT3JpZ2luczogW29yaWdpbl0sXG4gICAgc2Vzc2lvbjoge1xuICAgICAgZXhwaXJlc0luOiBjb25maWcuc2Vzc2lvblR0bFNlY29uZHMsXG4gICAgICB1cGRhdGVBZ2U6IE1hdGgubWluKDI0ICogNjAgKiA2MCwgY29uZmlnLnNlc3Npb25UdGxTZWNvbmRzKSxcbiAgICB9LFxuICAgIHZlcmlmaWNhdGlvbjoge1xuICAgICAgc3RvcmVJZGVudGlmaWVyOiBcImhhc2hlZFwiLFxuICAgICAgc3RvcmVJbkRhdGFiYXNlOiB0cnVlLFxuICAgIH0sXG4gICAgcmF0ZUxpbWl0OiB7XG4gICAgICBlbmFibGVkOiB0cnVlLFxuICAgICAgd2luZG93OiAxNSAqIDYwLFxuICAgICAgbWF4OiAxMCxcbiAgICAgIGN1c3RvbVJ1bGVzOiB7XG4gICAgICAgIFwiL2VtYWlsLW90cC9zZW5kLXZlcmlmaWNhdGlvbi1vdHBcIjogeyB3aW5kb3c6IDE1ICogNjAsIG1heDogNSB9LFxuICAgICAgICBcIi9zaWduLWluL2VtYWlsLW90cFwiOiB7IHdpbmRvdzogMTUgKiA2MCwgbWF4OiAxMCB9LFxuICAgICAgfSxcbiAgICB9LFxuICAgIGFkdmFuY2VkOiB7XG4gICAgICB1c2VTZWN1cmVDb29raWVzOiBvcmlnaW4uc3RhcnRzV2l0aChcImh0dHBzOi8vXCIpLFxuICAgICAgaXBBZGRyZXNzOiB7XG4gICAgICAgIGlwQWRkcmVzc0hlYWRlcnM6IFtcIngtcmVhbC1pcFwiXSxcbiAgICAgICAgZGlzYWJsZUlwVHJhY2tpbmc6IGZhbHNlLFxuICAgICAgfSxcbiAgICAgIGNvb2tpZXM6IHtcbiAgICAgICAgc2Vzc2lvbl90b2tlbjoge1xuICAgICAgICAgIG5hbWU6IGNvbmZpZy5zZXNzaW9uQ29va2llTmFtZSA/PyBTRVNTSU9OX0NPT0tJRV9OQU1FLFxuICAgICAgICAgIGF0dHJpYnV0ZXM6IHtcbiAgICAgICAgICAgIGh0dHBPbmx5OiB0cnVlLFxuICAgICAgICAgICAgc2FtZVNpdGU6IFwibGF4XCIsXG4gICAgICAgICAgICBzZWN1cmU6IG9yaWdpbi5zdGFydHNXaXRoKFwiaHR0cHM6Ly9cIiksXG4gICAgICAgICAgICBwYXRoOiBcIi9cIixcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICAgIGRhdGFiYXNlSG9va3M6IHtcbiAgICAgIHVzZXI6IHtcbiAgICAgICAgY3JlYXRlOiB7XG4gICAgICAgICAgYmVmb3JlOiBhc3luYyAodXNlcjogeyBlbWFpbDogc3RyaW5nIH0pID0+IHtcbiAgICAgICAgICAgIGlmICghKGF3YWl0IGNvbmZpZy5hdXRob3JpemVJZGVudGl0eShub3JtYWxpemVFbWFpbCh1c2VyLmVtYWlsKSkpKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBBUElFcnJvcihcIkZPUkJJRERFTlwiLCB7IG1lc3NhZ2U6IFwiSWRlbnRpdHkgaXMgbm90IGF1dGhvcml6ZWRcIiB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB7IGRhdGE6IHVzZXIgfTtcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICB9LFxuICAgIHBsdWdpbnM6IFtcbiAgICAgIGVtYWlsT1RQKHtcbiAgICAgICAgb3RwTGVuZ3RoOiA2LFxuICAgICAgICBleHBpcmVzSW46IGNvbmZpZy5jb2RlVHRsU2Vjb25kcyxcbiAgICAgICAgYWxsb3dlZEF0dGVtcHRzOiBjb25maWcubWF4QXR0ZW1wdHMsXG4gICAgICAgIHN0b3JlT1RQOiBcImhhc2hlZFwiLFxuICAgICAgICBhc3luYyBzZW5kVmVyaWZpY2F0aW9uT1RQKHsgZW1haWwsIG90cCwgdHlwZSB9KSB7XG4gICAgICAgICAgaWYgKHR5cGUgIT09IFwic2lnbi1pblwiKSByZXR1cm47XG4gICAgICAgICAgY29uc3QgYWRkcmVzcyA9IG5vcm1hbGl6ZUVtYWlsKGVtYWlsKTtcbiAgICAgICAgICBpZiAoIShhd2FpdCBjb25maWcuYXV0aG9yaXplSWRlbnRpdHkoYWRkcmVzcykpKSByZXR1cm47XG4gICAgICAgICAgdm9pZCBjb25maWcuc2VuZENvZGUoYWRkcmVzcywgb3RwLCBlbWFpbE9wdGlvbnMpLmNhdGNoKChlcnJvcjogdW5rbm93bikgPT4ge1xuICAgICAgICAgICAgbG9nZ2VyLmVycm9yKFwidmVyaWZpY2F0aW9uIGVtYWlsIGZhaWxlZFwiLCB7IGVtYWlsOiBhZGRyZXNzLCBlcnJvciB9KTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICAgIH0pLFxuICAgICAgcGFzc2tleSh7XG4gICAgICAgIHJwSUQsXG4gICAgICAgIHJwTmFtZTogY29uZmlnLmFwcE5hbWUsXG4gICAgICAgIG9yaWdpbixcbiAgICAgICAgYXV0aGVudGljYXRvclNlbGVjdGlvbjoge1xuICAgICAgICAgIHJlc2lkZW50S2V5OiBcInByZWZlcnJlZFwiLFxuICAgICAgICAgIHVzZXJWZXJpZmljYXRpb246IFwicHJlZmVycmVkXCIsXG4gICAgICAgIH0sXG4gICAgICAgIHJlZ2lzdHJhdGlvbjogeyByZXF1aXJlU2Vzc2lvbjogdHJ1ZSB9LFxuICAgICAgfSksXG4gICAgXSBhcyBjb25zdCxcbiAgfSBzYXRpc2ZpZXMgQmV0dGVyQXV0aE9wdGlvbnM7XG5cbiAgYXdhaXQgbWlncmF0ZUF1dGgob3B0aW9ucywgY29uZmlnLnN0b3JhZ2UpO1xuICBjb25zdCBhdXRoID0gYmV0dGVyQXV0aChvcHRpb25zKTtcblxuICBjb25zdCBzZXNzaW9uID0gYXN5bmMgKGhlYWRlcnM6IEhlYWRlcnMpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4gPT4ge1xuICAgIGNvbnN0IGN1cnJlbnQgPSBhd2FpdCBhdXRoLmFwaS5nZXRTZXNzaW9uKHsgaGVhZGVycyB9KTtcbiAgICBjb25zdCBlbWFpbCA9IG5vcm1hbGl6ZUVtYWlsKGN1cnJlbnQ/LnVzZXIuZW1haWwpO1xuICAgIGNvbnN0IGNyZWF0ZWRBdCA9IGN1cnJlbnQ/LnNlc3Npb24uY3JlYXRlZEF0O1xuICAgIGNvbnN0IGNyZWF0ZWRBdE1zID0gY3JlYXRlZEF0ID8gbmV3IERhdGUoY3JlYXRlZEF0KS5nZXRUaW1lKCkgOiBOdW1iZXIuTmFOO1xuICAgIGlmIChjb25maWcuc2Vzc2lvbkN1dG9mZk1zICYmICEoY3JlYXRlZEF0TXMgPj0gY29uZmlnLnNlc3Npb25DdXRvZmZNcykpIHJldHVybiB1bmRlZmluZWQ7XG4gICAgaWYgKCFlbWFpbCB8fCAhKGF3YWl0IGNvbmZpZy5hdXRob3JpemVJZGVudGl0eShlbWFpbCkpKSByZXR1cm4gdW5kZWZpbmVkO1xuICAgIHJldHVybiBlbWFpbDtcbiAgfTtcblxuICBjb25zdCBoYW5kbGVDb21wYXRpYmlsaXR5Um91dGUgPSBhc3luYyAocmVxdWVzdDogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2UgfCB1bmRlZmluZWQ+ID0+IHtcbiAgICBjb25zdCBwYXRoID0gbmV3IFVSTChyZXF1ZXN0LnVybCkucGF0aG5hbWU7XG4gICAgaWYgKHBhdGggPT09IGAke2Jhc2VQYXRofS9zdGF0dXNgKSB7XG4gICAgICBjb25zdCBlbWFpbCA9IGF3YWl0IHNlc3Npb24ocmVxdWVzdC5oZWFkZXJzKTtcbiAgICAgIHJldHVybiBqc29uUmVzcG9uc2Uoe1xuICAgICAgICBhdXRoZW50aWNhdGVkOiBCb29sZWFuKGVtYWlsKSxcbiAgICAgICAgLi4uKGVtYWlsID8geyBlbWFpbCB9IDoge30pLFxuICAgICAgICBlbmFibGVkOiB0cnVlLFxuICAgICAgICBwYXNza2V5c0VuYWJsZWQ6IHRydWUsXG4gICAgICB9KTtcbiAgICB9XG4gICAgaWYgKHBhdGggPT09IGAke2Jhc2VQYXRofS9yZXF1ZXN0YCAmJiByZXF1ZXN0Lm1ldGhvZCA9PT0gXCJQT1NUXCIpIHtcbiAgICAgIGNvbnN0IHBhcnNlZCA9IGF1dGhSZXF1ZXN0U2NoZW1hLnNhZmVQYXJzZShhd2FpdCByZXF1ZXN0SnNvbihyZXF1ZXN0KSk7XG4gICAgICBpZiAocGFyc2VkLnN1Y2Nlc3MpIHtcbiAgICAgICAgYXdhaXQgYXV0aC5hcGlcbiAgICAgICAgICAuc2VuZFZlcmlmaWNhdGlvbk9UUCh7XG4gICAgICAgICAgICBib2R5OiB7IGVtYWlsOiBub3JtYWxpemVFbWFpbChwYXJzZWQuZGF0YS5lbWFpbCksIHR5cGU6IFwic2lnbi1pblwiIH0sXG4gICAgICAgICAgICBoZWFkZXJzOiByZXF1ZXN0LmhlYWRlcnMsXG4gICAgICAgICAgfSlcbiAgICAgICAgICAuY2F0Y2goKCkgPT4gdW5kZWZpbmVkKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBqc29uUmVzcG9uc2UoeyBvazogdHJ1ZSB9KTtcbiAgICB9XG4gICAgaWYgKHBhdGggPT09IGAke2Jhc2VQYXRofS92ZXJpZnlgICYmIHJlcXVlc3QubWV0aG9kID09PSBcIlBPU1RcIikge1xuICAgICAgY29uc3QgcGFyc2VkID0gYXV0aFZlcmlmeVNjaGVtYS5zYWZlUGFyc2UoYXdhaXQgcmVxdWVzdEpzb24ocmVxdWVzdCkpO1xuICAgICAgaWYgKCFwYXJzZWQuc3VjY2VzcykgcmV0dXJuIGpzb25SZXNwb25zZSh7IG9rOiBmYWxzZSB9KTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgYXV0aC5hcGkuc2lnbkluRW1haWxPVFAoe1xuICAgICAgICAgIGJvZHk6IHtcbiAgICAgICAgICAgIGVtYWlsOiBub3JtYWxpemVFbWFpbChwYXJzZWQuZGF0YS5lbWFpbCksXG4gICAgICAgICAgICBvdHA6IHBhcnNlZC5kYXRhLmNvZGUsXG4gICAgICAgICAgICBuYW1lOiBkaXNwbGF5TmFtZShwYXJzZWQuZGF0YS5lbWFpbCksXG4gICAgICAgICAgfSxcbiAgICAgICAgICBoZWFkZXJzOiByZXF1ZXN0LmhlYWRlcnMsXG4gICAgICAgICAgYXNSZXNwb25zZTogdHJ1ZSxcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiBjb21wYXRpYmlsaXR5UmVzcG9uc2UocmVzcG9uc2UsIHsgb2s6IHJlc3BvbnNlLm9rIH0pO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiBqc29uUmVzcG9uc2UoeyBvazogZmFsc2UgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChwYXRoID09PSBgJHtiYXNlUGF0aH0vbG9nb3V0YCAmJiByZXF1ZXN0Lm1ldGhvZCA9PT0gXCJQT1NUXCIpIHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgYXV0aC5hcGkuc2lnbk91dCh7XG4gICAgICAgIGhlYWRlcnM6IHJlcXVlc3QuaGVhZGVycyxcbiAgICAgICAgYXNSZXNwb25zZTogdHJ1ZSxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGNvbXBhdGliaWxpdHlSZXNwb25zZShyZXNwb25zZSwgeyBvazogcmVzcG9uc2Uub2sgfSk7XG4gICAgfVxuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH07XG5cbiAgcmV0dXJuIHtcbiAgICBiYXNlUGF0aCxcbiAgICBwYXNza2V5c0VuYWJsZWQ6IHRydWUsXG4gICAgaGFuZGxlcjogYXN5bmMgKHJlcXVlc3QpID0+IChhd2FpdCBoYW5kbGVDb21wYXRpYmlsaXR5Um91dGUocmVxdWVzdCkpID8/IGF1dGguaGFuZGxlcihyZXF1ZXN0KSxcbiAgICBzZXNzaW9uLFxuICAgIHN0YXR1czogYXN5bmMgKGhlYWRlcnMpID0+IHtcbiAgICAgIGNvbnN0IGVtYWlsID0gYXdhaXQgc2Vzc2lvbihoZWFkZXJzKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGF1dGhlbnRpY2F0ZWQ6IEJvb2xlYW4oZW1haWwpLFxuICAgICAgICAuLi4oZW1haWwgPyB7IGVtYWlsIH0gOiB7fSksXG4gICAgICAgIGVuYWJsZWQ6IHRydWUsXG4gICAgICAgIHBhc3NrZXlzRW5hYmxlZDogdHJ1ZSxcbiAgICAgIH07XG4gICAgfSxcbiAgICBjbG9zZTogKCkgPT4gY29uZmlnLnN0b3JhZ2UuY2xvc2UoKSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gbm9ybWFsaXplRW1haWwoZW1haWw6IHN0cmluZyB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gIHJldHVybiBlbWFpbD8udHJpbSgpLnRvTG93ZXJDYXNlKCkgPz8gXCJcIjtcbn1cblxuZnVuY3Rpb24gZGlzcGxheU5hbWUoZW1haWw6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBub3JtYWxpemVFbWFpbChlbWFpbCkuc3BsaXQoXCJAXCIpWzBdIHx8IFwiVXNlclwiO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZXF1ZXN0SnNvbihyZXF1ZXN0OiBSZXF1ZXN0KTogUHJvbWlzZTx1bmtub3duPiB7XG4gIHRyeSB7XG4gICAgcmV0dXJuIGF3YWl0IHJlcXVlc3QuanNvbigpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbmZ1bmN0aW9uIGpzb25SZXNwb25zZShib2R5OiB1bmtub3duLCBzdGF0dXMgPSAyMDApOiBSZXNwb25zZSB7XG4gIHJldHVybiBuZXcgUmVzcG9uc2UoSlNPTi5zdHJpbmdpZnkoYm9keSksIHtcbiAgICBzdGF0dXMsXG4gICAgaGVhZGVyczogeyBcImNvbnRlbnQtdHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIiB9LFxuICB9KTtcbn1cblxuZnVuY3Rpb24gY29tcGF0aWJpbGl0eVJlc3BvbnNlKHJlc3BvbnNlOiBSZXNwb25zZSwgYm9keTogdW5rbm93bik6IFJlc3BvbnNlIHtcbiAgY29uc3QgaGVhZGVycyA9IG5ldyBIZWFkZXJzKHsgXCJjb250ZW50LXR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIgfSk7XG4gIGZvciAoY29uc3QgY29va2llIG9mIHJlc3BvbnNlLmhlYWRlcnMuZ2V0U2V0Q29va2llKCkpIGhlYWRlcnMuYXBwZW5kKFwic2V0LWNvb2tpZVwiLCBjb29raWUpO1xuICByZXR1cm4gbmV3IFJlc3BvbnNlKEpTT04uc3RyaW5naWZ5KGJvZHkpLCB7IHN0YXR1czogMjAwLCBoZWFkZXJzIH0pO1xufVxuIl19
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth database selection and migration locking.
|
|
3
|
+
*
|
|
4
|
+
* Callers pass a native AppKit Lakebase pool when available. Otherwise auth
|
|
5
|
+
* uses SQLite in the operating system's application-data directory.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type PgPoolLike } from "@dbx-tools/postgres";
|
|
10
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
11
|
+
export type AuthStorageMode = "auto" | "lakebase" | "sqlite";
|
|
12
|
+
export interface AuthStorageConfig {
|
|
13
|
+
storage?: AuthStorageMode;
|
|
14
|
+
sqlitePath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ResolvedAuthStorageConfig {
|
|
17
|
+
mode: AuthStorageMode;
|
|
18
|
+
sqlitePath: string;
|
|
19
|
+
}
|
|
20
|
+
export type AuthDatabase = NonNullable<BetterAuthOptions["database"]>;
|
|
21
|
+
export interface AuthStorage {
|
|
22
|
+
kind: "lakebase" | "sqlite";
|
|
23
|
+
database: AuthDatabase;
|
|
24
|
+
pool?: PgPoolLike;
|
|
25
|
+
path?: string;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare function resolveAuthStorageConfig(config?: AuthStorageConfig): ResolvedAuthStorageConfig;
|
|
29
|
+
export declare function shouldUseLakebase(config?: AuthStorageConfig): boolean;
|
|
30
|
+
export declare function createAuthStorage(config: AuthStorageConfig, pool?: PgPoolLike): Promise<AuthStorage>;
|
|
31
|
+
export declare function migrateAuth(options: BetterAuthOptions, storage: AuthStorage): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth database selection and migration locking.
|
|
3
|
+
*
|
|
4
|
+
* Callers pass a native AppKit Lakebase pool when available. Otherwise auth
|
|
5
|
+
* uses SQLite in the operating system's application-data directory.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
10
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
11
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
12
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
};
|
|
17
|
+
import { mkdirSync } from "node:fs";
|
|
18
|
+
import { dirname, resolve } from "node:path";
|
|
19
|
+
import { fileLock } from "@dbx-tools/core";
|
|
20
|
+
import { advisoryLock } from "@dbx-tools/postgres";
|
|
21
|
+
import envPaths from "env-paths";
|
|
22
|
+
const MIGRATION_LOCK = ["auth", "better-auth", "migrations"];
|
|
23
|
+
export function resolveAuthStorageConfig(config = {}) {
|
|
24
|
+
const mode = config.storage ?? "auto";
|
|
25
|
+
if (mode !== "auto" && mode !== "lakebase" && mode !== "sqlite") {
|
|
26
|
+
throw new TypeError('auth storage must be "auto", "lakebase", or "sqlite"');
|
|
27
|
+
}
|
|
28
|
+
const dataDirectory = envPaths("dbx-tools", { suffix: "" }).data;
|
|
29
|
+
return {
|
|
30
|
+
mode,
|
|
31
|
+
sqlitePath: resolve(config.sqlitePath ?? resolve(dataDirectory, "auth", "auth.sqlite")),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function shouldUseLakebase(config = {}) {
|
|
35
|
+
const resolved = resolveAuthStorageConfig(config);
|
|
36
|
+
if (resolved.mode === "lakebase")
|
|
37
|
+
return true;
|
|
38
|
+
if (resolved.mode === "sqlite")
|
|
39
|
+
return false;
|
|
40
|
+
return Boolean(process.env.LAKEBASE_ENDPOINT ?? process.env.PGHOST);
|
|
41
|
+
}
|
|
42
|
+
export async function createAuthStorage(config, pool) {
|
|
43
|
+
const resolved = resolveAuthStorageConfig(config);
|
|
44
|
+
if (pool && resolved.mode !== "sqlite") {
|
|
45
|
+
return {
|
|
46
|
+
kind: "lakebase",
|
|
47
|
+
database: pool,
|
|
48
|
+
pool,
|
|
49
|
+
close: async () => undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (resolved.mode === "lakebase") {
|
|
53
|
+
throw new Error("auth storage is lakebase but no Lakebase pool was supplied");
|
|
54
|
+
}
|
|
55
|
+
mkdirSync(dirname(resolved.sqlitePath), { recursive: true });
|
|
56
|
+
const database = await openSqlite(resolved.sqlitePath);
|
|
57
|
+
return {
|
|
58
|
+
kind: "sqlite",
|
|
59
|
+
database,
|
|
60
|
+
path: resolved.sqlitePath,
|
|
61
|
+
close: async () => {
|
|
62
|
+
database.close();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function migrateAuth(options, storage) {
|
|
67
|
+
const run = async () => {
|
|
68
|
+
const module = (await import(__rewriteRelativeImportExtension(migrationModuleUrl())));
|
|
69
|
+
const migrations = await module.getMigrations(options);
|
|
70
|
+
await migrations.runMigrations();
|
|
71
|
+
};
|
|
72
|
+
if (storage.kind === "lakebase" && storage.pool) {
|
|
73
|
+
await advisoryLock.withAdvisoryLock(storage.pool, MIGRATION_LOCK, run);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await fileLock.withFileLock(MIGRATION_LOCK, run);
|
|
77
|
+
}
|
|
78
|
+
async function openSqlite(path) {
|
|
79
|
+
if (process.versions.bun) {
|
|
80
|
+
const specifier = "bun:sqlite";
|
|
81
|
+
const { Database } = (await import(__rewriteRelativeImportExtension(specifier)));
|
|
82
|
+
const database = new Database(path, { create: true, strict: true });
|
|
83
|
+
database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON");
|
|
84
|
+
return database;
|
|
85
|
+
}
|
|
86
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
87
|
+
const database = new DatabaseSync(path);
|
|
88
|
+
database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON");
|
|
89
|
+
return database;
|
|
90
|
+
}
|
|
91
|
+
function migrationModuleUrl() {
|
|
92
|
+
const entry = import.meta.resolve("better-auth");
|
|
93
|
+
return new URL("./db/get-migration.mjs", entry).href;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdG9yYWdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O0dBT0c7Ozs7Ozs7OztBQUVILE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDcEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDN0MsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzNDLE9BQU8sRUFBRSxZQUFZLEVBQW1CLE1BQU0scUJBQXFCLENBQUM7QUFFcEUsT0FBTyxRQUFRLE1BQU0sV0FBVyxDQUFDO0FBdUNqQyxNQUFNLGNBQWMsR0FBRyxDQUFDLE1BQU0sRUFBRSxhQUFhLEVBQUUsWUFBWSxDQUFVLENBQUM7QUFFdEUsTUFBTSxVQUFVLHdCQUF3QixDQUN0QyxTQUE0QixFQUFFO0lBRTlCLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxPQUFPLElBQUksTUFBTSxDQUFDO0lBQ3RDLElBQUksSUFBSSxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssVUFBVSxJQUFJLElBQUksS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUNoRSxNQUFNLElBQUksU0FBUyxDQUFDLHNEQUFzRCxDQUFDLENBQUM7SUFDOUUsQ0FBQztJQUNELE1BQU0sYUFBYSxHQUFHLFFBQVEsQ0FBQyxXQUFXLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDakUsT0FBTztRQUNMLElBQUk7UUFDSixVQUFVLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxVQUFVLElBQUksT0FBTyxDQUFDLGFBQWEsRUFBRSxNQUFNLEVBQUUsYUFBYSxDQUFDLENBQUM7S0FDeEYsQ0FBQztBQUNKLENBQUM7QUFFRCxNQUFNLFVBQVUsaUJBQWlCLENBQUMsU0FBNEIsRUFBRTtJQUM5RCxNQUFNLFFBQVEsR0FBRyx3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RSxZQUFZLEVBQUUsR0FBRyxNQUFNLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUNyRCxNQUFNLFFBQVEsR0FBRyxJQUFJLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN4QyxRQUFRLENBQUMsSUFBSSxDQUFDLHFEQUFxRCxDQUFDLENBQUM7SUFDckUsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQztBQUVELFNBQVMsa0JBQWtCO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ2pELE9BQU8sSUFBSSxHQUFHLENBQUMsd0JBQXdCLEVBQUUsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ3ZELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEJldHRlciBBdXRoIGRhdGFiYXNlIHNlbGVjdGlvbiBhbmQgbWlncmF0aW9uIGxvY2tpbmcuXG4gKlxuICogQ2FsbGVycyBwYXNzIGEgbmF0aXZlIEFwcEtpdCBMYWtlYmFzZSBwb29sIHdoZW4gYXZhaWxhYmxlLiBPdGhlcndpc2UgYXV0aFxuICogdXNlcyBTUUxpdGUgaW4gdGhlIG9wZXJhdGluZyBzeXN0ZW0ncyBhcHBsaWNhdGlvbi1kYXRhIGRpcmVjdG9yeS5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgbWtkaXJTeW5jIH0gZnJvbSBcIm5vZGU6ZnNcIjtcbmltcG9ydCB7IGRpcm5hbWUsIHJlc29sdmUgfSBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgeyBmaWxlTG9jayB9IGZyb20gXCJAZGJ4LXRvb2xzL2NvcmVcIjtcbmltcG9ydCB7IGFkdmlzb3J5TG9jaywgdHlwZSBQZ1Bvb2xMaWtlIH0gZnJvbSBcIkBkYngtdG9vbHMvcG9zdGdyZXNcIjtcbmltcG9ydCB0eXBlIHsgQmV0dGVyQXV0aE9wdGlvbnMgfSBmcm9tIFwiYmV0dGVyLWF1dGhcIjtcbmltcG9ydCBlbnZQYXRocyBmcm9tIFwiZW52LXBhdGhzXCI7XG5cbmV4cG9ydCB0eXBlIEF1dGhTdG9yYWdlTW9kZSA9IFwiYXV0b1wiIHwgXCJsYWtlYmFzZVwiIHwgXCJzcWxpdGVcIjtcblxuZXhwb3J0IGludGVyZmFjZSBBdXRoU3RvcmFnZUNvbmZpZyB7XG4gIHN0b3JhZ2U/OiBBdXRoU3RvcmFnZU1vZGU7XG4gIHNxbGl0ZVBhdGg/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRBdXRoU3RvcmFnZUNvbmZpZyB7XG4gIG1vZGU6IEF1dGhTdG9yYWdlTW9kZTtcbiAgc3FsaXRlUGF0aDogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBBdXRoRGF0YWJhc2UgPSBOb25OdWxsYWJsZTxCZXR0ZXJBdXRoT3B0aW9uc1tcImRhdGFiYXNlXCJdPjtcblxuZXhwb3J0IGludGVyZmFjZSBBdXRoU3RvcmFnZSB7XG4gIGtpbmQ6IFwibGFrZWJhc2VcIiB8IFwic3FsaXRlXCI7XG4gIGRhdGFiYXNlOiBBdXRoRGF0YWJhc2U7XG4gIHBvb2w/OiBQZ1Bvb2xMaWtlO1xuICBwYXRoPzogc3RyaW5nO1xuICBjbG9zZSgpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG50eXBlIE1pZ3JhdGlvbk1vZHVsZSA9IHtcbiAgZ2V0TWlncmF0aW9ucyhvcHRpb25zOiBCZXR0ZXJBdXRoT3B0aW9ucyk6IFByb21pc2U8e1xuICAgIHJ1bk1pZ3JhdGlvbnMoKTogUHJvbWlzZTx2b2lkPjtcbiAgfT47XG59O1xuXG5pbnRlcmZhY2UgU3FsaXRlRGF0YWJhc2Uge1xuICBleGVjKHNxbDogc3RyaW5nKTogdW5rbm93bjtcbiAgY2xvc2UoKTogdm9pZDtcbn1cblxuaW50ZXJmYWNlIEJ1blNxbGl0ZU1vZHVsZSB7XG4gIERhdGFiYXNlOiBuZXcgKHBhdGg6IHN0cmluZywgb3B0aW9ucz86IHsgY3JlYXRlPzogYm9vbGVhbjsgc3RyaWN0PzogYm9vbGVhbiB9KSA9PiBTcWxpdGVEYXRhYmFzZTtcbn1cblxuY29uc3QgTUlHUkFUSU9OX0xPQ0sgPSBbXCJhdXRoXCIsIFwiYmV0dGVyLWF1dGhcIiwgXCJtaWdyYXRpb25zXCJdIGFzIGNvbnN0O1xuXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUF1dGhTdG9yYWdlQ29uZmlnKFxuICBjb25maWc6IEF1dGhTdG9yYWdlQ29uZmlnID0ge30sXG4pOiBSZXNvbHZlZEF1dGhTdG9yYWdlQ29uZmlnIHtcbiAgY29uc3QgbW9kZSA9IGNvbmZpZy5zdG9yYWdlID8/IFwiYXV0b1wiO1xuICBpZiAobW9kZSAhPT0gXCJhdXRvXCIgJiYgbW9kZSAhPT0gXCJsYWtlYmFzZVwiICYmIG1vZGUgIT09IFwic3FsaXRlXCIpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdhdXRoIHN0b3JhZ2UgbXVzdCBiZSBcImF1dG9cIiwgXCJsYWtlYmFzZVwiLCBvciBcInNxbGl0ZVwiJyk7XG4gIH1cbiAgY29uc3QgZGF0YURpcmVjdG9yeSA9IGVudlBhdGhzKFwiZGJ4LXRvb2xzXCIsIHsgc3VmZml4OiBcIlwiIH0pLmRhdGE7XG4gIHJldHVybiB7XG4gICAgbW9kZSxcbiAgICBzcWxpdGVQYXRoOiByZXNvbHZlKGNvbmZpZy5zcWxpdGVQYXRoID8/IHJlc29sdmUoZGF0YURpcmVjdG9yeSwgXCJhdXRoXCIsIFwiYXV0aC5zcWxpdGVcIikpLFxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gc2hvdWxkVXNlTGFrZWJhc2UoY29uZmlnOiBBdXRoU3RvcmFnZUNvbmZpZyA9IHt9KTogYm9vbGVhbiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmVzb2x2ZUF1dGhTdG9yYWdlQ29uZmlnKGNvbmZpZyk7XG4gIGlmIChyZXNvbHZlZC5tb2RlID09PSBcImxha2ViYXNlXCIpIHJldHVybiB0cnVlO1xuICBpZiAocmVzb2x2ZWQubW9kZSA9PT0gXCJzcWxpdGVcIikgcmV0dXJuIGZhbHNlO1xuICByZXR1cm4gQm9vbGVhbihwcm9jZXNzLmVudi5MQUtFQkFTRV9FTkRQT0lOVCA/PyBwcm9jZXNzLmVudi5QR0hPU1QpO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY3JlYXRlQXV0aFN0b3JhZ2UoXG4gIGNvbmZpZzogQXV0aFN0b3JhZ2VDb25maWcsXG4gIHBvb2w/OiBQZ1Bvb2xMaWtlLFxuKTogUHJvbWlzZTxBdXRoU3RvcmFnZT4ge1xuICBjb25zdCByZXNvbHZlZCA9IHJlc29sdmVBdXRoU3RvcmFnZUNvbmZpZyhjb25maWcpO1xuICBpZiAocG9vbCAmJiByZXNvbHZlZC5tb2RlICE9PSBcInNxbGl0ZVwiKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGtpbmQ6IFwibGFrZWJhc2VcIixcbiAgICAgIGRhdGFiYXNlOiBwb29sLFxuICAgICAgcG9vbCxcbiAgICAgIGNsb3NlOiBhc3luYyAoKSA9PiB1bmRlZmluZWQsXG4gICAgfTtcbiAgfVxuICBpZiAocmVzb2x2ZWQubW9kZSA9PT0gXCJsYWtlYmFzZVwiKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwiYXV0aCBzdG9yYWdlIGlzIGxha2ViYXNlIGJ1dCBubyBMYWtlYmFzZSBwb29sIHdhcyBzdXBwbGllZFwiKTtcbiAgfVxuXG4gIG1rZGlyU3luYyhkaXJuYW1lKHJlc29sdmVkLnNxbGl0ZVBhdGgpLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgY29uc3QgZGF0YWJhc2UgPSBhd2FpdCBvcGVuU3FsaXRlKHJlc29sdmVkLnNxbGl0ZVBhdGgpO1xuICByZXR1cm4ge1xuICAgIGtpbmQ6IFwic3FsaXRlXCIsXG4gICAgZGF0YWJhc2UsXG4gICAgcGF0aDogcmVzb2x2ZWQuc3FsaXRlUGF0aCxcbiAgICBjbG9zZTogYXN5bmMgKCkgPT4ge1xuICAgICAgZGF0YWJhc2UuY2xvc2UoKTtcbiAgICB9LFxuICB9O1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbWlncmF0ZUF1dGgob3B0aW9uczogQmV0dGVyQXV0aE9wdGlvbnMsIHN0b3JhZ2U6IEF1dGhTdG9yYWdlKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHJ1biA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICBjb25zdCBtb2R1bGUgPSAoYXdhaXQgaW1wb3J0KG1pZ3JhdGlvbk1vZHVsZVVybCgpKSkgYXMgTWlncmF0aW9uTW9kdWxlO1xuICAgIGNvbnN0IG1pZ3JhdGlvbnMgPSBhd2FpdCBtb2R1bGUuZ2V0TWlncmF0aW9ucyhvcHRpb25zKTtcbiAgICBhd2FpdCBtaWdyYXRpb25zLnJ1bk1pZ3JhdGlvbnMoKTtcbiAgfTtcblxuICBpZiAoc3RvcmFnZS5raW5kID09PSBcImxha2ViYXNlXCIgJiYgc3RvcmFnZS5wb29sKSB7XG4gICAgYXdhaXQgYWR2aXNvcnlMb2NrLndpdGhBZHZpc29yeUxvY2soc3RvcmFnZS5wb29sLCBNSUdSQVRJT05fTE9DSywgcnVuKTtcbiAgICByZXR1cm47XG4gIH1cbiAgYXdhaXQgZmlsZUxvY2sud2l0aEZpbGVMb2NrKE1JR1JBVElPTl9MT0NLLCBydW4pO1xufVxuXG5hc3luYyBmdW5jdGlvbiBvcGVuU3FsaXRlKHBhdGg6IHN0cmluZyk6IFByb21pc2U8QXV0aERhdGFiYXNlICYgeyBjbG9zZSgpOiB2b2lkIH0+IHtcbiAgaWYgKHByb2Nlc3MudmVyc2lvbnMuYnVuKSB7XG4gICAgY29uc3Qgc3BlY2lmaWVyID0gXCJidW46c3FsaXRlXCI7XG4gICAgY29uc3QgeyBEYXRhYmFzZSB9ID0gKGF3YWl0IGltcG9ydChzcGVjaWZpZXIpKSBhcyBCdW5TcWxpdGVNb2R1bGU7XG4gICAgY29uc3QgZGF0YWJhc2UgPSBuZXcgRGF0YWJhc2UocGF0aCwgeyBjcmVhdGU6IHRydWUsIHN0cmljdDogdHJ1ZSB9KTtcbiAgICBkYXRhYmFzZS5leGVjKFwiUFJBR01BIGpvdXJuYWxfbW9kZSA9IFdBTDsgUFJBR01BIGZvcmVpZ25fa2V5cyA9IE9OXCIpO1xuICAgIHJldHVybiBkYXRhYmFzZTtcbiAgfVxuICBjb25zdCB7IERhdGFiYXNlU3luYyB9ID0gYXdhaXQgaW1wb3J0KFwibm9kZTpzcWxpdGVcIik7XG4gIGNvbnN0IGRhdGFiYXNlID0gbmV3IERhdGFiYXNlU3luYyhwYXRoKTtcbiAgZGF0YWJhc2UuZXhlYyhcIlBSQUdNQSBqb3VybmFsX21vZGUgPSBXQUw7IFBSQUdNQSBmb3JlaWduX2tleXMgPSBPTlwiKTtcbiAgcmV0dXJuIGRhdGFiYXNlO1xufVxuXG5mdW5jdGlvbiBtaWdyYXRpb25Nb2R1bGVVcmwoKTogc3RyaW5nIHtcbiAgY29uc3QgZW50cnkgPSBpbXBvcnQubWV0YS5yZXNvbHZlKFwiYmV0dGVyLWF1dGhcIik7XG4gIHJldHVybiBuZXcgVVJMKFwiLi9kYi9nZXQtbWlncmF0aW9uLm1qc1wiLCBlbnRyeSkuaHJlZjtcbn1cbiJdfQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/auth.ts","../src/storage.ts","../index.ts"],"version":"5.9.3"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dbx-tools/auth",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
6
|
+
"directory": "packages/js/node/auth"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "projen build",
|
|
10
|
+
"compile": "projen compile",
|
|
11
|
+
"default": "projen default",
|
|
12
|
+
"package": "projen package",
|
|
13
|
+
"post-compile": "projen post-compile",
|
|
14
|
+
"pre-compile": "projen pre-compile",
|
|
15
|
+
"prepack": "projen prepack",
|
|
16
|
+
"test": "projen test",
|
|
17
|
+
"watch": "projen watch",
|
|
18
|
+
"projen": "projen"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "^1.3.14",
|
|
22
|
+
"@types/node": "^24.6.0",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@better-auth/passkey": "^1.6.25",
|
|
27
|
+
"@dbx-tools/core": "0.6.91",
|
|
28
|
+
"@dbx-tools/postgres": "0.6.91",
|
|
29
|
+
"@dbx-tools/shared-auth": "0.6.91",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.91",
|
|
31
|
+
"better-auth": "^1.6.25",
|
|
32
|
+
"env-paths": "^4.0.0"
|
|
33
|
+
},
|
|
34
|
+
"main": "./lib/index.js",
|
|
35
|
+
"license": "UNLICENSED",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"main": "./lib/index.js",
|
|
39
|
+
"types": "./lib/index.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./lib/index.d.ts",
|
|
43
|
+
"default": "./lib/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"version": "0.6.91",
|
|
49
|
+
"types": "./lib/index.d.ts",
|
|
50
|
+
"type": "module",
|
|
51
|
+
"exports": {
|
|
52
|
+
".": {
|
|
53
|
+
"types": "./lib/index.d.ts",
|
|
54
|
+
"default": "./lib/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./package.json": "./package.json"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"index.ts",
|
|
60
|
+
"src",
|
|
61
|
+
"lib"
|
|
62
|
+
],
|
|
63
|
+
"dbxToolsConfig": {
|
|
64
|
+
"tags": [
|
|
65
|
+
"node"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
69
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth passwordless runtime with email OTP and passkeys.
|
|
3
|
+
*
|
|
4
|
+
* Callers provide authorization, delivery, storage, origin, and secret. Better
|
|
5
|
+
* Auth owns users, OTP verification records, sessions, rate limits, passkey
|
|
6
|
+
* credentials, and their native HTTP routes.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { passkey } from "@better-auth/passkey";
|
|
12
|
+
import {
|
|
13
|
+
authRequestSchema,
|
|
14
|
+
authVerifySchema,
|
|
15
|
+
type AuthStatus,
|
|
16
|
+
SESSION_COOKIE_NAME,
|
|
17
|
+
} from "@dbx-tools/shared-auth";
|
|
18
|
+
import { log } from "@dbx-tools/shared-core";
|
|
19
|
+
import { APIError, betterAuth, type BetterAuthOptions } from "better-auth";
|
|
20
|
+
import { emailOTP } from "better-auth/plugins";
|
|
21
|
+
|
|
22
|
+
import type { AuthStorage } from "./storage.ts";
|
|
23
|
+
import { migrateAuth } from "./storage.ts";
|
|
24
|
+
|
|
25
|
+
const logger = log.logger("auth");
|
|
26
|
+
|
|
27
|
+
export type AuthorizeIdentity = (email: string) => boolean | Promise<boolean>;
|
|
28
|
+
|
|
29
|
+
export interface AuthEmailOptions {
|
|
30
|
+
subject: string;
|
|
31
|
+
brandName: string;
|
|
32
|
+
message: string;
|
|
33
|
+
codeTtlSeconds: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PasswordlessAuthOptions {
|
|
37
|
+
storage: AuthStorage;
|
|
38
|
+
baseURL: string;
|
|
39
|
+
basePath?: string;
|
|
40
|
+
appName: string;
|
|
41
|
+
secret: string;
|
|
42
|
+
sessionCookieName?: string;
|
|
43
|
+
sessionTtlSeconds: number;
|
|
44
|
+
sessionCutoffMs?: number;
|
|
45
|
+
codeTtlSeconds: number;
|
|
46
|
+
maxAttempts: number;
|
|
47
|
+
authorizeIdentity: AuthorizeIdentity;
|
|
48
|
+
sendCode(email: string, code: string, options: AuthEmailOptions): Promise<void>;
|
|
49
|
+
subject?: string;
|
|
50
|
+
message?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PasswordlessAuthRuntime {
|
|
54
|
+
readonly basePath: string;
|
|
55
|
+
readonly passkeysEnabled: boolean;
|
|
56
|
+
handler(request: Request): Promise<Response>;
|
|
57
|
+
session(headers: Headers): Promise<string | undefined>;
|
|
58
|
+
status(headers: Headers): Promise<AuthStatus>;
|
|
59
|
+
close(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function createPasswordlessAuth(
|
|
63
|
+
config: PasswordlessAuthOptions,
|
|
64
|
+
): Promise<PasswordlessAuthRuntime> {
|
|
65
|
+
const origin = new URL(config.baseURL).origin;
|
|
66
|
+
const rpID = new URL(origin).hostname;
|
|
67
|
+
const basePath = config.basePath ?? "/api/auth";
|
|
68
|
+
const emailOptions: AuthEmailOptions = {
|
|
69
|
+
subject: config.subject ?? "Your verification code",
|
|
70
|
+
brandName: config.appName,
|
|
71
|
+
message: config.message ?? "Your verification code is:",
|
|
72
|
+
codeTtlSeconds: config.codeTtlSeconds,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const options = {
|
|
76
|
+
appName: config.appName,
|
|
77
|
+
baseURL: origin,
|
|
78
|
+
basePath,
|
|
79
|
+
database: config.storage.database,
|
|
80
|
+
secret: config.secret,
|
|
81
|
+
trustedOrigins: [origin],
|
|
82
|
+
session: {
|
|
83
|
+
expiresIn: config.sessionTtlSeconds,
|
|
84
|
+
updateAge: Math.min(24 * 60 * 60, config.sessionTtlSeconds),
|
|
85
|
+
},
|
|
86
|
+
verification: {
|
|
87
|
+
storeIdentifier: "hashed",
|
|
88
|
+
storeInDatabase: true,
|
|
89
|
+
},
|
|
90
|
+
rateLimit: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
window: 15 * 60,
|
|
93
|
+
max: 10,
|
|
94
|
+
customRules: {
|
|
95
|
+
"/email-otp/send-verification-otp": { window: 15 * 60, max: 5 },
|
|
96
|
+
"/sign-in/email-otp": { window: 15 * 60, max: 10 },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
advanced: {
|
|
100
|
+
useSecureCookies: origin.startsWith("https://"),
|
|
101
|
+
ipAddress: {
|
|
102
|
+
ipAddressHeaders: ["x-real-ip"],
|
|
103
|
+
disableIpTracking: false,
|
|
104
|
+
},
|
|
105
|
+
cookies: {
|
|
106
|
+
session_token: {
|
|
107
|
+
name: config.sessionCookieName ?? SESSION_COOKIE_NAME,
|
|
108
|
+
attributes: {
|
|
109
|
+
httpOnly: true,
|
|
110
|
+
sameSite: "lax",
|
|
111
|
+
secure: origin.startsWith("https://"),
|
|
112
|
+
path: "/",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
databaseHooks: {
|
|
118
|
+
user: {
|
|
119
|
+
create: {
|
|
120
|
+
before: async (user: { email: string }) => {
|
|
121
|
+
if (!(await config.authorizeIdentity(normalizeEmail(user.email)))) {
|
|
122
|
+
throw new APIError("FORBIDDEN", { message: "Identity is not authorized" });
|
|
123
|
+
}
|
|
124
|
+
return { data: user };
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
plugins: [
|
|
130
|
+
emailOTP({
|
|
131
|
+
otpLength: 6,
|
|
132
|
+
expiresIn: config.codeTtlSeconds,
|
|
133
|
+
allowedAttempts: config.maxAttempts,
|
|
134
|
+
storeOTP: "hashed",
|
|
135
|
+
async sendVerificationOTP({ email, otp, type }) {
|
|
136
|
+
if (type !== "sign-in") return;
|
|
137
|
+
const address = normalizeEmail(email);
|
|
138
|
+
if (!(await config.authorizeIdentity(address))) return;
|
|
139
|
+
void config.sendCode(address, otp, emailOptions).catch((error: unknown) => {
|
|
140
|
+
logger.error("verification email failed", { email: address, error });
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
passkey({
|
|
145
|
+
rpID,
|
|
146
|
+
rpName: config.appName,
|
|
147
|
+
origin,
|
|
148
|
+
authenticatorSelection: {
|
|
149
|
+
residentKey: "preferred",
|
|
150
|
+
userVerification: "preferred",
|
|
151
|
+
},
|
|
152
|
+
registration: { requireSession: true },
|
|
153
|
+
}),
|
|
154
|
+
] as const,
|
|
155
|
+
} satisfies BetterAuthOptions;
|
|
156
|
+
|
|
157
|
+
await migrateAuth(options, config.storage);
|
|
158
|
+
const auth = betterAuth(options);
|
|
159
|
+
|
|
160
|
+
const session = async (headers: Headers): Promise<string | undefined> => {
|
|
161
|
+
const current = await auth.api.getSession({ headers });
|
|
162
|
+
const email = normalizeEmail(current?.user.email);
|
|
163
|
+
const createdAt = current?.session.createdAt;
|
|
164
|
+
const createdAtMs = createdAt ? new Date(createdAt).getTime() : Number.NaN;
|
|
165
|
+
if (config.sessionCutoffMs && !(createdAtMs >= config.sessionCutoffMs)) return undefined;
|
|
166
|
+
if (!email || !(await config.authorizeIdentity(email))) return undefined;
|
|
167
|
+
return email;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleCompatibilityRoute = async (request: Request): Promise<Response | undefined> => {
|
|
171
|
+
const path = new URL(request.url).pathname;
|
|
172
|
+
if (path === `${basePath}/status`) {
|
|
173
|
+
const email = await session(request.headers);
|
|
174
|
+
return jsonResponse({
|
|
175
|
+
authenticated: Boolean(email),
|
|
176
|
+
...(email ? { email } : {}),
|
|
177
|
+
enabled: true,
|
|
178
|
+
passkeysEnabled: true,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (path === `${basePath}/request` && request.method === "POST") {
|
|
182
|
+
const parsed = authRequestSchema.safeParse(await requestJson(request));
|
|
183
|
+
if (parsed.success) {
|
|
184
|
+
await auth.api
|
|
185
|
+
.sendVerificationOTP({
|
|
186
|
+
body: { email: normalizeEmail(parsed.data.email), type: "sign-in" },
|
|
187
|
+
headers: request.headers,
|
|
188
|
+
})
|
|
189
|
+
.catch(() => undefined);
|
|
190
|
+
}
|
|
191
|
+
return jsonResponse({ ok: true });
|
|
192
|
+
}
|
|
193
|
+
if (path === `${basePath}/verify` && request.method === "POST") {
|
|
194
|
+
const parsed = authVerifySchema.safeParse(await requestJson(request));
|
|
195
|
+
if (!parsed.success) return jsonResponse({ ok: false });
|
|
196
|
+
try {
|
|
197
|
+
const response = await auth.api.signInEmailOTP({
|
|
198
|
+
body: {
|
|
199
|
+
email: normalizeEmail(parsed.data.email),
|
|
200
|
+
otp: parsed.data.code,
|
|
201
|
+
name: displayName(parsed.data.email),
|
|
202
|
+
},
|
|
203
|
+
headers: request.headers,
|
|
204
|
+
asResponse: true,
|
|
205
|
+
});
|
|
206
|
+
return compatibilityResponse(response, { ok: response.ok });
|
|
207
|
+
} catch {
|
|
208
|
+
return jsonResponse({ ok: false });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (path === `${basePath}/logout` && request.method === "POST") {
|
|
212
|
+
const response = await auth.api.signOut({
|
|
213
|
+
headers: request.headers,
|
|
214
|
+
asResponse: true,
|
|
215
|
+
});
|
|
216
|
+
return compatibilityResponse(response, { ok: response.ok });
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
basePath,
|
|
223
|
+
passkeysEnabled: true,
|
|
224
|
+
handler: async (request) => (await handleCompatibilityRoute(request)) ?? auth.handler(request),
|
|
225
|
+
session,
|
|
226
|
+
status: async (headers) => {
|
|
227
|
+
const email = await session(headers);
|
|
228
|
+
return {
|
|
229
|
+
authenticated: Boolean(email),
|
|
230
|
+
...(email ? { email } : {}),
|
|
231
|
+
enabled: true,
|
|
232
|
+
passkeysEnabled: true,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
close: () => config.storage.close(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeEmail(email: string | undefined): string {
|
|
240
|
+
return email?.trim().toLowerCase() ?? "";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function displayName(email: string): string {
|
|
244
|
+
return normalizeEmail(email).split("@")[0] || "User";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function requestJson(request: Request): Promise<unknown> {
|
|
248
|
+
try {
|
|
249
|
+
return await request.json();
|
|
250
|
+
} catch {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
256
|
+
return new Response(JSON.stringify(body), {
|
|
257
|
+
status,
|
|
258
|
+
headers: { "content-type": "application/json" },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function compatibilityResponse(response: Response, body: unknown): Response {
|
|
263
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
264
|
+
for (const cookie of response.headers.getSetCookie()) headers.append("set-cookie", cookie);
|
|
265
|
+
return new Response(JSON.stringify(body), { status: 200, headers });
|
|
266
|
+
}
|
package/src/storage.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Auth database selection and migration locking.
|
|
3
|
+
*
|
|
4
|
+
* Callers pass a native AppKit Lakebase pool when available. Otherwise auth
|
|
5
|
+
* uses SQLite in the operating system's application-data directory.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdirSync } from "node:fs";
|
|
11
|
+
import { dirname, resolve } from "node:path";
|
|
12
|
+
import { fileLock } from "@dbx-tools/core";
|
|
13
|
+
import { advisoryLock, type PgPoolLike } from "@dbx-tools/postgres";
|
|
14
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
15
|
+
import envPaths from "env-paths";
|
|
16
|
+
|
|
17
|
+
export type AuthStorageMode = "auto" | "lakebase" | "sqlite";
|
|
18
|
+
|
|
19
|
+
export interface AuthStorageConfig {
|
|
20
|
+
storage?: AuthStorageMode;
|
|
21
|
+
sqlitePath?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResolvedAuthStorageConfig {
|
|
25
|
+
mode: AuthStorageMode;
|
|
26
|
+
sqlitePath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AuthDatabase = NonNullable<BetterAuthOptions["database"]>;
|
|
30
|
+
|
|
31
|
+
export interface AuthStorage {
|
|
32
|
+
kind: "lakebase" | "sqlite";
|
|
33
|
+
database: AuthDatabase;
|
|
34
|
+
pool?: PgPoolLike;
|
|
35
|
+
path?: string;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type MigrationModule = {
|
|
40
|
+
getMigrations(options: BetterAuthOptions): Promise<{
|
|
41
|
+
runMigrations(): Promise<void>;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
interface SqliteDatabase {
|
|
46
|
+
exec(sql: string): unknown;
|
|
47
|
+
close(): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface BunSqliteModule {
|
|
51
|
+
Database: new (path: string, options?: { create?: boolean; strict?: boolean }) => SqliteDatabase;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MIGRATION_LOCK = ["auth", "better-auth", "migrations"] as const;
|
|
55
|
+
|
|
56
|
+
export function resolveAuthStorageConfig(
|
|
57
|
+
config: AuthStorageConfig = {},
|
|
58
|
+
): ResolvedAuthStorageConfig {
|
|
59
|
+
const mode = config.storage ?? "auto";
|
|
60
|
+
if (mode !== "auto" && mode !== "lakebase" && mode !== "sqlite") {
|
|
61
|
+
throw new TypeError('auth storage must be "auto", "lakebase", or "sqlite"');
|
|
62
|
+
}
|
|
63
|
+
const dataDirectory = envPaths("dbx-tools", { suffix: "" }).data;
|
|
64
|
+
return {
|
|
65
|
+
mode,
|
|
66
|
+
sqlitePath: resolve(config.sqlitePath ?? resolve(dataDirectory, "auth", "auth.sqlite")),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function shouldUseLakebase(config: AuthStorageConfig = {}): boolean {
|
|
71
|
+
const resolved = resolveAuthStorageConfig(config);
|
|
72
|
+
if (resolved.mode === "lakebase") return true;
|
|
73
|
+
if (resolved.mode === "sqlite") return false;
|
|
74
|
+
return Boolean(process.env.LAKEBASE_ENDPOINT ?? process.env.PGHOST);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function createAuthStorage(
|
|
78
|
+
config: AuthStorageConfig,
|
|
79
|
+
pool?: PgPoolLike,
|
|
80
|
+
): Promise<AuthStorage> {
|
|
81
|
+
const resolved = resolveAuthStorageConfig(config);
|
|
82
|
+
if (pool && resolved.mode !== "sqlite") {
|
|
83
|
+
return {
|
|
84
|
+
kind: "lakebase",
|
|
85
|
+
database: pool,
|
|
86
|
+
pool,
|
|
87
|
+
close: async () => undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (resolved.mode === "lakebase") {
|
|
91
|
+
throw new Error("auth storage is lakebase but no Lakebase pool was supplied");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
mkdirSync(dirname(resolved.sqlitePath), { recursive: true });
|
|
95
|
+
const database = await openSqlite(resolved.sqlitePath);
|
|
96
|
+
return {
|
|
97
|
+
kind: "sqlite",
|
|
98
|
+
database,
|
|
99
|
+
path: resolved.sqlitePath,
|
|
100
|
+
close: async () => {
|
|
101
|
+
database.close();
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function migrateAuth(options: BetterAuthOptions, storage: AuthStorage): Promise<void> {
|
|
107
|
+
const run = async (): Promise<void> => {
|
|
108
|
+
const module = (await import(migrationModuleUrl())) as MigrationModule;
|
|
109
|
+
const migrations = await module.getMigrations(options);
|
|
110
|
+
await migrations.runMigrations();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (storage.kind === "lakebase" && storage.pool) {
|
|
114
|
+
await advisoryLock.withAdvisoryLock(storage.pool, MIGRATION_LOCK, run);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await fileLock.withFileLock(MIGRATION_LOCK, run);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function openSqlite(path: string): Promise<AuthDatabase & { close(): void }> {
|
|
121
|
+
if (process.versions.bun) {
|
|
122
|
+
const specifier = "bun:sqlite";
|
|
123
|
+
const { Database } = (await import(specifier)) as BunSqliteModule;
|
|
124
|
+
const database = new Database(path, { create: true, strict: true });
|
|
125
|
+
database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON");
|
|
126
|
+
return database;
|
|
127
|
+
}
|
|
128
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
129
|
+
const database = new DatabaseSync(path);
|
|
130
|
+
database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON");
|
|
131
|
+
return database;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function migrationModuleUrl(): string {
|
|
135
|
+
const entry = import.meta.resolve("better-auth");
|
|
136
|
+
return new URL("./db/get-migration.mjs", entry).href;
|
|
137
|
+
}
|