@biowiki/auth-sdk 0.2.0 → 0.3.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/CHANGELOG.md +12 -0
- package/README.md +81 -14
- package/dist/index.d.ts +13 -1
- package/dist/index.js +55 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Add `watchWebSession` for BFF-owned browser sessions, with visible-page polling,
|
|
6
|
+
focus checks, idempotent HTTP 401 handling and cleanup of listeners.
|
|
7
|
+
- Add `endWebSession` for same-origin BFF logout followed by a validated,
|
|
8
|
+
top-level Identity end-session navigation.
|
|
9
|
+
- Document Web single logout and its separation from native App sessions.
|
|
10
|
+
- Keep existing direct OAuth/PKCE client APIs compatible.
|
|
11
|
+
- Add tests for expired sessions, temporary outages, listener cleanup and trusted
|
|
12
|
+
logout redirects.
|
package/README.md
CHANGED
|
@@ -10,10 +10,83 @@ The package is published publicly to the npm registry at `https://registry.npmjs
|
|
|
10
10
|
Then install a pinned version:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install @biowiki/auth-sdk@0.
|
|
13
|
+
npm install @biowiki/auth-sdk@0.3.0
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Web BFF sessions (recommended for Tide / Collector / Dev)
|
|
17
|
+
|
|
18
|
+
Version 0.3.0 adds `watchWebSession` and `endWebSession` for applications whose
|
|
19
|
+
same-origin BFF owns the HttpOnly session. These helpers do not use browser
|
|
20
|
+
access-token storage or perform frontend authorization-code exchange. Existing
|
|
21
|
+
`BioWikiAuthClient` APIs remain available for older integrations.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { watchWebSession, endWebSession } from '@biowiki/auth-sdk';
|
|
25
|
+
|
|
26
|
+
const stopWatching = watchWebSession({
|
|
27
|
+
// Tide: '/auth/session'; Collector / Dev: '/proxy-api/auth/session'
|
|
28
|
+
sessionUrl: '/proxy-api/auth/session',
|
|
29
|
+
intervalMs: 15_000,
|
|
30
|
+
onSignedOut() {
|
|
31
|
+
clearCurrentUserAndPermissions();
|
|
32
|
+
showSignedOutPage(); // Idempotent; do not automatically start login again.
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Call from the application's logout button. Show a pending/disabled state.
|
|
37
|
+
async function logout() {
|
|
38
|
+
try {
|
|
39
|
+
await endWebSession('/proxy-api/auth/logout', 'https://api.biowiki.io', {
|
|
40
|
+
onSignedOut: clearCurrentUserAndPermissions,
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
clearCurrentUserAndPermissions();
|
|
44
|
+
showLogoutRetry(error); // Current BFF may have exited; unified logout may be incomplete.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Dispose on application/component teardown.
|
|
49
|
+
// e.g. Vue: onUnmounted(stopWatching);
|
|
50
|
+
// or call stopWatching() from your teardown hook.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Replace the example UI callbacks with your application store/router functions.
|
|
54
|
+
Do not call `stopWatching()` immediately after initialization in real code; call
|
|
55
|
+
it when the application or owning component is destroyed.
|
|
56
|
+
|
|
57
|
+
The watcher queries only a same-origin session endpoint. It checks visible pages
|
|
58
|
+
every 15 seconds and on focus/visibility changes. HTTP 401 triggers signed-out
|
|
59
|
+
handling once until a later successful check. Network errors and 5xx responses
|
|
60
|
+
are retried instead of clearing valid login state. Also handle 401 from protected
|
|
61
|
+
business requests in the application's shared request interceptor.
|
|
62
|
+
|
|
63
|
+
`endWebSession` posts to the same-origin BFF and, when the response supplies
|
|
64
|
+
`X-SSO-End-Session`, performs a top-level navigation to the trusted Identity
|
|
65
|
+
origin's `/oauth/end-session`. The server uses a short-lived signed ticket to
|
|
66
|
+
clear the matching SSO cookie and return to the product with `logged_out=1`.
|
|
67
|
+
Do not replace that navigation with fetch or an iframe. On a `logged_out=1`
|
|
68
|
+
landing page, do not automatically restart authorization.
|
|
69
|
+
|
|
70
|
+
For Tide, retain its session-version consistency header and clear UI state before
|
|
71
|
+
navigation:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
await endWebSession('/auth/logout', 'https://api.biowiki.io', {
|
|
75
|
+
headers: { 'X-Tide-Session': sessionVersion },
|
|
76
|
+
onSignedOut: clearCurrentUserAndPermissions,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`onSignedOut` runs after a successful BFF response and before Identity navigation.
|
|
81
|
+
For a failed response, handle the error and offer retry; the BFF may already have
|
|
82
|
+
cleared the local session. Both callbacks should be idempotent.
|
|
83
|
+
|
|
84
|
+
This requires the deployed Identity/BFF Web-sid logout implementation. Web
|
|
85
|
+
logout affects only the current browser's associated Web sessions; native App
|
|
86
|
+
sessions and other browser sessions remain independent. Use the BFF login URL
|
|
87
|
+
(`/auth/login` for Tide, `/proxy-api/auth/login` for Collector/Dev) for login.
|
|
88
|
+
|
|
89
|
+
## Use (existing direct OAuth client)
|
|
17
90
|
|
|
18
91
|
```ts
|
|
19
92
|
import { BioWikiAuthClient } from '@biowiki/auth-sdk';
|
|
@@ -38,18 +111,12 @@ In the callback route, call `completeLogin()` exactly once. For protected pages,
|
|
|
38
111
|
|
|
39
112
|
## Logout
|
|
40
113
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
to the registered
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
await fetch('/api/bff/auth/logout', { method: 'POST' });
|
|
49
|
-
} finally {
|
|
50
|
-
await auth.logout();
|
|
51
|
-
}
|
|
52
|
-
```
|
|
114
|
+
Call `logout()` after the application has revoked its current API session. The
|
|
115
|
+
SDK clears its local access token and performs a top-level navigation to the
|
|
116
|
+
Identity Service logout endpoint. Identity then clears the SSO cookies and
|
|
117
|
+
redirects only to the registered client's origin. This front-channel flow works
|
|
118
|
+
for local development as well as production without relying on third-party
|
|
119
|
+
cookies.
|
|
53
120
|
|
|
54
121
|
## Registration
|
|
55
122
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/** BFF session synchronization. No access/refresh token is stored in the browser. */
|
|
2
|
+
declare function watchWebSession(options: {
|
|
3
|
+
sessionUrl: string;
|
|
4
|
+
onSignedOut: () => void;
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
}): () => void;
|
|
7
|
+
/** Completes browser SSO cookie cleanup after the BFF has revoked its web sid. */
|
|
8
|
+
declare function endWebSession(logoutUrl: string, identityOrigin: string, options?: {
|
|
9
|
+
headers?: HeadersInit;
|
|
10
|
+
onSignedOut?: () => void;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
|
|
1
13
|
interface AuthSdkConfig {
|
|
2
14
|
authUrl: string;
|
|
3
15
|
clientId: string;
|
|
@@ -69,4 +81,4 @@ declare class BioWikiAuthClient {
|
|
|
69
81
|
private authRequest;
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
export { type AuthSdkConfig, BioWikiAuthClient, type LoginOptions, type PopupCallbackMessage, type RegistrationCodeInput, type RegistrationInput, type RegistrationResult, type TokenSet, relayPopupCallback };
|
|
84
|
+
export { type AuthSdkConfig, BioWikiAuthClient, type LoginOptions, type PopupCallbackMessage, type RegistrationCodeInput, type RegistrationInput, type RegistrationResult, type TokenSet, endWebSession, relayPopupCallback, watchWebSession };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
// src/web-session.ts
|
|
2
|
+
function watchWebSession(options) {
|
|
3
|
+
let authenticated;
|
|
4
|
+
let pending = false;
|
|
5
|
+
let stopped = false;
|
|
6
|
+
const sessionUrl = new URL(options.sessionUrl, window.location.origin);
|
|
7
|
+
if (sessionUrl.origin !== window.location.origin) throw new Error("Session endpoint must be same-origin");
|
|
8
|
+
const check = async () => {
|
|
9
|
+
if (pending || stopped || document.visibilityState === "hidden") return;
|
|
10
|
+
pending = true;
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(sessionUrl, { credentials: "same-origin", cache: "no-store" });
|
|
13
|
+
if (stopped) return;
|
|
14
|
+
if (response.ok) authenticated = true;
|
|
15
|
+
else if (response.status === 401 && authenticated !== false) {
|
|
16
|
+
authenticated = false;
|
|
17
|
+
options.onSignedOut();
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
} finally {
|
|
21
|
+
pending = false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const wake = () => {
|
|
25
|
+
void check();
|
|
26
|
+
};
|
|
27
|
+
const timer = window.setInterval(wake, options.intervalMs ?? 15e3);
|
|
28
|
+
window.addEventListener("focus", wake);
|
|
29
|
+
document.addEventListener("visibilitychange", wake);
|
|
30
|
+
wake();
|
|
31
|
+
return () => {
|
|
32
|
+
stopped = true;
|
|
33
|
+
clearInterval(timer);
|
|
34
|
+
window.removeEventListener("focus", wake);
|
|
35
|
+
document.removeEventListener("visibilitychange", wake);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function endWebSession(logoutUrl, identityOrigin, options = {}) {
|
|
39
|
+
const endpoint = new URL(logoutUrl, window.location.origin);
|
|
40
|
+
if (endpoint.origin !== window.location.origin) throw new Error("Logout endpoint must be same-origin");
|
|
41
|
+
const response = await fetch(endpoint, { method: "POST", credentials: "same-origin", cache: "no-store", headers: options.headers });
|
|
42
|
+
if (!response.ok) throw new Error("Unified logout is incomplete. Please retry.");
|
|
43
|
+
options.onSignedOut?.();
|
|
44
|
+
const next = response.headers.get("X-SSO-End-Session");
|
|
45
|
+
if (!next) return;
|
|
46
|
+
const url = new URL(next);
|
|
47
|
+
if (url.origin !== new URL(identityOrigin).origin || url.pathname !== "/oauth/end-session") {
|
|
48
|
+
throw new Error("Unexpected logout redirect");
|
|
49
|
+
}
|
|
50
|
+
window.location.assign(url.toString());
|
|
51
|
+
}
|
|
52
|
+
|
|
1
53
|
// src/index.ts
|
|
2
54
|
function relayPopupCallback(url = window.location.href) {
|
|
3
55
|
if (!window.opener || window.opener === window) return false;
|
|
@@ -399,6 +451,8 @@ var BioWikiAuthClient = class {
|
|
|
399
451
|
};
|
|
400
452
|
export {
|
|
401
453
|
BioWikiAuthClient,
|
|
402
|
-
|
|
454
|
+
endWebSession,
|
|
455
|
+
relayPopupCallback,
|
|
456
|
+
watchWebSession
|
|
403
457
|
};
|
|
404
458
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface AuthSdkConfig {\n authUrl: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n}\n\nexport interface LoginOptions {\n mode?: 'redirect' | 'popup';\n returnTo?: string;\n}\n\nexport interface PopupCallbackMessage {\n type: 'biowiki.oauth.callback';\n code?: string;\n state?: string;\n error?: string;\n errorDescription?: string;\n}\n\n/** Relays an OAuth callback from a popup to the window that opened it. */\nexport function relayPopupCallback(url = window.location.href) {\n if (!window.opener || window.opener === window) return false;\n const callback = new URL(url);\n const state = callback.searchParams.get('state');\n const marker = `biowiki.auth-sdk:popup:${state}`;\n if (!state || sessionStorage.getItem(marker) !== '1') return false;\n sessionStorage.removeItem(marker);\n const payload: PopupCallbackMessage = {\n type: 'biowiki.oauth.callback',\n code: callback.searchParams.get('code') ?? undefined,\n state: callback.searchParams.get('state') ?? undefined,\n error: callback.searchParams.get('error') ?? undefined,\n errorDescription: callback.searchParams.get('error_description') ?? undefined\n };\n window.opener.postMessage(payload, window.location.origin);\n // The opener closes this window after receiving the message, avoiding a\n // race between delivery and its popup-closed polling timer.\n return true;\n}\n\nexport interface TokenSet {\n accessToken: string;\n expiresIn: number;\n}\n\nexport interface RegistrationInput {\n email: string;\n password: string;\n verificationCode: string;\n}\n\nexport interface RegistrationResult<TUser = unknown> {\n user: TUser;\n registered: true;\n}\n\nexport interface RegistrationCodeInput {\n email: string;\n botToken?: string;\n}\n\nconst storagePrefix = 'biowiki.auth-sdk';\n\nfunction base64Url(bytes: Uint8Array) {\n return btoa(String.fromCharCode(...bytes))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replaceAll('=', '');\n}\n\nfunction randomValue(bytes = 32) {\n const value = new Uint8Array(bytes);\n crypto.getRandomValues(value);\n return base64Url(value);\n}\n\nasync function challenge(verifier: string) {\n const input = new TextEncoder().encode(verifier);\n if (crypto.subtle) {\n const digest = await crypto.subtle.digest('SHA-256', input);\n return base64Url(new Uint8Array(digest));\n }\n // Browsers do not expose SubtleCrypto for HTTP pages served from a LAN IP.\n return base64Url(sha256(input));\n}\n\nfunction sha256(input: Uint8Array) {\n const bitLength = input.length * 8;\n const paddedLength = Math.ceil((input.length + 9) / 64) * 64;\n const bytes = new Uint8Array(paddedLength);\n bytes.set(input);\n bytes[input.length] = 0x80;\n\n let length = bitLength;\n for (let index = 0; index < 8; index += 1) {\n bytes[paddedLength - 1 - index] = length & 0xff;\n length = Math.floor(length / 256);\n }\n\n const words = new Uint32Array(64);\n const hash = new Uint32Array([\n 0x6a09e667,\n 0xbb67ae85,\n 0x3c6ef372,\n 0xa54ff53a,\n 0x510e527f,\n 0x9b05688c,\n 0x1f83d9ab,\n 0x5be0cd19\n ]);\n\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let index = 0; index < 16; index += 1) {\n const position = offset + index * 4;\n words[index] =\n (bytes[position] << 24) |\n (bytes[position + 1] << 16) |\n (bytes[position + 2] << 8) |\n bytes[position + 3];\n }\n for (let index = 16; index < 64; index += 1) {\n const a = words[index - 15];\n const b = words[index - 2];\n const sigma0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);\n const sigma1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);\n words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;\n }\n\n let [a, b, c, d, e, f, g, h] = hash;\n for (let index = 0; index < 64; index += 1) {\n const sigma1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));\n const choice = (e & f) ^ (~e & g);\n const temp1 = (h + sigma1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0;\n const sigma0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const temp2 = (sigma0 + majority) >>> 0;\n [h, g, f, e, d, c, b, a] = [g, f, e, (d + temp1) >>> 0, c, b, a, (temp1 + temp2) >>> 0];\n }\n\n hash[0] = (hash[0] + a) >>> 0;\n hash[1] = (hash[1] + b) >>> 0;\n hash[2] = (hash[2] + c) >>> 0;\n hash[3] = (hash[3] + d) >>> 0;\n hash[4] = (hash[4] + e) >>> 0;\n hash[5] = (hash[5] + f) >>> 0;\n hash[6] = (hash[6] + g) >>> 0;\n hash[7] = (hash[7] + h) >>> 0;\n }\n\n const digest = new Uint8Array(32);\n for (let index = 0; index < hash.length; index += 1) {\n digest[index * 4] = hash[index] >>> 24;\n digest[index * 4 + 1] = hash[index] >>> 16;\n digest[index * 4 + 2] = hash[index] >>> 8;\n digest[index * 4 + 3] = hash[index];\n }\n return digest;\n}\n\nconst SHA256_CONSTANTS = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\nexport class BioWikiAuthClient {\n constructor(private readonly config: AuthSdkConfig) {}\n\n private key(name: string) {\n return `${storagePrefix}:${this.config.clientId}:${name}`;\n }\n\n getToken() {\n return sessionStorage.getItem(this.key('access-token')) ?? '';\n }\n\n getDeviceId() {\n const key = this.key('device-id');\n const existing = localStorage.getItem(key);\n if (existing) return existing;\n const deviceId = `web-${randomValue(24)}`;\n localStorage.setItem(key, deviceId);\n return deviceId;\n }\n\n isAuthenticated() {\n return Boolean(this.getToken());\n }\n\n async requestRegistrationCode({ email, botToken }: RegistrationCodeInput) {\n return this.authRequest('/api/v1/auth/email/send-code', {\n email,\n purpose: 'register',\n ...(botToken ? { bot_token: botToken } : {})\n });\n }\n\n async register<TUser = unknown>(input: RegistrationInput): Promise<RegistrationResult<TUser>> {\n return this.authRequest<RegistrationResult<TUser>>('/api/v1/auth/register', {\n email: input.email,\n password: input.password,\n verification_code: input.verificationCode\n });\n }\n\n async login(options: LoginOptions | string = '/'): Promise<{ returnTo: string; token: TokenSet } | void> {\n const normalized = typeof options === 'string' ? { returnTo: options, mode: 'redirect' as const } : options;\n if (normalized.mode === 'popup') return this.loginWithPopup(normalized.returnTo ?? '/');\n await this.startAuthorization(normalized.returnTo ?? '/');\n }\n\n async beginRegistration(options: LoginOptions | string = '/'): Promise<{ returnTo: string; token: TokenSet } | void> {\n const normalized = typeof options === 'string' ? { returnTo: options, mode: 'redirect' as const } : options;\n if (normalized.mode === 'popup') return this.loginWithPopup(normalized.returnTo ?? '/', 'signup');\n await this.startAuthorization(normalized.returnTo ?? '/', 'signup');\n }\n\n private async loginWithPopup(returnTo: string, screenHint?: 'signup') {\n if (new URL(this.config.redirectUri).origin !== window.location.origin) {\n throw new Error('Popup redirect URI must use the current application origin.');\n }\n const popup = window.open('about:blank', `biowiki-login-${randomValue(12)}`, 'popup,width=520,height=720,resizable=yes,scrollbars=yes');\n let authorizationUrl: string;\n try {\n authorizationUrl = await this.prepareAuthorization(returnTo, screenHint);\n } catch (error) {\n popup?.close();\n throw error;\n }\n if (!popup) {\n window.location.assign(authorizationUrl);\n return;\n }\n const expectedState = new URL(authorizationUrl).searchParams.get('state')!;\n return new Promise<{ returnTo: string; token: TokenSet }>((resolve, reject) => {\n const expectedOrigin = new URL(this.config.redirectUri).origin;\n let timer: ReturnType<typeof setInterval> | undefined;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n if (timer) clearInterval(timer);\n if (timeout) clearTimeout(timeout);\n };\n const fail = (error: Error) => {\n cleanup();\n sessionStorage.removeItem(this.key(`transaction:${expectedState}`));\n if (!popup.closed) popup.close();\n reject(error);\n };\n const onMessage = async (event: MessageEvent<PopupCallbackMessage>) => {\n if (event.origin !== expectedOrigin || event.source !== popup || event.data?.type !== 'biowiki.oauth.callback') return;\n if (event.data.state !== expectedState) return;\n cleanup();\n if (!popup.closed) popup.close();\n if (event.data.error) {\n if (!popup.closed) popup.close();\n sessionStorage.removeItem(this.key(`transaction:${expectedState}`));\n reject(new Error(event.data.errorDescription || event.data.error));\n return;\n }\n try {\n const callback = new URL(this.config.redirectUri);\n if (!event.data.code || !event.data.state) throw new Error('Invalid OAuth popup callback.');\n callback.searchParams.set('code', event.data.code);\n callback.searchParams.set('state', event.data.state);\n const result = await this.completeLogin(callback.toString());\n if (!popup.closed) popup.close();\n resolve(result);\n } catch (error) {\n if (!popup.closed) popup.close();\n reject(error instanceof Error ? error : new Error('OAuth popup login failed.'));\n }\n };\n window.addEventListener('message', onMessage);\n timer = setInterval(() => {\n if (popup.closed) fail(new Error('Login popup was closed.'));\n }, 500);\n timeout = setTimeout(() => fail(new Error('Login popup timed out.')), 120_000);\n try {\n // about:blank inherits the opener origin. This marker remains local to\n // the popup's tab storage when it returns to the application callback.\n popup.sessionStorage.setItem(`biowiki.auth-sdk:popup:${expectedState}`, '1');\n popup.location.assign(authorizationUrl);\n } catch (error) {\n fail(error instanceof Error ? error : new Error('Unable to open login popup.'));\n }\n });\n }\n\n private async startAuthorization(returnTo: string, screenHint?: 'signup') {\n window.location.assign(await this.prepareAuthorization(returnTo, screenHint));\n }\n\n private async prepareAuthorization(returnTo: string, screenHint?: 'signup') {\n const state = randomValue();\n const verifier = randomValue(48);\n sessionStorage.setItem(this.key(`transaction:${state}`), JSON.stringify({ verifier, returnTo, createdAt: Date.now() }));\n const authorize = new URL('/oauth/authorize', this.config.authUrl);\n authorize.search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n scope: this.config.scope ?? 'openid profile email',\n state,\n code_challenge: await challenge(verifier),\n code_challenge_method: 'S256'\n }).toString();\n if (screenHint) authorize.searchParams.set('screen_hint', screenHint);\n return authorize.toString();\n }\n\n async completeLogin(url = window.location.href): Promise<{ returnTo: string; token: TokenSet }> {\n const callback = new URL(url);\n const code = callback.searchParams.get('code');\n const state = callback.searchParams.get('state');\n const transactionKey = this.key(`transaction:${state}`);\n const rawTransaction = sessionStorage.getItem(transactionKey);\n const transaction = rawTransaction ? JSON.parse(rawTransaction) : null;\n if (!code || !state || !transaction?.verifier || Date.now() - transaction.createdAt > 10 * 60 * 1000) {\n throw new Error('Invalid OAuth callback state.');\n }\n sessionStorage.removeItem(transactionKey);\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n signal: AbortSignal.timeout(15000),\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n code_verifier: transaction.verifier\n })\n });\n if (!response.ok) throw new Error('OAuth authorization code exchange failed.');\n const payload = await response.json();\n if (typeof payload.access_token !== 'string' || !payload.access_token) {\n throw new Error('OAuth response did not contain an access token.');\n }\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n const candidate = transaction.returnTo;\n const returnTo = typeof candidate === 'string' && candidate.startsWith('/') && !candidate.startsWith('//') && !candidate.includes('\\\\') ? candidate : '/';\n return { returnTo, token };\n }\n\n async refresh(): Promise<TokenSet> {\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({ grant_type: 'refresh_token', client_id: this.config.clientId })\n });\n if (!response.ok) throw new Error('OAuth refresh failed.');\n const payload = await response.json();\n if (typeof payload.access_token !== 'string' || !payload.access_token) {\n throw new Error('OAuth response did not contain an access token.');\n }\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n return token;\n }\n\n async getUser<T>() {\n const token = this.getToken() || (await this.refresh()).accessToken;\n const response = await fetch(new URL('/oauth/userinfo', this.config.authUrl), {\n headers: { authorization: `Bearer ${token}` },\n credentials: 'include'\n });\n if (!response.ok) throw new Error('OAuth userinfo request failed.');\n return response.json() as Promise<T>;\n }\n\n async getSession<TUser = unknown>(): Promise<TUser | null> {\n try {\n return await this.getUser<TUser>();\n } catch {\n return null;\n }\n }\n\n async requireAuth(returnTo = window.location.pathname + window.location.search) {\n if (this.isAuthenticated()) return true;\n await this.login(returnTo);\n return false;\n }\n\n async logout(returnTo = `${window.location.origin}/`) {\n const logout = new URL('/oauth/logout', this.config.authUrl);\n logout.search = new URLSearchParams({\n client_id: this.config.clientId,\n post_logout_redirect_uri: returnTo\n }).toString();\n sessionStorage.removeItem(this.key('access-token'));\n window.location.assign(logout.toString());\n }\n\n private async authRequest<T = { success: true; message: string }>(path: string, body: Record<string, string>) {\n const response = await fetch(new URL(path, this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body)\n });\n const payload = await response.json().catch(() => null);\n if (!response.ok) {\n throw new Error(payload?.error?.message ?? 'Identity registration request failed.');\n }\n return (payload?.data ?? payload) as T;\n }\n}\n"],"mappings":"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","names":["a","b"]}
|
|
1
|
+
{"version":3,"sources":["../src/web-session.ts","../src/index.ts"],"sourcesContent":["/** BFF session synchronization. No access/refresh token is stored in the browser. */\nexport function watchWebSession(options: {\n sessionUrl: string;\n onSignedOut: () => void;\n intervalMs?: number;\n}) {\n let authenticated: boolean | undefined;\n let pending = false;\n let stopped = false;\n const sessionUrl = new URL(options.sessionUrl, window.location.origin);\n if (sessionUrl.origin !== window.location.origin) throw new Error('Session endpoint must be same-origin');\n const check = async () => {\n if (pending || stopped || document.visibilityState === 'hidden') return;\n pending = true;\n try {\n const response = await fetch(sessionUrl, { credentials: 'same-origin', cache: 'no-store' });\n if (stopped) return;\n if (response.ok) authenticated = true;\n else if (response.status === 401 && authenticated !== false) {\n authenticated = false;\n options.onSignedOut();\n }\n // Temporary network/service failures must not be treated as logout.\n } catch { /* Retry on next focus or interval. */ }\n finally { pending = false; }\n };\n const wake = () => { void check(); };\n const timer = window.setInterval(wake, options.intervalMs ?? 15000);\n window.addEventListener('focus', wake);\n document.addEventListener('visibilitychange', wake);\n wake();\n return () => {\n stopped = true;\n clearInterval(timer);\n window.removeEventListener('focus', wake);\n document.removeEventListener('visibilitychange', wake);\n };\n}\n\n/** Completes browser SSO cookie cleanup after the BFF has revoked its web sid. */\nexport async function endWebSession(\n logoutUrl: string,\n identityOrigin: string,\n options: { headers?: HeadersInit; onSignedOut?: () => void } = {},\n) {\n const endpoint = new URL(logoutUrl, window.location.origin);\n if (endpoint.origin !== window.location.origin) throw new Error('Logout endpoint must be same-origin');\n const response = await fetch(endpoint, { method: 'POST', credentials: 'same-origin', cache: 'no-store', headers: options.headers });\n if (!response.ok) throw new Error('Unified logout is incomplete. Please retry.');\n options.onSignedOut?.();\n const next = response.headers.get('X-SSO-End-Session');\n if (!next) return;\n const url = new URL(next);\n if (url.origin !== new URL(identityOrigin).origin || url.pathname !== '/oauth/end-session') {\n throw new Error('Unexpected logout redirect');\n }\n window.location.assign(url.toString());\n}\n","export interface AuthSdkConfig {\n authUrl: string;\n clientId: string;\n redirectUri: string;\n scope?: string;\n}\n\nexport interface LoginOptions {\n mode?: 'redirect' | 'popup';\n returnTo?: string;\n}\n\nexport interface PopupCallbackMessage {\n type: 'biowiki.oauth.callback';\n code?: string;\n state?: string;\n error?: string;\n errorDescription?: string;\n}\n\n/** Relays an OAuth callback from a popup to the window that opened it. */\nexport function relayPopupCallback(url = window.location.href) {\n if (!window.opener || window.opener === window) return false;\n const callback = new URL(url);\n const state = callback.searchParams.get('state');\n const marker = `biowiki.auth-sdk:popup:${state}`;\n if (!state || sessionStorage.getItem(marker) !== '1') return false;\n sessionStorage.removeItem(marker);\n const payload: PopupCallbackMessage = {\n type: 'biowiki.oauth.callback',\n code: callback.searchParams.get('code') ?? undefined,\n state: callback.searchParams.get('state') ?? undefined,\n error: callback.searchParams.get('error') ?? undefined,\n errorDescription: callback.searchParams.get('error_description') ?? undefined\n };\n window.opener.postMessage(payload, window.location.origin);\n // The opener closes this window after receiving the message, avoiding a\n // race between delivery and its popup-closed polling timer.\n return true;\n}\n\nexport interface TokenSet {\n accessToken: string;\n expiresIn: number;\n}\n\nexport interface RegistrationInput {\n email: string;\n password: string;\n verificationCode: string;\n}\n\nexport interface RegistrationResult<TUser = unknown> {\n user: TUser;\n registered: true;\n}\n\nexport interface RegistrationCodeInput {\n email: string;\n botToken?: string;\n}\n\nconst storagePrefix = 'biowiki.auth-sdk';\n\nfunction base64Url(bytes: Uint8Array) {\n return btoa(String.fromCharCode(...bytes))\n .replaceAll('+', '-')\n .replaceAll('/', '_')\n .replaceAll('=', '');\n}\n\nfunction randomValue(bytes = 32) {\n const value = new Uint8Array(bytes);\n crypto.getRandomValues(value);\n return base64Url(value);\n}\n\nasync function challenge(verifier: string) {\n const input = new TextEncoder().encode(verifier);\n if (crypto.subtle) {\n const digest = await crypto.subtle.digest('SHA-256', input);\n return base64Url(new Uint8Array(digest));\n }\n // Browsers do not expose SubtleCrypto for HTTP pages served from a LAN IP.\n return base64Url(sha256(input));\n}\n\nfunction sha256(input: Uint8Array) {\n const bitLength = input.length * 8;\n const paddedLength = Math.ceil((input.length + 9) / 64) * 64;\n const bytes = new Uint8Array(paddedLength);\n bytes.set(input);\n bytes[input.length] = 0x80;\n\n let length = bitLength;\n for (let index = 0; index < 8; index += 1) {\n bytes[paddedLength - 1 - index] = length & 0xff;\n length = Math.floor(length / 256);\n }\n\n const words = new Uint32Array(64);\n const hash = new Uint32Array([\n 0x6a09e667,\n 0xbb67ae85,\n 0x3c6ef372,\n 0xa54ff53a,\n 0x510e527f,\n 0x9b05688c,\n 0x1f83d9ab,\n 0x5be0cd19\n ]);\n\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let index = 0; index < 16; index += 1) {\n const position = offset + index * 4;\n words[index] =\n (bytes[position] << 24) |\n (bytes[position + 1] << 16) |\n (bytes[position + 2] << 8) |\n bytes[position + 3];\n }\n for (let index = 16; index < 64; index += 1) {\n const a = words[index - 15];\n const b = words[index - 2];\n const sigma0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);\n const sigma1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);\n words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;\n }\n\n let [a, b, c, d, e, f, g, h] = hash;\n for (let index = 0; index < 64; index += 1) {\n const sigma1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));\n const choice = (e & f) ^ (~e & g);\n const temp1 = (h + sigma1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0;\n const sigma0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const temp2 = (sigma0 + majority) >>> 0;\n [h, g, f, e, d, c, b, a] = [g, f, e, (d + temp1) >>> 0, c, b, a, (temp1 + temp2) >>> 0];\n }\n\n hash[0] = (hash[0] + a) >>> 0;\n hash[1] = (hash[1] + b) >>> 0;\n hash[2] = (hash[2] + c) >>> 0;\n hash[3] = (hash[3] + d) >>> 0;\n hash[4] = (hash[4] + e) >>> 0;\n hash[5] = (hash[5] + f) >>> 0;\n hash[6] = (hash[6] + g) >>> 0;\n hash[7] = (hash[7] + h) >>> 0;\n }\n\n const digest = new Uint8Array(32);\n for (let index = 0; index < hash.length; index += 1) {\n digest[index * 4] = hash[index] >>> 24;\n digest[index * 4 + 1] = hash[index] >>> 16;\n digest[index * 4 + 2] = hash[index] >>> 8;\n digest[index * 4 + 3] = hash[index];\n }\n return digest;\n}\n\nconst SHA256_CONSTANTS = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n]);\n\nexport class BioWikiAuthClient {\n constructor(private readonly config: AuthSdkConfig) {}\n\n private key(name: string) {\n return `${storagePrefix}:${this.config.clientId}:${name}`;\n }\n\n getToken() {\n return sessionStorage.getItem(this.key('access-token')) ?? '';\n }\n\n getDeviceId() {\n const key = this.key('device-id');\n const existing = localStorage.getItem(key);\n if (existing) return existing;\n const deviceId = `web-${randomValue(24)}`;\n localStorage.setItem(key, deviceId);\n return deviceId;\n }\n\n isAuthenticated() {\n return Boolean(this.getToken());\n }\n\n async requestRegistrationCode({ email, botToken }: RegistrationCodeInput) {\n return this.authRequest('/api/v1/auth/email/send-code', {\n email,\n purpose: 'register',\n ...(botToken ? { bot_token: botToken } : {})\n });\n }\n\n async register<TUser = unknown>(input: RegistrationInput): Promise<RegistrationResult<TUser>> {\n return this.authRequest<RegistrationResult<TUser>>('/api/v1/auth/register', {\n email: input.email,\n password: input.password,\n verification_code: input.verificationCode\n });\n }\n\n async login(options: LoginOptions | string = '/'): Promise<{ returnTo: string; token: TokenSet } | void> {\n const normalized = typeof options === 'string' ? { returnTo: options, mode: 'redirect' as const } : options;\n if (normalized.mode === 'popup') return this.loginWithPopup(normalized.returnTo ?? '/');\n await this.startAuthorization(normalized.returnTo ?? '/');\n }\n\n async beginRegistration(options: LoginOptions | string = '/'): Promise<{ returnTo: string; token: TokenSet } | void> {\n const normalized = typeof options === 'string' ? { returnTo: options, mode: 'redirect' as const } : options;\n if (normalized.mode === 'popup') return this.loginWithPopup(normalized.returnTo ?? '/', 'signup');\n await this.startAuthorization(normalized.returnTo ?? '/', 'signup');\n }\n\n private async loginWithPopup(returnTo: string, screenHint?: 'signup') {\n if (new URL(this.config.redirectUri).origin !== window.location.origin) {\n throw new Error('Popup redirect URI must use the current application origin.');\n }\n const popup = window.open('about:blank', `biowiki-login-${randomValue(12)}`, 'popup,width=520,height=720,resizable=yes,scrollbars=yes');\n let authorizationUrl: string;\n try {\n authorizationUrl = await this.prepareAuthorization(returnTo, screenHint);\n } catch (error) {\n popup?.close();\n throw error;\n }\n if (!popup) {\n window.location.assign(authorizationUrl);\n return;\n }\n const expectedState = new URL(authorizationUrl).searchParams.get('state')!;\n return new Promise<{ returnTo: string; token: TokenSet }>((resolve, reject) => {\n const expectedOrigin = new URL(this.config.redirectUri).origin;\n let timer: ReturnType<typeof setInterval> | undefined;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n if (timer) clearInterval(timer);\n if (timeout) clearTimeout(timeout);\n };\n const fail = (error: Error) => {\n cleanup();\n sessionStorage.removeItem(this.key(`transaction:${expectedState}`));\n if (!popup.closed) popup.close();\n reject(error);\n };\n const onMessage = async (event: MessageEvent<PopupCallbackMessage>) => {\n if (event.origin !== expectedOrigin || event.source !== popup || event.data?.type !== 'biowiki.oauth.callback') return;\n if (event.data.state !== expectedState) return;\n cleanup();\n if (!popup.closed) popup.close();\n if (event.data.error) {\n if (!popup.closed) popup.close();\n sessionStorage.removeItem(this.key(`transaction:${expectedState}`));\n reject(new Error(event.data.errorDescription || event.data.error));\n return;\n }\n try {\n const callback = new URL(this.config.redirectUri);\n if (!event.data.code || !event.data.state) throw new Error('Invalid OAuth popup callback.');\n callback.searchParams.set('code', event.data.code);\n callback.searchParams.set('state', event.data.state);\n const result = await this.completeLogin(callback.toString());\n if (!popup.closed) popup.close();\n resolve(result);\n } catch (error) {\n if (!popup.closed) popup.close();\n reject(error instanceof Error ? error : new Error('OAuth popup login failed.'));\n }\n };\n window.addEventListener('message', onMessage);\n timer = setInterval(() => {\n if (popup.closed) fail(new Error('Login popup was closed.'));\n }, 500);\n timeout = setTimeout(() => fail(new Error('Login popup timed out.')), 120_000);\n try {\n // about:blank inherits the opener origin. This marker remains local to\n // the popup's tab storage when it returns to the application callback.\n popup.sessionStorage.setItem(`biowiki.auth-sdk:popup:${expectedState}`, '1');\n popup.location.assign(authorizationUrl);\n } catch (error) {\n fail(error instanceof Error ? error : new Error('Unable to open login popup.'));\n }\n });\n }\n\n private async startAuthorization(returnTo: string, screenHint?: 'signup') {\n window.location.assign(await this.prepareAuthorization(returnTo, screenHint));\n }\n\n private async prepareAuthorization(returnTo: string, screenHint?: 'signup') {\n const state = randomValue();\n const verifier = randomValue(48);\n sessionStorage.setItem(this.key(`transaction:${state}`), JSON.stringify({ verifier, returnTo, createdAt: Date.now() }));\n const authorize = new URL('/oauth/authorize', this.config.authUrl);\n authorize.search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n scope: this.config.scope ?? 'openid profile email',\n state,\n code_challenge: await challenge(verifier),\n code_challenge_method: 'S256'\n }).toString();\n if (screenHint) authorize.searchParams.set('screen_hint', screenHint);\n return authorize.toString();\n }\n\n async completeLogin(url = window.location.href): Promise<{ returnTo: string; token: TokenSet }> {\n const callback = new URL(url);\n const code = callback.searchParams.get('code');\n const state = callback.searchParams.get('state');\n const transactionKey = this.key(`transaction:${state}`);\n const rawTransaction = sessionStorage.getItem(transactionKey);\n const transaction = rawTransaction ? JSON.parse(rawTransaction) : null;\n if (!code || !state || !transaction?.verifier || Date.now() - transaction.createdAt > 10 * 60 * 1000) {\n throw new Error('Invalid OAuth callback state.');\n }\n sessionStorage.removeItem(transactionKey);\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n signal: AbortSignal.timeout(15000),\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n code_verifier: transaction.verifier\n })\n });\n if (!response.ok) throw new Error('OAuth authorization code exchange failed.');\n const payload = await response.json();\n if (typeof payload.access_token !== 'string' || !payload.access_token) {\n throw new Error('OAuth response did not contain an access token.');\n }\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n const candidate = transaction.returnTo;\n const returnTo = typeof candidate === 'string' && candidate.startsWith('/') && !candidate.startsWith('//') && !candidate.includes('\\\\') ? candidate : '/';\n return { returnTo, token };\n }\n\n async refresh(): Promise<TokenSet> {\n const response = await fetch(new URL('/oauth/token', this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n 'x-device-id': this.getDeviceId()\n },\n body: new URLSearchParams({ grant_type: 'refresh_token', client_id: this.config.clientId })\n });\n if (!response.ok) throw new Error('OAuth refresh failed.');\n const payload = await response.json();\n if (typeof payload.access_token !== 'string' || !payload.access_token) {\n throw new Error('OAuth response did not contain an access token.');\n }\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n return token;\n }\n\n async getUser<T>() {\n const token = this.getToken() || (await this.refresh()).accessToken;\n const response = await fetch(new URL('/oauth/userinfo', this.config.authUrl), {\n headers: { authorization: `Bearer ${token}` },\n credentials: 'include'\n });\n if (!response.ok) throw new Error('OAuth userinfo request failed.');\n return response.json() as Promise<T>;\n }\n\n async getSession<TUser = unknown>(): Promise<TUser | null> {\n try {\n return await this.getUser<TUser>();\n } catch {\n return null;\n }\n }\n\n async requireAuth(returnTo = window.location.pathname + window.location.search) {\n if (this.isAuthenticated()) return true;\n await this.login(returnTo);\n return false;\n }\n\n async logout(returnTo = `${window.location.origin}/`) {\n const logout = new URL('/oauth/logout', this.config.authUrl);\n logout.search = new URLSearchParams({\n client_id: this.config.clientId,\n post_logout_redirect_uri: returnTo\n }).toString();\n sessionStorage.removeItem(this.key('access-token'));\n window.location.assign(logout.toString());\n }\n\n private async authRequest<T = { success: true; message: string }>(path: string, body: Record<string, string>) {\n const response = await fetch(new URL(path, this.config.authUrl), {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body)\n });\n const payload = await response.json().catch(() => null);\n if (!response.ok) {\n throw new Error(payload?.error?.message ?? 'Identity registration request failed.');\n }\n return (payload?.data ?? payload) as T;\n }\n}\n\nexport { watchWebSession, endWebSession } from \"./web-session.js\";\n"],"mappings":"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","names":["a","b"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@biowiki/auth-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Browser OAuth 2.0 Authorization Code + PKCE client for BioWiki applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|