@oxyhq/core 9.2.0 → 9.2.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.
@@ -34,7 +34,8 @@ export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHand
34
34
  export { normalizeProfileLinks } from './utils/profileLinks';
35
35
  export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
36
36
  export type { PublicApplication, ConnectedApp, } from './mixins/OxyServices.connectedApps';
37
- export type { AccountKind, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode, AccountCredentialType, AccountCredentialEnvironment, AccountCredentialStatus, AccountCredential, AccountCredentialWithSecret, RotateAccountCredentialResult, ListAccountsOptions, CreateAccountInput, UpdateAccountInput, InviteAccountMemberInput, UpdateAccountMemberInput, TransferAccountOwnershipInput, CreateAccountCredentialInput, AccountSuccessResult, SwitchAccountResult, Application, ApplicationType, ApplicationStatus, ApplicationCredential, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, } from './mixins/OxyServices.accounts';
37
+ export type { AccountKind, OrganizationCategory, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode, AccountCredentialType, AccountCredentialEnvironment, AccountCredentialStatus, AccountCredential, AccountCredentialWithSecret, RotateAccountCredentialResult, ListAccountsOptions, CreateAccountInput, UpdateAccountInput, InviteAccountMemberInput, UpdateAccountMemberInput, TransferAccountOwnershipInput, CreateAccountCredentialInput, AccountSuccessResult, SwitchAccountResult, Application, ApplicationType, ApplicationStatus, ApplicationCredential, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, } from './mixins/OxyServices.accounts';
38
+ export { ORGANIZATION_CATEGORIES } from './mixins/OxyServices.accounts';
38
39
  export type { ReputationCategory, TrustTier, ReputationTransactionStatus, ReputationTargetEntityType, ReputationDisputeStatus, ReputationInfluenceContext, ReputationTransaction, ReputationBalanceBreakdown, ReputationInfluence, ReputationReliability, ReputationBalance, ReputationDispute, ReputationRule, ReputationLeaderboardEntry, ReputationInfluenceResult, ReverseReputationTransactionResult, AwardReputationInput, CreateReputationDisputeInput, ResolveReputationDisputeInput, UpsertReputationRuleInput, ReverseReputationTransactionInput, } from './mixins/OxyServices.reputation';
39
40
  export { buildUserDid } from './mixins/OxyServices.identity';
40
41
  export type { IdentityRecordType, UnlinkableAuthMethodType, LinkAuthMethodResult, PublishRecordResult, VerifyRecordResult, VerifyDomainResult, RemoveDomainResult, } from './mixins/OxyServices.identity';
@@ -33,6 +33,7 @@
33
33
  * registers the switched session into the operator's device-set directly).
34
34
  */
35
35
  import type { User } from '../models/interfaces';
36
+ import type { OrganizationCategory } from '@oxyhq/contracts';
36
37
  import type { SessionLoginResponse } from '../models/session';
37
38
  import type { OxyServicesBase } from '../OxyServices.base';
38
39
  /**
@@ -42,6 +43,9 @@ import type { OxyServicesBase } from '../OxyServices.base';
42
43
  * and have no direct login.
43
44
  */
44
45
  export type AccountKind = 'personal' | 'organization' | 'project' | 'bot';
46
+ /** Real-estate / team taxonomy for `kind: 'organization'` accounts. */
47
+ export type { OrganizationCategory } from '@oxyhq/contracts';
48
+ export { ORGANIZATION_CATEGORIES } from '@oxyhq/contracts';
45
49
  /**
46
50
  * The calling user's relationship to an account node, as resolved by the API:
47
51
  * - `self` — the caller's own personal (root) account.
@@ -143,6 +147,8 @@ export interface CreateAccountInput {
143
147
  };
144
148
  bio?: string;
145
149
  avatar?: string;
150
+ /** Meaningful only when `kind` is `organization`. */
151
+ organizationCategory?: OrganizationCategory;
146
152
  }
147
153
  /** Input accepted by `updateAccount`. Tree placement changes go through `/move`. */
148
154
  export interface UpdateAccountInput {
@@ -153,6 +159,8 @@ export interface UpdateAccountInput {
153
159
  };
154
160
  bio?: string | null;
155
161
  avatar?: string | null;
162
+ /** Clears the category when `null`; only valid on `kind: 'organization'`. */
163
+ organizationCategory?: OrganizationCategory | null;
156
164
  }
157
165
  /** Input accepted by `inviteAccountMember`. The owner role cannot be invited. */
158
166
  export interface InviteAccountMemberInput {
@@ -319,6 +319,7 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
319
319
  deviceId: string;
320
320
  expiresAt: string;
321
321
  user: User;
322
+ deviceSecret?: string;
322
323
  }>;
