@hanzo/rooms 0.1.85 → 0.1.86

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/Orgs.js CHANGED
@@ -47,7 +47,8 @@ import { matches, refusal, slugify } from './lib/orgname.js';
47
47
  import { team as workspace } from './lib/host.js';
48
48
  import { checkoutUrl, money } from './lib/pay.js';
49
49
  import { cartUrl, charge, priced, usePlan, usePlans } from './lib/plans.js';
50
- import { createInvitation, inviteLink } from './lib/invite.js';
50
+ import { createInvitation, inviteLink, setOrganizationProfile } from './lib/invite.js';
51
+ import { createOrg, say } from './lib/org.js';
51
52
  import { countSignup } from './lib/intent.js';
52
53
  import { BAD, mix } from './lib/mix.js';
53
54
  import { useRooms } from './host.js';
@@ -647,12 +648,15 @@ function Everyone({ q, skip }) {
647
648
  *
648
649
  * The slug follows the name the way IAM's onboarding makes it, until it is typed
649
650
  * over; a slug typed is IAM's to take as it is, so one IAM would not keep is
650
- * refused here with the rule, never rewritten. IAM's own onboarding makes it
651
- * (`POST /v1/iam/onboard`): the organization, the reader as its owner and its
652
- * first API key, the founder's account untouched. It is sent the name when the
653
- * name makes the slug, and the slug when the slug was typed, the display name
654
- * then written on the row (`/v1/iam/organizations/profile`). The token in hand
655
- * predates it, so it is refreshed before the organization opens.
651
+ * refused here with the rule, never rewritten. The create is the one every
652
+ * surface uses, `POST /v1/account/orgs` (lib/org.ts `createOrg`): it files the
653
+ * organization in IAM and records the reader as its owner through IAM's
654
+ * membership API, the account untouched. IAM's own `/v1/iam/onboard` is not
655
+ * reachable from here: the edge answers no app origin's CORS on it, because it
656
+ * re-keys the session (cloud cors_origin.go `sessionRoutes`). It is sent the
657
+ * name when the name makes the slug, and the slug when the slug was typed, the
658
+ * display name then written on the row (lib/invite.ts `setOrganizationProfile`).
659
+ * The token in hand predates it, so it is refreshed before the organization opens.
656
660
  */
657
661
  function Found({ onClose, onMade }) {
658
662
  const { sdk } = useIam();
@@ -681,29 +685,15 @@ function Found({ onClose, onMade }) {
681
685
  return setRefused(wrong);
682
686
  setBusy(true);
683
687
  setRefused(null);
684
- const headers = { Authorization: `Bearer ${bearer() ?? ''}`, 'Content-Type': 'application/json' };
685
- const why = async (r) => {
686
- const body = (await r.json().catch(() => null));
687
- return body?.error || body?.detail || body?.msg || body?.message || `IAM answered ${r.status}.`;
688
- };
689
688
  try {
690
689
  const named = slugify(display) === slug;
691
- const r = await fetch(`${api()}/v1/iam/onboard`, { method: 'POST', headers, body: JSON.stringify({ name: named ? display : slug }) });
692
- if (!r.ok)
693
- throw new Error(await why(r));
694
- const made = (await r.json().catch(() => null))?.org;
695
- if (!made)
696
- throw new Error('IAM made no organization. Try again.');
690
+ const made = await createOrg(named ? display : slug);
697
691
  renew();
698
692
  await sdk.refreshAccessToken().catch(() => null);
699
693
  if (!named) {
700
- const p = await fetch(`${api()}/v1/iam/organizations/profile`, {
701
- method: 'POST',
702
- headers: { ...headers, Authorization: `Bearer ${bearer() ?? ''}` },
703
- body: JSON.stringify({ owner: 'admin', name: made, displayName: display }),
694
+ await setOrganizationProfile('admin', made, { displayName: display }).catch((err) => {
695
+ throw new Error(`${made} is made, and it is still called ${made}: ${say(err)}`);
704
696
  });
705
- if (!p.ok)
706
- throw new Error(`${made} is made, and it is still called ${made}: ${await why(p)}`);
707
697
  }
708
698
  // A token that does not carry it yet is minted on the next load, which opens it.
709
699
  if (!claimed().includes(made)) {
@@ -714,7 +704,7 @@ function Found({ onClose, onMade }) {
714
704
  onMade(made);
715
705
  }
716
706
  catch (err) {
717
- setRefused(err instanceof Error ? err.message : 'Could not create it.');
707
+ setRefused(say(err));
718
708
  setBusy(false);
719
709
  }
720
710
  };
@@ -129,10 +129,18 @@ export declare function follow(was: string | undefined, moved: () => void): () =
129
129
  */
130
130
  export declare const DIRECTORIES: readonly string[];
131
131
  /**
132
- * The organizations this person works in, as the token names them: every org of
133
- * its `orgs` but a directory, which is a home and nobody's tenant. Empty for an
134
- * account that has not made one: the SDK's own list falls back to the `owner`
135
- * claim, which is the application's org and nobody's to work in.
132
+ * The orgs the platform runs under its own names (hanzoai/authz claims.go
133
+ * `platformOrgs`): the brands and the estate's own namespaces. One is a tenant
134
+ * only of the people who run it, its owners and admins, and of a SuperAdmin; a
135
+ * customer who landed in a brand org as a plain member belongs to none of its
136
+ * data, and cloud refuses every request scoped to it.
137
+ */
138
+ export declare const PLATFORM: readonly string[];
139
+ /**
140
+ * The organizations this person works in: the token's `orgs` its claims open
141
+ * (`opened`). Empty for an account that has not made one, or that is only a plain
142
+ * member of a brand org: the SDK's own list falls back to the `owner` claim, which
143
+ * is the application's org and nobody's to work in.
136
144
  */
137
145
  export declare function orgs(): string[];
138
146
  /** The org of the person's own row: the first entry of the token's `orgs`, a directory or not. */
@@ -14,7 +14,7 @@
14
14
  * who was signed in, which reads as a permissions problem rather than a typo.
15
15
  * Two derivations of one name never agree; this is the one derivation.
16
16
  */
17
- import { superAdmin as sudo } from '@hanzo/iam/browser';
17
+ import { superAdmin as sudoOf } from '@hanzo/iam/browser';
18
18
  import { api } from './api.js';
19
19
  import { where } from '../where.js';
20
20
  /** The SDK's own keys, `hanzo_iam_`-prefixed. The far side of a boundary. */
@@ -323,13 +323,45 @@ function listed(token) {
323
323
  return out;
324
324
  }
325
325
  /**
326
- * The organizations this person works in, as the token names them: every org of
327
- * its `orgs` but a directory, which is a home and nobody's tenant. Empty for an
328
- * account that has not made one: the SDK's own list falls back to the `owner`
329
- * claim, which is the application's org and nobody's to work in.
326
+ * The orgs the platform runs under its own names (hanzoai/authz claims.go
327
+ * `platformOrgs`): the brands and the estate's own namespaces. One is a tenant
328
+ * only of the people who run it, its owners and admins, and of a SuperAdmin; a
329
+ * customer who landed in a brand org as a plain member belongs to none of its
330
+ * data, and cloud refuses every request scoped to it.
331
+ */
332
+ export const PLATFORM = [
333
+ 'hanzo', 'lux', 'zoo', 'pars', 'platform', 'hanzoai', 'luxfi', 'zooai', 'hanzo-inc', 'hanzo-apps', 'hanzo-community',
334
+ ];
335
+ /**
336
+ * The orgs a token opens to the person it names, home first, each once:
337
+ * hanzoai/authz `Claims.opens`, the rule cloud scopes every request by. A
338
+ * directory opens to no one, `admin` to a SuperAdmin, a platform org to its
339
+ * owners and admins and a SuperAdmin, and any other org to every member.
340
+ */
341
+ function opened(token) {
342
+ const c = claimsOf(token);
343
+ const sudo = sudoOf(c);
344
+ const set = c?.orgs;
345
+ const out = [];
346
+ for (const ref of Array.isArray(set) ? set : []) {
347
+ const m = ref;
348
+ const o = m?.org;
349
+ if (typeof o !== 'string' || !o || out.includes(o) || DIRECTORIES.includes(o))
350
+ continue;
351
+ const runs = typeof m?.role === 'string' && ['owner', 'admin'].includes(m.role.trim().toLowerCase());
352
+ if (o === 'admin' ? sudo : PLATFORM.includes(o) ? sudo || runs : true)
353
+ out.push(o);
354
+ }
355
+ return out;
356
+ }
357
+ /**
358
+ * The organizations this person works in: the token's `orgs` its claims open
359
+ * (`opened`). Empty for an account that has not made one, or that is only a plain
360
+ * member of a brand org: the SDK's own list falls back to the `owner` claim, which
361
+ * is the application's org and nobody's to work in.
330
362
  */
331
363
  export function orgs() {
332
- return listed(bearer()).filter((o) => !DIRECTORIES.includes(o));
364
+ return opened(bearer());
333
365
  }
334
366
  /** The org of the person's own row: the first entry of the token's `orgs`, a directory or not. */
335
367
  export function home() {
@@ -452,7 +484,7 @@ export function choose() {
452
484
  * organization and the step into one.
453
485
  */
454
486
  export function superAdmin() {
455
- return sudo(claims());
487
+ return sudoOf(claims());
456
488
  }
457
489
  /** The organization a SuperAdmin stepped into through IAM, from the token's `assumed` claim, or null. */
458
490
  export function assumed() {
@@ -488,7 +520,7 @@ export async function support(org) {
488
520
  throw new Error(body?.msg || `IAM answered ${res.status}.`);
489
521
  tokens().setItem(ACCESS, next);
490
522
  tokens().setItem(EXPIRES, String(exp * 1000));
491
- pick(org ?? listed(next).find((o) => !DIRECTORIES.includes(o)) ?? '');
523
+ pick(org ?? opened(next)[0] ?? '');
492
524
  window.location.reload();
493
525
  }
494
526
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",