@plandesk/api 1.0.0-beta.4 → 1.0.0-beta.5
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/auth.js +4 -1
- package/dist/organizations.d.ts +15 -0
- package/dist/organizations.js +30 -0
- package/dist/routes/orgs.js +20 -1
- package/package.json +2 -2
- package/web/assets/{index-Cde1ref3.js → index-DOg4prbp.js} +88 -88
- package/web/assets/index-vdnxEKRS.css +2 -0
- package/web/index.html +2 -2
- package/web/assets/index-D29SyYFS.css +0 -2
package/dist/auth.js
CHANGED
|
@@ -192,6 +192,8 @@ const PUBLIC_AUTH_PATHS = new Set([
|
|
|
192
192
|
]);
|
|
193
193
|
/** Invitation accept: invitee may have a session but zero org memberships yet. */
|
|
194
194
|
const INVITATION_ACCEPT_PATH = /^\/api\/v1\/invitations\/[^/]+\/accept$/;
|
|
195
|
+
/** Invitation preview (GET only): the claim page renders "invited to X" pre-auth. */
|
|
196
|
+
const INVITATION_PREVIEW_PATH = /^\/api\/v1\/invitations\/[^/]+$/;
|
|
195
197
|
export function isInvitationAcceptPath(path) {
|
|
196
198
|
return INVITATION_ACCEPT_PATH.test(path);
|
|
197
199
|
}
|
|
@@ -204,7 +206,8 @@ export function isPublicAuthPath(path) {
|
|
|
204
206
|
return true;
|
|
205
207
|
}
|
|
206
208
|
// BA3c: accept is session-checked in-handler, not org-gated.
|
|
207
|
-
|
|
209
|
+
// Preview (GET /invitations/:id) is capability-gated by the unguessable id.
|
|
210
|
+
return isInvitationAcceptPath(path) || INVITATION_PREVIEW_PATH.test(path);
|
|
208
211
|
}
|
|
209
212
|
/**
|
|
210
213
|
* Pre-join share surfaces: meta (render "X invited you"), join (claim a guest
|
package/dist/organizations.d.ts
CHANGED
|
@@ -30,6 +30,21 @@ export type OrganizationMemberSummary = {
|
|
|
30
30
|
};
|
|
31
31
|
/** List better-auth members of an organization (email/name joined from user). */
|
|
32
32
|
export declare function listOrganizationMembers(auth: BetterAuthInstance, organizationId: string): Promise<OrganizationMemberSummary[]>;
|
|
33
|
+
export type InvitationPreview = {
|
|
34
|
+
organizationId: string;
|
|
35
|
+
organizationName: string;
|
|
36
|
+
role: string;
|
|
37
|
+
email: string;
|
|
38
|
+
status: string;
|
|
39
|
+
expiresAt: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Preview an invitation by id (capability: holding the link is authorization).
|
|
43
|
+
* Returns the org name + role + invited email so the claim page can orient the
|
|
44
|
+
* invitee before they sign in. No session required — mirrors the link-only,
|
|
45
|
+
* deliver-by-hand model; ids are unguessable so enumeration is infeasible.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getInvitationPreview(auth: BetterAuthInstance, invitationId: string): Promise<InvitationPreview | undefined>;
|
|
33
48
|
/**
|
|
34
49
|
* Display-friendly org for /auth/session and similar.
|
|
35
50
|
* Falls back to DEFAULT_ORG_ID → "Personal" when better-auth is not configured
|
package/dist/organizations.js
CHANGED
|
@@ -68,6 +68,36 @@ export async function listOrganizationMembers(auth, organizationId) {
|
|
|
68
68
|
};
|
|
69
69
|
}));
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Preview an invitation by id (capability: holding the link is authorization).
|
|
73
|
+
* Returns the org name + role + invited email so the claim page can orient the
|
|
74
|
+
* invitee before they sign in. No session required — mirrors the link-only,
|
|
75
|
+
* deliver-by-hand model; ids are unguessable so enumeration is infeasible.
|
|
76
|
+
*/
|
|
77
|
+
export async function getInvitationPreview(auth, invitationId) {
|
|
78
|
+
const adapter = (await auth.$context).adapter;
|
|
79
|
+
const invitation = await adapter.findOne({
|
|
80
|
+
model: 'invitation',
|
|
81
|
+
where: [{ field: 'id', value: invitationId }],
|
|
82
|
+
});
|
|
83
|
+
if (invitation === null) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const org = await adapter.findOne({
|
|
87
|
+
model: 'organization',
|
|
88
|
+
where: [{ field: 'id', value: invitation.organizationId }],
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
organizationId: invitation.organizationId,
|
|
92
|
+
organizationName: org?.name ?? '',
|
|
93
|
+
role: invitation.role,
|
|
94
|
+
email: invitation.email,
|
|
95
|
+
status: invitation.status,
|
|
96
|
+
expiresAt: invitation.expiresAt instanceof Date
|
|
97
|
+
? invitation.expiresAt.toISOString()
|
|
98
|
+
: String(invitation.expiresAt),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
71
101
|
/**
|
|
72
102
|
* Display-friendly org for /auth/session and similar.
|
|
73
103
|
* Falls back to DEFAULT_ORG_ID → "Personal" when better-auth is not configured
|
package/dist/routes/orgs.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getProjectInOrg, importProject, InvalidExportVersionError, PLANDESK_EXP
|
|
|
3
3
|
import { createScopedAgentKey } from '../agent-keys.js';
|
|
4
4
|
import { getAuthContext, getOrgAuthContext } from '../auth-context.js';
|
|
5
5
|
import { acceptOrganizationInvitation, createOrganizationInvitation, isAuthApiError, isInvitationRole, } from '../invitations.js';
|
|
6
|
-
import { getOrganizationById, listOrganizationMembers } from '../organizations.js';
|
|
6
|
+
import { getInvitationPreview, getOrganizationById, listOrganizationMembers, } from '../organizations.js';
|
|
7
7
|
import { requirePermission } from '../permissions.js';
|
|
8
8
|
function isPermissionSet(value) {
|
|
9
9
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -170,6 +170,25 @@ export function createOrgsRouter(db, options = {}) {
|
|
|
170
170
|
throw err;
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
|
+
/**
|
|
174
|
+
* Preview an invitation (capability: the unguessable id is the authorization).
|
|
175
|
+
* Public so the claim page can render "you've been invited to X as Y" before
|
|
176
|
+
* the invitee signs in. Returns org name + role + invited email + status.
|
|
177
|
+
*/
|
|
178
|
+
router.get('/invitations/:invitationId', async (c) => {
|
|
179
|
+
if (betterAuth === undefined) {
|
|
180
|
+
return c.json({ error: 'unavailable' }, 503);
|
|
181
|
+
}
|
|
182
|
+
const invitationId = c.req.param('invitationId');
|
|
183
|
+
if (invitationId.trim() === '') {
|
|
184
|
+
return c.json({ error: 'invalid_argument' }, 400);
|
|
185
|
+
}
|
|
186
|
+
const preview = await getInvitationPreview(betterAuth, invitationId);
|
|
187
|
+
if (preview === undefined) {
|
|
188
|
+
return c.json({ error: 'not_found' }, 404);
|
|
189
|
+
}
|
|
190
|
+
return c.json(preview, 200);
|
|
191
|
+
});
|
|
173
192
|
/**
|
|
174
193
|
* BA3c: accept invitation. Session-gated at the handler (public path so
|
|
175
194
|
* org-less invitees can reach it); not org-gated. Single-use → 410 on retry.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plandesk/api",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@libsql/kysely-libsql": "^0.4.1",
|
|
27
27
|
"better-auth": "1.6.23",
|
|
28
28
|
"hono": "^4.12.23",
|
|
29
|
-
"@plandesk/db": "1.0.0-beta.
|
|
29
|
+
"@plandesk/db": "1.0.0-beta.5"
|
|
30
30
|
},
|
|
31
31
|
"description": "Plan Desk REST + SSE API and service layer (local-first planning workspace).",
|
|
32
32
|
"license": "MIT",
|