@oxyhq/core 3.10.1 → 3.12.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.
Files changed (92) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/AuthManager.js +9 -2
  3. package/dist/cjs/HttpService.js +27 -9
  4. package/dist/cjs/OxyServices.base.js +3 -2
  5. package/dist/cjs/crypto/canonicalJson.js +107 -0
  6. package/dist/cjs/crypto/keyManager.js +67 -8
  7. package/dist/cjs/crypto/signatureService.js +189 -0
  8. package/dist/cjs/index.js +30 -4
  9. package/dist/cjs/mixins/OxyServices.assets.js +16 -1
  10. package/dist/cjs/mixins/OxyServices.auth.js +190 -1
  11. package/dist/cjs/mixins/OxyServices.civic.js +611 -0
  12. package/dist/cjs/mixins/OxyServices.identity.js +291 -0
  13. package/dist/cjs/mixins/OxyServices.sso.js +28 -1
  14. package/dist/cjs/mixins/OxyServices.user.js +1 -0
  15. package/dist/cjs/mixins/index.js +6 -0
  16. package/dist/cjs/server/cors.js +20 -21
  17. package/dist/cjs/server/rateLimit.js +32 -8
  18. package/dist/cjs/utils/profileLinks.js +52 -0
  19. package/dist/cjs/utils/ssoReturn.js +1 -1
  20. package/dist/esm/.tsbuildinfo +1 -1
  21. package/dist/esm/AuthManager.js +9 -2
  22. package/dist/esm/HttpService.js +27 -9
  23. package/dist/esm/OxyServices.base.js +3 -2
  24. package/dist/esm/crypto/canonicalJson.js +104 -0
  25. package/dist/esm/crypto/keyManager.js +67 -8
  26. package/dist/esm/crypto/signatureService.js +187 -0
  27. package/dist/esm/index.js +19 -1
  28. package/dist/esm/mixins/OxyServices.assets.js +16 -1
  29. package/dist/esm/mixins/OxyServices.auth.js +190 -1
  30. package/dist/esm/mixins/OxyServices.civic.js +605 -0
  31. package/dist/esm/mixins/OxyServices.identity.js +287 -0
  32. package/dist/esm/mixins/OxyServices.sso.js +28 -1
  33. package/dist/esm/mixins/OxyServices.user.js +1 -0
  34. package/dist/esm/mixins/index.js +6 -0
  35. package/dist/esm/server/cors.js +20 -21
  36. package/dist/esm/server/rateLimit.js +32 -8
  37. package/dist/esm/utils/profileLinks.js +49 -0
  38. package/dist/esm/utils/ssoReturn.js +1 -1
  39. package/dist/types/.tsbuildinfo +1 -1
  40. package/dist/types/HttpService.d.ts +3 -0
  41. package/dist/types/OxyServices.d.ts +2 -2
  42. package/dist/types/crypto/canonicalJson.d.ts +44 -0
  43. package/dist/types/crypto/keyManager.d.ts +7 -0
  44. package/dist/types/crypto/signatureService.d.ts +112 -0
  45. package/dist/types/index.d.ts +10 -2
  46. package/dist/types/mixins/OxyServices.auth.d.ts +136 -0
  47. package/dist/types/mixins/OxyServices.civic.d.ts +512 -0
  48. package/dist/types/mixins/OxyServices.identity.d.ts +249 -0
  49. package/dist/types/mixins/OxyServices.sso.d.ts +4 -1
  50. package/dist/types/mixins/index.d.ts +3 -1
  51. package/dist/types/models/interfaces.d.ts +3 -0
  52. package/dist/types/server/cors.d.ts +5 -5
  53. package/dist/types/utils/profileLinks.d.ts +36 -0
  54. package/dist/types/utils/ssoReturn.d.ts +1 -1
  55. package/package.json +2 -2
  56. package/src/AuthManager.ts +8 -2
  57. package/src/HttpService.ts +36 -8
  58. package/src/OxyServices.base.ts +3 -2
  59. package/src/OxyServices.ts +1 -1
  60. package/src/__tests__/authManager.security.test.ts +31 -0
  61. package/src/__tests__/httpServiceCsrf.test.ts +75 -0
  62. package/src/crypto/__tests__/canonicalJson.test.ts +116 -0
  63. package/src/crypto/__tests__/keyManager.atomicity.test.ts +41 -2
  64. package/src/crypto/__tests__/signChallengeShared.test.ts +64 -0
  65. package/src/crypto/__tests__/signedRecord.test.ts +345 -0
  66. package/src/crypto/canonicalJson.ts +120 -0
  67. package/src/crypto/keyManager.ts +62 -12
  68. package/src/crypto/signatureService.ts +225 -0
  69. package/src/index.ts +55 -2
  70. package/src/mixins/OxyServices.assets.ts +16 -1
  71. package/src/mixins/OxyServices.auth.ts +309 -1
  72. package/src/mixins/OxyServices.civic.ts +956 -0
  73. package/src/mixins/OxyServices.identity.ts +445 -0
  74. package/src/mixins/OxyServices.sso.ts +30 -1
  75. package/src/mixins/OxyServices.user.ts +1 -0
  76. package/src/mixins/__tests__/OxyServices.civic.test.ts +1097 -0
  77. package/src/mixins/__tests__/OxyServices.identity.test.ts +364 -0
  78. package/src/mixins/__tests__/assetCredentials.test.ts +47 -0
  79. package/src/mixins/__tests__/commonsSignIn.test.ts +277 -0
  80. package/src/mixins/__tests__/serviceAuth.test.ts +19 -0
  81. package/src/mixins/__tests__/sso.test.ts +31 -0
  82. package/src/mixins/index.ts +8 -0
  83. package/src/models/interfaces.ts +3 -0
  84. package/src/server/__tests__/cors.test.ts +5 -1
  85. package/src/server/__tests__/rateLimit.test.ts +116 -0
  86. package/src/server/cors.ts +25 -20
  87. package/src/server/rateLimit.ts +39 -8
  88. package/src/utils/__tests__/consumeSsoReturn.test.ts +1 -1
  89. package/src/utils/__tests__/profileLinks.test.ts +126 -0
  90. package/src/utils/__tests__/ssoReturn.test.ts +1 -1
  91. package/src/utils/profileLinks.ts +74 -0
  92. package/src/utils/ssoReturn.ts +2 -2
