@oxyhq/core 1.11.16 → 1.11.17

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.
@@ -193,11 +193,47 @@ export declare function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(B
193
193
  }>>;
194
194
  /**
195
195
  * Get access token by session ID
196
+ *
197
+ * SECURITY: this endpoint requires the caller to already hold a
198
+ * bearer token whose user owns the referenced session (C1 hardening
199
+ * in the API). For the device-flow / QR sign-in case where the
200
+ * client has no bearer token yet, use `claimSessionByToken` instead.
196
201
  */
197
202
  getTokenBySession(sessionId: string): Promise<{
198
203
  accessToken: string;
199
204
  expiresAt: string;
200
205
  }>;
206
+ /**
207
+ * Exchange a device-flow sessionToken for the first access token.
208
+ *
209
+ * The originating client holds a 128-bit `sessionToken` that nobody
210
+ * else has seen — it was generated client-side, sent once on
211
+ * `POST /auth/session/create`, and is never echoed back. After
212
+ * another authenticated device approves the session via
213
+ * `POST /auth/session/authorize/{sessionToken}` (bearer-authed) and
214
+ * the auth socket / poll loop notifies this client, the client
215
+ * exchanges its `sessionToken` here for the first access token,
216
+ * refresh token, sessionId, and the authorized user.
217
+ *
218
+ * This call requires no Authorization header — the high-entropy
219
+ * `sessionToken` IS the credential (RFC 8628 §3.4). The exchange is
220
+ * single-use; replay attempts are rejected with 401.
221
+ *
222
+ * @param sessionToken - The same sessionToken the SDK passed to
223
+ * `POST /auth/session/create` at the start of the flow.
224
+ * @param options.deviceFingerprint - Optional fingerprint of the
225
+ * originating client device.
226
+ */
227
+ claimSessionByToken(sessionToken: string, options?: {
228
+ deviceFingerprint?: string;
229
+ }): Promise<{
230
+ accessToken: string;
231
+ refreshToken: string;
232
+ sessionId: string;
233
+ deviceId: string;
234
+ expiresAt: string;
235
+ user: User;
236
+ }>;
201
237
  /**
202
238
  * Get sessions by session ID
203
239
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/core",
3
- "version": "1.11.16",
3
+ "version": "1.11.17",
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",
@@ -440,6 +440,11 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
440
440
 
441
441
  /**
442
442
  * Get access token by session ID
443
+ *
444
+ * SECURITY: this endpoint requires the caller to already hold a
445
+ * bearer token whose user owns the referenced session (C1 hardening
446
+ * in the API). For the device-flow / QR sign-in case where the
447
+ * client has no bearer token yet, use `claimSessionByToken` instead.
443
448
  */
444
449
  async getTokenBySession(sessionId: string): Promise<{ accessToken: string; expiresAt: string }> {
445
450
  try {
@@ -449,9 +454,67 @@ export function OxyServicesAuthMixin<T extends typeof OxyServicesBase>(Base: T)
449
454
  undefined,
450
455
  { cache: false, retry: false }
451
456
  );
452
-
457
+
453
458
  this.setTokens(res.accessToken);
454
-
459
+
460
+ return res;
461
+ } catch (error) {
462
+ throw this.handleError(error);
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Exchange a device-flow sessionToken for the first access token.
468
+ *
469
+ * The originating client holds a 128-bit `sessionToken` that nobody
470
+ * else has seen — it was generated client-side, sent once on
471
+ * `POST /auth/session/create`, and is never echoed back. After
472
+ * another authenticated device approves the session via
473
+ * `POST /auth/session/authorize/{sessionToken}` (bearer-authed) and
474
+ * the auth socket / poll loop notifies this client, the client
475
+ * exchanges its `sessionToken` here for the first access token,
476
+ * refresh token, sessionId, and the authorized user.
477
+ *
478
+ * This call requires no Authorization header — the high-entropy
479
+ * `sessionToken` IS the credential (RFC 8628 §3.4). The exchange is
480
+ * single-use; replay attempts are rejected with 401.
481
+ *
482
+ * @param sessionToken - The same sessionToken the SDK passed to
483
+ * `POST /auth/session/create` at the start of the flow.
484
+ * @param options.deviceFingerprint - Optional fingerprint of the
485
+ * originating client device.
486
+ */
487
+ async claimSessionByToken(
488
+ sessionToken: string,
489
+ options: { deviceFingerprint?: string } = {}
490
+ ): Promise<{
491
+ accessToken: string;
492
+ refreshToken: string;
493
+ sessionId: string;
494
+ deviceId: string;
495
+ expiresAt: string;
496
+ user: User;
497
+ }> {
498
+ try {
499
+ const res = await this.makeRequest<{
500
+ accessToken: string;
501
+ refreshToken: string;
502
+ sessionId: string;
503
+ deviceId: string;
504
+ expiresAt: string;
505
+ user: User;
506
+ }>(
507
+ 'POST',
508
+ '/auth/session/claim',
509
+ {
510
+ sessionToken,
511
+ ...(options.deviceFingerprint ? { deviceFingerprint: options.deviceFingerprint } : {}),
512
+ },
513
+ { cache: false, retry: false }
514
+ );
515
+
516
+ this.setTokens(res.accessToken, res.refreshToken);
517
+
455
518
  return res;
456
519
  } catch (error) {
457
520
  throw this.handleError(error);