323
324
  /**
324
325
  * MECHANISM A — same-device shared-keychain SSO.
@@ -1,4 +1,4 @@
1
- import type { UserNameResponse } from '@oxyhq/contracts';
1
+ import type { OrganizationCategory, UserNameResponse } from '@oxyhq/contracts';
2
2
  export interface OxyConfig {
3
3
  baseURL: string;
4
4
  cloudURL?: string;
@@ -129,6 +129,8 @@ export interface User {
129
129
  };
130
130
  isManagedAccount?: boolean;
131
131
  managedBy?: string;
132
+ /** Real-estate taxonomy when this user is a `kind: 'organization'` account. */
133
+ organizationCategory?: OrganizationCategory;
132
134
  notificationPreferences?: NotificationPreferences;
133
135
  userPreferences?: UserPreferences;
134
136
  [key: string]: unknown;
@@ -31,4 +31,10 @@ export interface SessionLoginResponse {
31
31
  user: MinimalUserData;
32
32
  /** JWT access token for API authentication */
33
33
  accessToken?: string;
34
+ /**
35
+ * Rotating zero-cookie device credential minted on sign-in / claim. Persisted
36
+ * first-party alongside `deviceId` so cold boot can re-mint via
37
+ * `POST /session/device/token`.
38
+ */
39
+ deviceSecret?: string;
34
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "9.2.0",
3
+ "version": "9.2.1",
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",
@@ -258,6 +258,26 @@ describe('runSessionColdBoot — shared-key-signin (native)', () => {
258
258
  );
259
259
  });
260
260
 
