@protobase/server 0.25.9 → 0.25.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/better-auth/create-auth.d.ts +59 -14
- package/dist/better-auth/create-auth.js +20 -8
- package/dist/better-auth/password-hashes.d.ts +4 -0
- package/dist/better-auth/password-hashes.js +31 -0
- package/dist/better-auth/snake-case-names.js +1 -6
- package/dist/better-auth/user-table.d.ts +31 -0
- package/dist/better-auth/user-table.js +69 -0
- package/dist/better-auth/users.d.ts +2 -0
- package/dist/better-auth/users.js +2 -1
- package/package.json +5 -4
|
@@ -8,6 +8,8 @@ import { OrganizationsOptions, ResolvedOrganizations } from "./organizations/opt
|
|
|
8
8
|
import { PlatformSignIn } from "./platform-sign-in.js";
|
|
9
9
|
import { SsoOptions } from "./saml-sign-in.js";
|
|
10
10
|
import { signUpPaths } from "./sign-up/sign-up-plugin.js";
|
|
11
|
+
import { PasswordHash } from "./password-hashes.js";
|
|
12
|
+
import { UserTableOptions } from "./user-table.js";
|
|
11
13
|
import "../index.js";
|
|
12
14
|
import { BetterAuthOptions } from "better-auth";
|
|
13
15
|
//#region src/better-auth/create-auth.d.ts
|
|
@@ -80,9 +82,22 @@ export type CreateAuthOptions = {
|
|
|
80
82
|
onUserCreated?: UserCreatedHook;
|
|
81
83
|
/**
|
|
82
84
|
* The id of a new user, by any route. Default Better Auth's random 32-character text. `.owner()` fields hold it: a
|
|
83
|
-
* `text` column for the default,
|
|
85
|
+
* `text` column for the default, an integer column when this takes the next value of the app's own users sequence,
|
|
86
|
+
* or a `uuid` column with `'uuid'`, which makes a random UUID in code and makes every id column of the store a `uuid`.
|
|
84
87
|
*/
|
|
85
|
-
generateUserId?: () => string | Promise<string
|
|
88
|
+
generateUserId?: (() => string | Promise<string>) | 'uuid';
|
|
89
|
+
/**
|
|
90
|
+
* The user table, when it is one another system made, such as Supabase's `auth.users`: its name, the columns named
|
|
91
|
+
* otherwise than Protobase names them, and a JSON column for each user's metadata, for example `{ table: 'users',
|
|
92
|
+
* columns: { role: 'app_role' }, metadata: 'raw_user_meta_data' }`. The other tables keep their names. Default the
|
|
93
|
+
* table `protobase auth migration` makes.
|
|
94
|
+
*/
|
|
95
|
+
users?: UserTableOptions;
|
|
96
|
+
/**
|
|
97
|
+
* Password hashes another system wrote that sign-in accepts besides Better Auth's own, such as `['bcrypt']` for
|
|
98
|
+
* Supabase Auth's. The first sign-in with one stores the password again as Better Auth hashes it. Default none.
|
|
99
|
+
*/
|
|
100
|
+
passwordHashes?: PasswordHash[];
|
|
86
101
|
/**
|
|
87
102
|
* The identity provider the operator's staff sign in with to sign in as people of this app, for support. Defaults
|
|
88
103
|
* to the one the platform passes in `PROTOBASE_OPERATOR_ISSUER`, `PROTOBASE_OPERATOR_CLIENT_ID` and
|
|
@@ -123,22 +138,35 @@ export type UserCreatedHook = NonNullable<UserCreateHooks['after']>;
|
|
|
123
138
|
export declare const normalizeRoles: (roles?: RolesInput) => string[];
|
|
124
139
|
export declare const createAuth: (options: CreateAuthOptions) => import("better-auth").Auth<{
|
|
125
140
|
user: {
|
|
141
|
+
modelName: string;
|
|
126
142
|
fields: {
|
|
143
|
+
name: string;
|
|
144
|
+
email: string;
|
|
127
145
|
emailVerified: string;
|
|
146
|
+
image: string;
|
|
128
147
|
createdAt: string;
|
|
129
148
|
updatedAt: string;
|
|
130
149
|
};
|
|
131
150
|
} | {
|
|
132
151
|
additionalFields: {
|
|
133
|
-
|
|
152
|
+
metadata?: {
|
|
153
|
+
readonly type: "json";
|
|
154
|
+
readonly required: false;
|
|
155
|
+
readonly fieldName: string;
|
|
156
|
+
} | undefined;
|
|
157
|
+
lastOrganizationId?: {
|
|
134
158
|
readonly type: "string";
|
|
135
159
|
readonly required: false;
|
|
136
160
|
readonly input: false;
|
|
137
161
|
readonly fieldName: "last_organization_id";
|
|
138
|
-
};
|
|
162
|
+
} | undefined;
|
|
139
163
|
};
|
|
164
|
+
modelName: string;
|
|
140
165
|
fields: {
|
|
166
|
+
name: string;
|
|
167
|
+
email: string;
|
|
141
168
|
emailVerified: string;
|
|
169
|
+
image: string;
|
|
142
170
|
createdAt: string;
|
|
143
171
|
updatedAt: string;
|
|
144
172
|
};
|
|
@@ -178,7 +206,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
178
206
|
updatedAt: string;
|
|
179
207
|
};
|
|
180
208
|
};
|
|
181
|
-
plugins:
|
|
209
|
+
plugins: ({
|
|
182
210
|
id: "admin";
|
|
183
211
|
version: string;
|
|
184
212
|
init(): {
|
|
@@ -1094,7 +1122,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
1094
1122
|
}>;
|
|
1095
1123
|
};
|
|
1096
1124
|
}>;
|
|
1097
|
-
}
|
|
1125
|
+
} | {
|
|
1098
1126
|
id: "jwt";
|
|
1099
1127
|
version: string;
|
|
1100
1128
|
options: NoInfer<{
|
|
@@ -1362,7 +1390,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
1362
1390
|
};
|
|
1363
1391
|
};
|
|
1364
1392
|
};
|
|
1365
|
-
}
|
|
1393
|
+
} | {
|
|
1366
1394
|
hooks: {
|
|
1367
1395
|
after: {
|
|
1368
1396
|
matcher: (context: Parameters<(context: import("better-auth").HookEndpointContext) => boolean>[0]) => boolean;
|
|
@@ -2030,7 +2058,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
2030
2058
|
ACCOUNT_TEMPORARILY_LOCKED: import("better-auth").RawError<"ACCOUNT_TEMPORARILY_LOCKED">;
|
|
2031
2059
|
INVALID_TWO_FACTOR_COOKIE: import("better-auth").RawError<"INVALID_TWO_FACTOR_COOKIE">;
|
|
2032
2060
|
};
|
|
2033
|
-
}
|
|
2061
|
+
} | {
|
|
2034
2062
|
endpoints: {
|
|
2035
2063
|
generatePasskeyAuthenticationOptions: import("better-auth").StrictEndpoint<"/passkey/generate-authenticate-options", {
|
|
2036
2064
|
method: "GET";
|
|
@@ -2332,7 +2360,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
2332
2360
|
expiresAt: Date;
|
|
2333
2361
|
token: string;
|
|
2334
2362
|
ipAddress?: string | null | undefined;
|
|
2335
|
-
userAgent
|
|
2363
|
+
userAgent?: string | null | undefined;
|
|
2336
2364
|
};
|
|
2337
2365
|
user: Record<string, any> & {
|
|
2338
2366
|
id: string;
|
|
@@ -2511,7 +2539,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
2511
2539
|
RESOLVED_USER_INVALID: import("better-auth").RawError<"RESOLVED_USER_INVALID">;
|
|
2512
2540
|
};
|
|
2513
2541
|
options: import("@better-auth/passkey").PasskeyOptions | undefined;
|
|
2514
|
-
}
|
|
2542
|
+
} | {
|
|
2515
2543
|
id: "email-otp";
|
|
2516
2544
|
version: string;
|
|
2517
2545
|
init(ctx: import("better-auth").AuthContext): {
|
|
@@ -5198,8 +5226,8 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5198
5226
|
appRoles: string[];
|
|
5199
5227
|
id: string;
|
|
5200
5228
|
createdAt: Date;
|
|
5201
|
-
userId: string;
|
|
5202
5229
|
role: string;
|
|
5230
|
+
userId: string;
|
|
5203
5231
|
organizationId: string;
|
|
5204
5232
|
};
|
|
5205
5233
|
}>;
|
|
@@ -5212,7 +5240,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5212
5240
|
handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
|
|
5213
5241
|
}[];
|
|
5214
5242
|
};
|
|
5215
|
-
}
|
|
5243
|
+
} | {
|
|
5216
5244
|
id: "protobase-users";
|
|
5217
5245
|
endpoints: {
|
|
5218
5246
|
getInvitation: import("better-auth").StrictEndpoint<"/invitation", {
|
|
@@ -5262,8 +5290,8 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5262
5290
|
appRoles: string[];
|
|
5263
5291
|
id: string;
|
|
5264
5292
|
createdAt: Date;
|
|
5265
|
-
userId: string;
|
|
5266
5293
|
role: string;
|
|
5294
|
+
userId: string;
|
|
5267
5295
|
organizationId: string;
|
|
5268
5296
|
};
|
|
5269
5297
|
}>;
|
|
@@ -5678,7 +5706,15 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5678
5706
|
handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
|
|
5679
5707
|
}[];
|
|
5680
5708
|
};
|
|
5681
|
-
}
|
|
5709
|
+
} | {
|
|
5710
|
+
id: "protobase-rehash-passwords";
|
|
5711
|
+
hooks: {
|
|
5712
|
+
after: {
|
|
5713
|
+
matcher: (ctx: import("better-auth").HookEndpointContext) => boolean;
|
|
5714
|
+
handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
|
|
5715
|
+
}[];
|
|
5716
|
+
};
|
|
5717
|
+
})[];
|
|
5682
5718
|
disabledPaths: string[];
|
|
5683
5719
|
databaseHooks: {
|
|
5684
5720
|
session?: {
|
|
@@ -5757,6 +5793,9 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5757
5793
|
};
|
|
5758
5794
|
};
|
|
5759
5795
|
advanced: {
|
|
5796
|
+
database?: {
|
|
5797
|
+
generateId: "uuid";
|
|
5798
|
+
} | undefined;
|
|
5760
5799
|
useSecureCookies: boolean;
|
|
5761
5800
|
defaultCookieAttributes: {
|
|
5762
5801
|
sameSite: "lax";
|
|
@@ -5785,6 +5824,12 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
5785
5824
|
url: string;
|
|
5786
5825
|
token: string;
|
|
5787
5826
|
}) => Promise<void>) | undefined;
|
|
5827
|
+
password?: {
|
|
5828
|
+
verify: ({ hash, password }: {
|
|
5829
|
+
hash: string;
|
|
5830
|
+
password: string;
|
|
5831
|
+
}) => Promise<boolean>;
|
|
5832
|
+
} | undefined;
|
|
5788
5833
|
enabled: true;
|
|
5789
5834
|
disableSignUp: true;
|
|
5790
5835
|
minPasswordLength: number;
|
|
@@ -21,8 +21,10 @@ import { signInPolicyPlugin } from "./policy-plugin.js";
|
|
|
21
21
|
import { emailInDomains, resolveSamlProviders, samlDisabledPaths, samlGuardPlugin, samlPlugin } from "./saml-sign-in.js";
|
|
22
22
|
import { emailCodeOptions, twoFactorCodeOptions, unusedEmailCodePaths } from "./sign-in-mail.js";
|
|
23
23
|
import { signUpPaths, signUpPlugin } from "./sign-up/sign-up-plugin.js";
|
|
24
|
+
import { rehashPasswordsPlugin, verifyAcceptedHash } from "./password-hashes.js";
|
|
24
25
|
import { staffSignInPlugin } from "./staff-plugin.js";
|
|
25
26
|
import { twoFactorAfterPasswordOrCode } from "./two-factor-challenge.js";
|
|
27
|
+
import { metadataField, userFieldsNamed, userModel, userTable, withUserColumns } from "./user-table.js";
|
|
26
28
|
import { usersPlugin } from "./users/users-plugin.js";
|
|
27
29
|
import { APIError } from "better-auth/api";
|
|
28
30
|
import { betterAuth } from "better-auth";
|
|
@@ -44,6 +46,7 @@ const tokenLifetime = "15m";
|
|
|
44
46
|
/** The configured roles with `admin` first; `admin` is added when missing. */
|
|
45
47
|
const normalizeRoles = (roles) => roleDefinitions(roles).names;
|
|
46
48
|
const adminImpersonationPaths = ["/admin/impersonate-user", "/admin/stop-impersonating"];
|
|
49
|
+
const newUserId = (generate) => generate === "uuid" ? crypto.randomUUID() : generate();
|
|
47
50
|
const noContext = () => Promise.reject(/* @__PURE__ */ new Error("The auth context is read only once createAuth has made the instance"));
|
|
48
51
|
const organizationPaths = ["/organization/set-active", "/organization/accept-invitation"];
|
|
49
52
|
/**
|
|
@@ -60,6 +63,12 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
60
63
|
baseURL: options.baseURL
|
|
61
64
|
};
|
|
62
65
|
const samlIds = saml.map((provider) => provider.id);
|
|
66
|
+
const users = userTable(options.users);
|
|
67
|
+
const passwordHashes = options.passwordHashes ?? [];
|
|
68
|
+
const userFields = {
|
|
69
|
+
...organizations && userFieldsNamed(lastOrganizationField, users),
|
|
70
|
+
...metadataField(users)
|
|
71
|
+
};
|
|
63
72
|
return {
|
|
64
73
|
appName,
|
|
65
74
|
database: options.database,
|
|
@@ -75,13 +84,14 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
75
84
|
enabled: true,
|
|
76
85
|
disableSignUp: true,
|
|
77
86
|
minPasswordLength: 12,
|
|
87
|
+
...passwordHashes.length > 0 && { password: { verify: verifyAcceptedHash(passwordHashes) } },
|
|
78
88
|
...mail && passwordResetOptions(mail)
|
|
79
89
|
},
|
|
80
90
|
...options.socialProviders && { socialProviders: options.socialProviders },
|
|
81
|
-
user:
|
|
82
|
-
...
|
|
83
|
-
additionalFields:
|
|
84
|
-
} :
|
|
91
|
+
user: Object.keys(userFields).length > 0 ? {
|
|
92
|
+
...userModel(users),
|
|
93
|
+
additionalFields: userFields
|
|
94
|
+
} : userModel(users),
|
|
85
95
|
session: coreNames.session,
|
|
86
96
|
verification: coreNames.verification,
|
|
87
97
|
account: {
|
|
@@ -162,8 +172,9 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
162
172
|
invitationUrl: organizations?.invitationUrl ?? defaultInvitationUrl(options.baseURL),
|
|
163
173
|
invitationSeconds: organizations?.invitationExpiresIn ?? 604800,
|
|
164
174
|
...organizations && { organizations }
|
|
165
|
-
})
|
|
166
|
-
|
|
175
|
+
}),
|
|
176
|
+
...passwordHashes.length > 0 ? [rehashPasswordsPlugin(passwordHashes)] : []
|
|
177
|
+
].map((plugin) => withUserColumns(plugin, users)),
|
|
167
178
|
disabledPaths: [
|
|
168
179
|
...unusedEmailCodePaths,
|
|
169
180
|
...adminImpersonationPaths,
|
|
@@ -183,7 +194,7 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
183
194
|
});
|
|
184
195
|
const withId = options.generateUserId ? {
|
|
185
196
|
...user,
|
|
186
|
-
id: await options.generateUserId
|
|
197
|
+
id: await newUserId(options.generateUserId)
|
|
187
198
|
} : user;
|
|
188
199
|
return { data: context?.path === "/admin/create-user" || samlProvider ? {
|
|
189
200
|
...withId,
|
|
@@ -231,7 +242,8 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
|
|
|
231
242
|
sameSite: "lax",
|
|
232
243
|
secure
|
|
233
244
|
},
|
|
234
|
-
backgroundTasks: { handler: () => {} }
|
|
245
|
+
backgroundTasks: { handler: () => {} },
|
|
246
|
+
...options.generateUserId === "uuid" && { database: { generateId: "uuid" } }
|
|
235
247
|
}
|
|
236
248
|
};
|
|
237
249
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { APIError, createAuthMiddleware } from "better-auth/api";
|
|
2
|
+
import { verifyPassword } from "better-auth/crypto";
|
|
3
|
+
import bcrypt from "bcryptjs";
|
|
4
|
+
//#region src/better-auth/password-hashes.ts
|
|
5
|
+
const formats = { bcrypt: {
|
|
6
|
+
matches: (hash) => /^\$2[aby]\$/.test(hash),
|
|
7
|
+
verify: (password, hash) => bcrypt.compare(password, hash)
|
|
8
|
+
} };
|
|
9
|
+
const formatOf = (accepted, hash) => accepted.map((name) => formats[name]).find((format) => format.matches(hash));
|
|
10
|
+
/** Better Auth's `password.verify`: the accepted formats first, then its own scrypt. */
|
|
11
|
+
const verifyAcceptedHash = (accepted) => ({ hash, password }) => formatOf(accepted, hash)?.verify(password, hash) ?? verifyPassword({
|
|
12
|
+
hash,
|
|
13
|
+
password
|
|
14
|
+
});
|
|
15
|
+
/** After a sign-in with a password in one of the accepted formats, stores it again as Better Auth hashes it. */
|
|
16
|
+
const rehashPasswordsPlugin = (accepted) => ({
|
|
17
|
+
id: "protobase-rehash-passwords",
|
|
18
|
+
hooks: { after: [{
|
|
19
|
+
matcher: (ctx) => ctx.path === "/sign-in/email",
|
|
20
|
+
handler: createAuthMiddleware(async (ctx) => {
|
|
21
|
+
if (ctx.context.returned instanceof APIError) return;
|
|
22
|
+
const { email, password } = ctx.body;
|
|
23
|
+
const found = await ctx.context.internalAdapter.findUserByEmail(email.toLowerCase(), { includeAccounts: true });
|
|
24
|
+
const account = found?.accounts.find((candidate) => candidate.providerId === "credential" && candidate.accountId === found.user.id);
|
|
25
|
+
if (!found || !account?.password || !formatOf(accepted, account.password)) return;
|
|
26
|
+
await ctx.context.internalAdapter.updatePassword(found.user.id, await ctx.context.password.hash(password));
|
|
27
|
+
})
|
|
28
|
+
}] }
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
export { rehashPasswordsPlugin, verifyAcceptedHash };
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
//#region src/better-auth/snake-case-names.ts
|
|
2
|
-
/** Better Auth's own tables, as its `
|
|
2
|
+
/** Better Auth's own tables, as its `session`, `account` and `verification` options name them; the user table's are in `user-table.ts`. */
|
|
3
3
|
const coreNames = {
|
|
4
|
-
user: { fields: {
|
|
5
|
-
emailVerified: "email_verified",
|
|
6
|
-
createdAt: "created_at",
|
|
7
|
-
updatedAt: "updated_at"
|
|
8
|
-
} },
|
|
9
4
|
session: { fields: {
|
|
10
5
|
expiresAt: "expires_at",
|
|
11
6
|
createdAt: "created_at",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "better-auth";
|
|
2
|
+
//#region src/better-auth/user-table.d.ts
|
|
3
|
+
/** Each column of the user table, by the name Protobase gives it, with Better Auth's field it holds. */
|
|
4
|
+
declare const userFields: {
|
|
5
|
+
readonly name: "name";
|
|
6
|
+
readonly email: "email";
|
|
7
|
+
readonly email_verified: "emailVerified";
|
|
8
|
+
readonly image: "image";
|
|
9
|
+
readonly created_at: "createdAt";
|
|
10
|
+
readonly updated_at: "updatedAt";
|
|
11
|
+
readonly role: "role";
|
|
12
|
+
readonly banned: "banned";
|
|
13
|
+
readonly ban_reason: "banReason";
|
|
14
|
+
readonly ban_expires: "banExpires";
|
|
15
|
+
readonly two_factor_enabled: "twoFactorEnabled";
|
|
16
|
+
readonly last_role: "lastRole";
|
|
17
|
+
readonly last_organization_id: "lastOrganizationId";
|
|
18
|
+
};
|
|
19
|
+
export type UserColumn = keyof typeof userFields;
|
|
20
|
+
export type UserTableOptions = {
|
|
21
|
+
/** The table's name, default `user`, for example `users` for Supabase's `auth.users`. */
|
|
22
|
+
table?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The columns named otherwise than Protobase names them: `{ role: 'app_role' }`, where `role` holds something else
|
|
25
|
+
* already, such as the Postgres role in Supabase's `auth.users`.
|
|
26
|
+
*/
|
|
27
|
+
columns?: Partial<Record<UserColumn, string>>;
|
|
28
|
+
/** A JSON column that holds each user's metadata, such as `raw_user_meta_data`; none by default. */
|
|
29
|
+
metadata?: string;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/better-auth/user-table.ts
|
|
2
|
+
/** Each column of the user table, by the name Protobase gives it, with Better Auth's field it holds. */
|
|
3
|
+
const userFields = {
|
|
4
|
+
name: "name",
|
|
5
|
+
email: "email",
|
|
6
|
+
email_verified: "emailVerified",
|
|
7
|
+
image: "image",
|
|
8
|
+
created_at: "createdAt",
|
|
9
|
+
updated_at: "updatedAt",
|
|
10
|
+
role: "role",
|
|
11
|
+
banned: "banned",
|
|
12
|
+
ban_reason: "banReason",
|
|
13
|
+
ban_expires: "banExpires",
|
|
14
|
+
two_factor_enabled: "twoFactorEnabled",
|
|
15
|
+
last_role: "lastRole",
|
|
16
|
+
last_organization_id: "lastOrganizationId"
|
|
17
|
+
};
|
|
18
|
+
/** The user table's names, from `createAuth`'s `users` option; unknown columns are an error. */
|
|
19
|
+
const userTable = ({ table = "user", columns = {}, metadata } = {}) => {
|
|
20
|
+
const unknown = Object.keys(columns).find((name) => !(name in userFields));
|
|
21
|
+
if (unknown !== void 0) throw new Error(`users.columns names "${unknown}", which is not a column of the user table: ${Object.keys(userFields).join(", ")}`);
|
|
22
|
+
const column = (name) => columns[name] ?? name;
|
|
23
|
+
return {
|
|
24
|
+
modelName: table,
|
|
25
|
+
column,
|
|
26
|
+
fieldNames: Object.fromEntries(Object.entries(userFields).map(([name, field]) => [field, column(name)])),
|
|
27
|
+
metadata
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** Better Auth's `user` option: the table and its core columns. */
|
|
31
|
+
const userModel = ({ modelName, column }) => ({
|
|
32
|
+
modelName,
|
|
33
|
+
fields: {
|
|
34
|
+
name: column("name"),
|
|
35
|
+
email: column("email"),
|
|
36
|
+
emailVerified: column("email_verified"),
|
|
37
|
+
image: column("image"),
|
|
38
|
+
createdAt: column("created_at"),
|
|
39
|
+
updatedAt: column("updated_at")
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
/** Each user field the user metadata column adds; none without one. */
|
|
43
|
+
const metadataField = ({ metadata }) => metadata ? { metadata: {
|
|
44
|
+
type: "json",
|
|
45
|
+
required: false,
|
|
46
|
+
fieldName: metadata
|
|
47
|
+
} } : void 0;
|
|
48
|
+
/** The fields, each with its column named as the app names it. */
|
|
49
|
+
const userFieldsNamed = (fields, { fieldNames }) => Object.fromEntries(Object.entries(fields).map(([field, attribute]) => [field, {
|
|
50
|
+
...attribute,
|
|
51
|
+
fieldName: fieldNames[field] ?? attribute.fieldName
|
|
52
|
+
}]));
|
|
53
|
+
/** The plugin, with the columns it adds to the user table named as the app names them. */
|
|
54
|
+
const withUserColumns = (plugin, table) => {
|
|
55
|
+
const user = plugin.schema?.user;
|
|
56
|
+
if (!user) return plugin;
|
|
57
|
+
return {
|
|
58
|
+
...plugin,
|
|
59
|
+
schema: {
|
|
60
|
+
...plugin.schema,
|
|
61
|
+
user: {
|
|
62
|
+
...user,
|
|
63
|
+
fields: userFieldsNamed(user.fields, table)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { metadataField, userFieldsNamed, userModel, userTable, withUserColumns };
|
|
@@ -8,12 +8,14 @@ export type LinkedAccount = {
|
|
|
8
8
|
providerId: string;
|
|
9
9
|
accountId: string;
|
|
10
10
|
};
|
|
11
|
+
/** `metadata` lands in the user metadata column, which `createAuth`'s `users.metadata` names; it needs one. */
|
|
11
12
|
export type NewUser = {
|
|
12
13
|
email: string;
|
|
13
14
|
password: string;
|
|
14
15
|
name?: string;
|
|
15
16
|
role?: string;
|
|
16
17
|
accounts?: LinkedAccount[];
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
17
19
|
};
|
|
18
20
|
export type StoredUser = {
|
|
19
21
|
id: string;
|
|
@@ -37,7 +37,8 @@ const createUser = async (auth, input) => {
|
|
|
37
37
|
email: input.email,
|
|
38
38
|
password: input.password,
|
|
39
39
|
name: input.name ?? input.email.split("@")[0],
|
|
40
|
-
role
|
|
40
|
+
role,
|
|
41
|
+
...input.metadata && { data: { metadata: input.metadata } }
|
|
41
42
|
} });
|
|
42
43
|
const { internalAdapter } = await auth.$context;
|
|
43
44
|
for (const account of accounts) await internalAdapter.linkAccount({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.10",
|
|
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": {
|
|
@@ -32,14 +32,15 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@better-auth/passkey": "^1.7.7",
|
|
34
34
|
"@better-auth/sso": "^1.7.7",
|
|
35
|
-
"@protobase/layout": "0.25.
|
|
36
|
-
"@protobase/query": "0.25.
|
|
37
|
-
"@protobase/schema": "0.25.
|
|
35
|
+
"@protobase/layout": "0.25.10",
|
|
36
|
+
"@protobase/query": "0.25.10",
|
|
37
|
+
"@protobase/schema": "0.25.10",
|
|
38
38
|
"@react-email/render": "^2.1.0",
|
|
39
39
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
40
40
|
"@sentry/core": "^11.7.0",
|
|
41
41
|
"@simplewebauthn/server": "^13.3.1",
|
|
42
42
|
"altcha-lib": "^2.6.0",
|
|
43
|
+
"bcryptjs": "^3.0.3",
|
|
43
44
|
"better-auth": "^1.7.7",
|
|
44
45
|
"cron-parser": "^5.10.1",
|
|
45
46
|
"file-type": "^22.1.1",
|