@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.
- package/lib/commonjs/ui/components/SignInModal.js +12 -9
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +5 -1
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +51 -20
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +55 -0
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -0
- package/lib/module/ui/components/SignInModal.js +12 -9
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +5 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +51 -20
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/utils/deviceFlowSignIn.js +51 -0
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +61 -0
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +61 -0
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ui/components/SignInModal.tsx +12 -9
- package/src/ui/context/OxyContext.tsx +8 -4
- package/src/ui/screens/OxyAuthScreen.tsx +52 -20
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +104 -0
- 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;
|
|
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<
|
|
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<
|
|
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,
|
|
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;
|
|
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;
|
|
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<
|
|
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<
|
|
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,
|
|
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;
|
|
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
|
@@ -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
|
-
//
|
|
157
|
-
// session.
|
|
158
|
-
//
|
|
159
|
-
|
|
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<
|
|
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<
|
|
1559
|
-
|
|
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<
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|