@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,88 @@
1
+ import { decodeJwt } from "jose";
2
+ //#region src/better-auth/platform-sign-in.ts
3
+ const signInIssuerVariable = "PROTOBASE_SIGN_IN_ISSUER";
4
+ const signInClientIdVariable = "PROTOBASE_SIGN_IN_CLIENT_ID";
5
+ const signInClientSecretVariable = "PROTOBASE_SIGN_IN_CLIENT_SECRET";
6
+ const signInProviderVariable = "PROTOBASE_SIGN_IN_PROVIDER";
7
+ const signInNameVariable = "PROTOBASE_SIGN_IN_NAME";
8
+ /**
9
+ * The provider the platform passes in `PROTOBASE_SIGN_IN_ISSUER`, `PROTOBASE_SIGN_IN_CLIENT_ID` and
10
+ * `PROTOBASE_SIGN_IN_CLIENT_SECRET` (with `PROTOBASE_SIGN_IN_PROVIDER` and `PROTOBASE_SIGN_IN_NAME` optional);
11
+ * `undefined` when none of the three is set. Some but not all of them stops the server at startup.
12
+ */
13
+ const readPlatformSignIn = (env) => {
14
+ const required = [
15
+ signInIssuerVariable,
16
+ signInClientIdVariable,
17
+ signInClientSecretVariable
18
+ ];
19
+ const [issuer, clientId, clientSecret] = required.map((name) => env[name]?.trim() || void 0);
20
+ if (!issuer && !clientId && !clientSecret) return void 0;
21
+ if (!issuer || !clientId || !clientSecret) throw new Error(`${required.join(", ")} go together: set all three for the platform sign-in provider, or none`);
22
+ const provider = env[signInProviderVariable]?.trim();
23
+ const name = env[signInNameVariable]?.trim();
24
+ return {
25
+ issuer,
26
+ clientId,
27
+ clientSecret,
28
+ ...provider && { provider },
29
+ ...name && { name }
30
+ };
31
+ };
32
+ const providerId = /^[a-z][a-z0-9-]*$/;
33
+ /** The provider with its defaults filled in; an issuer that is not a URL or a malformed id is an error. */
34
+ const resolvePlatformSignIn = (signIn) => {
35
+ if (!URL.canParse(signIn.issuer)) throw new Error(`The platform sign-in provider's issuer must be a URL, not "${signIn.issuer}"`);
36
+ const provider = signIn.provider ?? "oidc";
37
+ if (!providerId.test(provider)) throw new Error(`Invalid sign-in provider id "${provider}": use lowercase letters, digits or "-"`);
38
+ return {
39
+ ...signIn,
40
+ provider,
41
+ name: signIn.name ?? (provider === "github" ? "GitHub" : "SSO"),
42
+ scopes: signIn.scopes ?? []
43
+ };
44
+ };
45
+ /** The platform provider, unless the app configures a provider of its own with the same id, which then wins. */
46
+ const platformSignInFor = (signIn, socialProviders) => signIn && !socialProviders.includes(signIn.provider) ? signIn : void 0;
47
+ /**
48
+ * The person as an ID token Better Auth already verified (the provider's keys, this client and the nonce) says, once
49
+ * its issuer is the configured one; `null` without an ID token, so nothing comes from an unverified source.
50
+ */
51
+ const idTokenUser = (idToken, issuer) => {
52
+ if (!idToken) return null;
53
+ const claims = decodeJwt(idToken);
54
+ if (claims.iss !== issuer || typeof claims.sub !== "string" || !claims.sub || typeof claims.email !== "string") return null;
55
+ return {
56
+ id: claims.sub,
57
+ sub: claims.sub,
58
+ email: claims.email,
59
+ emailVerified: claims.email_verified === true,
60
+ name: typeof claims.name === "string" && claims.name ? claims.name : claims.email.split("@")[0],
61
+ ...typeof claims.picture === "string" && { image: claims.picture }
62
+ };
63
+ };
64
+ /**
65
+ * The provider for Better Auth's generic OAuth plugin: discovery, PKCE, the client secret in a Basic header, an ID
66
+ * token that must verify against the provider's keys, and no sign-up. A provider whose settings cannot be read at
67
+ * startup is left out until the next start.
68
+ */
69
+ const platformSignInProvider = (signIn) => ({
70
+ providerId: signIn.provider,
71
+ name: signIn.name,
72
+ clientId: signIn.clientId,
73
+ clientSecret: signIn.clientSecret,
74
+ discoveryUrl: `${signIn.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`,
75
+ scopes: [
76
+ "openid",
77
+ "email",
78
+ "profile",
79
+ ...signIn.scopes
80
+ ],
81
+ pkce: true,
82
+ requireIdTokenVerification: true,
83
+ authentication: "basic",
84
+ disableSignUp: true,
85
+ getUserInfo: async (tokens) => idTokenUser(tokens.idToken, signIn.issuer)
86
+ });
87
+ //#endregion
88
+ export { idTokenUser, platformSignInFor, platformSignInProvider, readPlatformSignIn, resolvePlatformSignIn, signInClientIdVariable, signInClientSecretVariable, signInIssuerVariable, signInNameVariable, signInProviderVariable };
@@ -36,8 +36,9 @@ const refuse = (status, refusal) => new APIError(status, refusal);
36
36
  * applies it to Better Auth's own endpoints. A method the policy turns off answers `403 SIGN_IN_METHOD_FORBIDDEN`;
