@oxy.so/services 11.0.0 → 11.2.0-oxy-one.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/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/boot/runProviderColdBoot.js +14 -1
- package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +25 -7
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +198 -41
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/commitSessionFlow.js +5 -0
- package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +56 -9
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/isolatedOAuthSession.js +52 -0
- package/lib/commonjs/ui/context/isolatedOAuthSession.js.map +1 -0
- package/lib/commonjs/ui/context/useOxyAccountGraph.js +7 -5
- package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/usePersonalPlans.js +43 -0
- package/lib/commonjs/ui/hooks/queries/usePersonalPlans.js.map +1 -0
- package/lib/commonjs/ui/hooks/useAuth.js +7 -3
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/oauth/applicationSessionLane.js +32 -0
- package/lib/commonjs/ui/oauth/applicationSessionLane.js.map +1 -0
- package/lib/commonjs/ui/oauth/completeOAuthCode.js +4 -2
- package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -1
- package/lib/commonjs/ui/oauth/nativeAuthTransport.js +119 -0
- package/lib/commonjs/ui/oauth/nativeAuthTransport.js.map +1 -0
- package/lib/commonjs/ui/session/createSessionClient.js +14 -2
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/deviceCredentialTracker.js +8 -0
- package/lib/commonjs/ui/session/deviceCredentialTracker.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/boot/runProviderColdBoot.js +14 -1
- package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +25 -7
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +200 -43
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/commitSessionFlow.js +5 -0
- package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +56 -9
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/isolatedOAuthSession.js +44 -0
- package/lib/module/ui/context/isolatedOAuthSession.js.map +1 -0
- package/lib/module/ui/context/useOxyAccountGraph.js +7 -5
- package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/module/ui/hooks/queries/usePersonalPlans.js +39 -0
- package/lib/module/ui/hooks/queries/usePersonalPlans.js.map +1 -0
- package/lib/module/ui/hooks/useAuth.js +7 -3
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/oauth/applicationSessionLane.js +26 -0
- package/lib/module/ui/oauth/applicationSessionLane.js.map +1 -0
- package/lib/module/ui/oauth/completeOAuthCode.js +4 -2
- package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -1
- package/lib/module/ui/oauth/nativeAuthTransport.js +115 -0
- package/lib/module/ui/oauth/nativeAuthTransport.js.map +1 -0
- package/lib/module/ui/session/createSessionClient.js +14 -2
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/deviceCredentialTracker.js +8 -0
- package/lib/module/ui/session/deviceCredentialTracker.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts +7 -0
- package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +3 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts +2 -0
- package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/isolatedOAuthSession.d.ts +19 -0
- package/lib/typescript/commonjs/ui/context/isolatedOAuthSession.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +3 -0
- package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +2 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +6 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts +64 -0
- package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/oauth/applicationSessionLane.d.ts +6 -0
- package/lib/typescript/commonjs/ui/oauth/applicationSessionLane.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts +2 -2
- package/lib/typescript/commonjs/ui/oauth/nativeAuthTransport.d.ts +28 -0
- package/lib/typescript/commonjs/ui/oauth/nativeAuthTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/deviceCredentialTracker.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts +7 -0
- package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts +2 -0
- package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +3 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts +2 -0
- package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +3 -2
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/isolatedOAuthSession.d.ts +19 -0
- package/lib/typescript/module/ui/context/isolatedOAuthSession.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +3 -0
- package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +2 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +6 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts +64 -0
- package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/oauth/applicationSessionLane.d.ts +6 -0
- package/lib/typescript/module/ui/oauth/applicationSessionLane.d.ts.map +1 -0
- package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts +2 -2
- package/lib/typescript/module/ui/oauth/nativeAuthTransport.d.ts +28 -0
- package/lib/typescript/module/ui/oauth/nativeAuthTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/deviceCredentialTracker.d.ts.map +1 -1
- package/package.json +30 -30
- package/src/index.ts +4 -0
- package/src/ui/boot/__tests__/coldBootNoIdpRedirect.test.ts +56 -2
- package/src/ui/boot/runProviderColdBoot.ts +22 -2
- package/src/ui/components/OxySignInButton.tsx +20 -4
- package/src/ui/context/OxyContext.tsx +171 -40
- package/src/ui/context/__tests__/commitSessionFlow.test.ts +13 -0
- package/src/ui/context/__tests__/isolatedOAuthSession.test.ts +35 -0
- package/src/ui/context/commitSessionFlow.ts +8 -0
- package/src/ui/context/hooks/useAuthOperations.ts +55 -9
- package/src/ui/context/isolatedOAuthSession.ts +48 -0
- package/src/ui/context/oxyContextTypes.ts +4 -0
- package/src/ui/context/useOxyAccountGraph.ts +8 -5
- package/src/ui/hooks/queries/usePersonalPlans.ts +23 -0
- package/src/ui/hooks/useAuth.ts +6 -2
- package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +1 -1
- package/src/ui/oauth/__tests__/nativeAuthTransport.test.ts +57 -0
- package/src/ui/oauth/__tests__/oauthBearerCommit.http.test.ts +131 -0
- package/src/ui/oauth/applicationSessionLane.ts +26 -0
- package/src/ui/oauth/completeOAuthCode.ts +3 -3
- package/src/ui/oauth/nativeAuthTransport.ts +67 -0
- package/src/ui/session/createSessionClient.ts +13 -1
- package/src/ui/session/deviceCredentialTracker.ts +7 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { OxyServices } from '@oxy.so/core';
|
|
2
|
+
import { openAuthorizeUrlNative } from '../components/oauthNavigation';
|
|
3
|
+
import { resolveApplicationSessionLane } from './applicationSessionLane';
|
|
4
|
+
import { completeOAuthCode } from './completeOAuthCode';
|
|
5
|
+
import { prepareAuthorizeRequest } from './oauthHandshake';
|
|
6
|
+
import type { OAuthSessionCommitInput } from './types';
|
|
7
|
+
|
|
8
|
+
export interface StartNativeOAuthSignInOptions {
|
|
9
|
+
/** Exact registered URI, including any fixed query. Never inferred. */
|
|
10
|
+
redirectUri: string;
|
|
11
|
+
}
|
|
12
|
+
export type NativeOAuthSignInResult =
|
|
13
|
+
| { status: 'signed-in' }
|
|
14
|
+
| { status: 'cancelled' }
|
|
15
|
+
| { status: 'unsupported'; reason: 'unsupported-platform' | 'missing-client-id' | 'identity-bound' | 'not-third-party' }
|
|
16
|
+
| { status: 'failed'; reason: 'invalid-redirect-uri' | 'client-resolution-failed' | 'native-callback-invalid' | 'state-mismatch' | 'exchange-failed' | 'already-in-progress' };
|
|
17
|
+
export interface NativeOAuthSignInContext {
|
|
18
|
+
platform: 'native' | 'unsupported';
|
|
19
|
+
oxyServices: OxyServices;
|
|
20
|
+
clientId: string | null;
|
|
21
|
+
authorizeBaseUrl?: string;
|
|
22
|
+
identityBound: boolean;
|
|
23
|
+
commitSession: (input: OAuthSessionCommitInput) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
const attempts = new WeakSet<OxyServices>();
|
|
26
|
+
|
|
27
|
+
function isExactRedirectUri(value: string): boolean {
|
|
28
|
+
if (!value || value !== value.trim() || /\s/.test(value)) return false;
|
|
29
|
+
try {
|
|
30
|
+
const uri = new URL(value);
|
|
31
|
+
return !uri.username && !uri.password && !uri.hash &&
|
|
32
|
+
!['code', 'state', 'error', 'error_description'].some((key) => uri.searchParams.has(key));
|
|
33
|
+
} catch { return false; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** SDK owns the in-memory handshake, observed native callback and session commit. */
|
|
37
|
+
export async function startNativeOAuthSignIn(
|
|
38
|
+
context: NativeOAuthSignInContext,
|
|
39
|
+
options: StartNativeOAuthSignInOptions,
|
|
40
|
+
): Promise<NativeOAuthSignInResult> {
|
|
41
|
+
if (context.platform !== 'native') return { status: 'unsupported', reason: 'unsupported-platform' };
|
|
42
|
+
if (!context.clientId) return { status: 'unsupported', reason: 'missing-client-id' };
|
|
43
|
+
if (context.identityBound) return { status: 'unsupported', reason: 'identity-bound' };
|
|
44
|
+
if (!isExactRedirectUri(options.redirectUri)) return { status: 'failed', reason: 'invalid-redirect-uri' };
|
|
45
|
+
if (attempts.has(context.oxyServices)) return { status: 'failed', reason: 'already-in-progress' };
|
|
46
|
+
attempts.add(context.oxyServices);
|
|
47
|
+
try {
|
|
48
|
+
let lane: 'device' | 'oauth';
|
|
49
|
+
try { lane = await resolveApplicationSessionLane(context.oxyServices, context.clientId); }
|
|
50
|
+
catch { return { status: 'failed', reason: 'client-resolution-failed' }; }
|
|
51
|
+
if (lane !== 'oauth') return { status: 'unsupported', reason: 'not-third-party' };
|
|
52
|
+
const prepared = await prepareAuthorizeRequest({ clientId: context.clientId, redirectUri: options.redirectUri, authorizeBaseUrl: context.authorizeBaseUrl });
|
|
53
|
+
const { redirectUrl } = await openAuthorizeUrlNative(prepared.authorizeUrl, options.redirectUri, { allowExternalFallback: false });
|
|
54
|
+
if (!redirectUrl) return { status: 'cancelled' };
|
|
55
|
+
const prefix = `${options.redirectUri}${options.redirectUri.includes('?') ? '&' : '?'}`;
|
|
56
|
+
if (!redirectUrl.startsWith(prefix) || redirectUrl.includes('#')) return { status: 'failed', reason: 'native-callback-invalid' };
|
|
57
|
+
const parameters = new URLSearchParams(redirectUrl.slice(prefix.length));
|
|
58
|
+
if ([...new URL(options.redirectUri).searchParams.keys()].some((key) => parameters.has(key))) return { status: 'failed', reason: 'native-callback-invalid' };
|
|
59
|
+
if (['code', 'state', 'error', 'error_description'].some((key) => parameters.getAll(key).length > 1)) return { status: 'failed', reason: 'native-callback-invalid' };
|
|
60
|
+
if (parameters.get('state') !== prepared.handshake.state) return { status: 'failed', reason: 'state-mismatch' };
|
|
61
|
+
const code = parameters.get('code');
|
|
62
|
+
if (parameters.has('error') || !code) return { status: 'failed', reason: 'native-callback-invalid' };
|
|
63
|
+
const completion = await completeOAuthCode({ oxyServices: context.oxyServices, clientId: context.clientId, code, returnedState: parameters.get('state'), handshake: prepared.handshake, redirectUri: options.redirectUri, commitSession: context.commitSession });
|
|
64
|
+
return completion.ok ? { status: 'signed-in' } : { status: 'failed', reason: completion.reason };
|
|
65
|
+
} catch { return { status: 'failed', reason: 'exchange-failed' }; }
|
|
66
|
+
finally { attempts.delete(context.oxyServices); }
|
|
67
|
+
}
|
|
@@ -40,15 +40,27 @@ export function createSessionClient(
|
|
|
40
40
|
oxyServices: OxyServices,
|
|
41
41
|
onUnauthenticated?: (origin: SessionStateOrigin) => void,
|
|
42
42
|
getPinnedAccountId?: () => string | null,
|
|
43
|
+
isDeviceSessionAllowed: () => boolean = () => true,
|
|
44
|
+
onFullExplicitSignOut?: () => Promise<void>,
|
|
43
45
|
): {
|
|
44
46
|
client: SessionClient;
|
|
45
47
|
host: ReturnType<typeof createSessionClientHost>;
|
|
46
48
|
} {
|
|
47
|
-
const
|
|
49
|
+
const baseHost = createSessionClientHost(oxyServices);
|
|
50
|
+
const host = {
|
|
51
|
+
...baseHost,
|
|
52
|
+
getAccessToken: () => isDeviceSessionAllowed() ? baseHost.getAccessToken() : null,
|
|
53
|
+
setTokens: (token: string) => { if (isDeviceSessionAllowed()) baseHost.setTokens(token); },
|
|
54
|
+
makeRequest: async <T>(method: 'GET' | 'POST', url: string, data?: unknown, requestOptions?: { cache?: boolean }): Promise<T> => {
|
|
55
|
+
if (!isDeviceSessionAllowed()) throw new Error('An isolated OAuth session cannot access the shared device');
|
|
56
|
+
return baseHost.makeRequest<T>(method, url, data, requestOptions);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
48
59
|
const transport = createTokenTransport(oxyServices, getPinnedAccountId);
|
|
49
60
|
const client = new SessionClient(host, {
|
|
50
61
|
transport,
|
|
51
62
|
onUnauthenticated,
|
|
63
|
+
onFullExplicitSignOut,
|
|
52
64
|
getPinnedAccountId,
|
|
53
65
|
});
|
|
54
66
|
return { client, host };
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* every load, save and clear passes through, so the press can read it at once.
|
|
8
8
|
* The cold boot loads the store on mount, long before anyone presses sign-in.
|
|
9
9
|
*/
|
|
10
|
-
import type { AuthStateStore } from '@oxy.so/core/session';
|
|
10
|
+
import type { AuthStateStore, AuthStateWriteGuard, PersistedAuthState } from '@oxy.so/core/session';
|
|
11
11
|
|
|
12
12
|
export interface HeldDeviceCredential {
|
|
13
13
|
deviceId: string;
|
|
@@ -31,6 +31,7 @@ function credentialOf(state: { deviceId?: string; deviceSecret?: string } | null
|
|
|
31
31
|
export function trackDeviceCredential(store: AuthStateStore): CredentialTrackingAuthStateStore {
|
|
32
32
|
let held: HeldDeviceCredential | null | undefined;
|
|
33
33
|
return {
|
|
34
|
+
...store,
|
|
34
35
|
load: async () => {
|
|
35
36
|
const state = await store.load();
|
|
36
37
|
held = credentialOf(state);
|
|
@@ -40,6 +41,11 @@ export function trackDeviceCredential(store: AuthStateStore): CredentialTracking
|
|
|
40
41
|
held = credentialOf(state);
|
|
41
42
|
return store.save(state);
|
|
42
43
|
},
|
|
44
|
+
...(store.saveIfCurrent ? {saveIfCurrent: async (state: PersistedAuthState, guard: AuthStateWriteGuard) => {
|
|
45
|
+
const committed = await store.saveIfCurrent?.(state, guard);
|
|
46
|
+
if (committed) held = credentialOf(state);
|
|
47
|
+
return committed === true;
|
|
48
|
+
}} : {}),
|
|
43
49
|
clear: async () => {
|
|
44
50
|
held = null;
|
|
45
51
|
await store.clear();
|