@protobase/server 0.5.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.
@@ -0,0 +1,16 @@
1
+ import { AdminAuth } from "./create-auth.js";
2
+ //#region src/better-auth/auth-schema.d.ts
3
+ /**
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
6
+ * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
7
+ * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
8
+ */
9
+ export declare const authSchemaMigration: (auth: AdminAuth) => Promise<string | undefined>;
10
+ /**
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.
14
+ */
15
+ export declare const checkAuthSchema: (auth: AdminAuth) => Promise<void>;
16
+ //#endregion
@@ -0,0 +1,45 @@
1
+ import { snakeCaseMigration } from "./snake-case-migration.js";
2
+ import { getMigrations } from "better-auth/db/migration";
3
+ //#region src/better-auth/auth-schema.ts
4
+ /** The command that writes the pending changes as a migration file of the project. */
5
+ const authMigrationCommand = "protobase auth migration";
6
+ /**
7
+ * Better Auth's statements, each made to skip what exists already, so a migration written from an empty database also
8
+ * applies to one an earlier Protobase set up in place.
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");
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
+ /**
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
21
+ * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
22
+ * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
23
+ */
24
+ const authSchemaMigration = async (auth) => {
25
+ if (hasCamelCaseNames(await getMigrations(auth.options, { throwOnUnsafe: false }))) return snakeCaseMigration(storeSchema(auth));
26
+ const { toBeCreated, toBeAdded, toBeAddedIndexes, compileMigrations } = await getMigrations(auth.options);
27
+ if (toBeCreated.length === 0 && toBeAdded.length === 0 && toBeAddedIndexes.length === 0) return void 0;
28
+ return skipExisting(await compileMigrations());
29
+ };
30
+ /** The tables and `table.column`s the admin store lacks; an index missing is not a reason to refuse requests. */
31
+ const missingTablesAndColumns = ({ toBeCreated, toBeAdded }) => [...toBeCreated.map(({ table }) => table), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
32
+ /**
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.
36
+ */
37
+ const checkAuthSchema = async (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);
41
+ if (missing.length === 0) return;
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.`);
43
+ };
44
+ //#endregion
45
+ export { authMigrationCommand, authSchemaMigration, checkAuthSchema, skipExisting };
@@ -2,6 +2,7 @@ import { SignInPolicy } from "./sign-in-policy.js";
2
2
  import { StaffSignIn } from "./staff-log.js";
3
3
  import { Mailer } from "../mail/smtp-mailer.js";
4
4
  import { OperatorProvider } from "./operator-provider.js";
5
+ import { PlatformSignIn } from "./platform-sign-in.js";
5
6
  import { BetterAuthOptions } from "better-auth";
6
7
  //#region src/better-auth/create-auth.d.ts
7
8
  export type CreateAuthOptions = {
@@ -33,7 +34,7 @@ export type CreateAuthOptions = {
33
34
  /**
34
35
  * Sign-in providers for Better Auth, for example `{ github: { clientId, clientSecret } }`; the callback is
35
36
  * `{baseURL}/api/auth/callback/<provider>`. A provider also signs up people without an account, with the default role.
36
- * Default none.
37
+ * One with the id of `signInProvider` takes its place. Default none.
37
38
  */
38
39
  socialProviders?: BetterAuthOptions['socialProviders'];
39
40
  /** Encrypt the OAuth access, refresh and ID tokens Better Auth stores for social sign-ins, with `secret`. Default off. */
@@ -47,6 +48,13 @@ export type CreateAuthOptions = {
47
48
  * whatever the environment says.
48
49
  */
49
50
  operator?: OperatorProvider | false;
51
+ /**
52
+ * A sign-in provider the platform adds for people who already have an account: an OpenID Connect provider that never
53
+ * signs anyone up. Defaults to the one the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID`
54
+ * and `PROTOBASE_SIGN_IN_CLIENT_SECRET` (see `readPlatformSignIn`), when set; `false` turns it off whatever the
55
+ * environment says. Left out when `socialProviders` has a provider with the same id.
56
+ */
57
+ signInProvider?: PlatformSignIn | false;
50
58
  };