37
37
  * someone without what it requires gets a session, so they can set it up, but no API token (`403 SIGN_IN_SETUP_REQUIRED`).
38
38
  */
39
- const signInPolicyPlugin = ({ mail, socialProviders, operator }) => {
39
+ const signInPolicyPlugin = ({ mail, socialProviders, operator, platformSignIn }) => {
40
40
  const currentPolicy = async (context) => effectiveSignInPolicy((await readSignInPolicy(context.adapter)).policy, { mail });
41
+ const providersUnder = (policy) => [...socialProviders, ...platformSignIn && policy.platformSignIn === "allowed" ? [platformSignIn.provider] : []];
41
42
  const policyAdmin = adminOnly("Only an admin can change how people sign in.");
42
43
  const policyAnswer = async (context) => {
43
44
  const stored = await readSignInPolicy(context.adapter);
@@ -47,6 +48,7 @@ const signInPolicyPlugin = ({ mail, socialProviders, operator }) => {
47
48
  effective: effectiveSignInPolicy(stored.policy, { mail }),
48
49
  mail,
49
50
  ...operator && { operator },
51
+ ...platformSignIn && { platformSignIn: platformSignIn.name },
50
52
  ...stored.savedAt && { savedAt: stored.savedAt },
51
53
  ...savedBy && { savedBy }
52
54
  };
@@ -72,9 +74,10 @@ const signInPolicyPlugin = ({ mail, socialProviders, operator }) => {
72
74
  message: "Choose one of the offered rules for every sign-in method."
73
75
  });
74
76
  const user = ctx.context.session.user;
77
+ const admin = await accountMethods(ctx.context, user, providersUnder(effectiveSignInPolicy(policy, { mail })));
75
78
  const problem = signInPolicyProblem(policy, {
76
79
  mail,
77
- admin: await accountMethods(ctx.context, user, socialProviders)
80
+ admin
78
81
  });
79
82
  if (problem) throw refuse("BAD_REQUEST", {
80
83
  code: "SIGN_IN_POLICY_REFUSED",
@@ -92,7 +95,7 @@ const signInPolicyPlugin = ({ mail, socialProviders, operator }) => {
92
95
  use: [sessionMiddleware]
93
96
  }, async (ctx) => {
94
97
  const policy = await currentPolicy(ctx.context);
95
- const { socialSignIn: _, ...account } = await accountMethods(ctx.context, ctx.context.session.user, socialProviders);
98
+ const { socialSignIn: _, ...account } = await accountMethods(ctx.context, ctx.context.session.user, providersUnder(policy));
96
99
  return ctx.json({
97
100
  policy,
98
101
  mail,
@@ -112,14 +115,16 @@ const signInPolicyPlugin = ({ mail, socialProviders, operator }) => {
112
115
  const policy = await currentPolicy(ctx.context);
113
116
  const refusal = policyRefusal(policy, {
114
117
  path,
115
- body: ctx.body
118
+ body: ctx.body,
119
+ params: ctx.params,
120
+ ...platformSignIn && { platformProvider: platformSignIn.provider }
116
121
  });
117
122
  if (refusal) throw refuse("FORBIDDEN", refusal);
118
123
  if (path === "/sign-in/email-otp") return vouchForCreatedAccount(ctx.context, ctx.body.email);
119
124
  if (!(path === "/token" && (policy.twoFactor === "required" || policy.passkey === "required") || path === "/passkey/delete-passkey" && policy.passkey === "required")) return;
120
125
  const session = await getSessionFromCtx(ctx);
121
126
  if (!session || isStaffSession(session.session)) return;
122
- const account = await accountMethods(ctx.context, session.user, socialProviders);
127
+ const account = await accountMethods(ctx.context, session.user, providersUnder(policy));
123
128
  if (path === "/token" && missingRequiredMethods(policy, account).length > 0) throw refuse("FORBIDDEN", setupRequired);
124
129
  if (path === "/passkey/delete-passkey" && account.passkeys <= 1) throw refuse("FORBIDDEN", lastPasskeyRequired);
125
130
  })
@@ -2,37 +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
- createdAt: {
28
- type: "date",
29
- required: true
30
- },
31
- createdBy: {
32
- type: "string",
33
- 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
+ }
34
48
  }
35
- } } };
49
+ } };
36
50
  /** The newest saved policy, or the default when none was saved. A row that no longer parses counts as none. */
