@stapel/workspaces-react 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.
- package/CHANGELOG.md +40 -0
- package/dist/api/generated/schema.d.ts +384 -4
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/types.d.ts +20 -5
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/workspacesApi.d.ts +23 -1
- package/dist/api/workspacesApi.d.ts.map +1 -1
- package/dist/api/workspacesApi.js +4 -0
- package/dist/api/workspacesApi.js.map +1 -1
- package/dist/default/InviteAcceptPage.d.ts +28 -0
- package/dist/default/InviteAcceptPage.d.ts.map +1 -0
- package/dist/default/InviteAcceptPage.js +141 -0
- package/dist/default/InviteAcceptPage.js.map +1 -0
- package/dist/default/MembersManager.d.ts +4 -4
- package/dist/default/MembersManager.d.ts.map +1 -1
- package/dist/default/MembersManager.js +61 -56
- package/dist/default/MembersManager.js.map +1 -1
- package/dist/default/index.d.ts +2 -0
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +1 -0
- package/dist/default/index.js.map +1 -1
- package/dist/flows/inviteAcceptFlow.d.ts +155 -0
- package/dist/flows/inviteAcceptFlow.d.ts.map +1 -0
- package/dist/flows/inviteAcceptFlow.js +126 -0
- package/dist/flows/inviteAcceptFlow.js.map +1 -0
- package/dist/headless/Can.d.ts +40 -0
- package/dist/headless/Can.d.ts.map +1 -0
- package/dist/headless/Can.js +30 -0
- package/dist/headless/Can.js.map +1 -0
- package/dist/headless/InviteAcceptFlow.d.ts +44 -0
- package/dist/headless/InviteAcceptFlow.d.ts.map +1 -0
- package/dist/headless/InviteAcceptFlow.js +67 -0
- package/dist/headless/InviteAcceptFlow.js.map +1 -0
- package/dist/headless/RoleSelect.d.ts +41 -0
- package/dist/headless/RoleSelect.d.ts.map +1 -0
- package/dist/headless/RoleSelect.js +34 -0
- package/dist/headless/RoleSelect.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +48 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +24 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +8 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
- package/dist/i18n/keys.d.ts +28 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +64 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +30 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +14 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/model/capabilities.d.ts +21 -0
- package/dist/model/capabilities.d.ts.map +1 -0
- package/dist/model/capabilities.js +32 -0
- package/dist/model/capabilities.js.map +1 -0
- package/dist/model/emailMask.d.ts +22 -0
- package/dist/model/emailMask.d.ts.map +1 -0
- package/dist/model/emailMask.js +36 -0
- package/dist/model/emailMask.js.map +1 -0
- package/dist/model/mutations.d.ts +23 -3
- package/dist/model/mutations.d.ts.map +1 -1
- package/dist/model/mutations.js +39 -1
- package/dist/model/mutations.js.map +1 -1
- package/dist/model/queries.d.ts +56 -6
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +75 -7
- package/dist/model/queries.js.map +1 -1
- package/dist/model/queryKeys.d.ts +2 -0
- package/dist/model/queryKeys.d.ts.map +1 -1
- package/dist/model/queryKeys.js +6 -0
- package/dist/model/queryKeys.js.map +1 -1
- package/llms.txt +40 -15
- package/manifest.json +225 -3
- package/package.json +5 -5
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +384 -4
- package/src/api/types.ts +20 -5
- package/src/api/workspacesApi.ts +50 -0
- package/src/default/InviteAcceptPage.tsx +373 -0
- package/src/default/MembersManager.tsx +155 -145
- package/src/default/index.ts +2 -0
- package/src/flows/inviteAcceptFlow.ts +289 -0
- package/src/headless/Can.tsx +49 -0
- package/src/headless/InviteAcceptFlow.tsx +105 -0
- package/src/headless/RoleSelect.tsx +58 -0
- package/src/i18n/generated/errors.gen.ts +24 -0
- package/src/i18n/generated/errors.json +62 -0
- package/src/i18n/generated/errors.ru.gen.ts +8 -0
- package/src/i18n/keys.ts +68 -0
- package/src/i18n/ru.ts +34 -0
- package/src/index.ts +26 -0
- package/src/model/capabilities.ts +37 -0
- package/src/model/emailMask.ts +37 -0
- package/src/model/mutations.ts +56 -4
- package/src/model/queries.ts +99 -7
- package/src/model/queryKeys.ts +10 -0
|
@@ -181,6 +181,49 @@ export interface paths {
|
|
|
181
181
|
patch?: never;
|
|
182
182
|
trace?: never;
|
|
183
183
|
};
|
|
184
|
+
"/workspaces/api/v1/{workspace_id}/members/provision": {
|
|
185
|
+
parameters: {
|
|
186
|
+
query?: never;
|
|
187
|
+
header?: never;
|
|
188
|
+
path?: never;
|
|
189
|
+
cookie?: never;
|
|
190
|
+
};
|
|
191
|
+
get?: never;
|
|
192
|
+
put?: never;
|
|
193
|
+
/**
|
|
194
|
+
* @description Provision an org-created (synthetic) member (org-program spec §C1).
|
|
195
|
+
*
|
|
196
|
+
* The org mints its own login/password account: full username is
|
|
197
|
+
* ``{workspace_slug}/{username_local}`` (namespaced — the slug is
|
|
198
|
+
* globally unique, so orgs cannot collide), the account is created by
|
|
199
|
+
* ``auth.provision_user`` with the workspace's first-login policy
|
|
200
|
+
* (``settings.security.provisioned_user_policy``: forced password change
|
|
201
|
+
* or mandatory MFA enrollment) and joins immediately
|
|
202
|
+
* (``accepted_at=now``, ``provisioned=True``).
|
|
203
|
+
*
|
|
204
|
+
* Gate stack, in order: HIGH step-up (``@requires_verification``, scope
|
|
205
|
+
* ``sensitive`` — the same store as admin step-up) → capability
|
|
206
|
+
* ``members.provision`` (403) → entitlement ``workspaces.provision_user``
|
|
207
|
+
* (402; degrades to allow without billing) → optional per-user debit
|
|
208
|
+
* (``STAPEL_WORKSPACES["PROVISION_USER_CREDITS"]`` > 0).
|
|
209
|
+
*
|
|
210
|
+
* Credentials: a synthetic account normally has no email — when the
|
|
211
|
+
* request omits ``email``, the server-generated password comes back in
|
|
212
|
+
* the response (``generated_password``) exactly once and no letter is
|
|
213
|
+
* sent. With ``email``, the ``workspace.provisioned_account`` letter
|
|
214
|
+
* carries the credentials as well. Auth's structured failures pass
|
|
215
|
+
* through keyed (``error.409.username_taken`` / auth's 400s); auth not
|
|
216
|
+
* wired → honest 503, this seam never degrades to allow.
|
|
217
|
+
*
|
|
218
|
+
* **Permissions:** `IsAuthenticated`
|
|
219
|
+
*/
|
|
220
|
+
post: operations["workspaces_api_v1_members_provision_create"];
|
|
221
|
+
delete?: never;
|
|
222
|
+
options?: never;
|
|
223
|
+
head?: never;
|
|
224
|
+
patch?: never;
|
|
225
|
+
trace?: never;
|
|
226
|
+
};
|
|
184
227
|
"/workspaces/api/v1/internal/{workspace_id}/members/{user_id}": {
|
|
185
228
|
parameters: {
|
|
186
229
|
query?: never;
|
|
@@ -223,6 +266,90 @@ export interface paths {
|
|
|
223
266
|
patch?: never;
|
|
224
267
|
trace?: never;
|
|
225
268
|
};
|
|
269
|
+
"/workspaces/api/v1/invitations/{token}": {
|
|
270
|
+
parameters: {
|
|
271
|
+
query?: never;
|
|
272
|
+
header?: never;
|
|
273
|
+
path?: never;
|
|
274
|
+
cookie?: never;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* @description Public invitation preview — what the /invite/{token} page renders.
|
|
278
|
+
*
|
|
279
|
+
* AllowAny by design (org-program spec §B2): the invitee has no session
|
|
280
|
+
* yet; the token in the URL is the bearer secret. The response leaks
|
|
281
|
+
* nothing harvestable — the email is masked, and ``status`` /
|
|
282
|
+
* ``email_registered`` are exactly what the frontend flow machine needs
|
|
283
|
+
* to route (login vs claim vs terminal-state screen). Throttled as an
|
|
284
|
+
* enumeration backstop. The token is never logged.
|
|
285
|
+
*
|
|
286
|
+
* **Permissions:** `AllowAny`
|
|
287
|
+
*/
|
|
288
|
+
get: operations["workspaces_api_v1_invitations_retrieve"];
|
|
289
|
+
put?: never;
|
|
290
|
+
post?: never;
|
|
291
|
+
delete?: never;
|
|
292
|
+
options?: never;
|
|
293
|
+
head?: never;
|
|
294
|
+
patch?: never;
|
|
295
|
+
trace?: never;
|
|
296
|
+
};
|
|
297
|
+
"/workspaces/api/v1/invitations/{token}/claim": {
|
|
298
|
+
parameters: {
|
|
299
|
+
query?: never;
|
|
300
|
+
header?: never;
|
|
301
|
+
path?: never;
|
|
302
|
+
cookie?: never;
|
|
303
|
+
};
|
|
304
|
+
get?: never;
|
|
305
|
+
put?: never;
|
|
306
|
+
/**
|
|
307
|
+
* @description Mint a login grant for a not-yet-registered invitee (spec §B2-B3).
|
|
308
|
+
*
|
|
309
|
+
* AllowAny — the whole point is that no account exists yet. Only valid
|
|
310
|
+
* for ``email_registered == false``: an existing account gets 409 and the
|
|
311
|
+
* frontend switches to login. The grant comes from auth's
|
|
312
|
+
* ``auth.issue_login_grant`` comm Function (``create_if_missing`` — the
|
|
313
|
+
* verified account materializes on exchange); if that Function is not
|
|
314
|
+
* wired, the answer is an honest 503 — an invite flow without auth is
|
|
315
|
+
* meaningless, so this seam never degrades to allow. The invitation is
|
|
316
|
+
* NOT consumed here: accept stays a separate, deliberate step after
|
|
317
|
+
* setup. Neither the invite token nor the grant token is ever logged.
|
|
318
|
+
*
|
|
319
|
+
* **Permissions:** `AllowAny`
|
|
320
|
+
*/
|
|
321
|
+
post: operations["workspaces_api_v1_invitations_claim_create"];
|
|
322
|
+
delete?: never;
|
|
323
|
+
options?: never;
|
|
324
|
+
head?: never;
|
|
325
|
+
patch?: never;
|
|
326
|
+
trace?: never;
|
|
327
|
+
};
|
|
328
|
+
"/workspaces/api/v1/invitations/{token}/decline": {
|
|
329
|
+
parameters: {
|
|
330
|
+
query?: never;
|
|
331
|
+
header?: never;
|
|
332
|
+
path?: never;
|
|
333
|
+
cookie?: never;
|
|
334
|
+
};
|
|
335
|
+
get?: never;
|
|
336
|
+
put?: never;
|
|
337
|
+
/**
|
|
338
|
+
* @description Decline an invitation — the invitee's terminal "no" (spec §B2).
|
|
339
|
+
*
|
|
340
|
+
* Authenticated + email-match, exactly like accept: only the invited
|
|
341
|
+
* account may resolve the invitation, in either direction. Decline ≠
|
|
342
|
+
* revoke — both states stay distinguishable in the preview ``status``.
|
|
343
|
+
*
|
|
344
|
+
* **Permissions:** `IsAuthenticated`
|
|
345
|
+
*/
|
|
346
|
+
post: operations["workspaces_api_v1_invitations_decline_create"];
|
|
347
|
+
delete?: never;
|
|
348
|
+
options?: never;
|
|
349
|
+
head?: never;
|
|
350
|
+
patch?: never;
|
|
351
|
+
trace?: never;
|
|
352
|
+
};
|
|
226
353
|
"/workspaces/api/v1/invitations/accept": {
|
|
227
354
|
parameters: {
|
|
228
355
|
query?: never;
|
|
@@ -250,6 +377,31 @@ export interface paths {
|
|
|
250
377
|
patch?: never;
|
|
251
378
|
trace?: never;
|
|
252
379
|
};
|
|
380
|
+
"/workspaces/api/v1/roles": {
|
|
381
|
+
parameters: {
|
|
382
|
+
query?: never;
|
|
383
|
+
header?: never;
|
|
384
|
+
path?: never;
|
|
385
|
+
cookie?: never;
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* @description The effective role registry — metadata for frontends (spec §A2).
|
|
389
|
+
*
|
|
390
|
+
* Lets a RoleSelect stop hardcoding the builtin four: builtin roles plus
|
|
391
|
+
* the deployment's ``STAPEL_WORKSPACES["ROLES"]`` overlay, capability
|
|
392
|
+
* strings verbatim (wildcards included), ordered by descending rank.
|
|
393
|
+
*
|
|
394
|
+
* **Permissions:** `IsAuthenticated`
|
|
395
|
+
*/
|
|
396
|
+
get: operations["workspaces_api_v1_roles_retrieve"];
|
|
397
|
+
put?: never;
|
|
398
|
+
post?: never;
|
|
399
|
+
delete?: never;
|
|
400
|
+
options?: never;
|
|
401
|
+
head?: never;
|
|
402
|
+
patch?: never;
|
|
403
|
+
trace?: never;
|
|
404
|
+
};
|
|
253
405
|
}
|
|
254
406
|
export type webhooks = Record<string, never>;
|
|
255
407
|
export interface components {
|
|
@@ -267,6 +419,41 @@ export interface components {
|
|
|
267
419
|
/** @description Invite token from the email link */
|
|
268
420
|
token: string;
|
|
269
421
|
};
|
|
422
|
+
/** @description Login-grant mint for an unregistered invitee (claim step). */
|
|
423
|
+
InvitationClaimResponse: {
|
|
424
|
+
/** @description Single-use, short-lived login grant token — exchange it at auth's POST /grant/exchange/ for a session (creates the verified account). A credential: never log it */
|
|
425
|
+
grant_token: string;
|
|
426
|
+
};
|
|
427
|
+
/** @description Public (AllowAny) invitation preview — what the /invite/{token} page renders before any auth decision. */
|
|
428
|
+
InvitationPreviewResponse: {
|
|
429
|
+
/**
|
|
430
|
+
* @description Display name of the inviting workspace
|
|
431
|
+
* @example Acme Engineering
|
|
432
|
+
*/
|
|
433
|
+
workspace_name: string;
|
|
434
|
+
/**
|
|
435
|
+
* @description Role granted on acceptance
|
|
436
|
+
* @example member
|
|
437
|
+
*/
|
|
438
|
+
role: string;
|
|
439
|
+
/**
|
|
440
|
+
* @description Invited email, masked for the public page
|
|
441
|
+
* @example m***@e***.com
|
|
442
|
+
*/
|
|
443
|
+
email_masked: string;
|
|
444
|
+
/**
|
|
445
|
+
* @description Invitation state. One of pending / expired / revoked / accepted / declined
|
|
446
|
+
* @example pending
|
|
447
|
+
*/
|
|
448
|
+
status: string;
|
|
449
|
+
/** @description Whether an account already exists for the invited email — steers the frontend to login vs claim */
|
|
450
|
+
email_registered: boolean;
|
|
451
|
+
/**
|
|
452
|
+
* @description ISO 8601 expiry time
|
|
453
|
+
* @example 2026-07-31T10:00:00Z
|
|
454
|
+
*/
|
|
455
|
+
expires_at: string;
|
|
456
|
+
};
|
|
270
457
|
/** @description InvitationResponse(id: uuid.UUID, workspace_id: uuid.UUID, email: str, role: str, expires_at: str, accepted_at: Optional[str], revoked_at: Optional[str], created_at: str) */
|
|
271
458
|
InvitationResponse: {
|
|
272
459
|
/** Format: uuid */
|
|
@@ -330,6 +517,12 @@ export interface components {
|
|
|
330
517
|
accepted_at: string | null;
|
|
331
518
|
/** @description ISO 8601 last access; null if never accessed */
|
|
332
519
|
last_accessed_at: string | null;
|
|
520
|
+
/** @description Whether this is an org-created (synthetic) member joined via members/provision */
|
|
521
|
+
provisioned?: boolean;
|
|
522
|
+
/** @description ISO 8601 suspension timestamp; null while active. Suspension is not removal — the role stays but access is closed */
|
|
523
|
+
suspended_at?: string | null;
|
|
524
|
+
/** @description Why the membership is suspended (canonical value no_mfa); null while active */
|
|
525
|
+
suspension_reason?: string | null;
|
|
333
526
|
};
|
|
334
527
|
PaginatedMemberResponseList: {
|
|
335
528
|
items: components["schemas"]["MemberResponse"][];
|
|
@@ -356,6 +549,78 @@ export interface components {
|
|
|
356
549
|
[key: string]: unknown;
|
|
357
550
|
} | null;
|
|
358
551
|
};
|
|
552
|
+
/** @description Provision an org-created (synthetic) member (org-program spec §C1). */
|
|
553
|
+
ProvisionMemberRequest: {
|
|
554
|
+
/**
|
|
555
|
+
* @description Local part of the login; the full username becomes "{workspace_slug}/{username_local}". Stock username alphabet, no slash
|
|
556
|
+
* @example jdoe
|
|
557
|
+
*/
|
|
558
|
+
username_local: string;
|
|
559
|
+
/**
|
|
560
|
+
* @description Role to grant (effective registry; owner is not provisionable)
|
|
561
|
+
* @example member
|
|
562
|
+
*/
|
|
563
|
+
role?: string;
|
|
564
|
+
/** @description Initial password chosen by the admin. Omitted: the server generates a crypto-strong one, returned once as generated_password */
|
|
565
|
+
password?: string | null;
|
|
566
|
+
/**
|
|
567
|
+
* @description Display-name hint forwarded to auth/profiles
|
|
568
|
+
* @example Jane Doe
|
|
569
|
+
*/
|
|
570
|
+
display_name?: string | null;
|
|
571
|
+
/** @description Optional email anchor (stored UNVERIFIED by auth). When present, the provisioned-account email with the credentials is sent there. Normally omitted — synthetic accounts have no email */
|
|
572
|
+
email?: string | null;
|
|
573
|
+
};
|
|
574
|
+
/** @description Result of provisioning an org member. */
|
|
575
|
+
ProvisionMemberResponse: {
|
|
576
|
+
/**
|
|
577
|
+
* Format: uuid
|
|
578
|
+
* @description UUID of the created account
|
|
579
|
+
*/
|
|
580
|
+
user_id: string;
|
|
581
|
+
/**
|
|
582
|
+
* @description Full namespaced login
|
|
583
|
+
* @example acme-eng/jdoe
|
|
584
|
+
*/
|
|
585
|
+
username: string;
|
|
586
|
+
/**
|
|
587
|
+
* @description Granted role
|
|
588
|
+
* @example member
|
|
589
|
+
*/
|
|
590
|
+
role: string;
|
|
591
|
+
/** @description Server-generated initial password — returned exactly ONCE, only when the request omitted password. Store it or hand it to the user now; it cannot be re-fetched. A credential: never log it */
|
|
592
|
+
generated_password?: string | null;
|
|
593
|
+
};
|
|
594
|
+
/** @description RoleListResponse(roles: List[stapel_workspaces.dto.RoleResponse] = <factory>) */
|
|
595
|
+
RoleListResponse: {
|
|
596
|
+
roles?: components["schemas"]["RoleResponse"][];
|
|
597
|
+
};
|
|
598
|
+
/** @description One role of the effective registry (builtin + STAPEL_WORKSPACES overlay). */
|
|
599
|
+
RoleResponse: {
|
|
600
|
+
/**
|
|
601
|
+
* @description Role key
|
|
602
|
+
* @example admin
|
|
603
|
+
*/
|
|
604
|
+
role: string;
|
|
605
|
+
/**
|
|
606
|
+
* @description Ordering weight; higher = more powerful
|
|
607
|
+
* @example 300
|
|
608
|
+
*/
|
|
609
|
+
rank: number;
|
|
610
|
+
/**
|
|
611
|
+
* @description Granted capability strings, verbatim (wildcards like * included)
|
|
612
|
+
* @example [
|
|
613
|
+
* "workspace.view",
|
|
614
|
+
* "members.invite"
|
|
615
|
+
* ]
|
|
616
|
+
*/
|
|
617
|
+
capabilities: string[];
|
|
618
|
+
/**
|
|
619
|
+
* @description Whether the role key is one of the builtin four
|
|
620
|
+
* @example true
|
|
621
|
+
*/
|
|
622
|
+
builtin: boolean;
|
|
623
|
+
};
|
|
359
624
|
/** @description Structured error returned by all Stapel API endpoints. */
|
|
360
625
|
StapelError: {
|
|
361
626
|
/**
|
|
@@ -363,10 +628,7 @@ export interface components {
|
|
|
363
628
|
* @example error.404.not_found
|
|
364
629
|
*/
|
|
365
630
|
localizable_error: string;
|
|
366
|
-
/**
|
|
367
|
-
* @description Human-readable message in English
|
|
368
|
-
* @example Requested resource not found
|
|
369
|
-
*/
|
|
631
|
+
/** @description Human-readable fallback/debug message — not reliably English, */
|
|
370
632
|
error: string;
|
|
371
633
|
/**
|
|
372
634
|
* @description Context values for template placeholders
|
|
@@ -377,6 +639,8 @@ export interface components {
|
|
|
377
639
|
params?: {
|
|
378
640
|
[key: string]: unknown;
|
|
379
641
|
};
|
|
642
|
+
/** @description Active Django locale `error` was rendered in (e.g */
|
|
643
|
+
error_language?: string;
|
|
380
644
|
};
|
|
381
645
|
/** @description Create-workspace payload. */
|
|
382
646
|
WorkspaceCreateRequest: {
|
|
@@ -454,6 +718,14 @@ export interface components {
|
|
|
454
718
|
* @example 2026-05-20T10:00:00Z
|
|
455
719
|
*/
|
|
456
720
|
updated_at: string;
|
|
721
|
+
/**
|
|
722
|
+
* @description Granted capability strings of the requesting user's role, verbatim from the registry (wildcards like * included)
|
|
723
|
+
* @example [
|
|
724
|
+
* "workspace.view",
|
|
725
|
+
* "members.view"
|
|
726
|
+
* ]
|
|
727
|
+
*/
|
|
728
|
+
my_capabilities?: string[];
|
|
457
729
|
};
|
|
458
730
|
};
|
|
459
731
|
responses: never;
|
|
@@ -682,6 +954,33 @@ export interface operations {
|
|
|
682
954
|
};
|
|
683
955
|
};
|
|
684
956
|
};
|
|
957
|
+
workspaces_api_v1_members_provision_create: {
|
|
958
|
+
parameters: {
|
|
959
|
+
query?: never;
|
|
960
|
+
header?: never;
|
|
961
|
+
path: {
|
|
962
|
+
workspace_id: string;
|
|
963
|
+
};
|
|
964
|
+
cookie?: never;
|
|
965
|
+
};
|
|
966
|
+
requestBody: {
|
|
967
|
+
content: {
|
|
968
|
+
"application/json": components["schemas"]["ProvisionMemberRequest"];
|
|
969
|
+
"application/x-www-form-urlencoded": components["schemas"]["ProvisionMemberRequest"];
|
|
970
|
+
"multipart/form-data": components["schemas"]["ProvisionMemberRequest"];
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
responses: {
|
|
974
|
+
201: {
|
|
975
|
+
headers: {
|
|
976
|
+
[name: string]: unknown;
|
|
977
|
+
};
|
|
978
|
+
content: {
|
|
979
|
+
"application/json": components["schemas"]["ProvisionMemberResponse"];
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
};
|
|
983
|
+
};
|
|
685
984
|
workspaces_api_v1_internal_members_retrieve: {
|
|
686
985
|
parameters: {
|
|
687
986
|
query?: never;
|
|
@@ -733,6 +1032,68 @@ export interface operations {
|
|
|
733
1032
|
};
|
|
734
1033
|
};
|
|
735
1034
|
};
|
|
1035
|
+
workspaces_api_v1_invitations_retrieve: {
|
|
1036
|
+
parameters: {
|
|
1037
|
+
query?: never;
|
|
1038
|
+
header?: never;
|
|
1039
|
+
path: {
|
|
1040
|
+
token: string;
|
|
1041
|
+
};
|
|
1042
|
+
cookie?: never;
|
|
1043
|
+
};
|
|
1044
|
+
requestBody?: never;
|
|
1045
|
+
responses: {
|
|
1046
|
+
200: {
|
|
1047
|
+
headers: {
|
|
1048
|
+
[name: string]: unknown;
|
|
1049
|
+
};
|
|
1050
|
+
content: {
|
|
1051
|
+
"application/json": components["schemas"]["InvitationPreviewResponse"];
|
|
1052
|
+
};
|
|
1053
|
+
};
|
|
1054
|
+
};
|
|
1055
|
+
};
|
|
1056
|
+
workspaces_api_v1_invitations_claim_create: {
|
|
1057
|
+
parameters: {
|
|
1058
|
+
query?: never;
|
|
1059
|
+
header?: never;
|
|
1060
|
+
path: {
|
|
1061
|
+
token: string;
|
|
1062
|
+
};
|
|
1063
|
+
cookie?: never;
|
|
1064
|
+
};
|
|
1065
|
+
requestBody?: never;
|
|
1066
|
+
responses: {
|
|
1067
|
+
200: {
|
|
1068
|
+
headers: {
|
|
1069
|
+
[name: string]: unknown;
|
|
1070
|
+
};
|
|
1071
|
+
content: {
|
|
1072
|
+
"application/json": components["schemas"]["InvitationClaimResponse"];
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
};
|
|
1077
|
+
workspaces_api_v1_invitations_decline_create: {
|
|
1078
|
+
parameters: {
|
|
1079
|
+
query?: never;
|
|
1080
|
+
header?: never;
|
|
1081
|
+
path: {
|
|
1082
|
+
token: string;
|
|
1083
|
+
};
|
|
1084
|
+
cookie?: never;
|
|
1085
|
+
};
|
|
1086
|
+
requestBody?: never;
|
|
1087
|
+
responses: {
|
|
1088
|
+
/** @description No response body */
|
|
1089
|
+
204: {
|
|
1090
|
+
headers: {
|
|
1091
|
+
[name: string]: unknown;
|
|
1092
|
+
};
|
|
1093
|
+
content?: never;
|
|
1094
|
+
};
|
|
1095
|
+
};
|
|
1096
|
+
};
|
|
736
1097
|
workspaces_api_v1_invitations_accept_create: {
|
|
737
1098
|
parameters: {
|
|
738
1099
|
query?: never;
|
|
@@ -758,4 +1119,23 @@ export interface operations {
|
|
|
758
1119
|
};
|
|
759
1120
|
};
|
|
760
1121
|
};
|
|
1122
|
+
workspaces_api_v1_roles_retrieve: {
|
|
1123
|
+
parameters: {
|
|
1124
|
+
query?: never;
|
|
1125
|
+
header?: never;
|
|
1126
|
+
path?: never;
|
|
1127
|
+
cookie?: never;
|
|
1128
|
+
};
|
|
1129
|
+
requestBody?: never;
|
|
1130
|
+
responses: {
|
|
1131
|
+
200: {
|
|
1132
|
+
headers: {
|
|
1133
|
+
[name: string]: unknown;
|
|
1134
|
+
};
|
|
1135
|
+
content: {
|
|
1136
|
+
"application/json": components["schemas"]["RoleListResponse"];
|
|
1137
|
+
};
|
|
1138
|
+
};
|
|
1139
|
+
};
|
|
1140
|
+
};
|
|
761
1141
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -38,15 +38,30 @@ export type MemberRoleUpdate = Schemas["PatchedMemberUpdateRequest"];
|
|
|
38
38
|
export type Invitation = Schemas["InvitationResponse"];
|
|
39
39
|
/** POST /invitations/accept request body — the token from the email link. */
|
|
40
40
|
export type InvitationAccept = Schemas["InvitationAcceptRequest"];
|
|
41
|
+
/** GET /invitations/{token} 200 body — the public (AllowAny) preview the
|
|
42
|
+
* `/invite/{token}` page renders before any auth decision (org-program §B2). */
|
|
43
|
+
export type InvitationPreview = Schemas["InvitationPreviewResponse"];
|
|
44
|
+
/** POST /invitations/{token}/claim 200 body — the single-use login grant for a
|
|
45
|
+
* not-yet-registered invitee; exchange it at auth's POST /grant/exchange/. */
|
|
46
|
+
export type InvitationClaim = Schemas["InvitationClaimResponse"];
|
|
47
|
+
/** One role of the effective registry (builtin + `STAPEL_WORKSPACES["ROLES"]`
|
|
48
|
+
* overlay) — GET /roles (org-program §A2). Capability strings are verbatim,
|
|
49
|
+
* wildcards (`*`, `members.*`) included. */
|
|
50
|
+
export type RoleInfo = Schemas["RoleResponse"];
|
|
51
|
+
/** GET /roles 200 body — the effective role registry, rank-descending. */
|
|
52
|
+
export type RoleList = Schemas["RoleListResponse"];
|
|
41
53
|
|
|
42
54
|
// ── documented corrections (drf-spectacular under-describes) ──────────────────
|
|
43
55
|
|
|
44
56
|
/**
|
|
45
|
-
* A workspace membership role, ordered least→most privileged.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
57
|
+
* A BUILTIN workspace membership role, ordered least→most privileged. Since
|
|
58
|
+
* stapel-workspaces 0.6.0 (org-program §A1) the effective registry is
|
|
59
|
+
* settings-extensible — a deployment can add roles (`secretary`, …) via
|
|
60
|
+
* `STAPEL_WORKSPACES["ROLES"]`, so `MemberResponse.role` is genuinely an open
|
|
61
|
+
* `string` on the wire and the registry endpoint (GET /roles, {@link RoleInfo})
|
|
62
|
+
* is the source of truth for what exists. This union names the four builtin
|
|
63
|
+
* keys that are ALWAYS present (with `owner` system-protected); use it for
|
|
64
|
+
* literals, not to validate server data.
|
|
50
65
|
*/
|
|
51
66
|
export type WorkspaceRole = "owner" | "admin" | "member" | "viewer";
|
|
52
67
|
|
package/src/api/workspacesApi.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
MemberRoleUpdate,
|
|
8
8
|
MembersParams,
|
|
9
9
|
InvitationAccept,
|
|
10
|
+
InvitationClaim,
|
|
11
|
+
InvitationPreview,
|
|
12
|
+
RoleList,
|
|
10
13
|
Workspace,
|
|
11
14
|
WorkspaceCreate,
|
|
12
15
|
WorkspaceList,
|
|
@@ -86,6 +89,34 @@ export interface WorkspacesApi {
|
|
|
86
89
|
|
|
87
90
|
/** Accept an invitation by its token — returns the caller's new membership. */
|
|
88
91
|
acceptInvitation(body: InvitationAccept): Promise<Member>;
|
|
92
|
+
|
|
93
|
+
// ── invite flow (org-program §B2, stapel-workspaces ≥0.7.0) ────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Public (AllowAny) invitation preview — what the `/invite/{token}` page
|
|
97
|
+
* renders before any auth decision. The token in the URL is the bearer
|
|
98
|
+
* secret: a credential, never log it.
|
|
99
|
+
*/
|
|
100
|
+
getInvitationPreview(token: string): Promise<InvitationPreview>;
|
|
101
|
+
/**
|
|
102
|
+
* Mint a login grant for a NOT-yet-registered invitee (AllowAny; only valid
|
|
103
|
+
* while `email_registered === false` — an existing account gets 409
|
|
104
|
+
* `email_already_registered` and the frontend switches to login). The
|
|
105
|
+
* invitation is NOT consumed: accept stays a separate, deliberate step.
|
|
106
|
+
* The returned `grant_token` is exchanged at AUTH's `POST /grant/exchange/`
|
|
107
|
+
* — deliberately not this pair's surface (pairs don't depend on each
|
|
108
|
+
* other); `InviteAcceptFlow` hands it outward via `onLoginGrant`.
|
|
109
|
+
*/
|
|
110
|
+
claimInvitation(token: string): Promise<InvitationClaim>;
|
|
111
|
+
/** Decline an invitation (authenticated + email-match) — the invitee's
|
|
112
|
+
* terminal "no". Decline ≠ revoke; both stay distinguishable in `status`. */
|
|
113
|
+
declineInvitation(token: string): Promise<void>;
|
|
114
|
+
|
|
115
|
+
// ── role registry (org-program §A2, stapel-workspaces ≥0.6.0) ──────────────
|
|
116
|
+
|
|
117
|
+
/** The effective role registry (builtin + deployment overlay), for
|
|
118
|
+
* RoleSelect-class UI — capability strings verbatim, rank-descending. */
|
|
119
|
+
listRoles(): Promise<RoleList>;
|
|
89
120
|
}
|
|
90
121
|
|
|
91
122
|
export function createWorkspacesApi(client: StapelClient): WorkspacesApi {
|
|
@@ -147,5 +178,24 @@ export function createWorkspacesApi(client: StapelClient): WorkspacesApi {
|
|
|
147
178
|
body satisfies InvitationAccept,
|
|
148
179
|
mutating()
|
|
149
180
|
),
|
|
181
|
+
|
|
182
|
+
getInvitationPreview: (token) =>
|
|
183
|
+
client.get(`/invitations/${encodeURIComponent(token)}`),
|
|
184
|
+
|
|
185
|
+
claimInvitation: (token) =>
|
|
186
|
+
client.post(
|
|
187
|
+
`/invitations/${encodeURIComponent(token)}/claim`,
|
|
188
|
+
undefined,
|
|
189
|
+
mutating()
|
|
190
|
+
),
|
|
191
|
+
|
|
192
|
+
declineInvitation: (token) =>
|
|
193
|
+
client.post(
|
|
194
|
+
`/invitations/${encodeURIComponent(token)}/decline`,
|
|
195
|
+
undefined,
|
|
196
|
+
mutating()
|
|
197
|
+
),
|
|
198
|
+
|
|
199
|
+
listRoles: () => client.get("/roles"),
|
|
150
200
|
};
|
|
151
201
|
}
|