@@ -25,6 +25,7 @@ export interface RequestOptions {
25
25
  timeout?: number;
26
26
  signal?: AbortSignal;
27
27
  headers?: Record<string, string>;
28
+ responseType?: 'blob';
28
29
  }
29
30
  interface RequestConfig extends RequestOptions {
30
31
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -183,6 +184,8 @@ export declare class HttpService {
183
184
  * Build full URL with query params
184
185
  */
185
186
  private buildURL;
187
+ private getCredentialsMode;
188
+ private shouldSendCredentials;
186
189
  /**
187
190
  * Fetch CSRF token from server (with deduplication)
188
191
  * Required for state-changing requests (POST, PUT, PATCH, DELETE)
@@ -122,7 +122,7 @@ export interface OxyServices extends InstanceType<ReturnType<typeof composeOxySe
122
122
  waitForIframeAuth(iframe: HTMLIFrameElement, timeout: number, expectedOrigin: string): Promise<SessionLoginResponse | null>;
123
123
  signInWithRedirect(options?: RedirectAuthOptions): void;
124
124
  signUpWithRedirect(options?: RedirectAuthOptions): void;
125
- exchangeSsoCode(code: string): Promise<SessionLoginResponse>;
125
+ exchangeSsoCode(code: string, state?: string): Promise<SessionLoginResponse>;
126
126
  generateSsoState(): string;
127
127
  auth(options?: {
128
128
  debug?: boolean;
@@ -153,7 +153,7 @@ export declare const OXY_CLOUD_URL = "https://cloud.oxy.so";
153
153
  /**
154
154
  * Export the default Oxy API URL (for documentation)
155
155
  */
156
- export declare const OXY_API_URL: any;
156
+ export declare const OXY_API_URL: string;
157
157
  /**
158
158
  * Pre-configured client instance for easy import
159
159
  * Uses OXY_API_URL as baseURL and OXY_CLOUD_URL as cloudURL
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Canonical JSON (RFC 8785 / JCS-style) serialization.
3
+ *
4
+ * `canonicalize(value)` produces a deterministic string for any JSON-compatible
5
+ * value so that a client which SIGNS a record and a server which VERIFIES it
6
+ * agree byte-for-byte on the signing input — regardless of the order in which
7
+ * object keys happen to be written, how the value was deserialized, or which
8
+ * runtime built it.
9
+ *
10
+ * This is the load-bearing primitive for the self-sovereign identity layer's
11
+ * signed records (`SignatureService.signRecord` + the API's record-verify path):
12
+ * both sides import THIS function from `@oxyhq/core`, so cross-implementation
13
+ * number/string formatting differences cannot cause a verify mismatch.
14
+ *
15
+ * Rules (the JSON Canonicalization Scheme subset we need):
16
+ * - Objects: keys are sorted (ascending, by UTF-16 code unit — the default
17
+ * `Array.prototype.sort` order) and serialized recursively. Properties whose
18
+ * value is `undefined`, a function, or a symbol are OMITTED (matching
19
+ * `JSON.stringify` object semantics).
20
+ * - Arrays: element order is PRESERVED; `undefined`/function/symbol elements
21
+ * serialize to `null` (matching `JSON.stringify` array semantics).
22
+ * - `null`, booleans, strings, and finite numbers serialize via the standard
23
+ * JSON representation.
24
+ * - Values exposing a `toJSON()` method (e.g. `Date`) are replaced by its
25
+ * result first, then serialized — so a `Date` and its ISO-string equivalent
26
+ * canonicalize identically (the wire always carries the string form).
27
+ * - Non-finite numbers (`NaN`, `Infinity`) and `bigint` are not part of the
28
+ * JSON data model and throw, rather than silently producing `null`.
29
+ *
30
+ * Platform-agnostic — zero dependencies, no `require()`, no react/react-native/
31
+ * expo. Safe in the dual CJS + ESM build.
32
+ */
33
+ /**
34
+ * Produce the canonical JSON string for `value`.
35
+ *
36
+ * Deterministic: two structurally-equal values yield identical strings even if
37
+ * their object keys were written in different orders. Use this — never an
38
+ * ad-hoc `JSON.stringify` of a hand-sorted object — as the signing input for
39
+ * signed records, so client signing and server verification cannot drift.
40
+ *
41
+ * @throws if `value` (or any nested member used as the top-level/primitive)
42
+ * contains a non-finite number or a `bigint`, which have no JSON form.
43
+ */
44
+ export declare function canonicalize(value: unknown): string;
@@ -186,6 +186,13 @@ export declare class KeyManager {
186
186
  * @internal
187
187
  */
188
188
  private static _persistIdentityAtomic;
189
+ /**
190
+ * Restore the backup slot to a previously-snapshotted state. Best-effort so
191
+ * the original persistence error remains the one surfaced to the caller.
192
+ *
193
+ * @internal
194
+ */
195
+ private static _rollbackBackup;
189
196
  /**
190
197
  * Restore the primary slot to a previously-snapshotted (privA, pubA) pair,
191
198
  * or delete it entirely if there was no prior identity. Best-effort: every
@@ -4,6 +4,48 @@
4
4
  * Handles signing and verification of messages using ECDSA secp256k1.
5
5
  * Used for authenticating requests and proving identity ownership.
6
6
  */
7
+ import type { SignedRecordEnvelope } from '@oxyhq/contracts';
8
+ /**
9
+ * The signing-input portion of a {@link SignedRecordEnvelope}: every field
10
+ * EXCEPT the `publicKey` and `signature`. Both the client (when signing) and
11
+ * the server (when verifying) canonicalize exactly these fields, so they agree
12
+ * on the bytes that the signature covers.
13
+ *
14
+ * The v2 chain fields (`seq`/`prev`/`collection`/`rkey`) are optional: a v1
15
+ * envelope omits them and is signed over only the base fields; a v2 envelope
16
+ * carries them and includes them in the signed bytes.
17
+ */
18
+ export type SignedRecordSigningFields = Pick<SignedRecordEnvelope, 'version' | 'type' | 'subject' | 'issuer' | 'record' | 'issuedAt'> & Partial<Pick<SignedRecordEnvelope, 'seq' | 'prev' | 'collection' | 'rkey'>>;
19
+ /**
20
+ * Compute the canonical signing input for a signed-record envelope.
21
+ *
22
+ * This is the single definition of "what the signature covers". `@oxyhq/core`
23
+ * (client signing) and `@oxyhq/api` (server verification) both call this, so a
24
+ * record signed by a client and verified by the server cannot drift.
25
+ *
26
+ * - **v1**: the canonical JSON of `{version, type, subject, issuer, record,
27
+ * issuedAt}` — BYTE-IDENTICAL to the original scheme, so every signature
28
+ * already in production keeps verifying.
29
+ * - **v2**: the canonical JSON additionally includes the hash-chain fields
30
+ * `{seq, prev, collection, rkey}`. Because {@link canonicalize} sorts keys,
31
+ * the on-the-wire field order is irrelevant; the resulting canonical key
32
+ * order is `collection, issuedAt, issuer, prev, record, rkey, seq, subject,
33
+ * type, version`. `prev` is `null` at genesis (serialized as `null`, not
34
+ * omitted), so it is always part of the signed bytes.
35
+ */
36
+ export declare function signedRecordSigningInput(fields: SignedRecordSigningFields): string;
37
+ /**
38
+ * Compute the `recordId` (content address) of a signed record: the SHA-256 hex
39
+ * digest of its canonical {@link signedRecordSigningInput}.
40
+ *
41
+ * Deterministic and stable across runtimes (it reuses the same canonicalization
42
+ * + SHA-256 the signature itself is built on). The recordId is what `prev`
43
+ * references in the per-subject hash chain, so `@oxyhq/core` (client) and
44
+ * `@oxyhq/api` (server) MUST compute it identically — both call this function.
45
+ * It is taken over the SIGNING input (excluding `publicKey`/`signature`), so it
46
+ * is a pure content address of the record's meaning, independent of who signed.
47
+ */
48
+ export declare function computeRecordId(fields: SignedRecordSigningFields): Promise<string>;
7
49
  export interface SignedMessage {
8
50
  message: string;
9
51
  signature: string;
@@ -63,6 +105,22 @@ export declare class SignatureService {
63
105
  * Used for challenge-response authentication
64
106
  */
65
107
  static signChallenge(challenge: string): Promise<AuthChallenge>;
108
+ /**
109
+ * Create a signed authentication challenge response using the SHARED identity
110
+ * key (the cross-app `group.so.oxy.shared` keychain key), not the primary
111
+ * device key.
112
+ *
113
+ * Mirrors {@link signChallenge} exactly — same message format
114
+ * (`auth:${publicKey}:${challenge}:${timestamp}`) so the server verification
115
+ * path is unchanged — but sources the shared public/private key from
116
+ * `KeyManager` and signs with `signWithKey`. Used by "Sign in with Oxy"
117
+ * same-device shared-keychain SSO (Mechanism A): a sibling native app proves
118
+ * control of the shared identity to mint its own session.
119
+ *
120
+ * Throws if no shared identity exists (native-only; the shared keychain is
121
+ * unavailable on web).
122
+ */
123
+ static signChallengeWithSharedKey(challenge: string): Promise<AuthChallenge>;
66
124
  /**
67
125
  * Verify a challenge response
68
126
  */
@@ -86,5 +144,59 @@ export declare class SignatureService {
86
144
  publicKey: string;
87
145
  timestamp: number;
88
146
  }>;
147
+ /**
148
+ * Build a signed-record envelope for a self-issued identity/profile record.
149
+ *
150
+ * The envelope is self-issued: `issuer` equals `subject` (the signer's DID).
151
+ * The signature covers the canonical JSON of every field EXCEPT `publicKey`
152
+ * and `signature` (see {@link signedRecordSigningInput}); `alg` is
153
+ * `ES256K-DER-SHA256` (secp256k1 over the SHA-256 of the canonical bytes,
154
+ * DER-encoded), the same scheme this service uses everywhere else.
155
+ *
156
+ * Requires a stored identity (native secure storage); throws if none exists.
157
+ *
158
+ * @param type - The record category (`'identity'` or `'profile'`).
159
+ * @param subject - The subject DID the record is about (also the issuer).
160
+ * @param record - The arbitrary record payload to attest to.
161
+ */
162
+ static signRecord(type: SignedRecordEnvelope['type'], subject: string, record: Record<string, unknown>): Promise<SignedRecordEnvelope>;
163
+ /**
164
+ * Build a signed-record envelope (v2) carrying the per-subject hash-chain
165
+ * fields.
166
+ *
167
+ * Identical to {@link signRecord} (self-issued: `issuer === subject`; same
168
+ * `ES256K-DER-SHA256` scheme over {@link signedRecordSigningInput}) but
169
+ * `version` is `2` and the signed bytes additionally cover the chain fields:
170
+ *
171
+ * @param type - The record category.
172
+ * @param subject - The subject DID the record is about (also the issuer).
173
+ * @param record - The arbitrary record payload to attest to.
174
+ * @param chain - The hash-chain coordinates:
175
+ * - `seq` — strictly-increasing sequence number for this subject's chain.
176
+ * - `prev` — the `recordId` of the previous record, or `null` at genesis.
177
+ * - `collection` + `rkey` — the AtProto-style record key.
178
+ *
179
+ * The caller is responsible for fetching the current chain head (so `seq` /
180
+ * `prev` are correct) before signing. Requires a stored identity; throws if
181
+ * none exists.
182
+ */
183
+ static signRecordV2(type: SignedRecordEnvelope['type'], subject: string, record: Record<string, unknown>, chain: {
184
+ seq: number;
185
+ prev: string | null;
186
+ collection: string;
187
+ rkey: string;
188
+ }): Promise<SignedRecordEnvelope>;
189
+ /**
190
+ * Verify a signed-record envelope: recompute the canonical signing input from
191
+ * the envelope's own fields and check the signature against the envelope's
192
+ * `publicKey`.
193
+ *
194
+ * Note: this confirms the signature is internally consistent with the
195
+ * embedded `publicKey`. It does NOT establish that `publicKey` is an
196
+ * authorized verification method for `subject` — that authorization check is
197
+ * the server's responsibility (it asserts the key is a current verification
198
+ * method on the subject's DID).
199
+ */
200
+ static verifyRecord(envelope: SignedRecordEnvelope): Promise<boolean>;
89
201
  }
90
202
  export default SignatureService;
@@ -30,6 +30,7 @@ export type { SilentAuthOptions } from './mixins/OxyServices.silent';
30
30
  export type { RedirectAuthOptions } from './mixins/OxyServices.redirect';
31
31
  export { ServiceCredentialMismatchError } from './mixins/OxyServices.auth';
32
32
  export type { ServiceTokenResponse } from './mixins/OxyServices.auth';
33
+ export type { CommonsSignInHandle, CommonsSignInStatus, CommonsApprovalInfo, CommonsSignInActionResult, } from './mixins/OxyServices.auth';
33
34
  export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
34
35
  export type { CreateManagedAccountInput, ManagedAccountManager, ManagedAccount, } from './mixins/OxyServices.managedAccounts';
35
36
  export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/OxyServices.contacts';
@@ -38,9 +39,15 @@ export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData';
38
39
  export { getNormalizedUserId, normalizeUserIdentity, normalizeUserIdentityOrNull, } from './utils/userIdentity';
39
40
  export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHandle';
40
41
  export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHandle';
42
+ export { normalizeProfileLinks } from './utils/profileLinks';
43
+ export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
41
44
  export type { Application, PublicApplication, ApplicationMember, ApplicationCredential, ApplicationRole, ApplicationType, ApplicationStatus, ApplicationMemberStatus, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, InviteApplicationMemberInput, UpdateApplicationMemberInput, TransferApplicationOwnershipInput, CreateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, ApplicationSuccessResult, } from './mixins/OxyServices.applications';
42
45
  export type { Workspace, WorkspaceMember, WorkspaceRole, WorkspaceType, WorkspaceStatus, WorkspaceMemberStatus, CreateWorkspaceInput, UpdateWorkspaceInput, InviteWorkspaceMemberInput, UpdateWorkspaceMemberInput, TransferWorkspaceOwnershipInput, WorkspaceSuccessResult, } from './mixins/OxyServices.workspaces';
43
46
  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';
47
+ export { buildUserDid } from './mixins/OxyServices.identity';
48
+ export type { IdentityRecordType, UnlinkableAuthMethodType, LinkAuthMethodResult, PublishRecordResult, VerifyRecordResult, VerifyDomainResult, RemoveDomainResult, } from './mixins/OxyServices.identity';
49
+ export { parseIdPayload, parseAttestPayload, verifyPublicCardAttestation, } from './mixins/OxyServices.civic';
50
+ export type { CivicCardResult, IdCardRef, AttestQrPayload, ParsedAttestPayload, SubmitRealLifeAttestationInput, DenyValidationResult, VouchForPersonInput, WithdrawVouchResult, IssueCredentialInput, RevokeCredentialResult, } from './mixins/OxyServices.civic';
44
51
  export { SessionSyncRequiredError, AuthenticationFailedError, ensureValidToken, isAuthenticationError, withAuthErrorHandling, authenticatedApiCall, } from './utils/authHelpers';
45
52
  export type { HandleApiErrorOptions } from './utils/authHelpers';
46
53
  export { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual, } from './utils/sessionUtils';
@@ -48,8 +55,9 @@ export type { ClientSession, StorageKeys, MinimalUserData, SessionLoginResponse,
48
55
  export type { RefreshAllResponse, RefreshAllAccount, RefreshAllAccountUser, RefreshCookieResponse, } from './models/interfaces';
49
56
  export { KeyManager, IdentityAlreadyExistsError, IdentityPersistError, } from './crypto/keyManager';
50
57
  export type { KeyPair } from './crypto/keyManager';
51
- export { SignatureService } from './crypto/signatureService';
52
- export type { SignedMessage, AuthChallenge } from './crypto/signatureService';
58
+ export { SignatureService, signedRecordSigningInput, computeRecordId } from './crypto/signatureService';
59
+ export type { SignedMessage, AuthChallenge, SignedRecordSigningFields } from './crypto/signatureService';
60
+ export { canonicalize } from './crypto/canonicalJson';
53
61
  export { RecoveryPhraseService } from './crypto/recoveryPhrase';
54
62
  export type { RecoveryPhraseResult } from './crypto/recoveryPhrase';
55
63
  export { DeviceManager } from './utils/deviceManager';
@@ -6,6 +6,7 @@
6
6
  import type { User, RefreshAllResponse, RefreshCookieResponse } from '../models/interfaces';
7
7
  import type { SessionLoginResponse } from '../models/session';
8
8
  import type { OxyServicesBase } from '../OxyServices.base';
9
+ import type { PublicApplication } from './OxyServices.applications';
9
10
  export interface ChallengeResponse {
10
11
  challenge: string;
11
12
  expiresAt: string;
@@ -48,6 +49,60 @@ export interface PublicKeyCheckResponse {
48
49
  registered: boolean;
49
50
  message: string;
50
51
  }
52
+ /**
53
+ * Handle returned by {@link OxyServicesAuthMixin.startCommonsSignIn} for a
54
+ * relying-party app initiating a "Sign in with Oxy" flow.
55
+ *
56
+ * `sessionToken` is the SECRET, high-entropy device-flow credential — it stays
57
+ * on the initiating client, is exchanged once via `claimSessionByToken`, and is
58
+ * NEVER placed in the QR/deep-link. `authorizeCode` is the PUBLIC handle carried
59
+ * in `qrPayload`; the approver (Commons) resolves it via
60
+ * {@link OxyServicesAuthMixin.getCommonsApprovalInfo}.
61
+ */
62
+ export interface CommonsSignInHandle {
63
+ /** Secret device-flow token (held by the initiator; exchanged via `claimSessionByToken`). */
64
+ sessionToken: string;
65
+ /** Public, single-use authorize code carried in the QR / deep-link. */
66
+ authorizeCode: string;
67
+ /** Ready-to-render deep-link / universal-link string (`oxycommons://approve?...`). */
68
+ qrPayload: string;
69
+ /** Server-authoritative expiry (epoch milliseconds). */
70
+ expiresAt: number;
71
+ /** Session lifecycle status as reported by the server (e.g. `'pending'`). */
72
+ status: string;
73
+ }
74
+ /** Poll result for a "Sign in with Oxy" device-flow session (`GET /auth/session/status`). */
75
+ export interface CommonsSignInStatus {
76
+ /** True once an approver has authorized the session. */
77
+ authorized: boolean;
78
+ /** The authorized session id (present once `authorized`). */
79
+ sessionId?: string;
80
+ /** The approving identity's public key (present once `authorized`). */
81
+ publicKey?: string;
82
+ /** Lifecycle status (`'pending'` | `'authorized'` | `'cancelled'` | `'expired'`). */
83
+ status?: string;
84
+ }
85
+ /**
86
+ * Server-resolved approval context shown by the approver (Commons) before
87
+ * authorizing — the TRUSTED identity of the requesting app, resolved from the
88
+ * `authorizeCode` server-side (never from the QR string).
89
+ */
90
+ export interface CommonsApprovalInfo {
91
+ /** Sanitized, display-safe identity of the requesting application. */
92
+ application: PublicApplication;
93
+ /** OAuth scopes the application is requesting. */
94
+ scopes: string[];
95
+ /** The origin the session is bound to (the RP web origin), when applicable. */
96
+ boundOrigin?: string;
97
+ /** Server-authoritative expiry (epoch milliseconds). */
98
+ expiresAt: number;
99
+ /** Session lifecycle status. */
100
+ status: string;
101
+ }
102
+ /** Result of approving / denying a "Sign in with Oxy" request. */
103
+ export interface CommonsSignInActionResult {
104
+ success: boolean;
105
+ }
51
106
  export interface ServiceTokenResponse {
52
107
  token: string;
53
108
  expiresIn: number;
@@ -275,6 +330,87 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
275
330
  expiresAt: string;
276
331
  user: User;
277
332
  }>;
333
+ /**
334
+ * MECHANISM A — same-device shared-keychain SSO.
335
+ *
336
+ * Native-only. If this device holds a shared identity (the cross-app
337
+ * `group.so.oxy.shared` keychain key), prove control of it and mint a
338
+ * session: `requestChallenge(sharedPublicKey)` → `signChallengeWithSharedKey`
339
+ * → `verifyChallenge` (which plants the tokens). Returns `null` on web or
340
+ * when no shared identity is present — never throws for the absent-identity
341
+ * case, so a cold-boot caller can fall through to the next step.
342
+ *
343
+ * The cold-boot wiring that CALLS this lives in `OxyContext`
344
+ * (`@oxyhq/services`); this method just performs the exchange.
345
+ */
346
+ signInWithSharedIdentity(opts?: {
347
+ deviceName?: string;
348
+ deviceFingerprint?: string;
349
+ }): Promise<SessionLoginResponse | null>;
350
+ /**
351
+ * MECHANISM B (relying party) — begin a "Sign in with Oxy" handoff.
352
+ *
353
+ * Generates a secret device-flow `sessionToken` client-side (it never
354
+ * appears in the QR), registers it with `POST /auth/session/create`, and
355
+ * returns the server-issued public `authorizeCode` + ready-to-render
356
+ * `qrPayload`. Render the QR (web) / open the deep-link (same-device); the
357
+ * approver resolves the code and authorizes. Then poll with
358
+ * {@link pollCommonsSignIn} and, on `authorized`, exchange the
359
+ * `sessionToken` via the existing `claimSessionByToken`.
360
+ *
361
+ * @param params.clientId - The RP's registered OAuth client id
362
+ * (ApplicationCredential publicKey); required so the server can resolve the
363
+ * requesting application's identity.
364
+ */
365
+ startCommonsSignIn(params: {
366
+ clientId: string;
367
+ }): Promise<CommonsSignInHandle>;
368
+ /**
369
+ * MECHANISM B (relying party) — poll a device-flow session for approval.
370
+ *
371
+ * Backstop for the auth socket. On `authorized` (with a `sessionId`), the
372
+ * caller exchanges the secret `sessionToken` via the existing
373
+ * `claimSessionByToken` to mint the first access token.
374
+ *
375
+ * @param sessionToken - The secret token from {@link startCommonsSignIn}.
376
+ */
377
+ pollCommonsSignIn(sessionToken: string): Promise<CommonsSignInStatus>;
378
+ /**
379
+ * MECHANISM B (approver / Commons) — resolve the TRUSTED identity of a
380
+ * sign-in request from its public `authorizeCode`.
381
+ *
382
+ * The returned `application` is resolved server-side and is the only safe
383
+ * thing to display in the approval UI — NEVER trust the app/name/origin
384
+ * strings carried in the QR payload. Public (no auth required).
385
+ *
386
+ * @param authorizeCode - The public code scanned from the QR / deep-link.
387
+ */
388
+ getCommonsApprovalInfo(authorizeCode: string): Promise<CommonsApprovalInfo>;
389
+ /**
390
+ * MECHANISM B (approver / Commons) — approve a sign-in request by signing a
391
+ * fresh challenge with the PRIMARY local identity key.
392
+ *
393
+ * Commons holds the user's identity as its primary key (not the shared
394
+ * key), so this uses `signChallenge`. The signed-but-cookieless authorize
395
+ * endpoint resolves the user from the verified signer — the RP that started
396
+ * the flow then claims its session. Native-only (requires a local identity).
397
+ *
398
+ * @param params.authorizeCode - The public code being approved.
399
+ * @param params.deviceName - Optional human-readable device label.
400
+ * @param params.deviceFingerprint - Optional device fingerprint.
401
+ */
402
+ approveCommonsSignIn(params: {
403
+ authorizeCode: string;
404
+ deviceName?: string;
405
+ deviceFingerprint?: string;
406
+ }): Promise<CommonsSignInActionResult>;
407
+ /**
408
+ * MECHANISM B (approver / Commons) — deny a sign-in request, cancelling the
409
+ * device-flow session so the RP stops waiting.
410
+ *
411
+ * @param authorizeCode - The public code being denied.
412
+ */
413
+ denyCommonsSignIn(authorizeCode: string): Promise<CommonsSignInActionResult>;
278
414
  /**
279
415
  * Refresh every device-local refresh-cookie slot in a single round trip
280
416
  * (Google-style multi-account rebuild).