@protobase/server 0.6.0 → 0.7.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.
@@ -2,13 +2,15 @@ import { AdminAuth } from "./create-auth.js";
2
2
  //#region src/better-auth/auth-schema.d.ts
3
3
  /**
4
4
  * The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
5
+ * for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
5
6
  * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
6
7
  * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
7
8
  */
8
9
  export declare const authSchemaMigration: (auth: AdminAuth) => Promise<string | undefined>;
9
10
  /**
10
- * Refuses to go on while the admin store lacks a table or column Better Auth needs, since every sign-in would fail.
11
- * The server never changes the schema itself: the project's migrations do, before the new version serves.
11
+ * Refuses to go on while the admin store lacks a table or column Better Auth needs, or still has the camelCase names
12
+ * of Protobase 0.6, since every sign-in would fail. The server never changes the schema itself: the project's
13
+ * migrations do, before the new version serves.
12
14
  */
13
15
  export declare const checkAuthSchema: (auth: AdminAuth) => Promise<void>;
14
16
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { snakeCaseMigration } from "./snake-case-migration.js";
1
2
  import { getMigrations } from "better-auth/db/migration";
2
3
  //#region src/better-auth/auth-schema.ts
3
4
  /** The command that writes the pending changes as a migration file of the project. */
@@ -8,28 +9,37 @@ const authMigrationCommand = "protobase auth migration";
8
9
  */
9
10
  const skipExisting = (sql) => sql.split(";\n\n").map((statement) => statement.replace(/^create table (?!if not exists )/, "create table if not exists ").replace(/^create (unique )?index (?!if not exists )/, (_, unique) => `create ${unique ?? ""}index if not exists `).replace(/^(alter table .+? )add column (?!if not exists )/, "$1add column if not exists ")).join(";\n\n");
10
11
  /**
12
+ * Better Auth adds `email_verified` to a `user` table that exists only when the table still has the camelCase names
13
+ * of Protobase 0.6, whose `emailVerified` it does not know: such a store is renamed, never given new columns beside
14
+ * the old ones.
15
+ */
16
+ const hasCamelCaseNames = ({ toBeAdded }) => toBeAdded.some(({ table, fields }) => table === "user" && "email_verified" in fields);
17
+ const storeSchema = ({ options: { database } }) => database && "schemaName" in database ? database.schemaName : void 0;
18
+ /**
11
19
  * The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
20
+ * for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
12
21
  * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
13
22
  * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
14
23
  */
15
24
  const authSchemaMigration = async (auth) => {
25
+ if (hasCamelCaseNames(await getMigrations(auth.options, { throwOnUnsafe: false }))) return snakeCaseMigration(storeSchema(auth));
16
26
  const { toBeCreated, toBeAdded, toBeAddedIndexes, compileMigrations } = await getMigrations(auth.options);
17
27
  if (toBeCreated.length === 0 && toBeAdded.length === 0 && toBeAddedIndexes.length === 0) return void 0;
18
28
  return skipExisting(await compileMigrations());
19
29
  };
20
30
  /** The tables and `table.column`s the admin store lacks; an index missing is not a reason to refuse requests. */
