@oxy.so/core 7.23.1 → 7.24.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.
@@ -679,6 +679,16 @@ const dict = {
679
679
  add: 'Add your links',
680
680
  placeholder: 'Enter URL',
681
681
  yourLinks: 'Your Links',
682
+ linkTitle: 'Title',
683
+ moveUp: 'Move up',
684
+ moveDown: 'Move down',
685
+ errors: {
686
+ empty: 'Enter a link.',
687
+ invalid: 'Enter a valid link.',
688
+ insecure: 'Links must start with https://.',
689
+ duplicate: 'That link is already on your profile.',
690
+ limit: 'You can add up to {{count}} links.',
691
+ },
682
692
  count: '{{count}} link added',
683
693
  count_plural: '{{count}} links added',
684
694
  },
@@ -381,6 +381,16 @@ const dict = {
381
381
  add: 'Añade tus enlaces',
382
382
  placeholder: 'Introduce la URL',
383
383
  yourLinks: 'Tus enlaces',
384
+ linkTitle: 'Título',
385
+ moveUp: 'Subir',
386
+ moveDown: 'Bajar',
387
+ errors: {
388
+ empty: 'Introduce un enlace.',
389
+ invalid: 'Introduce un enlace válido.',
390
+ insecure: 'Los enlaces deben empezar por https://.',
391
+ duplicate: 'Ese enlace ya está en tu perfil.',
392
+ limit: 'Puedes añadir hasta {{count}} enlaces.',
393
+ },
384
394
  count: '{{count}} enlace añadido',
385
395
  count_plural: '{{count}} enlaces añadidos',
386
396
  },
package/dist/esm/index.js CHANGED
@@ -35,7 +35,7 @@ export { ASSET_MULTIPART_UPLOAD_MAX_BYTES, assetUrlCacheTTL, MAX_UPLOAD_BYTES }
35
35
  export { DirectUploadInterruptedError } from './api/directUpload.js';
36
36
  export { buildUserDid } from './api/identity.js';
37
37
  export { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from './api/accounts.js';
38
- // The store's installer (ADR 0053): what a runtime that installs applications registers beside
38
+ // The store's installer (ADR 0059): what a runtime that installs applications registers beside
39
39
  // `oxy.store`, and what a store asks it.
40
40
  export { getStoreInstaller, setStoreInstaller, StoreInstallerError, } from './api/storeInstaller.js';
41
41
  export { REPUTATION_CACHE_PREFIX } from './api/reputation.js';
@@ -18,7 +18,7 @@ import type { AccountAccessGrant, OrganizationType } from '@oxy.so/contracts';
18
18
  * no per-request "acting-as" header. Identity is carried by the session/token,
19
19
  * so a switch propagates through reload and cross-domain exactly like a login.
20
20
  */
21
- import type { AgentKeyOperation, AgentKeyOperationProof } from '@oxy.so/contracts';
21
+ import type { AgentKeyOperation, AgentKeyOperationProof, OrganizationIdentityKeyState, OrganizationKeyApplyRequest, OrganizationKeyChallenge, OrganizationKeyChallengeRequest, OrganizationKeyOperation } from '@oxy.so/contracts';
22
22
  import type { User } from '../models/interfaces';
23
23
  import type { AccountCategoryId, AccountKind, AvatarCharacter, ChildAccountKind } from '@oxy.so/contracts';
24
24
  import type { SessionLoginResponse } from '../models/session';
@@ -384,6 +384,44 @@ export declare class AccountAgentKeysApi {
384
384
  revokedMethodIds: string[];
385
385
  }>;
386
386
  }
