@orangecheck/auth-client 2.17.2 → 2.18.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/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +206 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +198 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -27,6 +27,91 @@ function buildAddAccountUrl(cfg, returnTo) {
|
|
|
27
27
|
u.searchParams.set("add", "1");
|
|
28
28
|
return u.toString();
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
// src/tab-session.ts
|
|
32
|
+
var TAB_SESSION_HEADER = "x-oc-tab-session";
|
|
33
|
+
var TAB_SESSION_STORAGE_KEY = "oc_tab_session";
|
|
34
|
+
var TAB_ADOPT_HASH = "#oc-adopt";
|
|
35
|
+
function readTabSession() {
|
|
36
|
+
if (typeof window === "undefined") return null;
|
|
37
|
+
try {
|
|
38
|
+
const raw = window.sessionStorage.getItem(TAB_SESSION_STORAGE_KEY);
|
|
39
|
+
if (!raw) return null;
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
if (typeof parsed.token !== "string" || parsed.token.length === 0) return null;
|
|
42
|
+
if (typeof parsed.didOc !== "string" || parsed.didOc.length === 0) return null;
|
|
43
|
+
return { token: parsed.token, didOc: parsed.didOc };
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function writeTabSession(session) {
|
|
49
|
+
if (typeof window === "undefined") return;
|
|
50
|
+
try {
|
|
51
|
+
window.sessionStorage.setItem(TAB_SESSION_STORAGE_KEY, JSON.stringify(session));
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function clearTabSession() {
|
|
56
|
+
if (typeof window === "undefined") return;
|
|
57
|
+
try {
|
|
58
|
+
window.sessionStorage.removeItem(TAB_SESSION_STORAGE_KEY);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function tabSessionHeader() {
|
|
63
|
+
const pin = readTabSession();
|
|
64
|
+
return pin ? { [TAB_SESSION_HEADER]: pin.token } : {};
|
|
65
|
+
}
|
|
66
|
+
function isPinnableUrl(url, authOrigin) {
|
|
67
|
+
if (typeof window === "undefined") return false;
|
|
68
|
+
try {
|
|
69
|
+
const u = new URL(url, window.location.href);
|
|
70
|
+
return u.origin === window.location.origin || u.origin === authOrigin;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function installTabFetchInterceptor(authOrigin) {
|
|
76
|
+
if (typeof window === "undefined") return () => {
|
|
77
|
+
};
|
|
78
|
+
const original = window.fetch;
|
|
79
|
+
const wrapped = (input, init) => {
|
|
80
|
+
try {
|
|
81
|
+
const pin = readTabSession();
|
|
82
|
+
if (pin) {
|
|
83
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
84
|
+
if (isPinnableUrl(url, authOrigin)) {
|
|
85
|
+
const headers = new Headers(
|
|
86
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
87
|
+
);
|
|
88
|
+
if (!headers.has("authorization") && !headers.has(TAB_SESSION_HEADER)) {
|
|
89
|
+
headers.set(TAB_SESSION_HEADER, pin.token);
|
|
90
|
+
init = { ...init, headers };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
return original.call(window, input, init);
|
|
97
|
+
};
|
|
98
|
+
window.fetch = wrapped;
|
|
99
|
+
return () => {
|
|
100
|
+
if (window.fetch === wrapped) window.fetch = original;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function consumeTabAdoptMarker() {
|
|
104
|
+
if (typeof window === "undefined") return false;
|
|
105
|
+
if (!window.location.hash.includes(TAB_ADOPT_HASH.slice(1))) return false;
|
|
106
|
+
clearTabSession();
|
|
107
|
+
try {
|
|
108
|
+
const url = new URL(window.location.href);
|
|
109
|
+
url.hash = "";
|
|
110
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
30
115
|
var SessionContext = React3.createContext(null);
|
|
31
116
|
function normalizeDisplayIdentity(raw, didOc) {
|
|
32
117
|
const di = raw.display_identity ?? raw.displayIdentity;
|
|
@@ -53,7 +138,7 @@ async function fetchHostRoster(cfg) {
|
|
|
53
138
|
const res = await fetch(`${cfg.authOrigin}${cfg.mePath}`, {
|
|
54
139
|
method: "GET",
|
|
55
140
|
credentials: "include",
|
|
56
|
-
headers: { Accept: "application/json" }
|
|
141
|
+
headers: { Accept: "application/json", ...tabSessionHeader() }
|
|
57
142
|
});
|
|
58
143
|
if (!res.ok) return [];
|
|
59
144
|
const body = await res.json();
|
|
@@ -90,14 +175,50 @@ function OcSessionProvider({
|
|
|
90
175
|
const [roster, setRoster] = React3.useState([]);
|
|
91
176
|
const [status, setStatus] = React3.useState("loading");
|
|
92
177
|
const [error, setError] = React3.useState(null);
|
|
178
|
+
const [tabPinned, setTabPinned] = React3.useState(false);
|
|
179
|
+
const pinInFlightRef = React3.useRef(false);
|
|
180
|
+
const pinThisTab = React3.useCallback(
|
|
181
|
+
async (didOc) => {
|
|
182
|
+
if (pinInFlightRef.current || readTabSession()) return;
|
|
183
|
+
pinInFlightRef.current = true;
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetch(`${cfg.authOrigin}/api/auth/tab`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
credentials: "include",
|
|
188
|
+
headers: { Accept: "application/json" }
|
|
189
|
+
});
|
|
190
|
+
if (!res.ok) return;
|
|
191
|
+
const body = await res.json();
|
|
192
|
+
if (body.ok && typeof body.token === "string" && body.account?.did_oc === didOc) {
|
|
193
|
+
writeTabSession({ token: body.token, didOc });
|
|
194
|
+
setTabPinned(true);
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
} finally {
|
|
198
|
+
pinInFlightRef.current = false;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
[cfg.authOrigin]
|
|
202
|
+
);
|
|
93
203
|
const refresh = React3.useCallback(async () => {
|
|
94
204
|
if (typeof window === "undefined") return;
|
|
95
205
|
try {
|
|
96
|
-
|
|
206
|
+
let pin = readTabSession();
|
|
207
|
+
let res = await fetch(cfg.mePath, {
|
|
97
208
|
method: "GET",
|
|
98
209
|
credentials: "include",
|
|
99
|
-
headers: { Accept: "application/json" }
|
|
210
|
+
headers: { Accept: "application/json", ...tabSessionHeader() }
|
|
100
211
|
});
|
|
212
|
+
if (res.status === 401 && pin) {
|
|
213
|
+
clearTabSession();
|
|
214
|
+
pin = null;
|
|
215
|
+
setTabPinned(false);
|
|
216
|
+
res = await fetch(cfg.mePath, {
|
|
217
|
+
method: "GET",
|
|
218
|
+
credentials: "include",
|
|
219
|
+
headers: { Accept: "application/json" }
|
|
220
|
+
});
|
|
221
|
+
}
|
|
101
222
|
if (res.status === 401) {
|
|
102
223
|
setAccount(null);
|
|
103
224
|
setRoster([]);
|
|
@@ -112,11 +233,17 @@ function OcSessionProvider({
|
|
|
112
233
|
}
|
|
113
234
|
const body = await res.json();
|
|
114
235
|
const acct = normalizeAccount(body.account);
|
|
236
|
+
if (acct && pin && acct.didOc !== pin.didOc) {
|
|
237
|
+
clearTabSession();
|
|
238
|
+
pin = null;
|
|
239
|
+
}
|
|
240
|
+
setTabPinned(pin !== null);
|
|
115
241
|
const rosterEntries = Array.isArray(body.roster) ? body.roster.map(normalizeRosterEntry).filter((r) => r !== null) : [];
|
|
116
242
|
setAccount(acct);
|
|
117
243
|
setRoster(rosterEntries);
|
|
118
244
|
setStatus(acct ? "authenticated" : "anonymous");
|
|
119
245
|
setError(null);
|
|
246
|
+
if (acct && !pin) void pinThisTab(acct.didOc);
|
|
120
247
|
const currentOrigin = typeof window !== "undefined" ? window.location.origin : null;
|
|
121
248
|
if (acct && rosterEntries.length === 0 && currentOrigin !== null && !cfg.authOrigin.startsWith(currentOrigin)) {
|
|
122
249
|
const peers = await fetchHostRoster(cfg);
|
|
@@ -126,10 +253,12 @@ function OcSessionProvider({
|
|
|
126
253
|
setStatus("error");
|
|
127
254
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
128
255
|
}
|
|
129
|
-
}, [cfg]);
|
|
256
|
+
}, [cfg, pinThisTab]);
|
|
130
257
|
React3.useEffect(() => {
|
|
258
|
+
consumeTabAdoptMarker();
|
|
131
259
|
void refresh();
|
|
132
260
|
}, [refresh]);
|
|
261
|
+
React3.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
|
|
133
262
|
const signOut = React3.useCallback(
|
|
134
263
|
async (opts) => {
|
|
135
264
|
const scope = opts?.scope ?? "all";
|
|
@@ -139,6 +268,10 @@ function OcSessionProvider({
|
|
|
139
268
|
const res = await fetch(url.toString(), {
|
|
140
269
|
method: "POST",
|
|
141
270
|
credentials: "include",
|
|
271
|
+
// Per-tab · carry the pin so scope='current' signs out
|
|
272
|
+
// the account THIS tab is operating as, not whichever
|
|
273
|
+
// account the shared cookie happens to point at.
|
|
274
|
+
headers: { ...tabSessionHeader() },
|
|
142
275
|
// `keepalive` lets the logout round-trip complete even if
|
|
143
276
|
// the caller hard-navigates away in the same tick (e.g.
|
|
144
277
|
// `<OcAccountMenu>` redirects home immediately on sign-out).
|
|
@@ -146,11 +279,15 @@ function OcSessionProvider({
|
|
|
146
279
|
// and the `.ochk.io` cookie may never get cleared.
|
|
147
280
|
keepalive: true
|
|
148
281
|
});
|
|
282
|
+
clearTabSession();
|
|
283
|
+
setTabPinned(false);
|
|
149
284
|
if (scope === "current" && res.ok) {
|
|
150
285
|
await refresh();
|
|
151
286
|
return;
|
|
152
287
|
}
|
|
153
288
|
} catch {
|
|
289
|
+
clearTabSession();
|
|
290
|
+
setTabPinned(false);
|
|
154
291
|
}
|
|
155
292
|
setAccount(null);
|
|
156
293
|
setRoster([]);
|
|
@@ -164,7 +301,7 @@ function OcSessionProvider({
|
|
|
164
301
|
const res = await fetch(`${cfg.authOrigin}/api/auth/switch`, {
|
|
165
302
|
method: "POST",
|
|
166
303
|
credentials: "include",
|
|
167
|
-
headers: { "Content-Type": "application/json" },
|
|
304
|
+
headers: { "Content-Type": "application/json", ...tabSessionHeader() },
|
|
168
305
|
body: JSON.stringify({ did_oc: didOc })
|
|
169
306
|
});
|
|
170
307
|
if (!res.ok) {
|
|
@@ -176,6 +313,14 @@ function OcSessionProvider({
|
|
|
176
313
|
}
|
|
177
314
|
throw new Error(`[@orangecheck/auth-client] switchAccount failed: ${reason}`);
|
|
178
315
|
}
|
|
316
|
+
try {
|
|
317
|
+
const body = await res.json();
|
|
318
|
+
if (typeof body.token === "string" && body.account?.did_oc) {
|
|
319
|
+
writeTabSession({ token: body.token, didOc: body.account.did_oc });
|
|
320
|
+
setTabPinned(true);
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
179
324
|
await refresh();
|
|
180
325
|
},
|
|
181
326
|
[cfg.authOrigin, refresh]
|
|
@@ -193,7 +338,7 @@ function OcSessionProvider({
|
|
|
193
338
|
const res = await fetch(`${cfg.authOrigin}/api/auth/account`, {
|
|
194
339
|
method: "PATCH",
|
|
195
340
|
credentials: "include",
|
|
196
|
-
headers: { "Content-Type": "application/json" },
|
|
341
|
+
headers: { "Content-Type": "application/json", ...tabSessionHeader() },
|
|
197
342
|
body: JSON.stringify({ display_identity: kind })
|
|
198
343
|
});
|
|
199
344
|
if (!res.ok) {
|
|
@@ -205,6 +350,14 @@ function OcSessionProvider({
|
|
|
205
350
|
}
|
|
206
351
|
throw new Error(`[@orangecheck/auth-client] setDisplayIdentity failed: ${reason}`);
|
|
207
352
|
}
|
|
353
|
+
try {
|
|
354
|
+
const body = await res.json();
|
|
355
|
+
const pin = readTabSession();
|
|
356
|
+
if (pin && typeof body.token === "string" && body.account?.did_oc === pin.didOc) {
|
|
357
|
+
writeTabSession({ token: body.token, didOc: pin.didOc });
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
208
361
|
await refresh();
|
|
209
362
|
},
|
|
210
363
|
[cfg.authOrigin, refresh]
|
|
@@ -215,6 +368,7 @@ function OcSessionProvider({
|
|
|
215
368
|
status,
|
|
216
369
|
account,
|
|
217
370
|
roster,
|
|
371
|
+
tabPinned,
|
|
218
372
|
error,
|
|
219
373
|
refresh,
|
|
220
374
|
signOut,
|
|
@@ -227,6 +381,7 @@ function OcSessionProvider({
|
|
|
227
381
|
status,
|
|
228
382
|
account,
|
|
229
383
|
roster,
|
|
384
|
+
tabPinned,
|
|
230
385
|
error,
|
|
231
386
|
refresh,
|
|
232
387
|
signOut,
|
|
@@ -1752,6 +1907,18 @@ function safeReturnTo(input) {
|
|
|
1752
1907
|
if (!candidate.startsWith("/") || candidate.startsWith("//")) return "/";
|
|
1753
1908
|
return candidate;
|
|
1754
1909
|
}
|
|
1910
|
+
function familyReturnTarget(input) {
|
|
1911
|
+
if (typeof input !== "string" || input.length === 0) return void 0;
|
|
1912
|
+
if (input.startsWith("/") && !input.startsWith("//")) return input;
|
|
1913
|
+
try {
|
|
1914
|
+
const u = new URL(input);
|
|
1915
|
+
if (u.protocol !== "https:") return void 0;
|
|
1916
|
+
const host = u.hostname.toLowerCase();
|
|
1917
|
+
if (host === "ochk.io" || host.endsWith(".ochk.io")) return u.toString();
|
|
1918
|
+
} catch {
|
|
1919
|
+
}
|
|
1920
|
+
return void 0;
|
|
1921
|
+
}
|
|
1755
1922
|
function hardNavigate(target) {
|
|
1756
1923
|
if (typeof window === "undefined") return;
|
|
1757
1924
|
window.location.assign(target);
|
|
@@ -1770,7 +1937,20 @@ function OcSignIn({
|
|
|
1770
1937
|
}) {
|
|
1771
1938
|
const walletEnabled = paths?.wallet ?? true;
|
|
1772
1939
|
const emailEnabled = paths?.email ?? true;
|
|
1773
|
-
const
|
|
1940
|
+
const [resolvedReturn, setResolvedReturn] = React3.useState(
|
|
1941
|
+
() => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
|
|
1942
|
+
);
|
|
1943
|
+
React3.useEffect(() => {
|
|
1944
|
+
const fromProp = familyReturnTarget(returnTo);
|
|
1945
|
+
if (fromProp) {
|
|
1946
|
+
setResolvedReturn(fromProp);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
if (typeof window === "undefined") return;
|
|
1950
|
+
const q = new URLSearchParams(window.location.search);
|
|
1951
|
+
const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
|
|
1952
|
+
setResolvedReturn(fromQuery ?? "/");
|
|
1953
|
+
}, [returnTo]);
|
|
1774
1954
|
const [addMode, setAddMode] = React3.useState(Boolean(addProp));
|
|
1775
1955
|
React3.useEffect(() => {
|
|
1776
1956
|
if (typeof window === "undefined") return;
|
|
@@ -1794,18 +1974,20 @@ function OcSignIn({
|
|
|
1794
1974
|
async (account) => {
|
|
1795
1975
|
if (resolveReturnTo) {
|
|
1796
1976
|
try {
|
|
1797
|
-
|
|
1977
|
+
const resolved = familyReturnTarget(await resolveReturnTo(account));
|
|
1978
|
+
hardNavigate(resolved ?? resolvedReturn);
|
|
1798
1979
|
return;
|
|
1799
1980
|
} catch {
|
|
1800
1981
|
}
|
|
1801
1982
|
}
|
|
1802
|
-
hardNavigate(
|
|
1983
|
+
hardNavigate(resolvedReturn);
|
|
1803
1984
|
},
|
|
1804
|
-
[resolveReturnTo,
|
|
1985
|
+
[resolveReturnTo, resolvedReturn]
|
|
1805
1986
|
);
|
|
1806
1987
|
const handleSuccess = React3.useCallback(
|
|
1807
1988
|
async (account, token, via) => {
|
|
1808
1989
|
const proceed = () => {
|
|
1990
|
+
clearTabSession();
|
|
1809
1991
|
if (onSuccess) onSuccess(account, token);
|
|
1810
1992
|
else void navigate(account);
|
|
1811
1993
|
};
|
|
@@ -1970,7 +2152,7 @@ function OcSignIn({
|
|
|
1970
2152
|
}
|
|
1971
2153
|
)
|
|
1972
2154
|
] }),
|
|
1973
|
-
/* @__PURE__ */ jsx(ProviderSignIn, { authOrigin, returnTo:
|
|
2155
|
+
/* @__PURE__ */ jsx(ProviderSignIn, { authOrigin, returnTo: resolvedReturn, add: addMode }),
|
|
1974
2156
|
linkPrompt && /* @__PURE__ */ jsxs("label", { "data-oc-signin-linkalso": "", style: linkAlsoStyle, children: [
|
|
1975
2157
|
/* @__PURE__ */ jsx(
|
|
1976
2158
|
"input",
|
|
@@ -2040,7 +2222,8 @@ function ProviderIcon({ id }) {
|
|
|
2040
2222
|
}
|
|
2041
2223
|
function ProviderSignIn({
|
|
2042
2224
|
authOrigin,
|
|
2043
|
-
returnTo
|
|
2225
|
+
returnTo,
|
|
2226
|
+
add
|
|
2044
2227
|
}) {
|
|
2045
2228
|
const [providers, setProviders] = React3.useState([]);
|
|
2046
2229
|
const [origin, setOrigin] = React3.useState("");
|
|
@@ -2056,7 +2239,7 @@ function ProviderSignIn({
|
|
|
2056
2239
|
};
|
|
2057
2240
|
}, [authOrigin]);
|
|
2058
2241
|
if (providers.length === 0) return null;
|
|
2059
|
-
const providerReturnTo = origin ? `${origin}${returnTo}` : returnTo;
|
|
2242
|
+
const providerReturnTo = returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo;
|
|
2060
2243
|
const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
|
|
2061
2244
|
return /* @__PURE__ */ jsxs("div", { "data-oc-signin-providers": "", style: { marginTop: 20 }, children: [
|
|
2062
2245
|
/* @__PURE__ */ jsxs(
|
|
@@ -2085,7 +2268,7 @@ function ProviderSignIn({
|
|
|
2085
2268
|
{
|
|
2086
2269
|
href: `${authOrigin}/api/auth/${p.id}/start?return_to=${encodeURIComponent(
|
|
2087
2270
|
providerReturnTo
|
|
2088
|
-
)}`,
|
|
2271
|
+
)}${add ? "&add=1" : ""}`,
|
|
2089
2272
|
"data-oc-signin-provider": p.id,
|
|
2090
2273
|
style: {
|
|
2091
2274
|
display: "flex",
|
|
@@ -2766,6 +2949,6 @@ function handleSudoRequired(body, args = {}) {
|
|
|
2766
2949
|
return false;
|
|
2767
2950
|
}
|
|
2768
2951
|
|
|
2769
|
-
export { DEFAULT_CONFIG, DISPLAY_IDENTITY_KINDS, OcAccountChip, OcAccountPill, OcAddressInput, OcLinkedIdentities, OcSessionProvider, OcSignIn, OcSignInButton, buildAddAccountUrl, buildSignInUrl, fetchOcLinkedIdentities, handleSudoRequired, redirectToSudo, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister };
|
|
2952
|
+
export { DEFAULT_CONFIG, DISPLAY_IDENTITY_KINDS, OcAccountChip, OcAccountPill, OcAddressInput, OcLinkedIdentities, OcSessionProvider, OcSignIn, OcSignInButton, TAB_ADOPT_HASH, TAB_SESSION_HEADER, TAB_SESSION_STORAGE_KEY, buildAddAccountUrl, buildSignInUrl, clearTabSession, consumeTabAdoptMarker, fetchOcLinkedIdentities, handleSudoRequired, installTabFetchInterceptor, readTabSession, redirectToSudo, tabSessionHeader, useOcAddressSuggestion, useOcSession, useOptionalOcSession, useStepUpAuth, useWebAuthnList, useWebAuthnRegister, writeTabSession };
|
|
2770
2953
|
//# sourceMappingURL=index.mjs.map
|
|
2771
2954
|
//# sourceMappingURL=index.mjs.map
|