21
- const missingAuthSchema = async (auth) => {
22
- const { toBeCreated, toBeAdded } = await getMigrations(auth.options, { throwOnUnsafe: false });
23
- return [...toBeCreated.map(({ table }) => table), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
24
- };
31
+ const missingTablesAndColumns = ({ toBeCreated, toBeAdded }) => [...toBeCreated.map(({ table }) => table), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
25
32
  /**
26
- * Refuses to go on while the admin store lacks a table or column Better Auth needs, since every sign-in would fail.
27
- * The server never changes the schema itself: the project's migrations do, before the new version serves.
33
+ * Refuses to go on while the admin store lacks a table or column Better Auth needs, or still has the camelCase names
34
+ * of Protobase 0.6, since every sign-in would fail. The server never changes the schema itself: the project's
35
+ * migrations do, before the new version serves.
28
36
  */
29
37
  const checkAuthSchema = async (auth) => {
30
- const missing = await missingAuthSchema(auth);
38
+ const plan = await getMigrations(auth.options, { throwOnUnsafe: false });
39
+ if (hasCamelCaseNames(plan)) throw new Error(`The auth schema still has the camelCase names of Protobase 0.6, which this version names in snake_case. Write their rename as a migration with \`${authMigrationCommand}\`, apply it with the project's migrations, then start again.`);
40
+ const missing = missingTablesAndColumns(plan);
31
41
  if (missing.length === 0) return;
32
42
  throw new Error(`The auth schema is behind this version of Protobase (missing ${missing.join(", ")}). Write the change as a migration with \`${authMigrationCommand}\`, apply it with the project's migrations, then start again.`);
33
43
  };
34
44
  //#endregion
35
- export { authMigrationCommand, authSchemaMigration, checkAuthSchema, missingAuthSchema, skipExisting };
45
+ export { authMigrationCommand, authSchemaMigration, checkAuthSchema, skipExisting };
@@ -62,11 +62,47 @@ export type UserCreatedHook = NonNullable<UserCreateHooks['after']>;
62
62
  /** The configured roles with `admin` first; `admin` is added when missing. */
63
63
  export declare const normalizeRoles: (roles?: string[]) => string[];
64
64
  export declare const createAuth: (options: CreateAuthOptions) => import("better-auth").Auth<{
65
+ user: {
66
+ fields: {
67
+ emailVerified: string;
68
+ createdAt: string;
69
+ updatedAt: string;
70
+ };
71
+ };
72
+ session: {
73
+ fields: {
74
+ expiresAt: string;
75
+ createdAt: string;
76
+ updatedAt: string;
77
+ ipAddress: string;
78
+ userAgent: string;
79
+ userId: string;
80
+ };
81
+ };
82
+ verification: {
83
+ fields: {
84
+ expiresAt: string;
85
+ createdAt: string;
86
+ updatedAt: string;
87
+ };
88
+ };
65
89
  account: {
66
90
  encryptOAuthTokens?: true | undefined;
67
91
  accountLinking: {
68
92
  allowDifferentEmails: true;
69
93
  };
94
+ fields: {
95
+ accountId: string;
96
+ providerId: string;
97
+ userId: string;
98
+ accessToken: string;
99
+ refreshToken: string;
100
+ idToken: string;
101
+ accessTokenExpiresAt: string;
102
+ refreshTokenExpiresAt: string;
103
+ createdAt: string;
104
+ updatedAt: string;
105
+ };
70
106
  };
71
107
  plugins: [{
72
108
  id: "admin";
@@ -954,6 +990,19 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
954
990
  };
955
991
  };
956
992
  options: NoInfer<{
993
+ schema: {
994
+ user: {
995
+ fields: {
996
+ banReason: string;
997
+ banExpires: string;
998
+ };
999
+ };
1000
+ session: {
1001
+ fields: {
1002
+ impersonatedBy: string;
1003
+ };
1004
+ };
1005
+ };
957
1006
  defaultRole: string;
958
1007
  roles: {
959
1008
  [k: string]: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
@@ -975,6 +1024,16 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
975
1024
  id: "jwt";
976
1025
  version: string;
977
1026
  options: NoInfer<{
1027
+ schema: {
1028
+ jwks: {
1029
+ fields: {
1030
+ publicKey: string;
1031
+ privateKey: string;
1032
+ createdAt: string;
1033
+ expiresAt: string;
1034
+ };
1035
+ };
1036
+ };
978
1037
  jwt: {
979
1038
  expirationTime: string;
980
1039
  issuer: string;
@@ -3021,6 +3080,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3021
3080
  id: "protobase-sign-in-policy";
3022
3081
  schema: {
3023
3082
  readonly signInPolicy: {
3083
+ readonly modelName: "sign_in_policy";
3024
3084
  readonly fields: {
3025
3085
  readonly password: {
3026
3086
  readonly type: "string";
@@ -3029,6 +3089,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3029
3089
  readonly emailCode: {
3030
3090
  readonly type: "string";
3031
3091
  readonly required: true;
3092
+ readonly fieldName: "email_code";
3032
3093
  };
3033
3094
  readonly passkey: {
3034
3095
  readonly type: "string";
@@ -3037,24 +3098,29 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3037
3098
  readonly twoFactor: {
3038
3099
  readonly type: "string";
3039
3100
  readonly required: true;
3101
+ readonly fieldName: "two_factor";
3040
3102
  };
3041
3103
  readonly staffAccess: {
3042
3104
  readonly type: "string";
3043
3105
  readonly required: true;
3044
3106
  readonly defaultValue: "allowed";
3107
+ readonly fieldName: "staff_access";
3045
3108
  };
3046
3109
  readonly platformSignIn: {
3047
3110
  readonly type: "string";
3048
3111
  readonly required: true;
3049
3112
  readonly defaultValue: "allowed";
3113
+ readonly fieldName: "platform_sign_in";
3050
3114
  };
3051
3115
  readonly createdAt: {
3052
3116
  readonly type: "date";
3053
3117
  readonly required: true;
3118
+ readonly fieldName: "created_at";
3054
3119
  };
3055
3120
  readonly createdBy: {
3056
3121
  readonly type: "string";
3057
3122
  readonly required: false;
3123
+ readonly fieldName: "created_by";
3058
3124
  };
3059
3125
  };
3060
3126
  };
@@ -3073,7 +3139,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3073
3139
  expiresAt: Date;
3074
3140
  token: string;
3075
3141
  ipAddress?: string | null | undefined;
3076
- userAgent?: string | null | undefined;
3142
+ userAgent? /**
3143
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3144
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3145
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3146
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3147
+ */ : string | null | undefined;
3077
3148
  };
3078
3149
  user: Record<string, any> & {
3079
3150
  id: string;
@@ -3097,7 +3168,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3097
3168
  expiresAt: Date;
3098
3169
  token: string;
3099
3170
  ipAddress?: string | null | undefined;
3100
- userAgent?: string | null | undefined;
3171
+ userAgent? /**
3172
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3173
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3174
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3175
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3176
+ */ : string | null | undefined;
3101
3177
  };
3102
3178
  user: Record<string, any> & {
3103
3179
  id: string;
@@ -3120,7 +3196,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3120
3196
  expiresAt: Date;
3121
3197
  token: string;
3122
3198
  ipAddress?: string | null | undefined;
3123
- userAgent?: string | null | undefined;
3199
+ userAgent? /**
3200
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3201
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3202
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3203
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3204
+ */ : string | null | undefined;
3124
3205
  };
3125
3206
  user: Record<string, any> & {
3126
3207
  id: string;
@@ -3175,7 +3256,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3175
3256
  expiresAt: Date;
3176
3257
  token: string;
3177
3258
  ipAddress?: string | null | undefined;
3178
- userAgent?: string | null | undefined;
3259
+ userAgent? /**
3260
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3261
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3262
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3263
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3264
+ */ : string | null | undefined;
3179
3265
  };
3180
3266
  user: Record<string, any> & {
3181
3267
  id: string;
@@ -3199,7 +3285,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3199
3285
  expiresAt: Date;
3200
3286
  token: string;
3201
3287
  ipAddress?: string | null | undefined;
3202
- userAgent?: string | null | undefined;
3288
+ userAgent? /**
3289
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3290
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3291
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3292
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3293
+ */ : string | null | undefined;
3203
3294
  };
3204
3295
  user: Record<string, any> & {
3205
3296
  id: string;
@@ -3222,7 +3313,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3222
3313
  expiresAt: Date;
3223
3314
  token: string;
3224
3315
  ipAddress?: string | null | undefined;
3225
- userAgent?: string | null | undefined;
3316
+ userAgent? /**
3317
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3318
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3319
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3320
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3321
+ */ : string | null | undefined;
3226
3322
  };
3227
3323
  user: Record<string, any> & {
3228
3324
  id: string;
@@ -3277,7 +3373,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3277
3373
  expiresAt: Date;
3278
3374
  token: string;
3279
3375
  ipAddress?: string | null | undefined;
3280
- userAgent?: string | null | undefined;
3376
+ userAgent? /**
3377
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3378
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3379
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3380
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3381
+ */ : string | null | undefined;
3281
3382
  };
3282
3383
  user: Record<string, any> & {
3283
3384
  id: string;
@@ -3299,7 +3400,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3299
3400
  twoFactor: boolean;
3300
3401
  authenticatorApp: boolean;
3301
3402
  };
3302
- missing: ("passkey" | "twoFactor")[];
3403
+ missing: ("twoFactor" | "passkey")[];
3303
3404
  }>;
3304
3405
  };
3305
3406
  hooks: {
@@ -3312,14 +3413,17 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3312
3413
  id: "protobase-staff-sign-in";
3313
3414
  schema: {
3314
3415
  readonly staffSignIn: {
3416
+ readonly modelName: "staff_sign_in";
3315
3417
  readonly fields: {
3316
3418
  readonly userId: {
3317
3419
  readonly type: "string";
3318
3420
  readonly required: true;
3421
+ readonly fieldName: "user_id";
3319
3422
  };
3320
3423
  readonly userEmail: {
3321
3424
  readonly type: "string";
3322
3425
  readonly required: true;
3426
+ readonly fieldName: "user_email";
3323
3427
  };
3324
3428
  readonly staff: {
3325
3429
  readonly type: "string";
@@ -3328,10 +3432,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3328
3432
  readonly staffName: {
3329
3433
  readonly type: "string";
3330
3434
  readonly required: false;
3435
+ readonly fieldName: "staff_name";
3331
3436
  };
3332
3437
  readonly staffSubject: {
3333
3438
  readonly type: "string";
3334
3439
  readonly required: true;
3440
+ readonly fieldName: "staff_subject";
3335
3441
  };
3336
3442
  readonly issuer: {
3337
3443
  readonly type: "string";
@@ -3344,18 +3450,22 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3344
3450
  readonly sessionId: {
3345
3451
  readonly type: "string";
3346
3452
  readonly required: true;
3453
+ readonly fieldName: "session_id";
3347
3454
  };
3348
3455
  readonly startedAt: {
3349
3456
  readonly type: "date";
3350
3457
  readonly required: true;
3458
+ readonly fieldName: "started_at";
3351
3459
  };
3352
3460
  readonly expiresAt: {
3353
3461
  readonly type: "date";
3354
3462
  readonly required: true;
3463
+ readonly fieldName: "expires_at";
3355
3464
  };
3356
3465
  readonly endedAt: {
3357
3466
  readonly type: "date";
3358
3467
  readonly required: false;
3468
+ readonly fieldName: "ended_at";
3359
3469
  };
3360
3470
  };
3361
3471
  };
@@ -3373,7 +3483,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3373
3483
  expiresAt: Date;
3374
3484
  token: string;
3375
3485
  ipAddress?: string | null | undefined;
3376
- userAgent?: string | null | undefined;
3486
+ userAgent? /**
3487
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3488
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3489
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3490
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3491
+ */ : string | null | undefined;
3377
3492
  };
3378
3493
  user: Record<string, any> & {
3379
3494
  id: string;
@@ -3401,7 +3516,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3401
3516
  expiresAt: Date;
3402
3517
  token: string;
3403
3518
  ipAddress?: string | null | undefined;
3404
- userAgent?: string | null | undefined;
3519
+ userAgent? /**
3520
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3521
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3522
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3523
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3524
+ */ : string | null | undefined;
3405
3525
  };
3406
3526
  user: Record<string, any> & {
3407
3527
  id: string;
@@ -3430,7 +3550,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3430
3550
  expiresAt: Date;
3431
3551
  token: string;
3432
3552
  ipAddress?: string | null | undefined;
3433
- userAgent?: string | null | undefined;
3553
+ userAgent? /**
3554
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3555
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3556
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3557
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3558
+ */ : string | null | undefined;
3434
3559
  };
3435
3560
  user: Record<string, any> & {
3436
3561
  id: string;
@@ -3454,7 +3579,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3454
3579
  expiresAt: Date;
3455
3580
  token: string;
3456
3581
  ipAddress?: string | null | undefined;
3457
- userAgent?: string | null | undefined;
3582
+ userAgent? /**
3583
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3584
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3585
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3586
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3587
+ */ : string | null | undefined;
3458
3588
  };
3459
3589
  user: Record<string, any> & {
3460
3590
  id: string;
@@ -3477,7 +3607,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3477
3607
  expiresAt: Date;
3478
3608
  token: string;
3479
3609
  ipAddress?: string | null | undefined;
3480
- userAgent?: string | null | undefined;
3610
+ userAgent? /**
3611
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
3612
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
3613
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
3614
+ * environment says. Left out when `socialProviders` has a provider with the same id.
3615
+ */ : string | null | undefined;
3481
3616
  };
3482
3617
  user: Record<string, any> & {
3483
3618
  id: string;
@@ -5,6 +5,7 @@ import { passwordResetOptions } from "./password-reset.js";
5
5
  import { platformSignInFor, platformSignInProvider, readPlatformSignIn, resolvePlatformSignIn } from "./platform-sign-in.js";
6
6
  import { signInPolicyPlugin } from "./policy-plugin.js";
7
7
  import { emailCodeOptions, twoFactorCodeOptions, unusedEmailCodePaths } from "./sign-in-mail.js";
8
+ import { coreNames, pluginNames } from "./snake-case-names.js";
8
9
  import { staffSignInPlugin } from "./staff-plugin.js";
9
10
  import { twoFactorAfterPasswordOrCode } from "./two-factor-challenge.js";
10
11
  import { passkey } from "@better-auth/passkey";
@@ -60,16 +61,22 @@ const betterAuthOptions = (options, { roles, defaultRole, mailer, operator, plat
60
61
  ...mailer && passwordResetOptions(mailer)
61
62
  },
62
63
  ...options.socialProviders && { socialProviders: options.socialProviders },
64
+ user: coreNames.user,
65
+ session: coreNames.session,
66
+ verification: coreNames.verification,
63
67
  account: {
68
+ ...coreNames.account,
64
69
  accountLinking: { allowDifferentEmails: true },
65
70
  ...options.encryptOAuthTokens && { encryptOAuthTokens: true }
66
71
  },
67
72
  plugins: [
68
73
  admin({
74
+ schema: pluginNames.admin,
69
75
  defaultRole: defaultRole ?? "unassigned",
70
76
  roles: Object.fromEntries(roles.map((role) => [role, role === "admin" ? adminAc : userAc]))
71
77
  }),
72
78
  jwt({
79
+ schema: pluginNames.jwt,
73
80
  jwt: {
74
81
  expirationTime: tokenLifetime,
75
82
  issuer: options.baseURL,
@@ -82,10 +89,11 @@ const betterAuthOptions = (options, { roles, defaultRole, mailer, operator, plat
82
89
  disableSettingJwtHeader: true
83
90
  }),
84
91
  twoFactorAfterPasswordOrCode({
92
+ schema: pluginNames.twoFactor,
85
93
  allowPasswordless: true,
86
94
  ...mailer && { otpOptions: twoFactorCodeOptions(mailer) }
87
95
  }),
88
- passkey(),
96
+ passkey({ schema: pluginNames.passkey }),
89
97
  ...mailer ? [emailOTP(emailCodeOptions(mailer))] : [],
90
98
  ...signIn ? [genericOAuth({ config: [platformSignInProvider(signIn)] })] : [],
91
99
  signInPolicyPlugin({
@@ -2,42 +2,51 @@ import { defaultSignInPolicy, parseSignInPolicy } from "./sign-in-policy.js";
2
2
  //#region src/better-auth/policy-store.ts
3
3
  /** Better Auth's model for the sign-in policy: one row per save, the newest applies, the older ones are its history. */
4
4
  const signInPolicyModel = "signInPolicy";
5
- const signInPolicySchema = { [signInPolicyModel]: { fields: {
6
- password: {
7
- type: "string",
8
- required: true
9
- },
10
- emailCode: {
11
- type: "string",
12
- required: true
13
- },
14
- passkey: {
15
- type: "string",
16
- required: true
17
- },
18
- twoFactor: {
19
- type: "string",
20
- required: true
21
- },
22
- staffAccess: {
23
- type: "string",
24
- required: true,
25
- defaultValue: "allowed"
26
- },
27
- platformSignIn: {
28
- type: "string",
29
- required: true,
30
- defaultValue: "allowed"
31
- },
32
- createdAt: {
33
- type: "date",
34
- required: true
35
- },
36
- createdBy: {
37
- type: "string",
38
- required: false
5
+ const signInPolicySchema = { [signInPolicyModel]: {
6
+ modelName: "sign_in_policy",
7
+ fields: {
8
+ password: {
9
+ type: "string",
10
+ required: true
11
+ },
12
+ emailCode: {
13
+ type: "string",
14
+ required: true,
15
+ fieldName: "email_code"
16
+ },
17
+ passkey: {
18
+ type: "string",
19
+ required: true
20
+ },
21
+ twoFactor: {
22
+ type: "string",
23
+ required: true,
24
+ fieldName: "two_factor"
25
+ },
26
+ staffAccess: {
27
+ type: "string",
28
+ required: true,
29
+ defaultValue: "allowed",
30
+ fieldName: "staff_access"
31
+ },
32
+ platformSignIn: {
33
+ type: "string",
34
+ required: true,
35
+ defaultValue: "allowed",
36
+ fieldName: "platform_sign_in"
37
+ },
38
+ createdAt: {
39
+ type: "date",
40
+ required: true,
41
+ fieldName: "created_at"
42
+ },
43
+ createdBy: {
44
+ type: "string",
45
+ required: false,
46
+ fieldName: "created_by"
47
+ }
39
48
  }
40
- } } };
49
+ } };
41
50
  /** The newest saved policy, or the default when none was saved. A row that no longer parses counts as none. */
42
51
  const readSignInPolicy = async (adapter) => {
43
52
  const [row] = await adapter.findMany({
@@ -0,0 +1,122 @@
1
+ //#region src/better-auth/snake-case-migration.ts
2
+ const schemaName = /^[a-z_][a-z0-9_]*$/;
3
+ /**
4
+ * The migration that renames the auth store of Protobase 0.6 in place, from Better Auth's camelCase names to the
5
+ * snake_case ones of `snake-case-names.ts`: tables, columns, and the indexes and constraints named after them. Data,
6
+ * defaults, keys and indexes stay as they are. On a store that has the snake_case names already it does nothing, so a
7
+ * project's migrations can carry it after an auth migration written with them. `schema` is the store's schema, the
8
+ * connection's current one when left out.
9
+ */
10
+ const snakeCaseMigration = (schema) => {
11
+ if (schema !== void 0 && !schemaName.test(schema)) throw new Error(`Invalid auth schema name "${schema}"`);
12
+ const s = schema === void 0 ? "" : `"${schema}".`;
13
+ const current = schema === void 0 ? "current_schema()" : `'${schema}'`;
14
+ return `-- Renames the auth store of Protobase 0.6 to snake_case. A store set up with the snake_case names has no "emailVerified"
15
+ -- and is left as it is.
16
+ do $$
17
+ declare
18
+ constraint_row record;
19
+ begin
20
+ if not exists (select from information_schema.columns where table_schema = ${current} and table_name = 'user' and column_name = 'emailVerified') then
21
+ return;
22
+ end if;
23
+
24
+ alter table ${s}"twoFactor" rename to two_factor;
25
+ alter table ${s}"signInPolicy" rename to sign_in_policy;
26
+ alter table ${s}"staffSignIn" rename to staff_sign_in;
27
+
28
+ alter table ${s}"user" rename column "emailVerified" to email_verified;
29
+ alter table ${s}"user" rename column "createdAt" to created_at;
30
+ alter table ${s}"user" rename column "updatedAt" to updated_at;
31
+ alter table ${s}"user" rename column "banReason" to ban_reason;
32
+ alter table ${s}"user" rename column "banExpires" to ban_expires;
33
+ alter table ${s}"user" rename column "twoFactorEnabled" to two_factor_enabled;
34
+
35
+ alter table ${s}session rename column "expiresAt" to expires_at;
36
+ alter table ${s}session rename column "createdAt" to created_at;
37
+ alter table ${s}session rename column "updatedAt" to updated_at;
38
+ alter table ${s}session rename column "ipAddress" to ip_address;
39
+ alter table ${s}session rename column "userAgent" to user_agent;
40
+ alter table ${s}session rename column "userId" to user_id;
41
+ alter table ${s}session rename column "impersonatedBy" to impersonated_by;
42
+
43
+ alter table ${s}account rename column "accountId" to account_id;
44
+ alter table ${s}account rename column "providerId" to provider_id;
45
+ alter table ${s}account rename column "userId" to user_id;
46
+ alter table ${s}account rename column "accessToken" to access_token;
47
+ alter table ${s}account rename column "refreshToken" to refresh_token;
48
+ alter table ${s}account rename column "idToken" to id_token;
49
+ alter table ${s}account rename column "accessTokenExpiresAt" to access_token_expires_at;
50
+ alter table ${s}account rename column "refreshTokenExpiresAt" to refresh_token_expires_at;
51
+ alter table ${s}account rename column "createdAt" to created_at;
52
+ alter table ${s}account rename column "updatedAt" to updated_at;
53
+
54
+ alter table ${s}verification rename column "expiresAt" to expires_at;
55
+ alter table ${s}verification rename column "createdAt" to created_at;
56
+ alter table ${s}verification rename column "updatedAt" to updated_at;
57
+
58
+ alter table ${s}jwks rename column "publicKey" to public_key;
59
+ alter table ${s}jwks rename column "privateKey" to private_key;
60
+ alter table ${s}jwks rename column "createdAt" to created_at;
61
+ alter table ${s}jwks rename column "expiresAt" to expires_at;
62
+
63
+ alter table ${s}two_factor rename column "backupCodes" to backup_codes;
64
+ alter table ${s}two_factor rename column "userId" to user_id;
65
+ alter table ${s}two_factor rename column "failedVerificationCount" to failed_verification_count;
66
+ alter table ${s}two_factor rename column "lockedUntil" to locked_until;
67
+
68
+ alter table ${s}passkey rename column "publicKey" to public_key;
69
+ alter table ${s}passkey rename column "userId" to user_id;
70
+ alter table ${s}passkey rename column "credentialID" to credential_id;
71
+ alter table ${s}passkey rename column "deviceType" to device_type;
72
+ alter table ${s}passkey rename column "backedUp" to backed_up;
73
+ alter table ${s}passkey rename column "createdAt" to created_at;
74
+
75
+ alter table ${s}sign_in_policy rename column "emailCode" to email_code;
76
+ alter table ${s}sign_in_policy rename column "twoFactor" to two_factor;
77
+ alter table ${s}sign_in_policy rename column "staffAccess" to staff_access;
78
+ alter table ${s}sign_in_policy rename column "platformSignIn" to platform_sign_in;
79
+ alter table ${s}sign_in_policy rename column "createdAt" to created_at;
80
+ alter table ${s}sign_in_policy rename column "createdBy" to created_by;
81
+
82
+ alter table ${s}staff_sign_in rename column "userId" to user_id;
83
+ alter table ${s}staff_sign_in rename column "userEmail" to user_email;
84
+ alter table ${s}staff_sign_in rename column "staffName" to staff_name;
85
+ alter table ${s}staff_sign_in rename column "staffSubject" to staff_subject;
86
+ alter table ${s}staff_sign_in rename column "sessionId" to session_id;
87
+ alter table ${s}staff_sign_in rename column "startedAt" to started_at;
88
+ alter table ${s}staff_sign_in rename column "expiresAt" to expires_at;
89
+ alter table ${s}staff_sign_in rename column "endedAt" to ended_at;
90
+
91
+ -- A primary key's index takes the new name with it.
92
+ alter table ${s}two_factor rename constraint "twoFactor_pkey" to two_factor_pkey;
93
+ alter table ${s}sign_in_policy rename constraint "signInPolicy_pkey" to sign_in_policy_pkey;
94
+ alter table ${s}staff_sign_in rename constraint "staffSignIn_pkey" to staff_sign_in_pkey;
95
+ alter table ${s}session rename constraint "session_userId_fkey" to session_user_id_fkey;
96
+ alter table ${s}account rename constraint "account_userId_fkey" to account_user_id_fkey;
97
+ alter table ${s}two_factor rename constraint "twoFactor_userId_fkey" to two_factor_user_id_fkey;
98
+ alter table ${s}passkey rename constraint "passkey_userId_fkey" to passkey_user_id_fkey;
99
+
100
+ alter index ${s}"session_userId_idx" rename to session_user_id_idx;
101
+ alter index ${s}"account_userId_idx" rename to account_user_id_idx;
102
+ alter index ${s}"twoFactor_secret_idx" rename to two_factor_secret_idx;
103
+ alter index ${s}"twoFactor_userId_idx" rename to two_factor_user_id_idx;
104
+ alter index ${s}"passkey_userId_idx" rename to passkey_user_id_idx;
105
+ alter index ${s}"passkey_credentialID_idx" rename to passkey_credential_id_idx;
106
+
107
+ -- Postgres 18 names each not-null constraint after its table and column, as "user_emailVerified_not_null"; earlier
108
+ -- versions keep no such constraints, so there is nothing to rename there.
109
+ for constraint_row in
110
+ select c.conrelid::regclass as table_name, c.conname as old_name, t.relname || '_' || a.attname || '_not_null' as new_name
111
+ from pg_constraint c
112
+ join pg_class t on t.oid = c.conrelid
113
+ join pg_attribute a on a.attrelid = c.conrelid and a.attnum = c.conkey[1]
114
+ where c.contype = 'n' and t.relnamespace = (select oid from pg_namespace where nspname = ${current}) and c.conname ~ '[A-Z]'
115
+ loop
116
+ execute format('alter table %s rename constraint %I to %I', constraint_row.table_name, constraint_row.old_name, constraint_row.new_name);
117
+ end loop;
118
+ end
119
+ $$;`;
120
+ };
121
+ //#endregion
122
+ export { snakeCaseMigration };
@@ -0,0 +1,72 @@
1
+ //#region src/better-auth/snake-case-names.ts
2
+ /** Better Auth's own tables, as its `user`, `session`, `account` and `verification` options name them. */
3
+ const coreNames = {
4
+ user: { fields: {
5
+ emailVerified: "email_verified",
6
+ createdAt: "created_at",
7
+ updatedAt: "updated_at"
8
+ } },
9
+ session: { fields: {
10
+ expiresAt: "expires_at",
11
+ createdAt: "created_at",
12
+ updatedAt: "updated_at",
13
+ ipAddress: "ip_address",
14
+ userAgent: "user_agent",
15
+ userId: "user_id"
16
+ } },
17
+ account: { fields: {
18
+ accountId: "account_id",
19
+ providerId: "provider_id",
20
+ userId: "user_id",
21
+ accessToken: "access_token",
22
+ refreshToken: "refresh_token",
23
+ idToken: "id_token",
24
+ accessTokenExpiresAt: "access_token_expires_at",
25
+ refreshTokenExpiresAt: "refresh_token_expires_at",
26
+ createdAt: "created_at",
27
+ updatedAt: "updated_at"
28
+ } },
29
+ verification: { fields: {
30
+ expiresAt: "expires_at",
31
+ createdAt: "created_at",
32
+ updatedAt: "updated_at"
33
+ } }
34
+ };
35
+ /** The `schema` option of each plugin with tables or columns of its own. */
36
+ const pluginNames = {
37
+ admin: {
38
+ user: { fields: {
39
+ banReason: "ban_reason",
40
+ banExpires: "ban_expires"
41
+ } },
42
+ session: { fields: { impersonatedBy: "impersonated_by" } }
43
+ },
44
+ jwt: { jwks: { fields: {
45
+ publicKey: "public_key",
46
+ privateKey: "private_key",
47
+ createdAt: "created_at",
48
+ expiresAt: "expires_at"
49
+ } } },
50
+ twoFactor: {
51
+ user: { fields: { twoFactorEnabled: "two_factor_enabled" } },
52
+ twoFactor: {
53
+ modelName: "two_factor",
54
+ fields: {
55
+ backupCodes: "backup_codes",
56
+ userId: "user_id",
57
+ failedVerificationCount: "failed_verification_count",
58
+ lockedUntil: "locked_until"
59
+ }
60
+ }
61
+ },
62
+ passkey: { passkey: { fields: {
63
+ publicKey: "public_key",
64
+ userId: "user_id",
65
+ credentialID: "credential_id",
66
+ deviceType: "device_type",
67
+ backedUp: "backed_up",
68
+ createdAt: "created_at"
69
+ } } }
70
+ };
71
+ //#endregion
72
+ export { coreNames, pluginNames };
@@ -1,52 +1,63 @@
1
1
  //#region src/better-auth/staff-log.ts
2
2
  /** Better Auth's model for the log of staff sign-ins: one row per session staff started as someone. */
3
3
  const staffSignInModel = "staffSignIn";
4
- const staffSignInSchema = { [staffSignInModel]: { fields: {
5
- userId: {
6
- type: "string",
7
- required: true
8
- },
9
- userEmail: {
10
- type: "string",
11
- required: true
12
- },
13
- staff: {
14
- type: "string",
15
- required: true
16
- },
17
- staffName: {
18
- type: "string",
19
- required: false
20
- },
21
- staffSubject: {
22
- type: "string",
23
- required: true
24
- },
25
- issuer: {
26
- type: "string",
27
- required: true
28
- },
29
- reason: {
30
- type: "string",
31
- required: true
32
- },
33
- sessionId: {
34
- type: "string",
35
- required: true
36
- },
37
- startedAt: {
38
- type: "date",
39
- required: true
40
- },
41
- expiresAt: {
42
- type: "date",
43
- required: true
44
- },
45
- endedAt: {
46
- type: "date",
47
- required: false
4
+ const staffSignInSchema = { [staffSignInModel]: {
5
+ modelName: "staff_sign_in",
6
+ fields: {
7
+ userId: {
8
+ type: "string",
9
+ required: true,
10
+ fieldName: "user_id"
11
+ },
12
+ userEmail: {
13
+ type: "string",
14
+ required: true,
15
+ fieldName: "user_email"
16
+ },
17
+ staff: {
18
+ type: "string",
19
+ required: true
20
+ },
21
+ staffName: {
22
+ type: "string",
23
+ required: false,
24
+ fieldName: "staff_name"
25
+ },
26
+ staffSubject: {
27
+ type: "string",
28
+ required: true,
29
+ fieldName: "staff_subject"
30
+ },
31
+ issuer: {
32
+ type: "string",
33
+ required: true
34
+ },
35
+ reason: {
36
+ type: "string",
37
+ required: true
38
+ },
39
+ sessionId: {
40
+ type: "string",
41
+ required: true,
42
+ fieldName: "session_id"
43
+ },
44
+ startedAt: {
45
+ type: "date",
46
+ required: true,
47
+ fieldName: "started_at"
48
+ },
49
+ expiresAt: {
50
+ type: "date",
51
+ required: true,
52
+ fieldName: "expires_at"
53
+ },
54
+ endedAt: {
55
+ type: "date",
56
+ required: false,
57
+ fieldName: "ended_at"
58
+ }
48
59
  }
49
- } } };
60
+ } };
50
61
  const entry = (row) => ({
51
62
  id: row.id,
52
63
  user: row.userEmail,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@better-auth/passkey": "^1.7.7",
33
- "@protobase/layout": "0.6.0",
34
- "@protobase/query": "0.6.0",
35
- "@protobase/schema": "0.6.0",
33
+ "@protobase/layout": "0.7.0",
34
+ "@protobase/query": "0.7.0",
35
+ "@protobase/schema": "0.7.0",
36
36
  "@scalar/hono-api-reference": "^0.12.10",
37
37
  "@simplewebauthn/server": "^13.3.1",
38
38
  "better-auth": "^1.7.7",