@oxyhq/services 10.0.0 → 10.1.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 (38) hide show
  1. package/lib/commonjs/ui/components/SignInModal.js +12 -9
  2. package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +5 -1
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/screens/OxyAuthScreen.js +51 -20
  6. package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
  7. package/lib/commonjs/utils/deviceFlowSignIn.js +55 -0
  8. package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -0
  9. package/lib/module/ui/components/SignInModal.js +12 -9
  10. package/lib/module/ui/components/SignInModal.js.map +1 -1
  11. package/lib/module/ui/context/OxyContext.js +5 -1
  12. package/lib/module/ui/context/OxyContext.js.map +1 -1
  13. package/lib/module/ui/screens/OxyAuthScreen.js +51 -20
  14. package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
  15. package/lib/module/utils/deviceFlowSignIn.js +51 -0
  16. package/lib/module/utils/deviceFlowSignIn.js.map +1 -0
  17. package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
  18. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +1 -1
  19. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
  21. package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +61 -0
  24. package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -0
  25. package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
  26. package/lib/typescript/module/ui/context/OxyContext.d.ts +1 -1
  27. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  28. package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
  29. package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
  30. package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
  31. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +61 -0
  32. package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -0
  33. package/package.json +1 -1
  34. package/src/ui/components/SignInModal.tsx +12 -9
  35. package/src/ui/context/OxyContext.tsx +8 -4
  36. package/src/ui/screens/OxyAuthScreen.tsx +52 -20
  37. package/src/utils/__tests__/deviceFlowSignIn.test.ts +104 -0
  38. package/src/utils/deviceFlowSignIn.ts +76 -0
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Shared, pure orchestration for completing the cross-app device-flow sign-in
5
+ * (the QR-code / "Open Oxy Auth" path used on native and web).
6
+ *
7
+ * THE BUG THIS FIXES (native): once another authenticated device approves the
8
+ * pending AuthSession, the originating client is notified (socket / poll /
9
+ * deep-link) with the authorized `sessionId`. Before any session-management
10
+ * code can use it, the client MUST exchange the secret 128-bit `sessionToken`
11
+ * (held only by this client, generated for THIS flow) for the first access
12
+ * token via `claimSessionByToken` — the device-flow equivalent of OAuth's
13
+ * code-for-token exchange (RFC 8628 §3.4).
14
+ *
15
+ * Skipping the claim leaves the SDK with NO bearer token, so the subsequent
16
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) call 401s
17
+ * against the C1-hardened API: the session is authorized server-side but the
18
+ * app never becomes authenticated and the UI sits "Waiting for
19
+ * authorization..." forever. The web `SignInModal` already claimed first; the
20
+ * native `OxyAuthScreen` did not. Consolidating the claim→switch sequence here
21
+ * keeps both paths identical and unit-testable, and prevents future drift.
22
+ */
23
+
24
+ /**
25
+ * The minimal `OxyServices` surface this orchestration needs. Kept as a
26
+ * structural type (rather than importing the full client) so the helper is
27
+ * trivially unit-testable with a stub and never pulls the RN/Expo runtime into
28
+ * a test bundle.
29
+ */
30
+
31
+ /**
32
+ * Complete a device-flow sign-in: claim the first access token with the secret
33
+ * `sessionToken` (planting the bearer), then hydrate the session via
34
+ * `switchSession`. Returns the authenticated user.
35
+ *
36
+ * Throws if either the claim or the switch fails; callers surface a retry UI.
37
+ */
38
+ export async function completeDeviceFlowSignIn({
39
+ oxyServices,
40
+ sessionId,
41
+ sessionToken,
42
+ switchSession
43
+ }) {
44
+ // 1) Plant the bearer + refresh tokens. Without this the bearer-protected
45
+ // `getTokenBySession` inside `switchSession` 401s (the native regression).
46
+ await oxyServices.claimSessionByToken(sessionToken);
47
+
48
+ // 2) Bearer is now planted — hydrate the session through the normal path.
49
+ return switchSession(sessionId);
50
+ }
51
+ //# sourceMappingURL=deviceFlowSignIn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["completeDeviceFlowSignIn","oxyServices","sessionId","sessionToken","switchSession","claimSessionByToken"],"sourceRoot":"../../../src","sources":["utils/deviceFlowSignIn.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;;AA4BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeA,wBAAwBA,CAAC;EAC7CC,WAAW;EACXC,SAAS;EACTC,YAAY;EACZC;AAC+B,CAAC,EAAiB;EACjD;EACA;EACA,MAAMH,WAAW,CAACI,mBAAmB,CAACF,YAAY,CAAC;;EAEnD;EACA,OAAOC,aAAa,CAACF,SAAS,CAAC;AACjC","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAsXxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAwXxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
@@ -45,7 +45,7 @@ export interface OxyContextState {
45
45
  handlePopupSession: (session: SessionLoginResponse) => Promise<void>;
46
46
  logout: (targetSessionId?: string) => Promise<void>;
47
47
  logoutAll: () => Promise<void>;
48
- switchSession: (sessionId: string) => Promise<void>;
48
+ switchSession: (sessionId: string) => Promise<User>;
49
49
  removeSession: (sessionId: string) => Promise<void>;
50
50
  refreshSessions: () => Promise<void>;
51
51
  setLanguage: (languageId: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Switch active session
3
3
  */
4
- export declare const useSwitchSession: () => import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
4
+ export declare const useSwitchSession: () => import("@tanstack/react-query").UseMutationResult<import("@oxyhq/core").User, Error, string, unknown>;
5
5
  /**
6
6
  * Logout from a session
7
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useServicesMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useServicesMutations.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,gBAAgB,uFAuB5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB;;EAiD5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,qFAuBxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,sFAuB/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,yFAoB3B,CAAC"}
1
+ {"version":3,"file":"useServicesMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useServicesMutations.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,gBAAgB,6GAuB5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB;;EAiD5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,qFAuBxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,sFAuB/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,yFAoB3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwY5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA2F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAua5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared, pure orchestration for completing the cross-app device-flow sign-in
3
+ * (the QR-code / "Open Oxy Auth" path used on native and web).
4
+ *
5
+ * THE BUG THIS FIXES (native): once another authenticated device approves the
6
+ * pending AuthSession, the originating client is notified (socket / poll /
7
+ * deep-link) with the authorized `sessionId`. Before any session-management
8
+ * code can use it, the client MUST exchange the secret 128-bit `sessionToken`
9
+ * (held only by this client, generated for THIS flow) for the first access
10
+ * token via `claimSessionByToken` — the device-flow equivalent of OAuth's
11
+ * code-for-token exchange (RFC 8628 §3.4).
12
+ *
13
+ * Skipping the claim leaves the SDK with NO bearer token, so the subsequent
14
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) call 401s
15
+ * against the C1-hardened API: the session is authorized server-side but the
16
+ * app never becomes authenticated and the UI sits "Waiting for
17
+ * authorization..." forever. The web `SignInModal` already claimed first; the
18
+ * native `OxyAuthScreen` did not. Consolidating the claim→switch sequence here
19
+ * keeps both paths identical and unit-testable, and prevents future drift.
20
+ */
21
+ import type { User } from '@oxyhq/core';
22
+ /**
23
+ * The minimal `OxyServices` surface this orchestration needs. Kept as a
24
+ * structural type (rather than importing the full client) so the helper is
25
+ * trivially unit-testable with a stub and never pulls the RN/Expo runtime into
26
+ * a test bundle.
27
+ */
28
+ export interface DeviceFlowClient {
29
+ /**
30
+ * Exchange the device-flow `sessionToken` for the first access + refresh
31
+ * token, planting them on the client. Single-use; replay is rejected by the
32
+ * API. No bearer required — the high-entropy `sessionToken` IS the credential.
33
+ */
34
+ claimSessionByToken: (sessionToken: string) => Promise<unknown>;
35
+ }
36
+ export interface CompleteDeviceFlowSignInOptions {
37
+ /** The OxyServices client (or any object exposing `claimSessionByToken`). */
38
+ oxyServices: DeviceFlowClient;
39
+ /** The authorized device session id, delivered by the socket / poll / link. */
40
+ sessionId: string;
41
+ /**
42
+ * The secret `sessionToken` generated for THIS flow and registered via
43
+ * `POST /auth/session/create`. Required to claim the first access token.
44
+ */
45
+ sessionToken: string;
46
+ /**
47
+ * The session-management `switchSession` from `useOxy()`. Hydrates the
48
+ * activated session (validates, fetches the user, persists, updates state).
49
+ * Runs AFTER the bearer is planted so its bearer-protected calls succeed.
50
+ */
51
+ switchSession: (sessionId: string) => Promise<User>;
52
+ }
53
+ /**
54
+ * Complete a device-flow sign-in: claim the first access token with the secret
55
+ * `sessionToken` (planting the bearer), then hydrate the session via
56
+ * `switchSession`. Returns the authenticated user.
57
+ *
58
+ * Throws if either the claim or the switch fails; callers surface a retry UI.
59
+ */
60
+ export declare function completeDeviceFlowSignIn({ oxyServices, sessionId, sessionToken, switchSession, }: CompleteDeviceFlowSignInOptions): Promise<User>;
61
+ //# sourceMappingURL=deviceFlowSignIn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviceFlowSignIn.d.ts","sourceRoot":"","sources":["../../../../src/utils/deviceFlowSignIn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,+BAA+B;IAC9C,6EAA6E;IAC7E,WAAW,EAAE,gBAAgB,CAAC;IAC9B,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,WAAW,EACX,SAAS,EACT,YAAY,EACZ,aAAa,GACd,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAsXxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"SignInModal.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/SignInModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyD/B,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,eAAe,YAI3B,CAAC;AAEF,eAAO,MAAM,oBAAoB,eAAqB,CAAC;AAEvD,4CAA4C;AAC5C,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAAG,CAAC,MAAM,IAAI,CAGxF,CAAC;AAEF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAwXxB,CAAC;AA8FF,eAAe,WAAW,CAAC"}
@@ -45,7 +45,7 @@ export interface OxyContextState {
45
45
  handlePopupSession: (session: SessionLoginResponse) => Promise<void>;
46
46
  logout: (targetSessionId?: string) => Promise<void>;
47
47
  logoutAll: () => Promise<void>;
48
- switchSession: (sessionId: string) => Promise<void>;
48
+ switchSession: (sessionId: string) => Promise<User>;
49
49
  removeSession: (sessionId: string) => Promise<void>;
50
50
  refreshSessions: () => Promise<void>;
51
51
  setLanguage: (languageId: string) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Switch active session
3
3
  */
4
- export declare const useSwitchSession: () => import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
4
+ export declare const useSwitchSession: () => import("@tanstack/react-query").UseMutationResult<import("@oxyhq/core").User, Error, string, unknown>;
5
5
  /**
6
6
  * Logout from a session
7
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useServicesMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useServicesMutations.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,gBAAgB,uFAuB5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB;;EAiD5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,qFAuBxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,sFAuB/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,yFAoB3B,CAAC"}
1
+ {"version":3,"file":"useServicesMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useServicesMutations.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,gBAAgB,6GAuB5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB;;EAiD5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,qFAuBxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,sFAuB/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,yFAoB3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwY5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"OxyAuthScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/OxyAuthScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA2F3D,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAua5C,CAAC;AA6FF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared, pure orchestration for completing the cross-app device-flow sign-in
3
+ * (the QR-code / "Open Oxy Auth" path used on native and web).
4
+ *
5
+ * THE BUG THIS FIXES (native): once another authenticated device approves the
6
+ * pending AuthSession, the originating client is notified (socket / poll /
7
+ * deep-link) with the authorized `sessionId`. Before any session-management
8
+ * code can use it, the client MUST exchange the secret 128-bit `sessionToken`
9
+ * (held only by this client, generated for THIS flow) for the first access
10
+ * token via `claimSessionByToken` — the device-flow equivalent of OAuth's
11
+ * code-for-token exchange (RFC 8628 §3.4).
12
+ *
13
+ * Skipping the claim leaves the SDK with NO bearer token, so the subsequent
14
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) call 401s
15
+ * against the C1-hardened API: the session is authorized server-side but the
16
+ * app never becomes authenticated and the UI sits "Waiting for
17
+ * authorization..." forever. The web `SignInModal` already claimed first; the
18
+ * native `OxyAuthScreen` did not. Consolidating the claim→switch sequence here
19
+ * keeps both paths identical and unit-testable, and prevents future drift.
20
+ */
21
+ import type { User } from '@oxyhq/core';
22
+ /**
23
+ * The minimal `OxyServices` surface this orchestration needs. Kept as a
24
+ * structural type (rather than importing the full client) so the helper is
25
+ * trivially unit-testable with a stub and never pulls the RN/Expo runtime into
26
+ * a test bundle.
27
+ */
28
+ export interface DeviceFlowClient {
29
+ /**
30
+ * Exchange the device-flow `sessionToken` for the first access + refresh
31
+ * token, planting them on the client. Single-use; replay is rejected by the
32
+ * API. No bearer required — the high-entropy `sessionToken` IS the credential.
33
+ */
34
+ claimSessionByToken: (sessionToken: string) => Promise<unknown>;
35
+ }
36
+ export interface CompleteDeviceFlowSignInOptions {
37
+ /** The OxyServices client (or any object exposing `claimSessionByToken`). */
38
+ oxyServices: DeviceFlowClient;
39
+ /** The authorized device session id, delivered by the socket / poll / link. */
40
+ sessionId: string;
41
+ /**
42
+ * The secret `sessionToken` generated for THIS flow and registered via
43
+ * `POST /auth/session/create`. Required to claim the first access token.
44
+ */
45
+ sessionToken: string;
46
+ /**
47
+ * The session-management `switchSession` from `useOxy()`. Hydrates the
48
+ * activated session (validates, fetches the user, persists, updates state).
49
+ * Runs AFTER the bearer is planted so its bearer-protected calls succeed.
50
+ */
51
+ switchSession: (sessionId: string) => Promise<User>;
52
+ }
53
+ /**
54
+ * Complete a device-flow sign-in: claim the first access token with the secret
55
+ * `sessionToken` (planting the bearer), then hydrate the session via
56
+ * `switchSession`. Returns the authenticated user.
57
+ *
58
+ * Throws if either the claim or the switch fails; callers surface a retry UI.
59
+ */
60
+ export declare function completeDeviceFlowSignIn({ oxyServices, sessionId, sessionToken, switchSession, }: CompleteDeviceFlowSignInOptions): Promise<User>;
61
+ //# sourceMappingURL=deviceFlowSignIn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deviceFlowSignIn.d.ts","sourceRoot":"","sources":["../../../../src/utils/deviceFlowSignIn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,+BAA+B;IAC9C,6EAA6E;IAC7E,WAAW,EAAE,gBAAgB,CAAC;IAC9B,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,WAAW,EACX,SAAS,EACT,YAAY,EACZ,aAAa,GACd,EAAE,+BAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "10.0.0",
3
+ "version": "10.1.0",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -35,6 +35,7 @@ import { Loading } from '@oxyhq/bloom/loading';
35
35
  import { useOxy } from '../context/OxyContext';
36
36
  import OxyLogo from './OxyLogo';
37
37
  import { createDebugLogger } from '@oxyhq/core';
38
+ import { completeDeviceFlowSignIn } from '../../utils/deviceFlowSignIn';
38
39
 
39
40
  const debug = createDebugLogger('SignInModal');
40
41
 
@@ -153,16 +154,18 @@ const SignInModal: React.FC = () => {
153
154
  isProcessingRef.current = true;
154
155
 
155
156
  try {
156
- // Plant the bearer + refresh tokens for this newly-authorized
157
- // session. Single-use replay attempts on this sessionToken
158
- // are rejected by the API.
159
- await oxyServices.claimSessionByToken(sessionToken);
160
-
161
- // Now the SDK has a bearer token, the normal session
162
- // management path can hydrate state from the sessionId.
163
- if (switchSession) {
164
- await switchSession(sessionId);
157
+ // Claim the first access token with the secret sessionToken, then
158
+ // hydrate the session. Shared with the native `OxyAuthScreen` via
159
+ // `completeDeviceFlowSignIn` so the two paths cannot drift.
160
+ if (!switchSession) {
161
+ throw new Error('Session management unavailable');
165
162
  }
163
+ await completeDeviceFlowSignIn({
164
+ oxyServices,
165
+ sessionId,
166
+ sessionToken,
167
+ switchSession,
168
+ });
166
169
 
167
170
  hideSignInModal();
168
171
  } catch (err) {
@@ -97,7 +97,7 @@ export interface OxyContextState {
97
97
  // Session management
98
98
  logout: (targetSessionId?: string) => Promise<void>;
99
99
  logoutAll: () => Promise<void>;
100
- switchSession: (sessionId: string) => Promise<void>;
100
+ switchSession: (sessionId: string) => Promise<User>;
101
101
  removeSession: (sessionId: string) => Promise<void>;
102
102
  refreshSessions: () => Promise<void>;
103
103
  setLanguage: (languageId: string) => Promise<void>;
@@ -1555,8 +1555,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1555
1555
  });
1556
1556
 
1557
1557
  const switchSessionForContext = useCallback(
1558
- async (sessionId: string): Promise<void> => {
1559
- await switchSession(sessionId);
1558
+ async (sessionId: string): Promise<User> => {
1559
+ // Propagate the activated user so callers (the device-flow sign-in,
1560
+ // `useSwitchSession`'s cache write, account chooser) receive it. The
1561
+ // underlying session-management `switchSession` already resolves the
1562
+ // `User`; the previous `Promise<void>` wrapper discarded it.
1563
+ return switchSession(sessionId);
1560
1564
  },
1561
1565
  [switchSession],
1562
1566
  );
@@ -1776,7 +1780,7 @@ const LOADING_STATE: OxyContextState = {
1776
1780
  handlePopupSession: () => rejectMissingProvider<void>(),
1777
1781
  logout: () => rejectMissingProvider<void>(),
1778
1782
  logoutAll: () => rejectMissingProvider<void>(),
1779
- switchSession: () => rejectMissingProvider<void>(),
1783
+ switchSession: () => rejectMissingProvider<User>(),
1780
1784
  removeSession: () => rejectMissingProvider<void>(),
1781
1785
  refreshSessions: () => rejectMissingProvider<void>(),
1782
1786
  setLanguage: () => rejectMissingProvider<void>(),
@@ -30,6 +30,7 @@ import { useOxy } from '../context/OxyContext';
30
30
  import QRCode from 'react-native-qrcode-svg';
31
31
  import OxyLogo from '../components/OxyLogo';
32
32
  import { createDebugLogger } from '@oxyhq/core';
33
+ import { completeDeviceFlowSignIn } from '../../utils/deviceFlowSignIn';
33
34
 
34
35
  const debug = createDebugLogger('OxyAuthScreen');
35
36
 
@@ -119,7 +120,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
119
120
  theme,
120
121
  }) => {
121
122
  const bloomTheme = useTheme();
122
- const { oxyServices, signIn, switchSession, clientId } = useOxy();
123
+ const { oxyServices, switchSession, clientId } = useOxy();
123
124
 
124
125
  const [authSession, setAuthSession] = useState<AuthSession | null>(null);
125
126
  const [isLoading, setIsLoading] = useState(true);
@@ -132,25 +133,42 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
132
133
  const isProcessingRef = useRef(false);
133
134
  const linkingHandledRef = useRef(false);
134
135
 
135
- // Handle successful authorization
136
- const handleAuthSuccess = useCallback(async (sessionId: string) => {
136
+ // Handle successful authorization.
137
+ //
138
+ // The auth-session socket / poll (or deep-link return) hands us the
139
+ // authorized `sessionId`. Before any session-management code can touch it we
140
+ // MUST first exchange the secret `sessionToken` (held only by this client,
141
+ // generated for THIS flow) for the first access token via
142
+ // `claimSessionByToken` — the device-flow equivalent of OAuth's
143
+ // code-for-token exchange (RFC 8628 §3.4).
144
+ //
145
+ // Without that exchange the SDK has no bearer token, so the subsequent
146
+ // `switchSession` -> `getTokenBySession` call (`GET /session/token/:id`) 401s
147
+ // against the C1-hardened API — the session is authorized server-side but the
148
+ // app never becomes authenticated and the sheet sits "Waiting for
149
+ // authorization..." forever. Once `claimSessionByToken` plants the tokens in
150
+ // the HttpService, the rest of the session wiring flows through the normal
151
+ // `switchSession` path. This mirrors `SignInModal`'s web flow exactly.
152
+ const handleAuthSuccess = useCallback(async (sessionId: string, sessionToken: string) => {
137
153
  if (isProcessingRef.current) return;
138
154
  isProcessingRef.current = true;
139
155
 
140
156
  try {
141
- // Switch to the new session (this will get token, user data, and update state)
142
- if (switchSession) {
143
- const user = await switchSession(sessionId);
144
- if (onAuthenticated) {
145
- onAuthenticated(user);
146
- }
147
- } else {
148
- // Fallback if switchSession not available (shouldn't happen, but for safety)
149
- await oxyServices.getTokenBySession(sessionId);
150
- const user = await oxyServices.getUserBySession(sessionId);
151
- if (onAuthenticated) {
152
- onAuthenticated(user);
153
- }
157
+ // Claim the first access token with the secret sessionToken, then
158
+ // hydrate the session. The claim step is what the native screen was
159
+ // previously missing see `completeDeviceFlowSignIn`. `switchSession`
160
+ // is always provided by the context here; the helper requires it.
161
+ if (!switchSession) {
162
+ throw new Error('Session management unavailable');
163
+ }
164
+ const user = await completeDeviceFlowSignIn({
165
+ oxyServices,
166
+ sessionId,
167
+ sessionToken,
168
+ switchSession,
169
+ });
170
+ if (onAuthenticated) {
171
+ onAuthenticated(user);
154
172
  }
155
173
  } catch (err) {
156
174
  debug.error('Error completing auth:', err);
@@ -189,7 +207,9 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
189
207
 
190
208
  if (payload.status === 'authorized' && payload.sessionId) {
191
209
  cleanup();
192
- handleAuthSuccess(payload.sessionId);
210
+ // `sessionToken` is this flow's secret credential (in closure) — pass
211
+ // it through so `handleAuthSuccess` can claim the first access token.
212
+ handleAuthSuccess(payload.sessionId, sessionToken);
193
213
  } else if (payload.status === 'cancelled') {
194
214
  cleanup();
195
215
  setError('Authorization was denied.');
@@ -228,7 +248,9 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
228
248
 
229
249
  if (response.authorized && response.sessionId) {
230
250
  cleanup();
231
- handleAuthSuccess(response.sessionId);
251
+ // Pass the original sessionToken (in closure) through; the claim
252
+ // exchange needs it to mint the first access token.
253
+ handleAuthSuccess(response.sessionId, sessionToken);
232
254
  } else if (response.status === 'cancelled') {
233
255
  cleanup();
234
256
  setError('Authorization was denied.');
@@ -379,10 +401,20 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
379
401
  }
380
402
 
381
403
  if (params.sessionId) {
404
+ // The deep-link return carries only `session_id` — the secret
405
+ // `sessionToken` for this flow lives in component state (generated in
406
+ // `generateAuthSession`). Without it we cannot claim the first access
407
+ // token, so the flow would 401 in `handleAuthSuccess`. If it is somehow
408
+ // unavailable, fall through to the socket/poll path (which carries the
409
+ // token in closure) rather than attempting an unauthenticated claim.
410
+ const flowSessionToken = authSession?.sessionToken;
411
+ if (!flowSessionToken) {
412
+ return;
413
+ }
382
414
  cleanup();
383
- handleAuthSuccess(params.sessionId);
415
+ handleAuthSuccess(params.sessionId, flowSessionToken);
384
416
  }
385
- }, [cleanup, handleAuthSuccess]);
417
+ }, [authSession, cleanup, handleAuthSuccess]);
386
418
 
387
419
  useEffect(() => {
388
420
  if (Platform.OS === 'web') {
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @jest-environment node
3
+ *
4
+ * Regression coverage for the NATIVE device-flow sign-in bug:
5
+ *
6
+ * On native, tapping "Sign In with Oxy" opens `OxyAuthScreen`, which creates
7
+ * a device-flow AuthSession and opens auth.oxy.so/authorize. The user signs
8
+ * in successfully, the API authorizes the session and notifies the client via
9
+ * the auth-session socket — but the screen then called `switchSession`
10
+ * DIRECTLY without first claiming the bearer with the secret `sessionToken`.
11
+ * `switchSession` -> `getTokenBySession` (`GET /session/token/:id`) requires a
12
+ * bearer the client did not yet hold, so it 401'd: the session was authorized
13
+ * server-side but the app never became authenticated ("nothing happens").
14
+ *
15
+ * The web `SignInModal` already claimed first; the native screen did not.
16
+ * `completeDeviceFlowSignIn` consolidates the claim->switch sequence so both
17
+ * paths are identical. These tests pin the ORDER (claim before switch) and the
18
+ * fail-fast behaviour.
19
+ */
20
+
21
+ import type { User } from '@oxyhq/core';
22
+ import {
23
+ completeDeviceFlowSignIn,
24
+ type DeviceFlowClient,
25
+ } from '../deviceFlowSignIn';
26
+
27
+ const SESSION_ID = 'session-id-123';
28
+ const SESSION_TOKEN = 'a'.repeat(32);
29
+ const USER = { id: 'user-1', username: 'nate', privacySettings: {} } as User;
30
+
31
+ describe('completeDeviceFlowSignIn', () => {
32
+ it('claims the sessionToken BEFORE switching the session', async () => {
33
+ const order: string[] = [];
34
+
35
+ const oxyServices: DeviceFlowClient = {
36
+ claimSessionByToken: jest.fn(async (token: string) => {
37
+ expect(token).toBe(SESSION_TOKEN);
38
+ order.push('claim');
39
+ }),
40
+ };
41
+ const switchSession = jest.fn(async (sessionId: string): Promise<User> => {
42
+ expect(sessionId).toBe(SESSION_ID);
43
+ order.push('switch');
44
+ return USER;
45
+ });
46
+
47
+ const user = await completeDeviceFlowSignIn({
48
+ oxyServices,
49
+ sessionId: SESSION_ID,
50
+ sessionToken: SESSION_TOKEN,
51
+ switchSession,
52
+ });
53
+
54
+ expect(order).toEqual(['claim', 'switch']);
55
+ expect(oxyServices.claimSessionByToken).toHaveBeenCalledWith(SESSION_TOKEN);
56
+ expect(switchSession).toHaveBeenCalledWith(SESSION_ID);
57
+ expect(user).toBe(USER);
58
+ });
59
+
60
+ it('does NOT switch the session when the claim fails (the native regression)', async () => {
61
+ const claimError = new Error('claim failed (401)');
62
+ const oxyServices: DeviceFlowClient = {
63
+ claimSessionByToken: jest.fn(async () => {
64
+ throw claimError;
65
+ }),
66
+ };
67
+ const switchSession = jest.fn(async (): Promise<User> => USER);
68
+
69
+ await expect(
70
+ completeDeviceFlowSignIn({
71
+ oxyServices,
72
+ sessionId: SESSION_ID,
73
+ sessionToken: SESSION_TOKEN,
74
+ switchSession,
75
+ }),
76
+ ).rejects.toThrow('claim failed (401)');
77
+
78
+ // The bearer was never planted, so we must not attempt the bearer-protected
79
+ // switch — surfacing the failure to the caller instead.
80
+ expect(switchSession).not.toHaveBeenCalled();
81
+ });
82
+
83
+ it('propagates a switchSession failure after a successful claim', async () => {
84
+ const switchError = new Error('session invalid');
85
+ const oxyServices: DeviceFlowClient = {
86
+ claimSessionByToken: jest.fn(async () => undefined),
87
+ };
88
+ const switchSession = jest.fn(async (): Promise<User> => {
89
+ throw switchError;
90
+ });
91
+
92
+ await expect(
93
+ completeDeviceFlowSignIn({
94
+ oxyServices,
95
+ sessionId: SESSION_ID,
96
+ sessionToken: SESSION_TOKEN,
97
+ switchSession,
98
+ }),
99
+ ).rejects.toThrow('session invalid');
100
+
101
+ expect(oxyServices.claimSessionByToken).toHaveBeenCalledTimes(1);
102
+ expect(switchSession).toHaveBeenCalledTimes(1);
103
+ });
104
+ });