37
51
  const readSignInPolicy = async (adapter) => {
38
52
  const [row] = await adapter.findMany({
@@ -13,6 +13,8 @@ const passkeyPaths = [
13
13
  "/passkey/verify-registration"
14
14
  ];
15
15
  const staffSignInPath = "/staff/sign-in";
16
+ const providerPaths = ["/sign-in/social", "/link-social"];
17
+ const callbackPath = "/callback/:id";
16
18
  const accountPaths = [
17
19
  "/email-otp/send-verification-otp",
18
20
  "/passkey/delete-passkey",
@@ -26,23 +28,29 @@ const isPoliced = (path) => path !== void 0 && ([
26
28
  ...emailCodePaths,
27
29
  ...passkeyPaths,
28
30
  ...accountPaths,
31
+ ...providerPaths,
32
+ callbackPath,
29
33
  "/staff/sign-in"
30
34
  ].includes(path) || path.startsWith("/reset-password/"));
31
- const methodOf = (path, body) => {
35
+ const providerOf = ({ path, body, params }) => providerPaths.includes(path) ? body?.provider : path === callbackPath ? params?.id : void 0;
36
+ const methodOf = (request) => {
37
+ const { path, body } = request;
32
38
  if (passwordPaths.includes(path) || path.startsWith("/reset-password/")) return "password";
33
39
  if (emailCodePaths.includes(path)) return "emailCode";
34
40
  if (path === "/email-otp/send-verification-otp" && body?.type === "sign-in") return "emailCode";
35
41
  if (passkeyPaths.includes(path)) return "passkey";
36
42
  if (path === "/two-factor/enable") return "twoFactor";
37
43
  if (path === "/staff/sign-in") return "staffAccess";
44
+ if (request.platformProvider !== void 0 && providerOf(request) === request.platformProvider) return "platformSignIn";
38
45
  };
39
46
  /**
40
47
  * Why the policy refuses a request to Better Auth at `path`, or `undefined` when it lets it through: a method the
41
48
  * policy turns off, or turning off two-factor authentication while the policy requires it. Removing the last passkey
42
49
  * while passkeys are required depends on the account, so the plugin checks that itself.
43
50
  */
44
- const policyRefusal = (policy, { path, body }) => {
45
- const method = methodOf(path, body);
51
+ const policyRefusal = (policy, request) => {
52
+ const { path } = request;
53
+ const method = methodOf(request);
46
54
  if (method && policy[method] === "forbidden") return {
47
55
  code: "SIGN_IN_METHOD_FORBIDDEN",
48
56
  message: signInPolicyMethods[method].forbidden
@@ -7,7 +7,8 @@ export type StaffAccessRule = 'allowed' | 'notify' | 'forbidden';
7
7
  * The app's sign-in policy. `password`, `emailCode` and `passkey` are ways to sign in; `twoFactor` is the second step
8
8
  * after a password or an emailed code. Only a passkey and two-factor authentication can be required: they are set up
9
9
  * once per person, so "required" sends everyone without one to set it up. `staffAccess` is whether the operator's
10
- * staff may sign in as people, for support.
10
+ * staff may sign in as people, for support. `platformSignIn` is whether people may sign in through the provider the
11
+ * platform adds (see `PlatformSignIn`), which the app's developer did not choose.
11
12
  */
12
13
  export type SignInPolicy = {
13
14
  password: 'allowed' | 'forbidden';
@@ -15,6 +16,7 @@ export type SignInPolicy = {
15
16
  passkey: SignInRule;
16
17
  twoFactor: SignInRule;
17
18
  staffAccess: StaffAccessRule;
19
+ platformSignIn: 'allowed' | 'forbidden';
18
20
  };
19
21
  /** Each method with the rules it takes, its name, and what the server answers when the policy turns it off. */
20
22
  export declare const signInPolicyMethods: {
@@ -43,6 +45,11 @@ export declare const signInPolicyMethods: {
43
45
  readonly rules: readonly ["allowed", "notify", "forbidden"];
44
46
  readonly forbidden: "Staff sign-in is turned off for this app.";
45
47
  };
48
+ readonly platformSignIn: {
49
+ readonly label: "Sign-in through the platform";
50
+ readonly rules: readonly ["allowed", "forbidden"];
51
+ readonly forbidden: "Signing in through the platform is turned off for this app.";
52
+ };
46
53
  };
47
54
  /** Everything allowed and nothing required: the policy of an app whose admins never saved one. */
48
55
  export declare const defaultSignInPolicy: SignInPolicy;
@@ -37,6 +37,11 @@ const signInPolicyMethods = {
37
37
  "forbidden"
38
38
  ],
39
39
  forbidden: "Staff sign-in is turned off for this app."
40
+ },
41
+ platformSignIn: {
42
+ label: "Sign-in through the platform",
43
+ rules: ["allowed", "forbidden"],
44
+ forbidden: "Signing in through the platform is turned off for this app."
40
45
  }
41
46
  };
42
47
  /** Everything allowed and nothing required: the policy of an app whose admins never saved one. */
@@ -45,7 +50,8 @@ const defaultSignInPolicy = {
45
50
  emailCode: "allowed",
46
51
  passkey: "allowed",
47
52
  twoFactor: "allowed",
48
- staffAccess: "allowed"
53
+ staffAccess: "allowed",
54
+ platformSignIn: "allowed"
49
55
  };
50
56
  const methodNames = Object.keys(signInPolicyMethods);
51
57
  /** A policy from a request body or a stored row; `undefined` when a method is missing or has a rule it does not take. */
@@ -57,13 +63,21 @@ const parseSignInPolicy = (input) => {
57
63
  /**
58
64
  * The policy as it applies. Emailed codes need mail; without mail, password sign-in is on whatever the policy says, so
59
65
  * an app whose mail settings went missing does not lock out everyone who had only emailed codes left. Staff sign-in
60
- * that emails the person is off without mail, since nobody would be told.
66
+ * that emails the person is off without mail, since nobody would be told. Sign-in through the platform is off while
67
+ * passkeys or two-factor authentication are required: it skips the app's second step, and the provider's own cannot
68
+ * be checked.
61
69
  */
62
- const effectiveSignInPolicy = (policy, { mail }) => mail ? policy : {
63
- ...policy,
64
- password: "allowed",
65
- emailCode: "forbidden",
66
- ...policy.staffAccess === "notify" && { staffAccess: "forbidden" }
70
+ const effectiveSignInPolicy = (policy, { mail }) => {
71
+ const strict = policy.passkey === "required" || policy.twoFactor === "required";
72
+ return {
73
+ ...policy,
74
+ ...!mail && {
75
+ password: "allowed",
76
+ emailCode: "forbidden"
77
+ },
78
+ ...!mail && policy.staffAccess === "notify" && { staffAccess: "forbidden" },
79
+ ...strict && { platformSignIn: "forbidden" }
80
+ };
67
81
  };
68
82
  /** The required methods `account` has not set up yet, in the order a setup page asks for them. */
69
83
  const missingRequiredMethods = (policy, account) => [...policy.twoFactor === "required" && !account.twoFactor ? ["twoFactor"] : [], ...policy.passkey === "required" && account.passkeys === 0 ? ["passkey"] : []];
@@ -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,