@oxyhq/services 22.1.0 → 22.2.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/OxyAuthChooser.js +37 -16
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/passkeyHubPopup.js +33 -0
- package/lib/commonjs/ui/components/passkeyHubPopup.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +7 -1
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +37 -16
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/passkeyHubPopup.js +29 -0
- package/lib/module/ui/components/passkeyHubPopup.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +7 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts +23 -0
- package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts +23 -0
- package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/ui/components/OxyAuthChooser.tsx +42 -18
- package/src/ui/components/passkeyHubPopup.ts +28 -0
- package/src/ui/context/OxyContext.tsx +6 -0
|
@@ -138,12 +138,20 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({ onComplete }) => {
|
|
|
138
138
|
const { t } = useI18n();
|
|
139
139
|
const queryClient = useQueryClient();
|
|
140
140
|
|
|
141
|
-
// A credential minted for `oxy.so` can only be
|
|
142
|
-
// dev host) —
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
141
|
+
// A credential minted for `oxy.so` can only be ASSERTED there (or a loopback
|
|
142
|
+
// dev host) — a hard WebAuthn RP-ID boundary the browser enforces, not
|
|
143
|
+
// feature detection. On a first-party Oxy web origin the ceremony runs
|
|
144
|
+
// directly ('direct'). On any OTHER web origin (b2) it can't run locally, so
|
|
145
|
+
// the passkey action instead opens a popup at the auth.oxy.so passkey hub —
|
|
146
|
+
// where the SAME ceremony IS first-party — and relays the resulting session
|
|
147
|
+
// back via `AccountDialogController.startPasskeyHubSignIn` ('hub'). Native
|
|
148
|
+
// has neither: Commons owns identity there ('none'). `isOxyRpOrigin()` reads
|
|
149
|
+
// `location` once and is stable for the component's lifetime.
|
|
150
|
+
const passkeyMode = useMemo<'direct' | 'hub' | 'none'>(() => {
|
|
151
|
+
if (!isWebBrowser()) return 'none';
|
|
152
|
+
return isOxyRpOrigin() ? 'direct' : 'hub';
|
|
153
|
+
}, []);
|
|
154
|
+
const passkeyAvailable = passkeyMode !== 'none';
|
|
147
155
|
|
|
148
156
|
const [signInPasskeyPending, setSignInPasskeyPending] = useState(false);
|
|
149
157
|
const [signInPasskeyError, setSignInPasskeyError] = useState<string | null>(null);
|
|
@@ -274,9 +282,16 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({ onComplete }) => {
|
|
|
274
282
|
t={t}
|
|
275
283
|
onRetry={() => void controller.showQr()}
|
|
276
284
|
passkeyAvailable={passkeyAvailable}
|
|
277
|
-
onSignInWithPasskey={
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
onSignInWithPasskey={
|
|
286
|
+
passkeyMode === 'direct'
|
|
287
|
+
? () => void handleSignInWithPasskey()
|
|
288
|
+
: () => void controller.startPasskeyHubSignIn()
|
|
289
|
+
}
|
|
290
|
+
// The hub-popup flow's pending/error state is `snapshot.signIn`
|
|
291
|
+
// (already rendered elsewhere in this view) — only the DIRECT ceremony
|
|
292
|
+
// uses this component-local pair.
|
|
293
|
+
passkeyPending={passkeyMode === 'direct' ? signInPasskeyPending : false}
|
|
294
|
+
passkeyError={passkeyMode === 'direct' ? signInPasskeyError : null}
|
|
280
295
|
onCreateAccount={() => controller.startSignup()}
|
|
281
296
|
/>
|
|
282
297
|
);
|
|
@@ -289,10 +304,11 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({ onComplete }) => {
|
|
|
289
304
|
theme={theme}
|
|
290
305
|
t={t}
|
|
291
306
|
oxyServices={oxyServices}
|
|
292
|
-
|
|
307
|
+
passkeyMode={passkeyMode}
|
|
293
308
|
onCreateWithPasskey={handleCreateWithPasskey}
|
|
294
309
|
createPending={createPasskeyPending}
|
|
295
310
|
createError={createPasskeyError}
|
|
311
|
+
onOpenHub={() => void controller.startPasskeyHubSignIn()}
|
|
296
312
|
onBackToSignIn={() => controller.setView('signin')}
|
|
297
313
|
/>
|
|
298
314
|
);
|
|
@@ -742,10 +758,12 @@ interface SignUpViewProps {
|
|
|
742
758
|
theme: Theme;
|
|
743
759
|
t: Translate;
|
|
744
760
|
oxyServices: OxyServices;
|
|
745
|
-
|
|
761
|
+
passkeyMode: 'direct' | 'hub' | 'none';
|
|
746
762
|
onCreateWithPasskey: (username: string) => Promise<void>;
|
|
747
763
|
createPending: boolean;
|
|
748
764
|
createError: string | null;
|
|
765
|
+
/** Open the auth.oxy.so hub popup (b2) — the 'hub' mode's only path. */
|
|
766
|
+
onOpenHub: () => void;
|
|
749
767
|
onBackToSignIn: () => void;
|
|
750
768
|
}
|
|
751
769
|
|
|
@@ -754,10 +772,11 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
754
772
|
theme,
|
|
755
773
|
t,
|
|
756
774
|
oxyServices,
|
|
757
|
-
|
|
775
|
+
passkeyMode,
|
|
758
776
|
onCreateWithPasskey,
|
|
759
777
|
createPending,
|
|
760
778
|
createError,
|
|
779
|
+
onOpenHub,
|
|
761
780
|
onBackToSignIn,
|
|
762
781
|
}) => {
|
|
763
782
|
const [username, setUsername] = useState('');
|
|
@@ -794,7 +813,7 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
794
813
|
// passkey creation is the de-emphasized alternative underneath for someone
|
|
795
814
|
// who doesn't want to install anything — secondary button, introduced by
|
|
796
815
|
// its own "No Commons?" framing, same subordination as the QR view's link.
|
|
797
|
-
if (
|
|
816
|
+
if (passkeyMode === 'direct') {
|
|
798
817
|
return (
|
|
799
818
|
<View style={styles.signInBlock}>
|
|
800
819
|
<Text style={[styles.qrHeadline, { color: theme.colors.text }]}>
|
|
@@ -851,15 +870,20 @@ const SignUpView: React.FC<SignUpViewProps> = ({
|
|
|
851
870
|
);
|
|
852
871
|
}
|
|
853
872
|
|
|
854
|
-
// Web, non-Oxy origin: passkey creation here
|
|
855
|
-
//
|
|
856
|
-
//
|
|
873
|
+
// Web, non-Oxy origin (b2): passkey creation can't run locally here — the
|
|
874
|
+
// same WebAuthn RP-ID boundary as sign-in — so this opens the SAME
|
|
875
|
+
// auth.oxy.so passkey hub popup `QrView`'s "No Commons?" link uses.
|
|
876
|
+
// Completing EITHER a sign-in OR a fresh sign-up there relays the resulting
|
|
877
|
+
// session back via the SAME poll/socket engine `showQr` drives.
|
|
857
878
|
return (
|
|
858
879
|
<View style={styles.centeredBlock}>
|
|
859
880
|
<Text style={[styles.mutedText, { color: theme.colors.textSecondary }]}>
|
|
860
|
-
{t('signup.
|
|
861
|
-
|
|
881
|
+
{t('signup.hubExplainer') ||
|
|
882
|
+
'Create your Oxy ID in a secure window, then come right back.'}
|
|
862
883
|
</Text>
|
|
884
|
+
<Button variant="primary" onPress={onOpenHub} style={styles.primaryButton}>
|
|
885
|
+
{t('signup.continueInWindow') || 'Continue in a new window'}
|
|
886
|
+
</Button>
|
|
863
887
|
<SignInFooterLink theme={theme} t={t} onPress={onBackToSignIn} />
|
|
864
888
|
</View>
|
|
865
889
|
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Popup opener for the cross-origin passkey hub (b2).
|
|
3
|
+
*
|
|
4
|
+
* On a non-Oxy web origin (e.g. mention.earth) a WebAuthn ceremony can't run
|
|
5
|
+
* locally — the credential's RP ID is bound to `oxy.so`. Instead,
|
|
6
|
+
* `AccountDialogController.startPasskeyHubSignIn` opens a popup at the
|
|
7
|
+
* auth.oxy.so passkey hub, where the SAME ceremony IS a first-party operation.
|
|
8
|
+
*
|
|
9
|
+
* Kept in its own module (mirroring `oauthNavigation.ts`'s
|
|
10
|
+
* `redirectToAuthorize`) so `window.open` sits behind a plain function the
|
|
11
|
+
* controller wiring in `OxyContext` can inject without touching `window`
|
|
12
|
+
* itself, and so a test can substitute a fake opener.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { PopupWindowHandle } from '@oxyhq/core';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Open a new, EMPTY popup window. Must be called SYNCHRONOUSLY from within a
|
|
19
|
+
* user-gesture handler (before any `await`) — opening it after an async call
|
|
20
|
+
* loses the gesture attribution and the browser silently blocks it. Returns
|
|
21
|
+
* `null` when blocked, or when there is no DOM `window` (native/SSR — the
|
|
22
|
+
* hub-popup flow does not apply there).
|
|
23
|
+
*/
|
|
24
|
+
export function openPasskeyHubPopup(): PopupWindowHandle | null {
|
|
25
|
+
const win = (globalThis as { window?: Window }).window;
|
|
26
|
+
if (!win) return null;
|
|
27
|
+
return win.open('', '_blank', 'width=420,height=640');
|
|
28
|
+
}
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
notifyAccountDialogVisibility,
|
|
33
33
|
} from '../navigation/accountDialogManager';
|
|
34
34
|
import { redirectToAuthorize } from '../components/oauthNavigation';
|
|
35
|
+
import { openPasskeyHubPopup } from '../components/passkeyHubPopup';
|
|
35
36
|
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
36
37
|
import { runProviderColdBoot } from '../boot/runProviderColdBoot';
|
|
37
38
|
import { loadPersistedDeviceCredential } from '../utils/deviceCredential';
|
|
@@ -668,6 +669,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
668
669
|
// controller short-circuits, so `redirectToAuthorize` never runs for the
|
|
669
670
|
// `oxycommons://` payload.
|
|
670
671
|
canOpenApp: isWebBrowser() ? undefined : (url) => Linking.canOpenURL(url),
|
|
672
|
+
// Web-only: lets `startPasskeyHubSignIn` open the auth.oxy.so passkey hub
|
|
673
|
+
// popup (b2) for a non-Oxy origin. `undefined` on native — there is no
|
|
674
|
+
// popup concept there, and off-origin passkey sign-in isn't reachable
|
|
675
|
+
// (Commons owns the native flow).
|
|
676
|
+
openPopup: isWebBrowser() ? openPasskeyHubPopup : undefined,
|
|
671
677
|
});
|
|
672
678
|
}
|
|
673
679
|
const accountDialogController = accountDialogControllerRef.current;
|