51
59
  type UserCreateHooks = NonNullable<NonNullable<NonNullable<BetterAuthOptions['databaseHooks']>['user']>['create']>;
52
60
  /** Better Auth's `databaseHooks.user.create.after`: the created user (with its `role`) and the request context, `null` outside a request. */
@@ -54,6 +62,48 @@ export type UserCreatedHook = NonNullable<UserCreateHooks['after']>;
54
62
  /** The configured roles with `admin` first; `admin` is added when missing. */
55
63
  export declare const normalizeRoles: (roles?: string[]) => string[];
56
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
+ };
89
+ account: {
90
+ encryptOAuthTokens?: true | undefined;
91
+ accountLinking: {
92
+ allowDifferentEmails: true;
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
+ };
106
+ };
57
107
  plugins: [{
58
108
  id: "admin";
59
109
  version: string;
@@ -940,6 +990,19 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
940
990
  };
941
991
  };
942
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
+ };
943
1006
  defaultRole: string;
944
1007
  roles: {
945
1008
  [k: string]: import("better-auth/plugins").Role<import("better-auth/plugins").ExactRoleStatements<{
@@ -961,6 +1024,16 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
961
1024
  id: "jwt";
962
1025
  version: string;
963
1026
  options: NoInfer<{
1027
+ schema: {
1028
+ jwks: {
1029
+ fields: {
1030
+ publicKey: string;
1031
+ privateKey: string;
1032
+ createdAt: string;
1033
+ expiresAt: string;
1034
+ };
1035
+ };
1036
+ };
964
1037
  jwt: {
965
1038
  expirationTime: string;
966
1039
  issuer: string;
@@ -2458,7 +2531,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
2458
2531
  RESOLVED_USER_INVALID: import("better-auth").RawError<"RESOLVED_USER_INVALID">;
2459
2532
  };
2460
2533
  options: import("@better-auth/passkey").PasskeyOptions | undefined;
2461
- }, ...{
2534
+ }, ...({
2462
2535
  id: "email-otp";
2463
2536
  version: string;
2464
2537
  init(ctx: import("better-auth").AuthContext): {
@@ -2990,10 +3063,24 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
2990
3063
  INVALID_OTP: import("better-auth").RawError<"INVALID_OTP">;
2991
3064
  TOO_MANY_ATTEMPTS: import("better-auth").RawError<"TOO_MANY_ATTEMPTS">;
2992
3065
  };
2993
- }[], {
3066
+ } | {
3067
+ id: "generic-oauth";
3068
+ version: string;
3069
+ init: (ctx: import("better-auth").AuthContext) => Promise<{
3070
+ context: {
3071
+ socialProviders: import("better-auth").OAuthProvider<object, Partial<import("better-auth").ProviderOptions<object>>>[];
3072
+ };
3073
+ }>;
3074
+ options: import("better-auth/plugins").GenericOAuthOptions<string>;
3075
+ $ERROR_CODES: {
3076
+ INVALID_OAUTH_CONFIGURATION: import("better-auth").RawError<"INVALID_OAUTH_CONFIGURATION">;
3077
+ TOKEN_URL_NOT_FOUND: import("better-auth").RawError<"TOKEN_URL_NOT_FOUND">;
3078
+ };
3079
+ })[], {
2994
3080
  id: "protobase-sign-in-policy";
2995
3081
  schema: {
2996
3082
  readonly signInPolicy: {
3083
+ readonly modelName: "sign_in_policy";
2997
3084
  readonly fields: {
2998
3085
  readonly password: {
2999
3086
  readonly type: "string";
@@ -3002,6 +3089,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3002
3089
  readonly emailCode: {
3003
3090
  readonly type: "string";
3004
3091
  readonly required: true;
3092
+ readonly fieldName: "email_code";
3005
3093
  };
3006
3094
  readonly passkey: {
3007
3095
  readonly type: "string";
@@ -3010,19 +3098,29 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3010
3098
  readonly twoFactor: {
3011
3099
  readonly type: "string";
3012
3100
  readonly required: true;
3101
+ readonly fieldName: "two_factor";
3013
3102
  };
3014
3103
  readonly staffAccess: {
3015
3104
  readonly type: "string";
3016
3105
  readonly required: true;
3017
3106
  readonly defaultValue: "allowed";
3107
+ readonly fieldName: "staff_access";
3108
+ };
3109
+ readonly platformSignIn: {
3110
+ readonly type: "string";
3111
+ readonly required: true;
3112
+ readonly defaultValue: "allowed";
3113
+ readonly fieldName: "platform_sign_in";
3018
3114
  };
3019
3115
  readonly createdAt: {
3020
3116
  readonly type: "date";
3021
3117
  readonly required: true;
3118
+ readonly fieldName: "created_at";
3022
3119
  };
3023
3120
  readonly createdBy: {
3024
3121
  readonly type: "string";
3025
3122
  readonly required: false;
3123
+ readonly fieldName: "created_by";
3026
3124
  };
3027
3125
  };
3028
3126
  };
@@ -3041,7 +3139,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3041
3139
  expiresAt: Date;
3042
3140
  token: string;
3043
3141
  ipAddress?: string | null | undefined;
3044
- 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;
3045
3148
  };
3046
3149
  user: Record<string, any> & {
3047
3150
  id: string;
@@ -3065,7 +3168,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3065
3168
  expiresAt: Date;
3066
3169
  token: string;
3067
3170
  ipAddress?: string | null | undefined;
3068
- 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;
3069
3177
  };
3070
3178
  user: Record<string, any> & {
3071
3179
  id: string;
@@ -3088,7 +3196,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3088
3196
  expiresAt: Date;
3089
3197
  token: string;
3090
3198
  ipAddress?: string | null | undefined;
3091
- 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;
3092
3205
  };
3093
3206
  user: Record<string, any> & {
3094
3207
  id: string;
@@ -3124,6 +3237,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3124
3237
  }, {
3125
3238
  savedBy?: string | undefined;
3126
3239
  savedAt?: string | undefined;
3240
+ platformSignIn?: string | undefined;
3127
3241
  operator?: string | undefined;
3128
3242
  policy: SignInPolicy;
3129
3243
  effective: SignInPolicy;
@@ -3142,7 +3256,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3142
3256
  expiresAt: Date;
3143
3257
  token: string;
3144
3258
  ipAddress?: string | null | undefined;
3145
- 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;
3146
3265
  };
3147
3266
  user: Record<string, any> & {
3148
3267
  id: string;
@@ -3166,7 +3285,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3166
3285
  expiresAt: Date;
3167
3286
  token: string;
3168
3287
  ipAddress?: string | null | undefined;
3169
- 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;
3170
3294
  };
3171
3295
  user: Record<string, any> & {
3172
3296
  id: string;
@@ -3189,7 +3313,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3189
3313
  expiresAt: Date;
3190
3314
  token: string;
3191
3315
  ipAddress?: string | null | undefined;
3192
- 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;
3193
3322
  };
3194
3323
  user: Record<string, any> & {
3195
3324
  id: string;
@@ -3226,6 +3355,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3226
3355
  }, {
3227
3356
  savedBy?: string | undefined;
3228
3357
  savedAt?: string | undefined;
3358
+ platformSignIn?: string | undefined;
3229
3359
  operator?: string | undefined;
3230
3360
  policy: SignInPolicy;
3231
3361
  effective: SignInPolicy;
@@ -3243,7 +3373,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3243
3373
  expiresAt: Date;
3244
3374
  token: string;
3245
3375
  ipAddress?: string | null | undefined;
3246
- 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;
3247
3382
  };
3248
3383
  user: Record<string, any> & {
3249
3384
  id: string;
@@ -3265,7 +3400,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3265
3400
  twoFactor: boolean;
3266
3401
  authenticatorApp: boolean;
3267
3402
  };
3268
- missing: ("passkey" | "twoFactor")[];
3403
+ missing: ("twoFactor" | "passkey")[];
3269
3404
  }>;
3270
3405
  };
3271
3406
  hooks: {
@@ -3278,14 +3413,17 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3278
3413
  id: "protobase-staff-sign-in";
3279
3414
  schema: {
3280
3415
  readonly staffSignIn: {
3416
+ readonly modelName: "staff_sign_in";
3281
3417
  readonly fields: {
3282
3418
  readonly userId: {
3283
3419
  readonly type: "string";
3284
3420
  readonly required: true;
3421
+ readonly fieldName: "user_id";
3285
3422
  };
3286
3423
  readonly userEmail: {
3287
3424
  readonly type: "string";
3288
3425
  readonly required: true;
3426
+ readonly fieldName: "user_email";
3289
3427
  };
3290
3428
  readonly staff: {
3291
3429
  readonly type: "string";
@@ -3294,10 +3432,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3294
3432
  readonly staffName: {
3295
3433
  readonly type: "string";
3296
3434
  readonly required: false;
3435
+ readonly fieldName: "staff_name";
3297
3436
  };
3298
3437
  readonly staffSubject: {
3299
3438
  readonly type: "string";
3300
3439
  readonly required: true;
3440
+ readonly fieldName: "staff_subject";
3301
3441
  };
3302
3442
  readonly issuer: {
3303
3443
  readonly type: "string";
@@ -3310,18 +3450,22 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3310
3450
  readonly sessionId: {
3311
3451
  readonly type: "string";
3312
3452
  readonly required: true;
3453
+ readonly fieldName: "session_id";
3313
3454
  };
3314
3455
  readonly startedAt: {
3315
3456
  readonly type: "date";
3316
3457
  readonly required: true;
3458
+ readonly fieldName: "started_at";
3317
3459
  };
3318
3460
  readonly expiresAt: {
3319
3461
  readonly type: "date";
3320
3462
  readonly required: true;
3463
+ readonly fieldName: "expires_at";
3321
3464
  };
3322
3465
  readonly endedAt: {
3323
3466
  readonly type: "date";
3324
3467
  readonly required: false;
3468
+ readonly fieldName: "ended_at";
3325
3469
  };
3326
3470
  };
3327
3471
  };
@@ -3339,7 +3483,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3339
3483
  expiresAt: Date;
3340
3484
  token: string;
3341
3485
  ipAddress?: string | null | undefined;
3342
- 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;
3343
3492
  };
3344
3493
  user: Record<string, any> & {
3345
3494
  id: string;
@@ -3367,7 +3516,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3367
3516
  expiresAt: Date;
3368
3517
  token: string;
3369
3518
  ipAddress?: string | null | undefined;
3370
- 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;
3371
3525
  };
3372
3526
  user: Record<string, any> & {
3373
3527
  id: string;
@@ -3396,7 +3550,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3396
3550
  expiresAt: Date;
3397
3551
  token: string;
3398
3552
  ipAddress?: string | null | undefined;
3399
- 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;
3400
3559
  };
3401
3560
  user: Record<string, any> & {
3402
3561
  id: string;
@@ -3420,7 +3579,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3420
3579
  expiresAt: Date;
3421
3580
  token: string;
3422
3581
  ipAddress?: string | null | undefined;
3423
- 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;
3424
3588
  };
3425
3589
  user: Record<string, any> & {
3426
3590
  id: string;
@@ -3443,7 +3607,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3443
3607
  expiresAt: Date;
3444
3608
  token: string;
3445
3609
  ipAddress?: string | null | undefined;
3446
- 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;
3447
3616
  };
3448
3617
  user: Record<string, any> & {
3449
3618
  id: string;
@@ -3556,9 +3725,6 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3556
3725
  handler: () => void;
3557
3726
  };
3558
3727
  };
3559
- account?: {
3560
- encryptOAuthTokens: true;
3561
- } | undefined;
3562
3728
  socialProviders?: import("better-auth").SocialProviders | undefined;
3563
3729
  appName: string;
3564
3730
  database: any;
@@ -3585,6 +3751,10 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
3585
3751
  passwordReset: boolean;
3586
3752
  socialProviders: string[];
3587
3753
  operator: string | undefined;
3754
+ platformSignIn: {
3755
+ provider: string;
3756
+ name: string;
3757
+ } | undefined;
3588
3758
  };
3589
3759
  export type AdminAuth = ReturnType<typeof createAuth>;
3590
3760
  //#endregion
@@ -2,17 +2,20 @@ import { readMailSettings } from "../mail/mail-settings.js";
2
2
  import { smtpMailer } from "../mail/smtp-mailer.js";
3
3
  import { readOperatorSettings, resolveOperator } from "./operator-provider.js";
4
4
  import { passwordResetOptions } from "./password-reset.js";
5
+ import { platformSignInFor, platformSignInProvider, readPlatformSignIn, resolvePlatformSignIn } from "./platform-sign-in.js";
5
6
  import { signInPolicyPlugin } from "./policy-plugin.js";
6
7
  import { emailCodeOptions, twoFactorCodeOptions, unusedEmailCodePaths } from "./sign-in-mail.js";
8
+ import { coreNames, pluginNames } from "./snake-case-names.js";
7
9
  import { staffSignInPlugin } from "./staff-plugin.js";
8
10
  import { twoFactorAfterPasswordOrCode } from "./two-factor-challenge.js";
9
11
  import { passkey } from "@better-auth/passkey";
10
12
  import { APIError } from "better-auth/api";
11
13
  import { betterAuth } from "better-auth";
12
- import { admin, emailOTP, jwt } from "better-auth/plugins";
14
+ import { admin, emailOTP, genericOAuth, jwt } from "better-auth/plugins";
13
15
  import { adminAc, userAc } from "better-auth/plugins/admin/access";
14
16
  //#region src/better-auth/create-auth.ts
15
17
  const platformOperator = () => readOperatorSettings(globalThis.process?.env ?? {});
18
+ const platformSignInSettings = () => readPlatformSignIn(globalThis.process?.env ?? {});
16
19
  const platformMailer = () => {
17
20
  const settings = readMailSettings(globalThis.process?.env ?? {});
18
21
  return settings && smtpMailer(settings);
@@ -33,9 +36,13 @@ const normalizeRoles = (roles = ["admin", "user"]) => {
33
36
  return ["admin", ...new Set(roles.filter((role) => role !== "admin"))];
34
37
  };
35
38
  const adminImpersonationPaths = ["/admin/impersonate-user", "/admin/stop-impersonating"];
36
- /** The options `createAuth` gives Better Auth, with the roles, default role, mailer and operator provider already resolved. */
37
- const betterAuthOptions = (options, { roles, defaultRole, mailer, operator }) => {
39
+ /**
40
+ * The options `createAuth` gives Better Auth, with the roles, default role, mailer, operator provider and platform
41
+ * sign-in provider already resolved.
42
+ */
43
+ const betterAuthOptions = (options, { roles, defaultRole, mailer, operator, platformSignIn }) => {
38
44
  const secure = options.baseURL.startsWith("https://");
45
+ const signIn = platformSignInFor(platformSignIn, Object.keys(options.socialProviders ?? {}));
39
46
  return {
40
47
  appName: options.appName ?? "Protobase",
41
48
  database: options.database,
@@ -54,13 +61,22 @@ const betterAuthOptions = (options, { roles, defaultRole, mailer, operator }) =>
54
61
  ...mailer && passwordResetOptions(mailer)
55
62
  },
56
63
  ...options.socialProviders && { socialProviders: options.socialProviders },
57
- ...options.encryptOAuthTokens && { account: { encryptOAuthTokens: true } },
64
+ user: coreNames.user,
65
+ session: coreNames.session,
66
+ verification: coreNames.verification,
67
+ account: {
68
+ ...coreNames.account,
69
+ accountLinking: { allowDifferentEmails: true },
70
+ ...options.encryptOAuthTokens && { encryptOAuthTokens: true }
71
+ },
58
72
  plugins: [
59
73
  admin({
74
+ schema: pluginNames.admin,
60
75
  defaultRole: defaultRole ?? "unassigned",
61
76
  roles: Object.fromEntries(roles.map((role) => [role, role === "admin" ? adminAc : userAc]))
62
77
  }),
63
78
  jwt({
79
+ schema: pluginNames.jwt,
64
80
  jwt: {
65
81
  expirationTime: tokenLifetime,
66
82
  issuer: options.baseURL,
@@ -73,15 +89,21 @@ const betterAuthOptions = (options, { roles, defaultRole, mailer, operator }) =>
73
89
  disableSettingJwtHeader: true
74
90
  }),
75
91
  twoFactorAfterPasswordOrCode({
92
+ schema: pluginNames.twoFactor,
76
93
  allowPasswordless: true,
77
94
  ...mailer && { otpOptions: twoFactorCodeOptions(mailer) }
78
95
  }),
79
- passkey(),
96
+ passkey({ schema: pluginNames.passkey }),
80
97
  ...mailer ? [emailOTP(emailCodeOptions(mailer))] : [],
98
+ ...signIn ? [genericOAuth({ config: [platformSignInProvider(signIn)] })] : [],
81
99
  signInPolicyPlugin({
82
100
  mail: Boolean(mailer),
83
101
  socialProviders: Object.keys(options.socialProviders ?? {}),
84
- ...operator && { operator: operator.name }
102
+ ...operator && { operator: operator.name },
103
+ ...signIn && { platformSignIn: {
104
+ provider: signIn.provider,
105
+ name: signIn.name
106
+ } }
85
107
  }),
86
108
  staffSignInPlugin({
87
109
  ...operator && { operator },
@@ -134,20 +156,27 @@ const createAuth = (options) => {
134
156
  const mailer = options.mailer === void 0 ? platformMailer() : options.mailer || void 0;
135
157
  const provider = options.operator === void 0 ? platformOperator() : options.operator || void 0;
136
158
  const operator = provider && resolveOperator(provider);
159
+ const signInSettings = options.signInProvider === void 0 ? platformSignInSettings() : options.signInProvider || void 0;
160
+ const socialProviders = Object.keys(options.socialProviders ?? {});
161
+ const platformSignIn = platformSignInFor(signInSettings && resolvePlatformSignIn(signInSettings), socialProviders);
137
162
  const auth = betterAuth(betterAuthOptions(options, {
138
163
  roles,
139
164
  defaultRole,
140
165
  mailer,
141
- operator
166
+ operator,
167
+ platformSignIn
142
168
  }));
143
- const socialProviders = Object.keys(options.socialProviders ?? {});
144
169
  return Object.assign(auth, {
145
170
  roles,
146
171
  defaultRole,
147
172
  mail: Boolean(mailer),
148
173
  passwordReset: Boolean(mailer),
149
174
  socialProviders,
150
- operator: operator?.name
175
+ operator: operator?.name,
176
+ platformSignIn: platformSignIn && {
177
+ provider: platformSignIn.provider,
178
+ name: platformSignIn.name
179
+ }
151
180
  });
152
181
  };
153
182
  //#endregion
@@ -0,0 +1,30 @@
1
+ import "better-auth/plugins";
2
+ //#region src/better-auth/platform-sign-in.d.ts
3
+ /**
4
+ * A sign-in provider the platform adds to the app, for people who already have an account: any OpenID Connect
5
+ * provider, with this app registered as a confidential client whose redirect URI is
6
+ * `{baseURL}/api/auth/callback/{provider}`. It never creates accounts.
7
+ */
8
+ export type PlatformSignIn = {
9
+ /** The provider's issuer URL; its settings are read from `{issuer}/.well-known/openid-configuration`. */
10
+ issuer: string;
11
+ clientId: string;
12
+ clientSecret: string;
13
+ /**
14
+ * The id of the provider: the button, the callback path, and the namespace of the linked accounts. Default `oidc`.
15
+ * Use `github` only when the provider's `sub` is the GitHub user id, so its accounts are those of Better Auth's own
16
+ * GitHub provider.
17
+ */
18
+ provider?: string;
19
+ /** Shown on the sign-in page as "Continue with {name}". Default `GitHub` for the `github` provider, `SSO` otherwise. */
20
+ name?: string;
21
+ /** Scopes asked for besides `openid email profile`. */
22
+ scopes?: string[];
23
+ };
24
+ /**
25
+ * The provider the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID` and
26
+ * `PROTOBASE_SIGN_IN_CLIENT_SECRET` (with `PROTOBASE_SIGN_IN_PROVIDER` and `PROTOBASE_SIGN_IN_NAME` optional);
27
+ * `undefined` when none of the three is set. Some but not all of them stops the server at startup.
28
+ */
29
+ export declare const readPlatformSignIn: (env: Record<string, string | undefined>) => PlatformSignIn | undefined;
30
+ //#endregion