387
+ /** A secp256k1 key pair held on this device: lowercase hex, the public key uncompressed. */
388
+ export interface OrganizationKeyPair {
389
+ privateKey: string;
390
+ publicKey: string;
391
+ }
392
+ /**
393
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
394
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
395
+ * Only the organization's owners and admins may read or change it; the key is
396
+ * created and held in Commons and never signs anybody in.
397
+ *
398
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
399
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
400
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
401
+ * signed-in person is not an owner or admin of a claimed, active organization.
402
+ */
403
+ export declare class AccountIdentityKeyApi {
404
+ private readonly ctx;
405
+ constructor(ctx: OxyContext);
406
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
407
+ get(accountId: string): Promise<OrganizationIdentityKeyState>;
408
+ /** A one-use challenge for one change, with the claims every signer signs. */
409
+ requestChallenge(accountId: string, request: OrganizationKeyChallengeRequest): Promise<OrganizationKeyChallenge>;
410
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
411
+ apply(accountId: string, request: OrganizationKeyApplyRequest): Promise<OrganizationIdentityKeyState>;
412
+ /**
413
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
414
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
415
+ * organization key the device generated (`create`, or `replace` with a new
416
+ * one); `previousKey` is the current organization key when the device holds
417
+ * it, so the replacement is recorded as proven by it.
418
+ */
419
+ link(accountId: string, input: {
420
+ operation: OrganizationKeyOperation;
421
+ key: OrganizationKeyPair;
422
+ previousKey?: OrganizationKeyPair;
423
+ }): Promise<OrganizationIdentityKeyState>;
424
+ }
387
425
  export interface AccountInvitation {
388
426
  id: string;
389
427
  accountId: string;
@@ -425,6 +463,8 @@ export declare class AccountsApi {
425
463
  readonly members: AccountMembersApi;
426
464
  readonly invitations: AccountInvitationsApi;
427
465
  readonly agentKeys: AccountAgentKeysApi;
466
+ /** An organization's identity key: its DID `#key-1` and Peable wallet key (ADR 0061). */
467
+ readonly identityKey: AccountIdentityKeyApi;
428
468
  constructor(ctx: OxyContext);
429
469
  /**
430
470
  * List the accounts the caller can access: their own personal (root)
@@ -5,7 +5,7 @@
5
5
  * `oxy.store` says what the store lists; an installer says what the machine can do with a
6
6
  * listing. A runtime that installs applications registers one (OxyOS's Bloom host registers one
7
7
  * backed by the system's install service before the application's code runs, OxyOS ADR 0030
8
- * decision 9; ADR 0053 here). Where none is registered — the web, iOS, Android — there is
8
+ * decision 9; ADR 0059 here). Where none is registered — the web, iOS, Android — there is
9
9
  * nothing to install, and a store shows what it shows without one. An application never asks
10
10
  * which platform it runs on: it asks whether there is an installer.
11
11
  *
@@ -670,6 +670,16 @@ declare const dict: {
670
670
  add: string;
671
671
  placeholder: string;
672
672
  yourLinks: string;
673
+ linkTitle: string;
674
+ moveUp: string;
675
+ moveDown: string;
676
+ errors: {
677
+ empty: string;
678
+ invalid: string;
679
+ insecure: string;
680
+ duplicate: string;
681
+ limit: string;
682
+ };
673
683
  count: string;
674
684
  count_plural: string;
675
685
  };
@@ -381,6 +381,16 @@ declare const dict: {
381
381
  add: string;
382
382
  placeholder: string;
383
383
  yourLinks: string;
384
+ linkTitle: string;
385
+ moveUp: string;
386
+ moveDown: string;
387
+ errors: {
388
+ empty: string;
389
+ invalid: string;
390
+ insecure: string;
391
+ duplicate: string;
392
+ limit: string;
393
+ };
384
394
  count: string;
385
395
  count_plural: string;
386
396
  };
@@ -23,8 +23,8 @@ export declare const oxyAppAssertionClaimsSchema: z.ZodObject<{
23
23
  sid: z.ZodString;
24
24
  app: z.ZodString;
25
25
  platform: z.ZodEnum<{
26
- android: "android";
27
26
  ios: "ios";
27
+ android: "android";
28
28
  web: "web";
29
29
  }>;
30
30
  iat: z.ZodNumber;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "7.23.1",
3
+ "version": "7.24.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -181,7 +181,7 @@
181
181
  "dependencies": {
182
182
  "@noble/ciphers": "^2.4.0",
183
183
  "@noble/hashes": "^2.4.0",
184
- "@oxy.so/contracts": "^6.9.0",
184
+ "@oxy.so/contracts": "^6.11.0",
185
185
  "@oxy.so/protocol": "^1.6.1",
186
186
  "@oxy.so/telemetry": "^2.1.0",
187
187
  "@scure/bip39": "^2.4.0",
@@ -18,13 +18,26 @@ import type { AccountAccessGrant, OrganizationType } from '@oxy.so/contracts';
18
18
  * no per-request "acting-as" header. Identity is carried by the session/token,
19
19
  * so a switch propagates through reload and cross-domain exactly like a login.
20
20
  */
21
- import type { AgentKeyOperation, AgentKeyOperationProof } from '@oxy.so/contracts';
21
+ import type {
22
+ AgentKeyOperation,
23
+ AgentKeyOperationProof,
24
+ OrganizationIdentityKeyState,
25
+ OrganizationKeyApplyRequest,
26
+ OrganizationKeyChallenge,
27
+ OrganizationKeyChallengeRequest,
28
+ OrganizationKeyOperation,
29
+ } from '@oxy.so/contracts';
22
30
  import {
23
31
  agentKeyListResponseCheck,
24
32
  agentKeyOperationChallengeCheck,
25
33
  agentKeyOperationCheck,
26
34
  agentKeyOperationResultCheck,
27
35
  executeAgentKeyOperationCheck,
36
+ organizationIdentityKeyStateCheck,
37
+ organizationKeyApplyRequestCheck,
38
+ organizationKeyChallengeCheck,
39
+ organizationKeyChallengeRequestCheck,
40
+ organizationKeyProofClaims,
28
41
  parseContract,
29
42
  } from '@oxy.so/contracts/client';
30
43
  import type { User } from '../models/interfaces';
@@ -457,6 +470,124 @@ export class AccountAgentKeysApi {
457
470
  }
458
471
  }
459
472
 
473
+ /** A secp256k1 key pair held on this device: lowercase hex, the public key uncompressed. */
474
+ export interface OrganizationKeyPair {
475
+ privateKey: string;
476
+ publicKey: string;
477
+ }
478
+
479
+ /**
480
+ * `oxy.accounts.identityKey` — an organization's identity key (ADR 0061): its
481
+ * DID `#key-1`, the key Peable derives its FairCoin receive addresses from.
482
+ * Only the organization's owners and admins may read or change it; the key is
483
+ * created and held in Commons and never signs anybody in.
484
+ *
485
+ * `get` answers `publicKey: null` for an organization that has no key yet (its
486
+ * DID lists no `#key-1`): that is when a client shows its owners "Set up the
487
+ * organization's Peable wallet". A 403 (`ORGANIZATION_KEY_FORBIDDEN`) means the
488
+ * signed-in person is not an owner or admin of a claimed, active organization.
489
+ */
490
+ export class AccountIdentityKeyApi {
491
+ constructor(private readonly ctx: OxyContext) {}
492
+
493
+ /** The organization's key (`null` without one) and every key it has had, oldest first. */
494
+ async get(accountId: string): Promise<OrganizationIdentityKeyState> {
495
+ return parseContract(
496
+ organizationIdentityKeyStateCheck,
497
+ await this.ctx.request<unknown>(
498
+ 'GET',
499
+ `/accounts/${enc(accountId)}/identity-key`,
500
+ undefined,
501
+ { cache: false },
502
+ ),
503
+ );
504
+ }
505
+
506
+ /** A one-use challenge for one change, with the claims every signer signs. */
507
+ async requestChallenge(
508
+ accountId: string,
509
+ request: OrganizationKeyChallengeRequest,
510
+ ): Promise<OrganizationKeyChallenge> {
511
+ return parseContract(
512
+ organizationKeyChallengeCheck,
513
+ await this.ctx.request<unknown>(
514
+ 'POST',
515
+ `/accounts/${enc(accountId)}/identity-key/challenge`,
516
+ parseContract(organizationKeyChallengeRequestCheck, request),
517
+ { cache: false },
518
+ ),
519
+ );
520
+ }
521
+
522
+ /** Apply a change with its signatures (see {@link link} for the whole ceremony). */
523
+ async apply(
524
+ accountId: string,
525
+ request: OrganizationKeyApplyRequest,
526
+ ): Promise<OrganizationIdentityKeyState> {
527
+ return parseContract(
528
+ organizationIdentityKeyStateCheck,
529
+ await this.ctx.request<unknown>(
530
+ 'POST',
531
+ `/accounts/${enc(accountId)}/identity-key`,
532
+ parseContract(organizationKeyApplyRequestCheck, request),
533
+ { cache: false },
534
+ ),
535
+ );
536
+ }
537
+
538
+ /**
539
+ * Commons' whole ceremony. NATIVE-ONLY: the actor is THIS device's personal
540
+ * root (`KeyManager`), which must be the signed-in person's. `key` is the
541
+ * organization key the device generated (`create`, or `replace` with a new
542
+ * one); `previousKey` is the current organization key when the device holds
543
+ * it, so the replacement is recorded as proven by it.
544
+ */
545
+ async link(
546
+ accountId: string,
547
+ input: {
548
+ operation: OrganizationKeyOperation;
549
+ key: OrganizationKeyPair;
550
+ previousKey?: OrganizationKeyPair;
551
+ },
552
+ ): Promise<OrganizationIdentityKeyState> {
553
+ const { KeyManager, signIdentityProof } = await import('../crypto/index');
554
+ const [rootPrivateKey, rootPublicKey] = await Promise.all([
555
+ KeyManager.getPrivateKey(),
556
+ KeyManager.getPublicKey(),
557
+ ]);
558
+ if (!rootPrivateKey || !rootPublicKey) {
559
+ throw new Error('No identity on this device to sign with');
560
+ }
561
+ const challenge = await this.requestChallenge(accountId, {
562
+ operation: input.operation,
563
+ publicKey: input.key.publicKey,
564
+ });
565
+ const root = rootPublicKey.trim().toLowerCase();
566
+ if (challenge.actorPublicKey !== root) {
567
+ throw new Error("This device's identity is not the signed-in account's");
568
+ }
569
+ const sign = async (signer: OrganizationKeyPair, publicKey: string) =>
570
+ (
571
+ await signIdentityProof(
572
+ { privateKey: signer.privateKey, publicKey },
573
+ organizationKeyProofClaims(challenge, publicKey),
574
+ )
575
+ ).signature;
576
+ const previousPublicKey = challenge.previousPublicKey;
577
+ return this.apply(accountId, {
578
+ operation: input.operation,
579
+ publicKey: challenge.publicKey,
580
+ challenge: challenge.challenge,
581
+ expiresAt: challenge.expiresAt,
582
+ actorSignature: await sign({ privateKey: rootPrivateKey, publicKey: root }, root),
583
+ keySignature: await sign(input.key, challenge.publicKey),
584
+ ...(input.previousKey && previousPublicKey
585
+ ? { previousKeySignature: await sign(input.previousKey, previousPublicKey) }
586
+ : {}),
587
+ });
588
+ }
589
+ }
590
+
460
591
  export interface AccountInvitation {
461
592
  id: string;
462
593
  accountId: string;
@@ -540,11 +671,14 @@ export class AccountsApi {
540
671
  readonly members: AccountMembersApi;
541
672
  readonly invitations: AccountInvitationsApi;
542
673
  readonly agentKeys: AccountAgentKeysApi;
674
+ /** An organization's identity key: its DID `#key-1` and Peable wallet key (ADR 0061). */
675
+ readonly identityKey: AccountIdentityKeyApi;
543
676
 
544
677
  constructor(private readonly ctx: OxyContext) {
545
678
  this.members = new AccountMembersApi(ctx);
546
679
  this.invitations = new AccountInvitationsApi(ctx);
547
680
  this.agentKeys = new AccountAgentKeysApi(ctx);
681
+ this.identityKey = new AccountIdentityKeyApi(ctx);
548
682
  }
549
683
 
550
684
  /**
@@ -5,7 +5,7 @@
5
5
  * `oxy.store` says what the store lists; an installer says what the machine can do with a
6
6
  * listing. A runtime that installs applications registers one (OxyOS's Bloom host registers one
7
7
  * backed by the system's install service before the application's code runs, OxyOS ADR 0030
8
- * decision 9; ADR 0053 here). Where none is registered — the web, iOS, Android — there is
8
+ * decision 9; ADR 0059 here). Where none is registered — the web, iOS, Android — there is
9
9
  * nothing to install, and a store shows what it shows without one. An application never asks
10
10
  * which platform it runs on: it asks whether there is an installer.
11
11
  *