@biowiki/auth-sdk 0.1.0 → 0.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/README.md CHANGED
@@ -5,16 +5,12 @@ It also maintains a stable per-application browser device ID in `localStorage` a
5
5
 
6
6
  ## Install
7
7
 
8
- The package is published only to the company private npm Registry. Configure the scope registry in the consuming project's `.npmrc`:
9
-
10
- ```ini
11
- @biowiki:registry=https://npm.company.example/repository/npm-private/
12
- ```
8
+ The package is published publicly to the npm registry at `https://registry.npmjs.org/`.
13
9
 
14
10
  Then install a pinned version:
15
11
 
16
12
  ```bash
17
- npm install @biowiki/auth-sdk@0.1.0
13
+ npm install @biowiki/auth-sdk@0.2.0
18
14
  ```
19
15
 
20
16
  ## Use
@@ -94,3 +90,57 @@ Set `NPM_REGISTRY` and `NPM_TOKEN` in CI secrets. Copy `.npmrc.example` to a use
94
90
  - Register the exact callback URL for each environment in Platform Console.
95
91
  - Configure Identity Service `CORS_ORIGIN` to include the application Origin.
96
92
  - The SDK does not create Tenants, Applications, OAuth Clients, or API credentials. Use `getSession()`, `isAuthenticated()`, and `requireAuth()` for browser session checks.
93
+
94
+ ## Popup login
95
+
96
+ Call from a user's click handler so browsers can open the window synchronously:
97
+
98
+ ```ts
99
+ const result = await auth.login({ mode: 'popup', returnTo: '/dashboard' });
100
+ if (result) {
101
+ // Update the application's session store or load the authenticated view.
102
+ window.location.assign(result.returnTo);
103
+ }
104
+ ```
105
+
106
+ Registration also supports `auth.beginRegistration({ mode: 'popup' })`.
107
+ A blocked popup automatically uses the existing full-page redirect flow, so the
108
+ callback route must support both modes:
109
+
110
+ ```ts
111
+ import { relayPopupCallback } from '@biowiki/auth-sdk';
112
+
113
+ if (!relayPopupCallback()) {
114
+ const { returnTo } = await auth.completeLogin();
115
+ window.location.replace(returnTo);
116
+ }
117
+ ```
118
+
119
+ Run the relay before session restoration, route access checks, or ordinary
120
+ callback code exchange. Only SDK-created popup transactions are relayed. The
121
+ callback sends code/state (or an OAuth error) to its own origin; the opener checks
122
+ origin, window identity, and state before redeeming the code with its own PKCE
123
+ verifier. Tokens and verifiers are never sent through `postMessage`. Each
124
+ transaction has its own storage key, and its callback is consumed once.
125
+
126
+ Requirements:
127
+
128
+ - `redirectUri` must have the same origin as the window initiating popup login.
129
+ Register that exact callback URI and the application's origin in OAuth config.
130
+ - Keep redirect login (`auth.login('/path')`) for URL-driven login routes, mobile
131
+ redirects, or providers that break opener communication.
132
+ - The application must use no COOP isolation or `same-origin-allow-popups`;
133
+ `Cross-Origin-Opener-Policy: same-origin` severs cross-origin opener access.
134
+ Identity emits `unsafe-none` on its OAuth/login/registration routes. Check
135
+ ingress/CDN headers and provider redirects too.
136
+ - Current browser refresh still uses Identity's HttpOnly `SameSite=Lax` cookie.
137
+ Unrelated application sites should use a BFF with a first-party session; this
138
+ popup change does not solve third-party cookie restrictions.
139
+ - Closing the popup rejects the promise; waiting more than two minutes also
140
+ rejects and closes it. Token exchange has a 15-second request timeout. Handle
141
+ rejection in the application's UI and allow retry.
142
+
143
+ Validate with `npm test` and `npm run typecheck`. Tests cover message validation,
144
+ PKCE exchange, replay, cancellation, timeout, blocked windows, registration,
145
+ OAuth errors, callback relay, and safe return paths. Live password/provider SSO
146
+ still needs staging acceptance against the deployed Identity and Gateway.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,19 @@ interface AuthSdkConfig {
4
4
  redirectUri: string;
5
5
  scope?: string;
6
6
  }
