@protobase/server 0.22.0 → 0.22.1
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/auth-mail/invitation-email.js +23 -27
- package/dist/better-auth/create-auth.d.ts +460 -24
- package/dist/better-auth/create-auth.js +12 -1
- package/dist/better-auth/invitations/account-from-invitation.js +50 -0
- package/dist/better-auth/invitations/invitation-endpoints.js +102 -0
- package/dist/better-auth/invitations/invitation-link-hook.js +34 -0
- package/dist/better-auth/invitations/invitation-of-token.js +24 -0
- package/dist/better-auth/{organizations → invitations}/invitation-token.js +7 -2
- package/dist/better-auth/last-admin-guard.js +62 -0
- package/dist/better-auth/last-admin.js +18 -0
- package/dist/better-auth/organizations/accept-invitation.js +4 -8
- package/dist/better-auth/organizations/invitation-endpoints.js +17 -114
- package/dist/better-auth/organizations/options.js +2 -1
- package/dist/better-auth/organizations/organization-plugin.js +1 -1
- package/dist/better-auth/organizations/protobase-plugin.js +7 -9
- package/dist/better-auth/organizations/role-definitions.js +5 -4
- package/dist/better-auth/passkey-plugin.js +1 -1
- package/dist/better-auth/roles/role-labels.js +14 -0
- package/dist/better-auth/sign-up/sign-up-plugin.js +1 -1
- package/dist/better-auth/status-route.js +3 -8
- package/dist/better-auth/users/app-invitation-store.js +62 -0
- package/dist/better-auth/users/user-endpoints.js +79 -0
- package/dist/better-auth/users/users-plugin.js +22 -0
- package/dist/better-auth/users.js +1 -2
- package/dist/check-views.js +5 -3
- package/dist/index.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { invitationLink, invitationToken } from "../invitations/invitation-token.js";
|
|
2
|
+
import { sendAuthEmail } from "../auth-mail/send-auth-email.js";
|
|
3
|
+
import { adminOnly } from "../admin-only.js";
|
|
4
|
+
import { createAppInvitation, deleteAppInvitation, listAppInvitations } from "./app-invitation-store.js";
|
|
5
|
+
import { roleLabels } from "../roles/role-labels.js";
|
|
6
|
+
import * as z$1 from "zod";
|
|
7
|
+
import { APIError, createAuthEndpoint } from "better-auth/api";
|
|
8
|
+
//#region src/better-auth/users/user-endpoints.ts
|
|
9
|
+
const admins = adminOnly("Only an admin manages users.");
|
|
10
|
+
const answerOf = ({ id, email, roles, expiresAt }) => ({
|
|
11
|
+
id,
|
|
12
|
+
email,
|
|
13
|
+
roles,
|
|
14
|
+
expiresAt: expiresAt.toISOString()
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* What an admin manages people with besides Better Auth's admin endpoints, which list, create, set roles and block:
|
|
18
|
+
* invitations to the app by email, which work with or without organizations, and the roles with their labels.
|
|
19
|
+
*/
|
|
20
|
+
const userEndpoints = ({ definitions, mail, invitationUrl, invitationSeconds }) => ({
|
|
21
|
+
/**
|
|
22
|
+
* `POST /users/invite` with `{ email, roles }`: invites an address without an account to make one, with `roles`, from
|
|
23
|
+
* the link it is mailed. Inviting it again replaces the earlier invitation, so its link stops working.
|
|
24
|
+
*/
|
|
25
|
+
inviteUser: createAuthEndpoint("/users/invite", {
|
|
26
|
+
method: "POST",
|
|
27
|
+
use: [admins],
|
|
28
|
+
body: z$1.object({
|
|
29
|
+
email: z$1.email(),
|
|
30
|
+
roles: z$1.array(z$1.string()).min(1)
|
|
31
|
+
})
|
|
32
|
+
}, async (ctx) => {
|
|
33
|
+
const unknown = ctx.body.roles.find((role) => !definitions.names.includes(role));
|
|
34
|
+
if (unknown !== void 0) throw new APIError("BAD_REQUEST", {
|
|
35
|
+
code: "UNKNOWN_ROLE",
|
|
36
|
+
message: `Unknown role "${unknown}"; use ${definitions.names.join(", ")}`
|
|
37
|
+
});
|
|
38
|
+
if (await ctx.context.internalAdapter.findUserByEmail(ctx.body.email.toLowerCase())) throw new APIError("BAD_REQUEST", {
|
|
39
|
+
code: "ACCOUNT_EXISTS",
|
|
40
|
+
message: "This address has an account already."
|
|
41
|
+
});
|
|
42
|
+
const inviter = ctx.context.session.user;
|
|
43
|
+
const invitation = await createAppInvitation(ctx.context, {
|
|
44
|
+
email: ctx.body.email,
|
|
45
|
+
roles: [...new Set(ctx.body.roles)],
|
|
46
|
+
inviterId: inviter.id
|
|
47
|
+
}, invitationSeconds);
|
|
48
|
+
if (mail) {
|
|
49
|
+
const url = invitationLink(invitationUrl, await invitationToken(ctx.context.secret, invitation));
|
|
50
|
+
await sendAuthEmail(mail, "invitationEmail", invitation.email, {
|
|
51
|
+
inviter: inviter.name || inviter.email,
|
|
52
|
+
url,
|
|
53
|
+
expiresAt: invitation.expiresAt
|
|
54
|
+
}, url);
|
|
55
|
+
}
|
|
56
|
+
return ctx.json(answerOf(invitation));
|
|
57
|
+
}),
|
|
58
|
+
/** `GET /users/invitations`: the pending invitations to the app, oldest first. */
|
|
59
|
+
listUserInvitations: createAuthEndpoint("/users/invitations", {
|
|
60
|
+
method: "GET",
|
|
61
|
+
use: [admins]
|
|
62
|
+
}, async (ctx) => ctx.json({ invitations: (await listAppInvitations(ctx.context)).map(answerOf) })),
|
|
63
|
+
/** `POST /users/invitations/cancel` with `{ id }`: revokes an invitation to the app, so its link stops working. */
|
|
64
|
+
cancelUserInvitation: createAuthEndpoint("/users/invitations/cancel", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
use: [admins],
|
|
67
|
+
body: z$1.object({ id: z$1.string().min(1) })
|
|
68
|
+
}, async (ctx) => {
|
|
69
|
+
await deleteAppInvitation(ctx.context, ctx.body.id);
|
|
70
|
+
return ctx.json({ id: ctx.body.id });
|
|
71
|
+
}),
|
|
72
|
+
/** `GET /users/roles`: every role with its label, to choose from when inviting, creating or changing a user. */
|
|
73
|
+
userRoles: createAuthEndpoint("/users/roles", {
|
|
74
|
+
method: "GET",
|
|
75
|
+
use: [admins]
|
|
76
|
+
}, async (ctx) => ctx.json(roleLabels(definitions)))
|
|
77
|
+
});
|
|
78
|
+
//#endregion
|
|
79
|
+
export { userEndpoints };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { invitationLinkHook } from "../invitations/invitation-link-hook.js";
|
|
2
|
+
import { invitationEndpoints } from "../invitations/invitation-endpoints.js";
|
|
3
|
+
import { userEndpoints } from "./user-endpoints.js";
|
|
4
|
+
//#region src/better-auth/users/users-plugin.ts
|
|
5
|
+
/**
|
|
6
|
+
* Managing people without the command line: invitations to the app and the roles to give, for admins, and the endpoints
|
|
7
|
+
* every invitation link opens, to the app or, with `organizations`, to one of them. Without a mailer, whoever invites
|
|
8
|
+
* gets the link in the answer.
|
|
9
|
+
*/
|
|
10
|
+
const usersPlugin = (options) => ({
|
|
11
|
+
id: "protobase-users",
|
|
12
|
+
endpoints: {
|
|
13
|
+
...userEndpoints(options),
|
|
14
|
+
...invitationEndpoints({
|
|
15
|
+
mail: Boolean(options.mail),
|
|
16
|
+
...options.organizations && { organizations: options.organizations }
|
|
17
|
+
})
|
|
18
|
+
},
|
|
19
|
+
hooks: { after: options.mail ? [] : [invitationLinkHook(["/users/invite"], options.invitationUrl)] }
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { usersPlugin };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseRoles } from "./parse-roles.js";
|
|
2
|
+
import { isActiveAdmin } from "./last-admin.js";
|
|
2
3
|
import { ownOrganizationsWithoutMembers } from "./organizations/host.js";
|
|
3
4
|
//#region src/better-auth/users.ts
|
|
4
5
|
/** The roles this auth instance allows, `admin` first. */
|
|
@@ -61,8 +62,6 @@ const allUsers = async (auth) => {
|
|
|
61
62
|
});
|
|
62
63
|
};
|
|
63
64
|
const rolesOf = (user) => parseRoles(user.role);
|
|
64
|
-
const isAdmin = (user) => rolesOf(user).includes("admin");
|
|
65
|
-
const isActiveAdmin = (user) => isAdmin(user) && !user.banned;
|
|
66
65
|
const findUser = async (auth, email) => {
|
|
67
66
|
const user = (await allUsers(auth)).find((candidate) => candidate.email.toLowerCase() === email.toLowerCase());
|
|
68
67
|
if (!user) throw new Error(`No user with email ${email}`);
|
package/dist/check-views.js
CHANGED
|
@@ -7,7 +7,7 @@ import { mapDisplayErrors, mapErrors } from "./view-map.js";
|
|
|
7
7
|
import { relatedColumnErrors } from "./view-related-column.js";
|
|
8
8
|
import { editableErrors } from "./view-editable.js";
|
|
9
9
|
import { conditionProblems } from "@protobase/layout";
|
|
10
|
-
import { actionInputErrors, addressErrors, addressPlaceholders, boardErrors, calendarErrors, chartFields, displayFields, fieldRefsIn, hrefField, hrefPlaceholders, layoutItems, linkActionError, promptError, promptFields } from "@protobase/schema/internal";
|
|
10
|
+
import { actionInputErrors, addressErrors, addressPlaceholders, boardErrors, calendarErrors, chartFields, displayFields, fieldRefsIn, hrefField, hrefPlaceholders, layoutItems, linkActionError, promptError, promptFields, updateSetErrors } from "@protobase/schema/internal";
|
|
11
11
|
import { isRecordCondition } from "@protobase/schema";
|
|
12
12
|
//#region src/check-views.ts
|
|
13
13
|
const linkFields = (run) => run?.kind !== "link" ? [] : "to" in run ? addressPlaceholders(run.to) : hrefPlaceholders(run.href);
|
|
@@ -85,9 +85,11 @@ const aiActionErrors = (view, model) => view.actions.flatMap(({ name, run }) =>
|
|
|
85
85
|
].map((error) => `action "${name}": ${error}`);
|
|
86
86
|
});
|
|
87
87
|
const inputErrors = (view, models) => view.actions.flatMap(({ name, run, inputs }) => {
|
|
88
|
-
|
|
88
|
+
const update = run?.kind === "update" ? updateSetErrors(run.values, inputs) : [];
|
|
89
|
+
if (!inputs) return update.map((error) => `action "${name}": ${error}`);
|
|
89
90
|
return [
|
|
90
|
-
...
|
|
91
|
+
...update,
|
|
92
|
+
...run && run.kind !== "update" ? [`inputs need a handler, and a${run.kind === "ai" ? "n" : ""} ${run.kind} action has none`] : [],
|
|
91
93
|
...actionInputErrors(inputs),
|
|
92
94
|
...inputs.flatMap((input) => input.resource !== void 0 && !models[input.resource] ? [`input "${input.name}" picks from resource "${input.resource}", which the app does not have`] : [])
|
|
93
95
|
].map((error) => `action "${name}": ${error}`);
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,8 @@ import { consoleAuditQueue } from "./audit/console-queue.js";
|
|
|
19
19
|
import { webauthnPath } from "./better-auth/origins.js";
|
|
20
20
|
import { readOperatorSettings } from "./better-auth/operator-provider.js";
|
|
21
21
|
import { parseRoles } from "./better-auth/parse-roles.js";
|
|
22
|
-
import { defaultSignInPolicy, signInPolicyMethods } from "./better-auth/sign-in-policy.js";
|
|
23
22
|
import { readPlatformSignIn } from "./better-auth/platform-sign-in.js";
|
|
23
|
+
import { defaultSignInPolicy, signInPolicyMethods } from "./better-auth/sign-in-policy.js";
|
|
24
24
|
import { createAuth, normalizeRoles } from "./better-auth/create-auth.js";
|
|
25
25
|
import { addAppRoleToOwners, addMember, createOrganization, listOrganizations, removeMember, setMemberRoles } from "./better-auth/organizations/host.js";
|
|
26
26
|
import { createUser, deleteUser, hasUsers, listUsers, roleChoices, setUserBanned, setUserRole } from "./better-auth/users.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
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": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@better-auth/passkey": "^1.7.7",
|
|
33
33
|
"@better-auth/sso": "^1.7.7",
|
|
34
|
-
"@protobase/layout": "0.22.
|
|
35
|
-
"@protobase/query": "0.22.
|
|
36
|
-
"@protobase/schema": "0.22.
|
|
34
|
+
"@protobase/layout": "0.22.1",
|
|
35
|
+
"@protobase/query": "0.22.1",
|
|
36
|
+
"@protobase/schema": "0.22.1",
|
|
37
37
|
"@react-email/render": "^2.1.0",
|
|
38
38
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
39
39
|
"@sentry/core": "^11.7.0",
|