@protobase/server 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-store.d.ts +3 -0
- package/dist/auth-store.js +3 -0
- package/dist/better-auth/auth-schema.d.ts +7 -8
- package/dist/better-auth/auth-schema.js +40 -42
- package/dist/better-auth/auth-tables.d.ts +1 -1
- package/dist/better-auth/auth-tables.js +11 -9
- package/dist/better-auth/authenticator.d.ts +1 -1
- package/dist/better-auth/create-auth.d.ts +20 -12
- package/dist/better-auth/create-auth.js +6 -1
- package/dist/better-auth/mcp/mcp-plugins.d.ts +1 -1
- package/dist/better-auth/store/adoption.js +87 -0
- package/dist/better-auth/store/ddl.js +18 -0
- package/dist/better-auth/store/drift.d.ts +9 -0
- package/dist/better-auth/store/drift.js +18 -0
- package/dist/better-auth/store/migration-sql.js +15 -0
- package/dist/better-auth/store/migrations/001-core.js +80 -0
- package/dist/better-auth/store/migrations/002-two-factor.js +25 -0
- package/dist/better-auth/store/migrations/003-passkeys.js +29 -0
- package/dist/better-auth/store/migrations/004-sign-in-policy.js +22 -0
- package/dist/better-auth/store/migrations/005-staff-sign-in.js +25 -0
- package/dist/better-auth/store/migrations/006-organizations.js +50 -0
- package/dist/better-auth/store/migrations/007-mcp.js +150 -0
- package/dist/better-auth/store/migrations/index.js +22 -0
- package/dist/better-auth/store/migrations/migration.d.ts +3 -0
- package/dist/better-auth/store/migrations/migration.js +14 -0
- package/dist/better-auth/store/migrations-table.d.ts +7 -0
- package/dist/better-auth/store/migrations-table.js +23 -0
- package/dist/better-auth/store/resources-outside.d.ts +7 -0
- package/dist/better-auth/store/resources-outside.js +20 -0
- package/dist/better-auth/store/store-db.js +13 -0
- package/dist/better-auth/store/store-names.d.ts +2 -0
- package/dist/better-auth/store/store-names.js +14 -0
- package/dist/better-auth/store/table-names.d.ts +7 -0
- package/dist/better-auth/store/table-names.js +10 -0
- package/dist/better-auth/users.d.ts +1 -1
- package/dist/comments/comment-users.d.ts +1 -1
- package/dist/config-exports.d.ts +2 -0
- package/dist/config-exports.js +2 -0
- package/dist/create-admin.d.ts +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.js +4 -2
- package/dist/jobs/job-types.d.ts +1 -1
- package/dist/notifications/device-store-of.d.ts +1 -1
- package/dist/notifications/history-store-of.d.ts +1 -1
- package/dist/notifications/schema.d.ts +1 -1
- package/dist/notifications/schema.js +1 -1
- package/dist/package.js +1 -1
- package/dist/project-config.d.ts +1 -1
- package/dist/vapid.d.ts +3 -0
- package/dist/vapid.js +3 -0
- package/dist/web-app/app-icons.d.ts +3 -24
- package/dist/web-app/app-icons.js +1 -25
- package/dist/web-app/web-manifest-route.d.ts +1 -0
- package/package.json +8 -5
- package/dist/better-auth/mcp/mcp-tables.js +0 -32
- package/dist/better-auth/snake-case-migration.js +0 -122
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { AdminAuth } from "./create-auth.js";
|
|
2
2
|
//#region src/better-auth/auth-schema.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* The SQL that brings the admin store
|
|
5
|
-
* for a store
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* column without a default on a table with rows, is an error.
|
|
4
|
+
* The SQL that brings the admin store to this version of Protobase: the migrations it has not applied, each followed by
|
|
5
|
+
* its row in `auth_migrations`; for a new store, that table first. A store an earlier Protobase made without the record
|
|
6
|
+
* is adopted: the migrations it matches are recorded as applied (`planAdoption`), the others follow. `undefined` when
|
|
7
|
+
* the store is current. The SQL never drops anything.
|
|
9
8
|
*/
|
|
10
9
|
export declare const authSchemaMigration: (auth: AdminAuth) => Promise<string | undefined>;
|
|
11
10
|
/**
|
|
12
|
-
* Refuses to go on while the admin store
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Refuses to go on while the admin store has not applied every migration of this version of Protobase, from its
|
|
12
|
+
* `auth_migrations` alone, since every sign-in would fail. The server never changes the schema itself: the project's
|
|
13
|
+
* migrations do, before the new version serves.
|
|
15
14
|
*/
|
|
16
15
|
export declare const checkAuthSchema: (auth: AdminAuth) => Promise<void>;
|
|
17
16
|
//#endregion
|
|
@@ -1,54 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { storeNames } from "./store/store-names.js";
|
|
2
|
+
import { latestStoreVersion, storeMigrations } from "./store/migrations/index.js";
|
|
3
|
+
import { appliedVersions, migrationsTableName } from "./store/migrations-table.js";
|
|
4
|
+
import { migrationSql, migrationsTableSql } from "./store/migration-sql.js";
|
|
5
|
+
import { adoptionSql, planAdoption, storeHasTables } from "./store/adoption.js";
|
|
6
|
+
import { storeDb } from "./store/store-db.js";
|
|
5
7
|
//#region src/better-auth/auth-schema.ts
|
|
6
8
|
/** The command that writes the pending changes as a migration file of the project. */
|
|
7
9
|
const authMigrationCommand = "protobase auth migration";
|
|
10
|
+
const refuseNewer = (applied) => {
|
|
11
|
+
const newest = applied.at(-1);
|
|
12
|
+
if (newest !== void 0 && newest > latestStoreVersion) throw new Error(`The auth store is at migration ${newest}, newer than the ${latestStoreVersion} this version of Protobase knows; upgrade Protobase`);
|
|
13
|
+
};
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Better Auth adds `email_verified` to a `user` table that exists only when the table still has the camelCase names
|
|
15
|
-
* of Protobase 0.6, whose `emailVerified` it does not know: such a store is renamed, never given new columns beside
|
|
16
|
-
* the old ones.
|
|
17
|
-
*/
|
|
18
|
-
const hasCamelCaseNames = ({ toBeAdded }) => toBeAdded.some(({ table, fields }) => table === "user" && "email_verified" in fields);
|
|
19
|
-
/**
|
|
20
|
-
* The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
|
|
21
|
-
* for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
|
|
22
|
-
* new tables, columns and indexes, each skipped where it exists, and never a drop but of MCP's tables once MCP is off
|
|
23
|
-
* (`dropMcpTables`). `undefined` when the store is current. A change Better Auth cannot make safely, such as a required
|
|
24
|
-
* column without a default on a table with rows, is an error.
|
|
15
|
+
* The SQL that brings the admin store to this version of Protobase: the migrations it has not applied, each followed by
|
|
16
|
+
* its row in `auth_migrations`; for a new store, that table first. A store an earlier Protobase made without the record
|
|
17
|
+
* is adopted: the migrations it matches are recorded as applied (`planAdoption`), the others follow. `undefined` when
|
|
18
|
+
* the store is current. The SQL never drops anything.
|
|
25
19
|
*/
|
|
26
20
|
const authSchemaMigration = async (auth) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
const names = storeNames(auth);
|
|
22
|
+
const db = storeDb(auth);
|
|
23
|
+
const applied = await appliedVersions(db, names);
|
|
24
|
+
if (applied !== void 0) {
|
|
25
|
+
refuseNewer(applied);
|
|
26
|
+
const pending = storeMigrations.filter((migration) => !applied.includes(migration.version));
|
|
27
|
+
return pending.length === 0 ? void 0 : pending.map((migration) => migrationSql(names, migration)).join("\n\n");
|
|
28
|
+
}
|
|
29
|
+
if (!await storeHasTables(db, names)) return [migrationsTableSql(names), ...storeMigrations.map((migration) => migrationSql(names, migration))].join("\n\n");
|
|
30
|
+
const { adopted, pending } = await planAdoption(db, names);
|
|
31
|
+
return [
|
|
32
|
+
migrationsTableSql(names),
|
|
33
|
+
adoptionSql(names, adopted),
|
|
34
|
+
...pending.map((migration) => migrationSql(names, migration))
|
|
35
|
+
].join("\n\n");
|
|
33
36
|
};
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
const missingTablesAndColumns = (auth, { toBeCreated, toBeAdded }) => [...toBeCreated.map(({ table }) => table).filter((table) => auth.mcp || !mcpTableNames.includes(table)), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
|
|
39
|
-
/**
|
|
40
|
-
* Refuses to go on while the admin store lacks a table or column Better Auth needs, still has the camelCase names of
|
|
41
|
-
* Protobase 0.6, since every sign-in would fail, or still has MCP's tables with MCP off, since their grants would come
|
|
42
|
-
* back with MCP. The server never changes the schema itself: the project's migrations do, before the new version serves.
|
|
38
|
+
* Refuses to go on while the admin store has not applied every migration of this version of Protobase, from its
|
|
39
|
+
* `auth_migrations` alone, since every sign-in would fail. The server never changes the schema itself: the project's
|
|
40
|
+
* migrations do, before the new version serves.
|
|
43
41
|
*/
|
|
44
42
|
const checkAuthSchema = async (auth) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
throw new Error(`The auth schema
|
|
43
|
+
const applied = await appliedVersions(storeDb(auth), storeNames(auth));
|
|
44
|
+
const write = `Write it as a migration with \`${authMigrationCommand}\`, apply it with the project's migrations, then start again.`;
|
|
45
|
+
if (applied === void 0) throw new Error(`The auth store has no ${migrationsTableName} table: it is new, or an earlier Protobase made it. ${write}`);
|
|
46
|
+
refuseNewer(applied);
|
|
47
|
+
const missing = storeMigrations.filter((migration) => !applied.includes(migration.version));
|
|
48
|
+
if (missing.length === 0) return;
|
|
49
|
+
throw new Error(`The auth schema is behind this version of Protobase (missing migrations ${missing.map(({ version, name }) => `${version} ${name}`).join(", ")}). ${write}`);
|
|
52
50
|
};
|
|
53
51
|
//#endregion
|
|
54
|
-
export { authMigrationCommand, authSchemaMigration, checkAuthSchema
|
|
52
|
+
export { authMigrationCommand, authSchemaMigration, checkAuthSchema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AdminAuth } from "./create-auth.js";
|
|
2
2
|
//#region src/better-auth/auth-tables.d.ts
|
|
3
|
-
/** The admin store's tables
|
|
3
|
+
/** The admin store's tables, which the assistant's queries and the app's resources must never read: the user table and `storeTableNames`. */
|
|
4
4
|
export declare const authStoreTables: (auth: AdminAuth) => {
|
|
5
5
|
schema: string | undefined;
|
|
6
6
|
name: string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { storeNames } from "./store/store-names.js";
|
|
2
|
+
import { storeTableNames } from "./store/table-names.js";
|
|
2
3
|
//#region src/better-auth/auth-tables.ts
|
|
3
|
-
/** The
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}));
|
|
4
|
+
/** The admin store's tables, which the assistant's queries and the app's resources must never read: the user table and `storeTableNames`. */
|
|
5
|
+
const authStoreTables = (auth) => {
|
|
6
|
+
const { schema, users } = storeNames(auth);
|
|
7
|
+
return [users.modelName, ...storeTableNames].map((name) => ({
|
|
8
|
+
schema,
|
|
9
|
+
name
|
|
10
|
+
}));
|
|
11
|
+
};
|
|
10
12
|
//#endregion
|
|
11
|
-
export { authStoreTables
|
|
13
|
+
export { authStoreTables };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AdminAuth } from "./create-auth.js";
|
|
2
1
|
import { Authenticator, TenantValue } from "../types.js";
|
|
3
2
|
import { JwtVerifyOptions } from "../auth/jwt.js";
|
|
4
3
|
import { TrustedApp } from "./app-tokens.js";
|
|
5
4
|
import { JwksCacheOptions } from "./jwks-cache.js";
|
|
5
|
+
import { AdminAuth } from "./create-auth.js";
|
|
6
6
|
//#region src/better-auth/authenticator.d.ts
|
|
7
7
|
export type BetterAuthAuthenticatorOptions = {
|
|
8
8
|
/** The Better Auth instance of this process: keys are read from it directly, no HTTP round trip. */
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UserColumn, UserTableOptions } from "./user-table.js";
|
|
2
|
+
import { Mailer } from "../mail/smtp-mailer.js";
|
|
3
|
+
import { SignInPolicy } from "./sign-in-policy.js";
|
|
4
|
+
import { OperatorProvider } from "./operator-provider.js";
|
|
5
|
+
import { PlatformSignIn } from "./platform-sign-in.js";
|
|
2
6
|
import { RoleDefinitions, RolesInput } from "./organizations/role-definitions.js";
|
|
7
|
+
import { OrganizationRole } from "./organizations/store.js";
|
|
8
|
+
import { OrganizationsOptions, ResolvedOrganizations } from "./organizations/options.js";
|
|
9
|
+
import { StaffSignIn } from "./staff-log.js";
|
|
10
|
+
import "../index.js";
|
|
3
11
|
import { ConnectedApp } from "./mcp/connected-apps.js";
|
|
4
12
|
import { ReplacingContext } from "./end-replaced-session.js";
|
|
5
13
|
import { UserDeletionListener } from "./user-deletion.js";
|
|
6
14
|
import "./auth-base-path.js";
|
|
7
|
-
import { Mailer } from "../mail/smtp-mailer.js";
|
|
8
|
-
import { OperatorProvider } from "./operator-provider.js";
|
|
9
|
-
import { OrganizationRole } from "./organizations/store.js";
|
|
10
|
-
import { SignInPolicy } from "./sign-in-policy.js";
|
|
11
15
|
import { McpAuthOptions } from "./mcp/mcp-plugins.js";
|
|
12
|
-
import { OrganizationsOptions, ResolvedOrganizations } from "./organizations/options.js";
|
|
13
|
-
import { PlatformSignIn } from "./platform-sign-in.js";
|
|
14
16
|
import { SsoOptions } from "./saml-sign-in.js";
|
|
15
17
|
import { signUpPaths } from "./sign-up/sign-up-plugin.js";
|
|
16
18
|
import { PasswordHash } from "./password-hashes.js";
|
|
17
|
-
import { UserTableOptions } from "./user-table.js";
|
|
18
|
-
import "../index.js";
|
|
19
19
|
import { BetterAuthOptions } from "better-auth";
|
|
20
20
|
import { JwtOptions } from "better-auth/plugins";
|
|
21
21
|
//#region src/better-auth/create-auth.d.ts
|
|
@@ -8752,9 +8752,6 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
8752
8752
|
};
|
|
8753
8753
|
};
|
|
8754
8754
|
advanced: {
|
|
8755
|
-
database?: {
|
|
8756
|
-
generateId: "uuid";
|
|
8757
|
-
} | undefined;
|
|
8758
8755
|
useSecureCookies: boolean;
|
|
8759
8756
|
cookiePrefix: string;
|
|
8760
8757
|
defaultCookieAttributes: {
|
|
@@ -8764,6 +8761,10 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
8764
8761
|
backgroundTasks: {
|
|
8765
8762
|
handler: () => void;
|
|
8766
8763
|
};
|
|
8764
|
+
database: {
|
|
8765
|
+
generateId?: "uuid" | undefined;
|
|
8766
|
+
validateSchema: false;
|
|
8767
|
+
};
|
|
8767
8768
|
};
|
|
8768
8769
|
socialProviders?: import("better-auth").SocialProviders | undefined;
|
|
8769
8770
|
appName: string;
|
|
@@ -8839,6 +8840,13 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
8839
8840
|
signUpRole: string | undefined;
|
|
8840
8841
|
/** The app keeps each user's metadata, in the column `users.metadata` names. */
|
|
8841
8842
|
userMetadata: boolean;
|
|
8843
|
+
/** The user table's name and columns, as `users` names them, for the store's migrations. */
|
|
8844
|
+
userTable: {
|
|
8845
|
+
modelName: string;
|
|
8846
|
+
column: (name: UserColumn) => string;
|
|
8847
|
+
fieldNames: Record<string, string>;
|
|
8848
|
+
metadata: string | undefined;
|
|
8849
|
+
};
|
|
8842
8850
|
socialProviders: string[];
|
|
8843
8851
|
/** The app's name it signs in and mails as: `PROTOBASE_APP_NAME`, else `appName`, else `Protobase`. */
|
|
8844
8852
|
appName: string;
|
|
@@ -306,7 +306,10 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
306
306
|
secure
|
|
307
307
|
},
|
|
308
308
|
backgroundTasks: { handler: () => {} },
|
|
309
|
-
|
|
309
|
+
database: {
|
|
310
|
+
validateSchema: false,
|
|
311
|
+
...options.generateUserId === "uuid" && { generateId: "uuid" }
|
|
312
|
+
}
|
|
310
313
|
}
|
|
311
314
|
};
|
|
312
315
|
};
|
|
@@ -402,6 +405,8 @@ const createAuth = (options) => {
|
|
|
402
405
|
signUpRole: mailer ? signUpRole : void 0,
|
|
403
406
|
/** The app keeps each user's metadata, in the column `users.metadata` names. */
|
|
404
407
|
userMetadata: Boolean(options.users?.metadata),
|
|
408
|
+
/** The user table's name and columns, as `users` names them, for the store's migrations. */
|
|
409
|
+
userTable: userTable(options.users),
|
|
405
410
|
socialProviders,
|
|
406
411
|
/** The app's name it signs in and mails as: `PROTOBASE_APP_NAME`, else `appName`, else `Protobase`. */
|
|
407
412
|
appName: appName ?? "Protobase",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ddl } from "./ddl.js";
|
|
2
|
+
import { migrationNames } from "./migrations/migration.js";
|
|
3
|
+
import { storeMigrations } from "./migrations/index.js";
|
|
4
|
+
import { recordMigrations } from "./migrations-table.js";
|
|
5
|
+
import { statementsSql } from "./migration-sql.js";
|
|
6
|
+
import { sql } from "kysely";
|
|
7
|
+
//#region src/better-auth/store/adoption.ts
|
|
8
|
+
const key = ({ table, column }) => `${table}.${column}`;
|
|
9
|
+
const columnsIn = async (db, schema) => (await sql`select table_name as "table", column_name as "column", udt_name as "type" from information_schema.columns
|
|
10
|
+
where table_schema = ${schema === void 0 ? sql`current_schema()` : schema} order by table_name, ordinal_position`.execute(db)).rows;
|
|
11
|
+
const scratchSchema = "protobase_auth_adoption";
|
|
12
|
+
/** For each migration, the columns it makes: every one runs in a scratch schema, in a transaction that is rolled back. */
|
|
13
|
+
const madeColumns = async (db, names) => {
|
|
14
|
+
const tx = await db.startTransaction().execute();
|
|
15
|
+
const run = async () => {
|
|
16
|
+
await sql`create schema ${sql.id(scratchSchema)}`.execute(tx);
|
|
17
|
+
const scratch = migrationNames({
|
|
18
|
+
...names,
|
|
19
|
+
schema: scratchSchema
|
|
20
|
+
});
|
|
21
|
+
const made = [];
|
|
22
|
+
let before = [];
|
|
23
|
+
for (const migration of storeMigrations) {
|
|
24
|
+
for (const statement of migration.statements(scratch)) await statement.execute(tx);
|
|
25
|
+
const after = await columnsIn(tx, scratchSchema);
|
|
26
|
+
made.push(after.filter((column) => !before.some((earlier) => key(earlier) === key(column))));
|
|
27
|
+
before = after;
|
|
28
|
+
}
|
|
29
|
+
return made;
|
|
30
|
+
};
|
|
31
|
+
return run().finally(() => tx.rollback().execute());
|
|
32
|
+
};
|
|
33
|
+
/** Whether the store has any table of the migrations besides the user table, which may be another system's. */
|
|
34
|
+
const storeHasTables = async (db, names) => {
|
|
35
|
+
const tables = new Set((await columnsIn(db, names.schema)).map((column) => column.table));
|
|
36
|
+
return storeMigrations.some((migration) => migration.tables.some((table) => tables.has(table)));
|
|
37
|
+
};
|
|
38
|
+
/** Which migrations the store matches and which it still needs; a migration it matches only partly is an error. */
|
|
39
|
+
const planAdoption = async (db, names) => {
|
|
40
|
+
const made = await madeColumns(db, names);
|
|
41
|
+
const found = new Map((await columnsIn(db, names.schema)).map((column) => [key(column), column.type]));
|
|
42
|
+
const adoption = {
|
|
43
|
+
adopted: [],
|
|
44
|
+
pending: []
|
|
45
|
+
};
|
|
46
|
+
const problems = [];
|
|
47
|
+
storeMigrations.forEach((migration, i) => {
|
|
48
|
+
const columns = made[i];
|
|
49
|
+
const there = columns.filter((column) => found.has(key(column)));
|
|
50
|
+
if (there.length === 0) return void adoption.pending.push(migration);
|
|
51
|
+
const missing = columns.filter((column) => !found.has(key(column))).map((column) => `lacks ${key(column)}`);
|
|
52
|
+
const otherType = there.filter((column) => found.get(key(column)) !== column.type).map((column) => `has ${key(column)} as ${found.get(key(column))}, not ${column.type}`);
|
|
53
|
+
if (missing.length === 0 && otherType.length === 0) return void adoption.adopted.push({
|
|
54
|
+
migration,
|
|
55
|
+
columns
|
|
56
|
+
});
|
|
57
|
+
problems.push(`${migration.version} ${migration.name} ${[...missing, ...otherType].join(", ")}`);
|
|
58
|
+
});
|
|
59
|
+
if (problems.length > 0) throw new Error(`The auth store an earlier Protobase made matches none of its migrations' versions: ${problems.join("; ")}. Bring it to the schema of Protobase 0.47 first, with that version's \`protobase auth migration\`, then run this again.`);
|
|
60
|
+
return adoption;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The SQL that records the adopted migrations as applied, once it finds every column they make with its type; it
|
|
64
|
+
* stops the migration, naming what it lacks, on a database that differs from the one it was written from.
|
|
65
|
+
*/
|
|
66
|
+
const adoptionSql = (names, adopted) => {
|
|
67
|
+
const expected = adopted.flatMap(({ columns }) => columns).map(({ table, column, type }) => ddl`(${sql.lit(table)}, ${sql.lit(column)}, ${sql.lit(type)})`);
|
|
68
|
+
const schema = names.schema === void 0 ? sql`current_schema()` : sql.lit(names.schema);
|
|
69
|
+
const check = ddl`do $adopt$
|
|
70
|
+
declare
|
|
71
|
+
missing text;
|
|
72
|
+
begin
|
|
73
|
+
select string_agg(expected.table_name || '.' || expected.column_name, ', ' order by expected.table_name, expected.column_name) into missing
|
|
74
|
+
from (values ${sql.join(expected, sql`,\n `)}) as expected (table_name, column_name, udt_name)
|
|
75
|
+
where not exists (
|
|
76
|
+
select from information_schema.columns found
|
|
77
|
+
where found.table_schema = ${schema} and found.table_name = expected.table_name and found.column_name = expected.column_name and found.udt_name = expected.udt_name
|
|
78
|
+
);
|
|
79
|
+
if missing is not null then
|
|
80
|
+
raise exception 'The auth store does not match the migrations this file adopts: it lacks %, or has them as other types', missing;
|
|
81
|
+
end if;
|
|
82
|
+
end
|
|
83
|
+
$adopt$`;
|
|
84
|
+
return `-- The auth store an earlier Protobase made, without a record of its migrations, adopted: ${adopted.map(({ migration }) => `${migration.version} ${migration.name}`).join(", ")}\n-- recorded as applied, once every table and column they make is there.\n${statementsSql([check, recordMigrations(names, adopted.map(({ migration }) => migration))])}`;
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { adoptionSql, planAdoption, storeHasTables };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DummyDriver, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, sql } from "kysely";
|
|
2
|
+
//#region src/better-auth/store/ddl.ts
|
|
3
|
+
/** A fragment of SQL whose strings are identifiers: `ddl\`create table ${'user'} (...)\`` is `create table "user" (...)`. */
|
|
4
|
+
const ddl = (strings, ...parts) => sql(strings, ...parts.map((part) => typeof part === "string" ? sql.id(part) : part));
|
|
5
|
+
const compiler = new Kysely({ dialect: {
|
|
6
|
+
createAdapter: () => new PostgresAdapter(),
|
|
7
|
+
createDriver: () => new DummyDriver(),
|
|
8
|
+
createIntrospector: (db) => new PostgresIntrospector(db),
|
|
9
|
+
createQueryCompiler: () => new PostgresQueryCompiler()
|
|
10
|
+
} });
|
|
11
|
+
/** The text of a fragment. One that holds a parameter, a value given as neither identifier nor `sql.lit`, is an error. */
|
|
12
|
+
const ddlText = (fragment) => {
|
|
13
|
+
const { sql: text, parameters } = fragment.compile(compiler);
|
|
14
|
+
if (parameters.length > 0) throw new Error(`A migration statement has no parameters; give each value with sql.lit: ${text}`);
|
|
15
|
+
return text;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ddl, ddlText };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AdminAuth } from "../create-auth.js";
|
|
2
|
+
//#region src/better-auth/store/drift.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* What Better Auth, with the features the app turns on, finds the store lacks or has otherwise: tables, `table.column`s,
|
|
5
|
+
* indexes and its other findings; none when the store matches. It compares the database's tables, so only
|
|
6
|
+
* `protobase dev` runs it, on the developer's machine: a deploy reads the record of migrations instead (`checkAuthSchema`).
|
|
7
|
+
*/
|
|
8
|
+
export declare const authSchemaDrift: (auth: AdminAuth) => Promise<string[]>;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getMigrations } from "better-auth/db/migration";
|
|
2
|
+
//#region src/better-auth/store/drift.ts
|
|
3
|
+
/**
|
|
4
|
+
* What Better Auth, with the features the app turns on, finds the store lacks or has otherwise: tables, `table.column`s,
|
|
5
|
+
* indexes and its other findings; none when the store matches. It compares the database's tables, so only
|
|
6
|
+
* `protobase dev` runs it, on the developer's machine: a deploy reads the record of migrations instead (`checkAuthSchema`).
|
|
7
|
+
*/
|
|
8
|
+
const authSchemaDrift = async (auth) => {
|
|
9
|
+
const { toBeCreated, toBeAdded, toBeAddedIndexes, schemaProblems } = await getMigrations(auth.options, { throwOnUnsafe: false });
|
|
10
|
+
return [
|
|
11
|
+
...toBeCreated.map(({ table }) => `lacks the table ${table}`),
|
|
12
|
+
...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `lacks the column ${table}.${field}`)),
|
|
13
|
+
...toBeAddedIndexes.map(({ name }) => `lacks the index ${name}`),
|
|
14
|
+
...schemaProblems
|
|
15
|
+
];
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { authSchemaDrift };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ddl, ddlText } from "./ddl.js";
|
|
2
|
+
import { migrationNames } from "./migrations/migration.js";
|
|
3
|
+
import { createMigrationsTable, recordMigrations } from "./migrations-table.js";
|
|
4
|
+
//#region src/better-auth/store/migration-sql.ts
|
|
5
|
+
/** The statements as SQL text. */
|
|
6
|
+
const statementsSql = (statements) => statements.map((statement) => `${ddlText(statement)};`).join("\n\n");
|
|
7
|
+
/** A migration's statements, then the row that records it, under a comment naming it. */
|
|
8
|
+
const migrationSql = (names, migration) => `-- ${migration.version} ${migration.name}: ${migration.title}.\n${statementsSql([...migration.statements(migrationNames(names)), recordMigrations(names, [migration])])}`;
|
|
9
|
+
/** The migrations table, for a store that has none yet, in the store's schema, made when it is missing. */
|
|
10
|
+
const migrationsTableSql = (names) => {
|
|
11
|
+
const schema = names.schema === void 0 ? [] : [ddl`create schema if not exists ${names.schema}`];
|
|
12
|
+
return `-- The record of the auth store's migrations.\n${statementsSql([...schema, createMigrationsTable(names)])}`;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { migrationSql, migrationsTableSql, statementsSql };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ddl } from "../ddl.js";
|
|
2
|
+
//#region src/better-auth/store/migrations/001-core.ts
|
|
3
|
+
/** Sign-in itself: users, with their roles and bans, sessions, accounts, codes and the token signing keys. */
|
|
4
|
+
const core = {
|
|
5
|
+
version: 1,
|
|
6
|
+
name: "core",
|
|
7
|
+
title: "Better Auth's users, sessions, accounts, verifications and signing keys",
|
|
8
|
+
tables: [
|
|
9
|
+
"session",
|
|
10
|
+
"account",
|
|
11
|
+
"verification",
|
|
12
|
+
"jwks"
|
|
13
|
+
],
|
|
14
|
+
statements: ({ table, user, userColumn: column, metadata, id, idRef }) => [
|
|
15
|
+
ddl`create table if not exists ${user} (id ${id} primary key)`,
|
|
16
|
+
ddl`alter table ${user}
|
|
17
|
+
add column if not exists ${column("name")} text not null,
|
|
18
|
+
add column if not exists ${column("email")} text not null unique,
|
|
19
|
+
add column if not exists ${column("email_verified")} boolean not null,
|
|
20
|
+
add column if not exists ${column("image")} text,
|
|
21
|
+
add column if not exists ${column("created_at")} timestamptz default current_timestamp not null,
|
|
22
|
+
add column if not exists ${column("updated_at")} timestamptz default current_timestamp not null,
|
|
23
|
+
add column if not exists ${column("role")} text,
|
|
24
|
+
add column if not exists ${column("banned")} boolean,
|
|
25
|
+
add column if not exists ${column("ban_reason")} text,
|
|
26
|
+
add column if not exists ${column("ban_expires")} timestamptz,
|
|
27
|
+
add column if not exists ${column("last_role")} text,
|
|
28
|
+
add column if not exists ${column("email_confirmed_at")} timestamptz`,
|
|
29
|
+
...metadata ? [ddl`alter table ${user} add column if not exists ${metadata} jsonb`] : [],
|
|
30
|
+
ddl`create table ${table("session")} (
|
|
31
|
+
id ${id} primary key,
|
|
32
|
+
expires_at timestamptz not null,
|
|
33
|
+
token text not null unique,
|
|
34
|
+
created_at timestamptz default current_timestamp not null,
|
|
35
|
+
updated_at timestamptz not null,
|
|
36
|
+
ip_address text,
|
|
37
|
+
user_agent text,
|
|
38
|
+
user_id ${idRef} not null references ${user} (id) on delete cascade,
|
|
39
|
+
impersonated_by text,
|
|
40
|
+
active_role text
|
|
41
|
+
)`,
|
|
42
|
+
ddl`create index session_user_id_idx on ${table("session")} (user_id)`,
|
|
43
|
+
ddl`create table ${table("account")} (
|
|
44
|
+
id ${id} primary key,
|
|
45
|
+
account_id text not null,
|
|
46
|
+
provider_id text not null,
|
|
47
|
+
user_id ${idRef} not null references ${user} (id) on delete cascade,
|
|
48
|
+
access_token text,
|
|
49
|
+
refresh_token text,
|
|
50
|
+
id_token text,
|
|
51
|
+
access_token_expires_at timestamptz,
|
|
52
|
+
refresh_token_expires_at timestamptz,
|
|
53
|
+
scope text,
|
|
54
|
+
password text,
|
|
55
|
+
created_at timestamptz default current_timestamp not null,
|
|
56
|
+
updated_at timestamptz not null
|
|
57
|
+
)`,
|
|
58
|
+
ddl`create index account_user_id_idx on ${table("account")} (user_id)`,
|
|
59
|
+
ddl`create table ${table("verification")} (
|
|
60
|
+
id ${id} primary key,
|
|
61
|
+
identifier text not null,
|
|
62
|
+
value text not null,
|
|
63
|
+
expires_at timestamptz not null,
|
|
64
|
+
created_at timestamptz default current_timestamp not null,
|
|
65
|
+
updated_at timestamptz default current_timestamp not null
|
|
66
|
+
)`,
|
|
67
|
+
ddl`create index verification_identifier_idx on ${table("verification")} (identifier)`,
|
|
68
|
+
ddl`create table ${table("jwks")} (
|
|
69
|
+
id ${id} primary key,
|
|
70
|
+
public_key text not null,
|
|
71
|
+
private_key text not null,
|
|
72
|
+
created_at timestamptz not null,
|
|
73
|
+
expires_at timestamptz,
|
|
74
|
+
alg text,
|
|
75
|
+
crv text
|
|
76
|
+
)`
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { core };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ddl } from "../ddl.js";
|
|
2
|
+
//#region src/better-auth/store/migrations/002-two-factor.ts
|
|
3
|
+
/** Two-step sign-in: each user's authenticator secret and backup codes. */
|
|
4
|
+
const twoFactor = {
|
|
5
|
+
version: 2,
|
|
6
|
+
name: "two_factor",
|
|
7
|
+
title: "The authenticator secrets and backup codes of two-step sign-in",
|
|
8
|
+
tables: ["two_factor"],
|
|
9
|
+
statements: ({ table, user, userColumn: column, id, idRef }) => [
|
|
10
|
+
ddl`alter table ${user} add column if not exists ${column("two_factor_enabled")} boolean`,
|
|
11
|
+
ddl`create table ${table("two_factor")} (
|
|
12
|
+
id ${id} primary key,
|
|
13
|
+
secret text not null,
|
|
14
|
+
backup_codes text not null,
|
|
15
|
+
user_id ${idRef} not null references ${user} (id) on delete cascade,
|
|
16
|
+
verified boolean,
|
|
17
|
+
failed_verification_count integer,
|
|
18
|
+
locked_until timestamptz
|
|
19
|
+
)`,
|
|
20
|
+
ddl`create index two_factor_secret_idx on ${table("two_factor")} (secret)`,
|
|
21
|
+
ddl`create index two_factor_user_id_idx on ${table("two_factor")} (user_id)`
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { twoFactor };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ddl } from "../ddl.js";
|
|
2
|
+
//#region src/better-auth/store/migrations/003-passkeys.ts
|
|
3
|
+
/** Passkeys, each with the RP ID it was made for. */
|
|
4
|
+
const passkeys = {
|
|
5
|
+
version: 3,
|
|
6
|
+
name: "passkeys",
|
|
7
|
+
title: "The users' passkeys",
|
|
8
|
+
tables: ["passkey"],
|
|
9
|
+
statements: ({ table, user, id, idRef }) => [
|
|
10
|
+
ddl`create table ${table("passkey")} (
|
|
11
|
+
id ${id} primary key,
|
|
12
|
+
name text,
|
|
13
|
+
public_key text not null,
|
|
14
|
+
user_id ${idRef} not null references ${user} (id) on delete cascade,
|
|
15
|
+
credential_id text not null,
|
|
16
|
+
counter integer not null,
|
|
17
|
+
device_type text not null,
|
|
18
|
+
backed_up boolean not null,
|
|
19
|
+
transports text,
|
|
20
|
+
created_at timestamptz,
|
|
21
|
+
aaguid text,
|
|
22
|
+
rp_id text
|
|
23
|
+
)`,
|
|
24
|
+
ddl`create index passkey_user_id_idx on ${table("passkey")} (user_id)`,
|
|
25
|
+
ddl`create index passkey_credential_id_idx on ${table("passkey")} (credential_id)`
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { passkeys };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ddl } from "../ddl.js";
|
|
2
|
+
//#region src/better-auth/store/migrations/004-sign-in-policy.ts
|
|
3
|
+
/** The sign-in policy admins save, each save a row of its own. */
|
|
4
|
+
const signInPolicy = {
|
|
5
|
+
version: 4,
|
|
6
|
+
name: "sign_in_policy",
|
|
7
|
+
title: "The sign-in policies admins saved",
|
|
8
|
+
tables: ["sign_in_policy"],
|
|
9
|
+
statements: ({ table, id }) => [ddl`create table ${table("sign_in_policy")} (
|
|
10
|
+
id ${id} primary key,
|
|
11
|
+
password text not null,
|
|
12
|
+
email_code text not null,
|
|
13
|
+
passkey text not null,
|
|
14
|
+
two_factor text not null,
|
|
15
|
+
staff_access text not null,
|
|
16
|
+
platform_sign_in text not null,
|
|
17
|
+
created_at timestamptz not null,
|
|
18
|
+
created_by text
|
|
19
|
+
)`]
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { signInPolicy };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ddl } from "../ddl.js";
|
|
2
|
+
//#region src/better-auth/store/migrations/005-staff-sign-in.ts
|
|
3
|
+
/** The log of the operator's staff signing in as the app's people. */
|
|
4
|
+
const staffSignIn = {
|
|
5
|
+
version: 5,
|
|
6
|
+
name: "staff_sign_in",
|
|
7
|
+
title: "The log of staff sign-ins as the app's people",
|
|
8
|
+
tables: ["staff_sign_in"],
|
|
9
|
+
statements: ({ table, id }) => [ddl`create table ${table("staff_sign_in")} (
|
|
10
|
+
id ${id} primary key,
|
|
11
|
+
user_id text not null,
|
|
12
|
+
user_email text not null,
|
|
13
|
+
staff text not null,
|
|
14
|
+
staff_name text,
|
|
15
|
+
staff_subject text not null,
|
|
16
|
+
issuer text not null,
|
|
17
|
+
reason text not null,
|
|
18
|
+
session_id text not null,
|
|
19
|
+
started_at timestamptz not null,
|
|
20
|
+
expires_at timestamptz not null,
|
|
21
|
+
ended_at timestamptz
|
|
22
|
+
)`]
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { staffSignIn };
|