7
+ interface LoginOptions {
8
+ mode?: 'redirect' | 'popup';
9
+ returnTo?: string;
10
+ }
11
+ interface PopupCallbackMessage {
12
+ type: 'biowiki.oauth.callback';
13
+ code?: string;
14
+ state?: string;
15
+ error?: string;
16
+ errorDescription?: string;
17
+ }
18
+ /** Relays an OAuth callback from a popup to the window that opened it. */
19
+ declare function relayPopupCallback(url?: string): boolean;
7
20
  interface TokenSet {
8
21
  accessToken: string;
9
22
  expiresIn: number;
@@ -33,9 +46,17 @@ declare class BioWikiAuthClient {
33
46
  message: string;
34
47
  }>;
35
48
  register<TUser = unknown>(input: RegistrationInput): Promise<RegistrationResult<TUser>>;
36
- login(returnTo?: string): Promise<void>;
37
- beginRegistration(returnTo?: string): Promise<void>;
49
+ login(options?: LoginOptions | string): Promise<{
50
+ returnTo: string;
51
+ token: TokenSet;
52
+ } | void>;
53
+ beginRegistration(options?: LoginOptions | string): Promise<{
54
+ returnTo: string;
55
+ token: TokenSet;
56
+ } | void>;
57
+ private loginWithPopup;
38
58
  private startAuthorization;
59
+ private prepareAuthorization;
39
60
  completeLogin(url?: string): Promise<{
40
61
  returnTo: string;
41
62
  token: TokenSet;
@@ -48,4 +69,4 @@ declare class BioWikiAuthClient {
48
69
  private authRequest;
49
70
  }
50
71
 
51
- export { type AuthSdkConfig, BioWikiAuthClient, type RegistrationCodeInput, type RegistrationInput, type RegistrationResult, type TokenSet };
72
+ export { type AuthSdkConfig, BioWikiAuthClient, type LoginOptions, type PopupCallbackMessage, type RegistrationCodeInput, type RegistrationInput, type RegistrationResult, type TokenSet, relayPopupCallback };
package/dist/index.js CHANGED
@@ -1,4 +1,21 @@
1
1
  // src/index.ts
2
+ function relayPopupCallback(url = window.location.href) {
3
+ if (!window.opener || window.opener === window) return false;
4
+ const callback = new URL(url);
5
+ const state = callback.searchParams.get("state");
6
+ const marker = `biowiki.auth-sdk:popup:${state}`;
7
+ if (!state || sessionStorage.getItem(marker) !== "1") return false;
8
+ sessionStorage.removeItem(marker);
9
+ const payload = {
10
+ type: "biowiki.oauth.callback",
11
+ code: callback.searchParams.get("code") ?? void 0,
12
+ state: callback.searchParams.get("state") ?? void 0,
13
+ error: callback.searchParams.get("error") ?? void 0,
14
+ errorDescription: callback.searchParams.get("error_description") ?? void 0
15
+ };
16
+ window.opener.postMessage(payload, window.location.origin);
17
+ return true;
18
+ }
2
19
  var storagePrefix = "biowiki.auth-sdk";
3
20
  function base64Url(bytes) {
4
21
  return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
@@ -180,18 +197,92 @@ var BioWikiAuthClient = class {
180
197
  verification_code: input.verificationCode
181
198
  });
182
199
  }
183
- async login(returnTo = "/") {
184
- await this.startAuthorization(returnTo);
200
+ async login(options = "/") {
201
+ const normalized = typeof options === "string" ? { returnTo: options, mode: "redirect" } : options;
202
+ if (normalized.mode === "popup") return this.loginWithPopup(normalized.returnTo ?? "/");
203
+ await this.startAuthorization(normalized.returnTo ?? "/");
204
+ }
205
+ async beginRegistration(options = "/") {
206
+ const normalized = typeof options === "string" ? { returnTo: options, mode: "redirect" } : options;
207
+ if (normalized.mode === "popup") return this.loginWithPopup(normalized.returnTo ?? "/", "signup");
208
+ await this.startAuthorization(normalized.returnTo ?? "/", "signup");
185
209
  }
186
- async beginRegistration(returnTo = "/") {
187
- await this.startAuthorization(returnTo, "signup");
210
+ async loginWithPopup(returnTo, screenHint) {
211
+ if (new URL(this.config.redirectUri).origin !== window.location.origin) {
212
+ throw new Error("Popup redirect URI must use the current application origin.");
213
+ }
214
+ const popup = window.open("about:blank", `biowiki-login-${randomValue(12)}`, "popup,width=520,height=720,resizable=yes,scrollbars=yes");
215
+ let authorizationUrl;
216
+ try {
217
+ authorizationUrl = await this.prepareAuthorization(returnTo, screenHint);
218
+ } catch (error) {
219
+ popup?.close();
220
+ throw error;
221
+ }
222
+ if (!popup) {
223
+ window.location.assign(authorizationUrl);
224
+ return;
225
+ }
226
+ const expectedState = new URL(authorizationUrl).searchParams.get("state");
227
+ return new Promise((resolve, reject) => {
228
+ const expectedOrigin = new URL(this.config.redirectUri).origin;
229
+ let timer;
230
+ let timeout;
231
+ const cleanup = () => {
232
+ window.removeEventListener("message", onMessage);
233
+ if (timer) clearInterval(timer);
234
+ if (timeout) clearTimeout(timeout);
235
+ };
236
+ const fail = (error) => {
237
+ cleanup();
238
+ sessionStorage.removeItem(this.key(`transaction:${expectedState}`));
239
+ if (!popup.closed) popup.close();
240
+ reject(error);
241
+ };
242
+ const onMessage = async (event) => {
243
+ if (event.origin !== expectedOrigin || event.source !== popup || event.data?.type !== "biowiki.oauth.callback") return;
244
+ if (event.data.state !== expectedState) return;
245
+ cleanup();
246
+ if (!popup.closed) popup.close();
247
+ if (event.data.error) {
248
+ if (!popup.closed) popup.close();
249
+ sessionStorage.removeItem(this.key(`transaction:${expectedState}`));
250
+ reject(new Error(event.data.errorDescription || event.data.error));
251
+ return;
252
+ }
253
+ try {
254
+ const callback = new URL(this.config.redirectUri);
255
+ if (!event.data.code || !event.data.state) throw new Error("Invalid OAuth popup callback.");
256
+ callback.searchParams.set("code", event.data.code);
257
+ callback.searchParams.set("state", event.data.state);
258
+ const result = await this.completeLogin(callback.toString());
259
+ if (!popup.closed) popup.close();
260
+ resolve(result);
261
+ } catch (error) {
262
+ if (!popup.closed) popup.close();
263
+ reject(error instanceof Error ? error : new Error("OAuth popup login failed."));
264
+ }
265
+ };
266
+ window.addEventListener("message", onMessage);
267
+ timer = setInterval(() => {
268
+ if (popup.closed) fail(new Error("Login popup was closed."));
269
+ }, 500);
270
+ timeout = setTimeout(() => fail(new Error("Login popup timed out.")), 12e4);
271
+ try {
272
+ popup.sessionStorage.setItem(`biowiki.auth-sdk:popup:${expectedState}`, "1");
273
+ popup.location.assign(authorizationUrl);
274
+ } catch (error) {
275
+ fail(error instanceof Error ? error : new Error("Unable to open login popup."));
276
+ }
277
+ });
188
278
  }
189
279
  async startAuthorization(returnTo, screenHint) {
280
+ window.location.assign(await this.prepareAuthorization(returnTo, screenHint));
281
+ }
282
+ async prepareAuthorization(returnTo, screenHint) {
190
283
  const state = randomValue();
191
284
  const verifier = randomValue(48);
192
- sessionStorage.setItem(this.key("state"), state);
193
- sessionStorage.setItem(this.key("verifier"), verifier);
194
- sessionStorage.setItem(this.key("return-to"), returnTo);
285
+ sessionStorage.setItem(this.key(`transaction:${state}`), JSON.stringify({ verifier, returnTo, createdAt: Date.now() }));
195
286
  const authorize = new URL("/oauth/authorize", this.config.authUrl);
196
287
  authorize.search = new URLSearchParams({
197
288
  response_type: "code",
@@ -203,18 +294,21 @@ var BioWikiAuthClient = class {
203
294
  code_challenge_method: "S256"
204
295
  }).toString();
205
296
  if (screenHint) authorize.searchParams.set("screen_hint", screenHint);
206
- window.location.assign(authorize.toString());
297
+ return authorize.toString();
207
298
  }
208
299
  async completeLogin(url = window.location.href) {
209
300
  const callback = new URL(url);
210
301
  const code = callback.searchParams.get("code");
211
302
  const state = callback.searchParams.get("state");
212
- const expectedState = sessionStorage.getItem(this.key("state"));
213
- const verifier = sessionStorage.getItem(this.key("verifier"));
214
- if (!code || !state || state !== expectedState || !verifier) {
303
+ const transactionKey = this.key(`transaction:${state}`);
304
+ const rawTransaction = sessionStorage.getItem(transactionKey);
305
+ const transaction = rawTransaction ? JSON.parse(rawTransaction) : null;
306
+ if (!code || !state || !transaction?.verifier || Date.now() - transaction.createdAt > 10 * 60 * 1e3) {
215
307
  throw new Error("Invalid OAuth callback state.");
216
308
  }
309
+ sessionStorage.removeItem(transactionKey);
217
310
  const response = await fetch(new URL("/oauth/token", this.config.authUrl), {
311
+ signal: AbortSignal.timeout(15e3),
218
312
  method: "POST",
219
313
  credentials: "include",
220
314
  headers: {
@@ -226,17 +320,18 @@ var BioWikiAuthClient = class {
226
320
  code,
227
321
  client_id: this.config.clientId,
228
322
  redirect_uri: this.config.redirectUri,
229
- code_verifier: verifier
323
+ code_verifier: transaction.verifier
230
324
  })
231
325
  });
232
326
  if (!response.ok) throw new Error("OAuth authorization code exchange failed.");
233
327
  const payload = await response.json();
328
+ if (typeof payload.access_token !== "string" || !payload.access_token) {
329
+ throw new Error("OAuth response did not contain an access token.");
330
+ }
234
331
  const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };
235
332
  sessionStorage.setItem(this.key("access-token"), token.accessToken);
236
- const returnTo = sessionStorage.getItem(this.key("return-to")) ?? "/";
237
- sessionStorage.removeItem(this.key("state"));
238
- sessionStorage.removeItem(this.key("verifier"));
239
- sessionStorage.removeItem(this.key("return-to"));
333
+ const candidate = transaction.returnTo;
334
+ const returnTo = typeof candidate === "string" && candidate.startsWith("/") && !candidate.startsWith("//") && !candidate.includes("\\") ? candidate : "/";
240
335
  return { returnTo, token };
241
336
  }
242
337
  async refresh() {
@@ -251,6 +346,9 @@ var BioWikiAuthClient = class {
251
346
  });
252
347
  if (!response.ok) throw new Error("OAuth refresh failed.");
253
348
  const payload = await response.json();
349
+ if (typeof payload.access_token !== "string" || !payload.access_token) {
350
+ throw new Error("OAuth response did not contain an access token.");
351
+ }
254
352
  const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };
255
353
  sessionStorage.setItem(this.key("access-token"), token.accessToken);
256
354
  return token;
@@ -300,6 +398,7 @@ var BioWikiAuthClient = class {
300
398
  }
301
399
  };
302
400
  export {
303
- BioWikiAuthClient
401
+ BioWikiAuthClient,
402
+ relayPopupCallback
304
403
  };
305
404
  //# 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 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(returnTo = '/') {\n await this.startAuthorization(returnTo);\n }\n\n async beginRegistration(returnTo = '/') {\n await this.startAuthorization(returnTo, 'signup');\n }\n\n private async startAuthorization(returnTo: string, screenHint?: 'signup') {\n const state = randomValue();\n const verifier = randomValue(48);\n sessionStorage.setItem(this.key('state'), state);\n sessionStorage.setItem(this.key('verifier'), verifier);\n sessionStorage.setItem(this.key('return-to'), returnTo);\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 window.location.assign(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 expectedState = sessionStorage.getItem(this.key('state'));\n const verifier = sessionStorage.getItem(this.key('verifier'));\n if (!code || !state || state !== expectedState || !verifier) {\n throw new Error('Invalid OAuth callback state.');\n }\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({\n grant_type: 'authorization_code',\n code,\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n code_verifier: verifier\n })\n });\n if (!response.ok) throw new Error('OAuth authorization code exchange failed.');\n const payload = await response.json();\n const token = { accessToken: payload.access_token, expiresIn: payload.expires_in };\n sessionStorage.setItem(this.key('access-token'), token.accessToken);\n const returnTo = sessionStorage.getItem(this.key('return-to')) ?? '/';\n sessionStorage.removeItem(this.key('state'));\n sessionStorage.removeItem(this.key('verifier'));\n sessionStorage.removeItem(this.key('return-to'));\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 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/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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biowiki/auth-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -17,13 +17,14 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "scripts": {
20
+ "test": "npm run build && node --test test/*.test.js",
20
21
  "build": "tsup",
21
22
  "typecheck": "tsc --noEmit",
22
23
  "prepack": "npm run typecheck && npm run build",
23
24
  "prepublishOnly": "npm run typecheck && npm run build"
24
25
  },
25
26
  "publishConfig": {
26
- "access": "restricted"
27
+ "access": "public"
27
28
  },
28
29
  "engines": {
29
30
  "node": ">=24 <25"