261
+ it('persists deviceSecret from shared-key verifyChallenge for the next mint lane', async () => {
262
+ const store = createMemoryAuthStateStore();
263
+ const signInWithSharedIdentity = jest.fn(async () => ({
264
+ ...sharedSession,
265
+ deviceSecret: 'shared-mint-secret',
266
+ }));
267
+ const { oxy } = makeOxy({ signInWithSharedIdentity });
268
+
269
+ await runSessionColdBoot({ oxy, store, platform: NATIVE });
270
+
271
+ const persisted = await store.load();
272
+ expect(persisted).toMatchObject({
273
+ sessionId: 'sess-shared',
274
+ userId: 'user-shared',
275
+ deviceId: 'dev-1',
276
+ deviceSecret: 'shared-mint-secret',
277
+ accessToken: 'access-shared',
278
+ });
279
+ });
280
+
261
281
  it('does NOT run the shared-key lane on web', async () => {
262
282
  const store = createMemoryAuthStateStore();
263
283
  const signInWithSharedIdentity = jest.fn(async () => sharedSession);
@@ -156,6 +156,19 @@ export async function runSessionColdBoot(
156
156
  if (!session?.accessToken) {
157
157
  return { kind: 'skip' };
158
158
  }
159
+ // `verifyChallenge` mints a rotating deviceSecret; persist it so the next
160
+ // boot can use the faster device-secret-mint lane (sockets + tab-focus
161
+ // re-mint depend on the credential being in the store).
162
+ if (session.deviceId && session.deviceSecret) {
163
+ await store.save({
164
+ sessionId: session.sessionId,
165
+ userId: session.user.id,
166
+ deviceId: session.deviceId,
167
+ deviceSecret: session.deviceSecret,
168
+ accessToken: session.accessToken,
169
+ expiresAt: session.expiresAt,
170
+ });
171
+ }
159
172
  return {
160
173
  kind: 'session',
161
174
  session: {
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ export type {
86
86
  // ---------------------------------------------------------------------------
87
87
  export type {
88
88
  AccountKind,
89
+ OrganizationCategory,
89
90
  AccountRelationship,
90
91
  AccountRole,
91
92
  AccountMemberStatus,
@@ -127,6 +128,8 @@ export type {
127
128
  ApplicationUsageStats,
128
129
  } from './mixins/OxyServices.accounts';
129
130
 
131
+ export { ORGANIZATION_CATEGORIES } from './mixins/OxyServices.accounts';
132
+
130
133
  // ---------------------------------------------------------------------------
131
134
  // Reputation (Oxy Trust: ledger, balances, disputes, rules, influence)
132
135
  // ---------------------------------------------------------------------------
@@ -33,6 +33,7 @@
33
33
  * registers the switched session into the operator's device-set directly).
34
34
  */
35
35
  import type { User } from '../models/interfaces';
36
+ import type { OrganizationCategory } from '@oxyhq/contracts';
36
37
  import type { SessionLoginResponse } from '../models/session';
37
38
  import type { OxyServicesBase } from '../OxyServices.base';
38
39
  import { normalizeUserIdentity } from '../utils/userIdentity';
@@ -50,6 +51,10 @@ import { CACHE_TIMES } from './mixinHelpers';
50
51
  */
51
52
  export type AccountKind = 'personal' | 'organization' | 'project' | 'bot';
52
53
 
54
+ /** Real-estate / team taxonomy for `kind: 'organization'` accounts. */
55
+ export type { OrganizationCategory } from '@oxyhq/contracts';
56
+ export { ORGANIZATION_CATEGORIES } from '@oxyhq/contracts';
57
+
53
58
  /**
54
59
  * The calling user's relationship to an account node, as resolved by the API:
55
60
  * - `self` — the caller's own personal (root) account.
@@ -155,6 +160,8 @@ export interface CreateAccountInput {
155
160
  name?: { first?: string; last?: string };
156
161
  bio?: string;
157
162
  avatar?: string;
163
+ /** Meaningful only when `kind` is `organization`. */
164
+ organizationCategory?: OrganizationCategory;
158
165
  }
159
166
 
160
167
  /** Input accepted by `updateAccount`. Tree placement changes go through `/move`. */
@@ -163,6 +170,8 @@ export interface UpdateAccountInput {
163
170
  name?: { first?: string; last?: string };
164
171
  bio?: string | null;
165
172
  avatar?: string | null;
173
+ /** Clears the category when `null`; only valid on `kind: 'organization'`. */
174
+ organizationCategory?: OrganizationCategory | null;
166
175
  }
167
176
 
168
177
  /** Input accepted by `inviteAccountMember`. The owner role cannot be invited. */
@@ -664,6 +664,7 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
664
664
  deviceId: string;
665
665
  expiresAt: string;
666
666
  user: User;
667
+ deviceSecret?: string;
667
668
  }> {
668
669
  try {
669
670
  const res = await this.makeRequest<{
@@ -672,6 +673,7 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
672
673
  deviceId: string;
673
674
  expiresAt: string;
674
675
  user: User;
676
+ deviceSecret?: string;
675
677
  }>(
676
678
  'POST',
677
679
  '/auth/session/claim',
@@ -1,4 +1,4 @@
1
- import type { UserNameResponse } from '@oxyhq/contracts';
1
+ import type { OrganizationCategory, UserNameResponse } from '@oxyhq/contracts';
2
2
 
3
3
  export interface OxyConfig {
4
4
  baseURL: string;
@@ -142,6 +142,8 @@ export interface User {
142
142
  // Managed account fields
143
143
  isManagedAccount?: boolean;
144
144
  managedBy?: string;
145
+ /** Real-estate taxonomy when this user is a `kind: 'organization'` account. */
146
+ organizationCategory?: OrganizationCategory;
145
147
  // User-controlled notification preferences. All channels default to on; users
146
148
  // opt out per-channel. Updated via `PUT /users/me`.
147
149
  notificationPreferences?: NotificationPreferences;
@@ -35,4 +35,10 @@ export interface SessionLoginResponse {
35
35
  user: MinimalUserData;
36
36
  /** JWT access token for API authentication */
37
37
  accessToken?: string;
38
+ /**
39
+ * Rotating zero-cookie device credential minted on sign-in / claim. Persisted
40
+ * first-party alongside `deviceId` so cold boot can re-mint via
41
+ * `POST /session/device/token`.
42
+ */
43
+ deviceSecret?: string;
38
44
  }
@@ -461,6 +461,7 @@ describe('AccountDialogController — sign in with Oxy', () => {
461
461
  accessToken: 'access-1',
462
462
  sessionId: 'sess-1',
463
463
  deviceId: 'device-1',
464
+ deviceSecret: 'claimed-secret',
464
465
  expiresAt: '2030-01-01T00:00:00Z',
465
466
  user: user('a1'),
466
467
  });
@@ -473,7 +474,13 @@ describe('AccountDialogController — sign in with Oxy', () => {
473
474
 
474
475
  await jest.advanceTimersByTimeAsync(1000); // second poll → authorized → claim
475
476
  expect(oxy.claimSessionByToken).toHaveBeenCalledWith('secret-tok');
476
- expect(commitSession).toHaveBeenCalledWith(expect.objectContaining({ sessionId: 'sess-1', accessToken: 'access-1' }));
477
+ expect(commitSession).toHaveBeenCalledWith(
478
+ expect.objectContaining({
479
+ sessionId: 'sess-1',
480
+ accessToken: 'access-1',
481
+ deviceSecret: 'claimed-secret',
482
+ }),
483
+ );
477
484
  expect(onSignedIn).toHaveBeenCalledWith(expect.objectContaining({ id: 'a1' }));
478
485
  expect(controller.getSnapshot().view).toBe('accounts');
479
486
  } finally {
@@ -638,7 +638,14 @@ export class AccountDialogController {
638
638
 
639
639
  private async claimAndComplete(sessionId: string, sessionToken: string): Promise<void> {
640
640
  this.setSignIn({ ...this.signIn, phase: 'authorized' });
641
- let claimed: { accessToken: string; sessionId: string; deviceId: string; expiresAt: string; user: User };
641
+ let claimed: {
642
+ accessToken: string;
643
+ sessionId: string;
644
+ deviceId: string;
645
+ expiresAt: string;
646
+ user: User;
647
+ deviceSecret?: string;
648
+ };
642
649
  try {
643
650
  claimed = await this.oxyServices.claimSessionByToken(sessionToken);
644
651
  } catch (error) {
@@ -666,6 +673,7 @@ export class AccountDialogController {
666
673
  expiresAt: claimed.expiresAt ?? '',
667
674
  user: minimalUser,
668
675
  accessToken: claimed.accessToken,
676
+ ...(claimed.deviceSecret ? { deviceSecret: claimed.deviceSecret } : {}),
669
677
  },
670
678
  minimalUser,
671
679
  );