@oxyhq/services 18.0.0 → 19.0.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/context/OxyContext.js +52 -83
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +15 -18
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/session/authStore.js +12 -50
- package/lib/commonjs/ui/session/authStore.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +6 -11
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/tokenTransport.js +3 -4
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +52 -83
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +15 -18
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/session/authStore.js +13 -51
- package/lib/module/ui/session/authStore.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +6 -11
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/tokenTransport.js +3 -4
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +15 -6
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +4 -5
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +11 -15
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +6 -10
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +3 -4
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +15 -6
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +4 -5
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/authStore.d.ts +11 -15
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +6 -10
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +3 -4
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ui/context/OxyContext.tsx +61 -92
- package/src/ui/context/__tests__/OxyContext.coldBoot.test.tsx +13 -12
- package/src/ui/context/hooks/useAuthOperations.ts +18 -20
- package/src/ui/session/__tests__/createSessionClient.test.ts +3 -1
- package/src/ui/session/__tests__/tokenTransport.test.ts +6 -5
- package/src/ui/session/authStore.ts +12 -55
- package/src/ui/session/createSessionClient.ts +5 -10
- package/src/ui/session/tokenTransport.ts +3 -4
|
@@ -2,6 +2,7 @@ import type React from 'react';
|
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import { OxyServices } from '@oxyhq/core';
|
|
4
4
|
import type { User, ApiError, SessionLoginResponse, AccountNode, CreateAccountInput, ClientSession, AccountDialogController, AccountDialogView } from '@oxyhq/core';
|
|
5
|
+
import type { SecurityAlert } from '@oxyhq/contracts';
|
|
5
6
|
import type { UseFollowHook } from '../hooks/useFollow.types.js';
|
|
6
7
|
import { useLanguageManagement } from '../hooks/useLanguageManagement.js';
|
|
7
8
|
import type { RouteName } from '../navigation/routes.js';
|
|
@@ -40,7 +41,7 @@ export interface OxyContextState {
|
|
|
40
41
|
*
|
|
41
42
|
* Commits a successful session into context state through the SAME path the
|
|
42
43
|
* QR device-flow and cold boot use (so `isAuthenticated` / `user` update and
|
|
43
|
-
* the
|
|
44
|
+
* the zero-cookie device credential is persisted). Returns a discriminated result
|
|
44
45
|
* so the caller can branch on the two-factor-required case — which creates NO
|
|
45
46
|
* session; the caller completes the 2FA challenge with the returned
|
|
46
47
|
* `loginToken` via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
@@ -52,18 +53,22 @@ export interface OxyContextState {
|
|
|
52
53
|
/**
|
|
53
54
|
* Complete a 2FA-gated password sign-in started by {@link signInWithPassword}.
|
|
54
55
|
* Presents the short-lived `loginToken` with a TOTP `token` or a `backupCode`;
|
|
55
|
-
* on success the session is committed exactly like a one-step sign-in.
|
|
56
|
+
* on success the session is committed exactly like a one-step sign-in. Returns
|
|
57
|
+
* any `securityAlert` the server attached so the caller can show the same
|
|
58
|
+
* "New sign-in detected" acknowledgement as the one-step path.
|
|
56
59
|
*/
|
|
57
60
|
completeTwoFactorSignIn: (params: {
|
|
58
61
|
loginToken: string;
|
|
59
62
|
token?: string;
|
|
60
63
|
backupCode?: string;
|
|
61
64
|
deviceName?: string;
|
|
62
|
-
}) => Promise<
|
|
65
|
+
}) => Promise<{
|
|
66
|
+
securityAlert?: SecurityAlert;
|
|
67
|
+
}>;
|
|
63
68
|
/**
|
|
64
69
|
* Commit a session obtained out-of-band (the "Sign in with Oxy" QR device
|
|
65
|
-
* flow). Plants tokens, persists the
|
|
66
|
-
* account into the device set, and hydrates the full user profile.
|
|
70
|
+
* flow). Plants tokens, persists the zero-cookie device credential, registers
|
|
71
|
+
* the account into the device set, and hydrates the full user profile.
|
|
67
72
|
*/
|
|
68
73
|
handleWebSession: (session: SessionLoginResponse) => Promise<void>;
|
|
69
74
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -134,7 +139,10 @@ export interface OxyContextState {
|
|
|
134
139
|
* Result of {@link OxyContextState.signInWithPassword}.
|
|
135
140
|
*
|
|
136
141
|
* `'ok'` — the password was accepted and the session committed (so
|
|
137
|
-
* `isAuthenticated` / `user` are updated and the
|
|
142
|
+
* `isAuthenticated` / `user` are updated and the device credential persisted).
|
|
143
|
+
* `securityAlert` is present when the server flagged this sign-in as anomalous
|
|
144
|
+
* (new device / location) — the caller shows a "New sign-in detected"
|
|
145
|
+
* acknowledgement before proceeding; the session is already committed.
|
|
138
146
|
*
|
|
139
147
|
* `'2fa_required'` — the account has two-factor auth enabled, so NO session was
|
|
140
148
|
* created. Complete the challenge with the returned short-lived `loginToken`
|
|
@@ -142,6 +150,7 @@ export interface OxyContextState {
|
|
|
142
150
|
*/
|
|
143
151
|
export type PasswordSignInResult = {
|
|
144
152
|
status: 'ok';
|
|
153
|
+
securityAlert?: SecurityAlert;
|
|
145
154
|
} | {
|
|
146
155
|
status: '2fa_required';
|
|
147
156
|
loginToken: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EACV,IAAI,EACJ,QAAQ,EACR,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,aAAa,EAGb,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
|
|
@@ -9,10 +9,9 @@ export interface UseAuthOperationsOptions {
|
|
|
9
9
|
/**
|
|
10
10
|
* The device-first persisted auth-state store. On EXPLICIT full sign-out the
|
|
11
11
|
* session blob is cleared (`store.clear()`) so a reload's cold boot finds no
|
|
12
|
-
*
|
|
13
|
-
* persisted so the next boot
|
|
14
|
-
*
|
|
15
|
-
* cleared here.
|
|
12
|
+
* device credential to restore; on sign-in the returned zero-cookie device
|
|
13
|
+
* credential (`deviceId` + `deviceSecret`) is persisted so the next boot
|
|
14
|
+
* warm-restores without a redirect.
|
|
16
15
|
*/
|
|
17
16
|
store: AuthStateStore;
|
|
18
17
|
activeSessionId: string | null;
|
|
@@ -51,7 +50,7 @@ export interface UseAuthOperationsResult {
|
|
|
51
50
|
logoutAll: () => Promise<void>;
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
54
|
-
* Clear the persisted
|
|
53
|
+
* Clear the persisted device credential on an explicit full sign-out.
|
|
55
54
|
* Best-effort and non-blocking: sign-out must never fail because a storage
|
|
56
55
|
* write threw. Exported so `OxyContext`'s zero-account branch (a REMOTE full
|
|
57
56
|
* sign-out pushed over the socket) runs the EXACT same cleanup as the local
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthOperations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/context/hooks/useAuthOperations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC
|
|
1
|
+
{"version":3,"file":"useAuthOperations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/context/hooks/useAuthOperations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,EAAE,cAAc,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACnF,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,0JAA0J;IAC1J,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAC7B,+MAA+M;IAC/M,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,uBAAuB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,uBAAuB;IACtC,uCAAuC;IACvC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,kCAAkC;IAClC,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,+BAA+B;IAC/B,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAMD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,cAAc,EACrB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,GAClD,IAAI,CAIN;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,wRAmB/B,wBAAwB,KAAG,uBAqR7B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAccountMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useAccountMutations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,IAAI,EACJ,eAAe,EAChB,MAAM,aAAa,CAAC;AAerB;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;
|
|
1
|
+
{"version":3,"file":"useAccountMutations.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/hooks/mutations/useAccountMutations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,IAAI,EACJ,eAAe,EAChB,MAAM,aAAa,CAAC;AAerB;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;aAyPb,CAAC;YAAkC,CAAC;;aAC1C,CAAC;YACO,CAAC;;;;;;;;;;;;;;;;;;;;;UAoBL,CAAC;mBACkB,CAAC;aAAmC,CAAC;aACrD,CAAC;;;UAEO,CAAC;mBACT,CAAC;aACN,CAAC;aAIP,CAAJ;;;;;;;;;EA3LD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe;SAMQ,MAAM;WAAS,MAAM;WAAS,MAAM;WAAS,MAAM;;;EA4EtF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,UAAU,8BAA8B;IACtC,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClC;;;;OAIG;IACH,WAAW,EAAE,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB;sBAoFf,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;sBARnD,OAAO,CAAC,eAAe,CAAC,KAAG,IAAI;2BAQpB,OAAO,CAAC,eAAe,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;iBAtO5D,CAAC;uBAA8B,CAAA;iBACpC,CAAF;cAAoB,CAAC;;;qBACrB,CAAJ;qBAEkB,CAAC;;;;;;;;;;oBAMf,CAAD;kBAAwB,CAAC;mBAE3B,CAAC;;;mBAIqC,CAAC;;;;;;;;;;;;;;;;;;;;;;CA8NzC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,wBAAwB;cAMoB,OAAO,CAAC,eAAe,CAAC;aAAW,MAAM;;;;cAgJjG,CAAC;AAEF,4CAA4C;AAC5C,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CACxI;AAED,4EAA4E;AAC5E,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,eAAO,MAAM,gCAAgC;;EAmD5C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;EA+CpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,uFAoBjC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa;UAWd,gBAAgB;iBACT,SAAS,GAAG,QAAQ,GAAG,UAAU;eACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;iBACrB,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI;WAS5C,CAAC"}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Platform `AuthStateStore` for `@oxyhq/services`.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The zero-cookie device model persists the durable device credential
|
|
5
|
+
* (`deviceId` + `deviceSecret`) per origin (web) / per device (native), and the
|
|
6
|
+
* SDK re-mints the access token from that credential on cold boot. `@oxyhq/core`
|
|
7
|
+
* owns the store shape + logic (`createWebAuthStateStore` /
|
|
8
|
+
* `createNativeAuthStateStore`); this module only supplies the platform storage
|
|
9
|
+
* seam:
|
|
9
10
|
*
|
|
10
11
|
* - web → `createWebAuthStateStore()` (localStorage, in-memory fallback).
|
|
11
|
-
* - native → `createNativeAuthStateStore(secureKV)` for the SESSION blob
|
|
12
|
-
*
|
|
13
|
-
* long-lived DEVICE token delegated to `KeyManager`'s shared keychain
|
|
14
|
-
* (`group.so.oxy.shared`) instead of per-app storage — so an in-app login on
|
|
15
|
-
* any native Oxy app attributes to the SAME server-side DeviceSession.
|
|
12
|
+
* - native → `createNativeAuthStateStore(secureKV)` for the SESSION blob over
|
|
13
|
+
* `expo-secure-store` (AsyncStorage fallback).
|
|
16
14
|
*
|
|
17
15
|
* `expo-secure-store` is loaded via a runtime-computed dynamic import (the same
|
|
18
16
|
* optional-native-module pattern `OxyProvider` uses for netinfo / keyboard
|
|
@@ -23,11 +21,9 @@ import { type AuthStateStore } from '@oxyhq/core';
|
|
|
23
21
|
/**
|
|
24
22
|
* Build the platform {@link AuthStateStore} for this runtime.
|
|
25
23
|
*
|
|
26
|
-
* Native
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* a locked/failed shared-keychain read degrades to "no device token" rather
|
|
30
|
-
* than throwing out of cold boot / refresh.
|
|
24
|
+
* Native persists the SESSION blob (`deviceId` + `deviceSecret` + cached access
|
|
25
|
+
* token) per-app in SecureStore; the SDK re-mints the access token from the
|
|
26
|
+
* device credential on the next cold boot.
|
|
31
27
|
*/
|
|
32
28
|
export declare function createPlatformAuthStateStore(): AuthStateStore;
|
|
33
29
|
//# sourceMappingURL=authStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/authStore.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"authStore.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/authStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAGL,KAAK,cAAc,EAEpB,MAAM,aAAa,CAAC;AAmFrB;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,cAAc,CAM7D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionClient, createSessionClientHost, type AuthStateStore, type OxyServices
|
|
1
|
+
import { SessionClient, createSessionClientHost, type AuthStateStore, type OxyServices } from '@oxyhq/core';
|
|
2
2
|
/**
|
|
3
3
|
* Wire a `SessionClient` for `@oxyhq/services`.
|
|
4
4
|
*
|
|
@@ -11,22 +11,18 @@ import { SessionClient, createSessionClientHost, type AuthStateStore, type OxySe
|
|
|
11
11
|
* core's lazy dynamic import of a bare specifier — bundler-fragile in
|
|
12
12
|
* Metro/Expo-web against the published core dist.
|
|
13
13
|
* - the device-first {@link createTokenTransport}, which mints a fallback token
|
|
14
|
-
*
|
|
14
|
+
* from the persisted device credential (`store` → `deviceId` + `deviceSecret`).
|
|
15
15
|
*
|
|
16
16
|
* `onUnauthenticated` fires when an applied device state has zero accounts (a
|
|
17
17
|
* device signout-all): the provider clears the persisted store + local state so
|
|
18
18
|
* a reload does not try to restore a dead session. The host is returned
|
|
19
19
|
* alongside the client so the caller can call `host.setCurrentAccountId(...)`
|
|
20
20
|
* as the active account changes.
|
|
21
|
+
*
|
|
22
|
+
* The realtime socket is bearer-only: a signed-out tab cannot join any device
|
|
23
|
+
* room, so there is no signed-out socket wiring.
|
|
21
24
|
*/
|
|
22
|
-
export declare function createSessionClient(oxyServices: OxyServices, store: AuthStateStore, onUnauthenticated?: () => void
|
|
23
|
-
/**
|
|
24
|
-
* Optional signed-out realtime wiring: `signedOutSocketAuth` (open the socket
|
|
25
|
-
* while signed out — web returns `true` to ride the `oxy_device` cookie,
|
|
26
|
-
* native returns the shared device token) and `onSessionAppeared` (self-acquire
|
|
27
|
-
* when a sibling signs in on this device).
|
|
28
|
-
*/
|
|
29
|
-
extra?: Pick<SessionClientOptions, 'signedOutSocketAuth' | 'onSessionAppeared'>): {
|
|
25
|
+
export declare function createSessionClient(oxyServices: OxyServices, store: AuthStateStore, onUnauthenticated?: () => void): {
|
|
30
26
|
client: SessionClient;
|
|
31
27
|
host: ReturnType<typeof createSessionClientHost>;
|
|
32
28
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSessionClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/createSessionClient.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"createSessionClient.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/createSessionClient.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,cAAc,EACrB,iBAAiB,CAAC,EAAE,MAAM,IAAI,GAC7B;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;CAClD,CAKA"}
|
|
@@ -5,10 +5,9 @@ import { type AuthStateStore, type OxyServices, type TokenTransport } from '@oxy
|
|
|
5
5
|
* `ensureActiveToken` is the fallback the client uses when a `session_state`
|
|
6
6
|
* push arrived WITHOUT an embedded `activeToken` and the app currently holds no
|
|
7
7
|
* bearer. It mints one through the ONE unified refresh path
|
|
8
|
-
* (`refreshPersistedSession`)
|
|
9
|
-
* (`
|
|
10
|
-
*
|
|
11
|
-
* persisted refresh token is the durable web credential.
|
|
8
|
+
* (`refreshPersistedSession`), which presents the persisted zero-cookie device
|
|
9
|
+
* credential (`deviceId` + `deviceSecret`) at `POST /session/device/token`. The
|
|
10
|
+
* persisted device credential is the sole durable restore credential.
|
|
12
11
|
*
|
|
13
12
|
* Concurrent pushes coalesce onto one in-flight mint. A failure is logged and
|
|
14
13
|
* swallowed: this method must never throw out (it runs inside the socket state
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenTransport.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/tokenTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAGrB
|
|
1
|
+
{"version":3,"file":"tokenTransport.d.ts","sourceRoot":"","sources":["../../../../../src/ui/session/tokenTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,cAAc,GACpB,cAAc,CAsChB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.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",
|
|
@@ -106,10 +106,10 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@oxyhq/contracts": "0.
|
|
109
|
+
"@oxyhq/contracts": "0.13.0"
|
|
110
110
|
},
|
|
111
111
|
"devDependencies": {
|
|
112
|
-
"@oxyhq/core": "
|
|
112
|
+
"@oxyhq/core": "9.0.0",
|
|
113
113
|
"nativewind": "5.0.0-preview.3",
|
|
114
114
|
"react-native-css": "^3.0.0",
|
|
115
115
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
"peerDependencies": {
|
|
154
154
|
"@expo/vector-icons": "^15.0.3",
|
|
155
155
|
"@oxyhq/bloom": ">=0.16.0",
|
|
156
|
-
"@oxyhq/core": "^
|
|
156
|
+
"@oxyhq/core": "^9.0.0",
|
|
157
157
|
"@react-native-async-storage/async-storage": "^2.0.0",
|
|
158
158
|
"@react-native-community/netinfo": "^11.4.1",
|
|
159
159
|
"@tanstack/query-async-storage-persister": "^5.101",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
createAccountDialogController,
|
|
32
32
|
logger as loggerUtil,
|
|
33
33
|
} from '@oxyhq/core';
|
|
34
|
+
import type { SecurityAlert } from '@oxyhq/contracts';
|
|
34
35
|
import {
|
|
35
36
|
registerAccountDialogControls,
|
|
36
37
|
notifyAccountDialogVisibility,
|
|
@@ -99,7 +100,7 @@ export interface OxyContextState {
|
|
|
99
100
|
*
|
|
100
101
|
* Commits a successful session into context state through the SAME path the
|
|
101
102
|
* QR device-flow and cold boot use (so `isAuthenticated` / `user` update and
|
|
102
|
-
* the
|
|
103
|
+
* the zero-cookie device credential is persisted). Returns a discriminated result
|
|
103
104
|
* so the caller can branch on the two-factor-required case — which creates NO
|
|
104
105
|
* session; the caller completes the 2FA challenge with the returned
|
|
105
106
|
* `loginToken` via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
@@ -113,19 +114,21 @@ export interface OxyContextState {
|
|
|
113
114
|
/**
|
|
114
115
|
* Complete a 2FA-gated password sign-in started by {@link signInWithPassword}.
|
|
115
116
|
* Presents the short-lived `loginToken` with a TOTP `token` or a `backupCode`;
|
|
116
|
-
* on success the session is committed exactly like a one-step sign-in.
|
|
117
|
+
* on success the session is committed exactly like a one-step sign-in. Returns
|
|
118
|
+
* any `securityAlert` the server attached so the caller can show the same
|
|
119
|
+
* "New sign-in detected" acknowledgement as the one-step path.
|
|
117
120
|
*/
|
|
118
121
|
completeTwoFactorSignIn: (params: {
|
|
119
122
|
loginToken: string;
|
|
120
123
|
token?: string;
|
|
121
124
|
backupCode?: string;
|
|
122
125
|
deviceName?: string;
|
|
123
|
-
}) => Promise<
|
|
126
|
+
}) => Promise<{ securityAlert?: SecurityAlert }>;
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
129
|
* Commit a session obtained out-of-band (the "Sign in with Oxy" QR device
|
|
127
|
-
* flow). Plants tokens, persists the
|
|
128
|
-
* account into the device set, and hydrates the full user profile.
|
|
130
|
+
* flow). Plants tokens, persists the zero-cookie device credential, registers
|
|
131
|
+
* the account into the device set, and hydrates the full user profile.
|
|
129
132
|
*/
|
|
130
133
|
handleWebSession: (session: SessionLoginResponse) => Promise<void>;
|
|
131
134
|
|
|
@@ -212,14 +215,17 @@ const OxyContext = createContext<OxyContextState | null>(null);
|
|
|
212
215
|
* Result of {@link OxyContextState.signInWithPassword}.
|
|
213
216
|
*
|
|
214
217
|
* `'ok'` — the password was accepted and the session committed (so
|
|
215
|
-
* `isAuthenticated` / `user` are updated and the
|
|
218
|
+
* `isAuthenticated` / `user` are updated and the device credential persisted).
|
|
219
|
+
* `securityAlert` is present when the server flagged this sign-in as anomalous
|
|
220
|
+
* (new device / location) — the caller shows a "New sign-in detected"
|
|
221
|
+
* acknowledgement before proceeding; the session is already committed.
|
|
216
222
|
*
|
|
217
223
|
* `'2fa_required'` — the account has two-factor auth enabled, so NO session was
|
|
218
224
|
* created. Complete the challenge with the returned short-lived `loginToken`
|
|
219
225
|
* via {@link OxyContextState.completeTwoFactorSignIn}.
|
|
220
226
|
*/
|
|
221
227
|
export type PasswordSignInResult =
|
|
222
|
-
| { status: 'ok' }
|
|
228
|
+
| { status: 'ok'; securityAlert?: SecurityAlert }
|
|
223
229
|
| { status: '2fa_required'; loginToken: string };
|
|
224
230
|
|
|
225
231
|
export interface OxyContextProviderProps {
|
|
@@ -272,14 +278,16 @@ const DEFAULT_SESSION_VALIDITY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
272
278
|
*/
|
|
273
279
|
const SESSION_HANDOFF_DEADLINE = 6000;
|
|
274
280
|
|
|
275
|
-
/** The internal commit input — a session plus the
|
|
276
|
-
* not on the public `SessionLoginResponse
|
|
277
|
-
*
|
|
281
|
+
/** The internal commit input — a session plus the zero-cookie device credential
|
|
282
|
+
* (`deviceId` + `deviceSecret`) that is not on the public `SessionLoginResponse`.
|
|
283
|
+
* All extras optional so `SessionLoginResponse` is assignable. */
|
|
278
284
|
interface CommitInput {
|
|
279
285
|
sessionId: string;
|
|
280
286
|
accessToken?: string;
|
|
281
|
-
refreshToken?: string;
|
|
282
287
|
deviceId?: string;
|
|
288
|
+
/** Rotating device secret (zero-cookie transport); persisted with `deviceId`
|
|
289
|
+
* so the cold boot can mint via `POST /session/device/token`. */
|
|
290
|
+
deviceSecret?: string;
|
|
283
291
|
expiresAt?: string;
|
|
284
292
|
userId?: string;
|
|
285
293
|
/** Minimal user carried by the sign-in response; a best-effort fallback used
|
|
@@ -368,9 +376,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
368
376
|
}
|
|
369
377
|
const oxyServices = oxyServicesRef.current;
|
|
370
378
|
|
|
371
|
-
// The device-first persisted auth-state store (per-origin
|
|
372
|
-
// web; SecureStore session blob
|
|
373
|
-
//
|
|
379
|
+
// The device-first persisted auth-state store (per-origin zero-cookie device
|
|
380
|
+
// credential on web; SecureStore session blob on native). Built ONCE per
|
|
381
|
+
// provider mount.
|
|
374
382
|
const authStoreRef = useRef<AuthStateStore | null>(null);
|
|
375
383
|
if (!authStoreRef.current) {
|
|
376
384
|
authStoreRef.current = createPlatformAuthStateStore();
|
|
@@ -545,19 +553,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
545
553
|
const markAuthResolvedRef = useRef(markAuthResolved);
|
|
546
554
|
markAuthResolvedRef.current = markAuthResolved;
|
|
547
555
|
|
|
548
|
-
// Re-boot indirection: the SessionClient is built in the ref initializer below
|
|
549
|
-
// (before `runColdBoot` is defined), but its `onSessionAppeared` must re-run the
|
|
550
|
-
// cold boot to self-acquire when a sibling signs in on this device. A ref bridges
|
|
551
|
-
// the ordering; it is assigned right after `runColdBoot` is declared.
|
|
552
|
-
const runColdBootRef = useRef<(() => Promise<void>) | null>(null);
|
|
553
|
-
|
|
554
556
|
// Server-authoritative device session client. Built ONCE per `oxyServices`
|
|
555
|
-
// instance. `onUnauthenticated` (a device signout-all pushed over the
|
|
556
|
-
// or bootstrapped as zero-account) clears the
|
|
557
|
-
// a reload does not try to restore a session
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
557
|
+
// instance. `onUnauthenticated` (a device signout-all pushed over the
|
|
558
|
+
// bearer-authenticated socket, or bootstrapped as zero-account) clears the
|
|
559
|
+
// persisted store + local state so a reload does not try to restore a session
|
|
560
|
+
// the device no longer has. The realtime socket is bearer-only now: a
|
|
561
|
+
// signed-out tab cannot join any device room, so there is no signed-out sync
|
|
562
|
+
// wiring here.
|
|
561
563
|
const sessionClientPairRef = useRef<ReturnType<typeof createSessionClient> | null>(null);
|
|
562
564
|
if (!sessionClientPairRef.current) {
|
|
563
565
|
sessionClientPairRef.current = createSessionClient(
|
|
@@ -569,22 +571,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
569
571
|
logger('Failed to clear local state on remote sign-out', clearError);
|
|
570
572
|
});
|
|
571
573
|
},
|
|
572
|
-
{
|
|
573
|
-
signedOutSocketAuth: async () => {
|
|
574
|
-
// Web (`*.oxy.so`): the first-party `oxy_device` cookie rides the
|
|
575
|
-
// same-site handshake automatically — just connect.
|
|
576
|
-
if (isWebBrowser()) return true;
|
|
577
|
-
// Native (no cookie jar): present the shared device token if we have one.
|
|
578
|
-
try {
|
|
579
|
-
return (await authStore.loadDeviceToken()) ?? false;
|
|
580
|
-
} catch {
|
|
581
|
-
return false;
|
|
582
|
-
}
|
|
583
|
-
},
|
|
584
|
-
onSessionAppeared: () => {
|
|
585
|
-
void runColdBootRef.current?.();
|
|
586
|
-
},
|
|
587
|
-
},
|
|
588
574
|
);
|
|
589
575
|
}
|
|
590
576
|
const { client: sessionClient, host: sessionClientHost } = sessionClientPairRef.current;
|
|
@@ -678,7 +664,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
678
664
|
}
|
|
679
665
|
}
|
|
680
666
|
await clearSessionState();
|
|
681
|
-
// Explicit FULL wipe: also drop the persisted
|
|
667
|
+
// Explicit FULL wipe: also drop the persisted device credential so a reload
|
|
682
668
|
// finds nothing to restore.
|
|
683
669
|
await authStore.clear();
|
|
684
670
|
useAccountStore.getState().reset();
|
|
@@ -736,10 +722,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
736
722
|
}, [logger, oxyServices]);
|
|
737
723
|
|
|
738
724
|
// Unified in-session refresh (SDK-owned; every RP inherits it). Installs the
|
|
739
|
-
// ONE core refresh handler (
|
|
740
|
-
//
|
|
741
|
-
// ~60s before expiry and on tab-focus. Replaces the deleted
|
|
742
|
-
// `inSessionTokenRefresh` module.
|
|
725
|
+
// ONE core refresh handler (re-mint from the persisted zero-cookie device
|
|
726
|
+
// credential via `POST /session/device/token`) plus the proactive scheduler
|
|
727
|
+
// that refreshes ~60s before expiry and on tab-focus. Replaces the deleted
|
|
728
|
+
// services-local `inSessionTokenRefresh` module.
|
|
743
729
|
useEffect(() => {
|
|
744
730
|
const dispose = installAuthRefreshHandler({ oxy: oxyServices, store: authStore });
|
|
745
731
|
const scheduler = startTokenRefreshScheduler(oxyServices);
|
|
@@ -751,27 +737,27 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
751
737
|
|
|
752
738
|
// ── Session commit funnel ────────────────────────────────────────────────
|
|
753
739
|
// The single place a session becomes committed: plant tokens, persist the
|
|
754
|
-
//
|
|
755
|
-
// device set, and hydrate the full user.
|
|
756
|
-
// (`handleWebSession`), password sign-in, 2FA
|
|
740
|
+
// zero-cookie device credential (`deviceId` + `deviceSecret`), register the
|
|
741
|
+
// account into the server-authoritative device set, and hydrate the full user.
|
|
742
|
+
// Used by the QR device flow (`handleWebSession`), password sign-in, 2FA
|
|
743
|
+
// completion, and the cold boot.
|
|
757
744
|
const commitSession = useCallback(
|
|
758
745
|
async (input: CommitInput, options: { activate: boolean }): Promise<void> => {
|
|
759
746
|
if (input.accessToken) {
|
|
760
747
|
oxyServices.setTokens(input.accessToken);
|
|
761
748
|
}
|
|
762
749
|
|
|
763
|
-
// Persist the durable blob when
|
|
764
|
-
// (
|
|
765
|
-
// this is idempotent.
|
|
766
|
-
//
|
|
767
|
-
if (input.
|
|
750
|
+
// Persist the durable blob when the zero-cookie device credential
|
|
751
|
+
// (`deviceId` + `deviceSecret`) is present. The cold boot has usually
|
|
752
|
+
// persisted already; this is idempotent. Without the credential there is
|
|
753
|
+
// nothing durable to persist and the session lives only for this runtime.
|
|
754
|
+
if (input.deviceId && input.deviceSecret && input.userId) {
|
|
768
755
|
try {
|
|
769
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
770
756
|
const next: PersistedAuthState = {
|
|
771
757
|
sessionId: input.sessionId,
|
|
772
|
-
refreshToken: input.refreshToken,
|
|
773
758
|
userId: input.userId,
|
|
774
|
-
|
|
759
|
+
deviceId: input.deviceId,
|
|
760
|
+
deviceSecret: input.deviceSecret,
|
|
775
761
|
...(input.accessToken ? { accessToken: input.accessToken } : {}),
|
|
776
762
|
...(input.expiresAt ? { expiresAt: input.expiresAt } : {}),
|
|
777
763
|
};
|
|
@@ -859,9 +845,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
859
845
|
{
|
|
860
846
|
sessionId: session.sessionId,
|
|
861
847
|
accessToken: session.accessToken,
|
|
862
|
-
// deviceFlow threads
|
|
848
|
+
// deviceFlow threads the rotating deviceSecret on the runtime object
|
|
863
849
|
// even though `SessionLoginResponse` does not type it.
|
|
864
|
-
|
|
850
|
+
deviceSecret: (session as { deviceSecret?: string }).deviceSecret,
|
|
865
851
|
deviceId: session.deviceId,
|
|
866
852
|
expiresAt: session.expiresAt,
|
|
867
853
|
userId: session.user.id,
|
|
@@ -935,9 +921,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
935
921
|
password: string,
|
|
936
922
|
opts?: { deviceName?: string; deviceFingerprint?: string },
|
|
937
923
|
): Promise<PasswordSignInResult> => {
|
|
938
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
939
924
|
const result = await oxyServices.passwordSignIn(identifier, password, {
|
|
940
|
-
deviceToken,
|
|
941
925
|
deviceName: opts?.deviceName,
|
|
942
926
|
deviceFingerprint: opts?.deviceFingerprint,
|
|
943
927
|
});
|
|
@@ -949,7 +933,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
949
933
|
{
|
|
950
934
|
sessionId: result.sessionId,
|
|
951
935
|
accessToken: result.accessToken,
|
|
952
|
-
|
|
936
|
+
deviceSecret: result.deviceSecret,
|
|
953
937
|
deviceId: result.deviceId,
|
|
954
938
|
expiresAt: result.expiresAt,
|
|
955
939
|
userId: result.user.id,
|
|
@@ -957,9 +941,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
957
941
|
},
|
|
958
942
|
{ activate: true },
|
|
959
943
|
);
|
|
960
|
-
return { status: 'ok' };
|
|
944
|
+
return { status: 'ok', ...(result.securityAlert ? { securityAlert: result.securityAlert } : {}) };
|
|
961
945
|
},
|
|
962
|
-
[oxyServices,
|
|
946
|
+
[oxyServices, commitSession],
|
|
963
947
|
);
|
|
964
948
|
|
|
965
949
|
const completeTwoFactorSignIn = useCallback(
|
|
@@ -968,20 +952,18 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
968
952
|
token?: string;
|
|
969
953
|
backupCode?: string;
|
|
970
954
|
deviceName?: string;
|
|
971
|
-
}): Promise<
|
|
972
|
-
const deviceToken = (await authStore.loadDeviceToken()) ?? undefined;
|
|
955
|
+
}): Promise<{ securityAlert?: SecurityAlert }> => {
|
|
973
956
|
const result = await oxyServices.completeTwoFactorSignIn({
|
|
974
957
|
loginToken: params.loginToken,
|
|
975
958
|
token: params.token,
|
|
976
959
|
backupCode: params.backupCode,
|
|
977
|
-
deviceToken,
|
|
978
960
|
deviceName: params.deviceName,
|
|
979
961
|
});
|
|
980
962
|
await commitSession(
|
|
981
963
|
{
|
|
982
964
|
sessionId: result.sessionId,
|
|
983
965
|
accessToken: result.accessToken,
|
|
984
|
-
|
|
966
|
+
deviceSecret: result.deviceSecret,
|
|
985
967
|
deviceId: result.deviceId,
|
|
986
968
|
expiresAt: result.expiresAt,
|
|
987
969
|
userId: result.user.id,
|
|
@@ -989,8 +971,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
989
971
|
},
|
|
990
972
|
{ activate: true },
|
|
991
973
|
);
|
|
974
|
+
return { securityAlert: result.securityAlert };
|
|
992
975
|
},
|
|
993
|
-
[oxyServices,
|
|
976
|
+
[oxyServices, commitSession],
|
|
994
977
|
);
|
|
995
978
|
|
|
996
979
|
// ── Cold boot ────────────────────────────────────────────────────────────
|
|
@@ -1031,19 +1014,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1031
1014
|
markAuthResolvedRef.current();
|
|
1032
1015
|
},
|
|
1033
1016
|
onSignedOut: () => {
|
|
1017
|
+
// The realtime socket is bearer-only: a signed-out tab cannot join any
|
|
1018
|
+
// device room, so there is no signed-out socket to start here.
|
|
1034
1019
|
markAuthResolvedRef.current();
|
|
1035
|
-
// Open the signed-out realtime channel so this idle tab joins its
|
|
1036
|
-
// `device:<id>` room (web `oxy_device` cookie / native device token)
|
|
1037
|
-
// and self-acquires when a sibling signs in. Idempotent, best-effort.
|
|
1038
|
-
void sessionClient.start().catch((startError) => {
|
|
1039
|
-
if (__DEV__) {
|
|
1040
|
-
loggerUtil.debug(
|
|
1041
|
-
'signed-out socket start failed (non-fatal)',
|
|
1042
|
-
{ component: 'OxyContext', method: 'runColdBoot' },
|
|
1043
|
-
startError as unknown,
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
1020
|
},
|
|
1048
1021
|
onStepError: (id, error) => {
|
|
1049
1022
|
if (__DEV__) {
|
|
@@ -1067,20 +1040,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1067
1040
|
// Backstop: resolve on every exit path so the gate can never hang.
|
|
1068
1041
|
markAuthResolvedRef.current();
|
|
1069
1042
|
}
|
|
1070
|
-
}, [oxyServices, authStore
|
|
1071
|
-
// Bridge for `onSessionAppeared` (SessionClient) → re-run the cold boot to
|
|
1072
|
-
// self-acquire when a sibling signs in on this device while this tab is idle.
|
|
1073
|
-
runColdBootRef.current = runColdBoot;
|
|
1043
|
+
}, [oxyServices, authStore]);
|
|
1074
1044
|
|
|
1075
1045
|
useEffect(() => {
|
|
1076
1046
|
if (initialized) {
|
|
1077
1047
|
return;
|
|
1078
1048
|
}
|
|
1079
1049
|
// IdP mode (`coldBoot={false}`): this provider is NOT the ecosystem session
|
|
1080
|
-
// authority, so it never runs the device-first restore
|
|
1081
|
-
// signed
|
|
1082
|
-
//
|
|
1083
|
-
// is no boot spinner; a deliberate sign-in still commits a normal session.
|
|
1050
|
+
// authority, so it never runs the device-first restore. Resolve auth
|
|
1051
|
+
// immediately as signed out so there is no boot spinner; a deliberate sign-in
|
|
1052
|
+
// still commits a normal session.
|
|
1084
1053
|
if (!coldBoot) {
|
|
1085
1054
|
setInitialized(true);
|
|
1086
1055
|
markAuthResolved();
|
|
@@ -1204,7 +1173,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
1204
1173
|
{
|
|
1205
1174
|
sessionId: result.sessionId,
|
|
1206
1175
|
accessToken: result.accessToken,
|
|
1207
|
-
|
|
1176
|
+
deviceSecret: (result as { deviceSecret?: string }).deviceSecret,
|
|
1208
1177
|
deviceId: result.deviceId,
|
|
1209
1178
|
expiresAt: result.expiresAt,
|
|
1210
1179
|
userId: result.user.id,
|
|
@@ -1367,7 +1336,7 @@ const LOADING_STATE: OxyContextState = {
|
|
|
1367
1336
|
getPublicKey: () => Promise.resolve(null),
|
|
1368
1337
|
signIn: () => rejectMissingProvider<User>(),
|
|
1369
1338
|
signInWithPassword: () => rejectMissingProvider<PasswordSignInResult>(),
|
|
1370
|
-
completeTwoFactorSignIn: () => rejectMissingProvider<
|
|
1339
|
+
completeTwoFactorSignIn: () => rejectMissingProvider<{ securityAlert?: SecurityAlert }>(),
|
|
1371
1340
|
handleWebSession: () => rejectMissingProvider<void>(),
|
|
1372
1341
|
logout: () => rejectMissingProvider<void>(),
|
|
1373
1342
|
logoutAll: () => rejectMissingProvider<void>(),
|