@orangecheck/me-client 0.33.0 → 0.34.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
@@ -41,8 +41,27 @@ import { signInWithOc } from '@orangecheck/me-client/popup';
41
41
 
42
42
  const result = await signInWithOc();
43
43
  if (result) {
44
- // A token bound to your origin. Send it to your server as a Bearer.
45
- await fetch('/api/me', { headers: { Authorization: `Bearer ${result.token}` } });
44
+ // A token bound to your origin. Hand it to your server once; it keeps it
45
+ // in an httpOnly cookie, out of reach of your page's scripts.
46
+ await fetch('/api/auth/session', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ token: result.token }),
50
+ });
51
+ }
52
+ ```
53
+
54
+ ```ts
55
+ // Server · pages/api/auth/session.ts
56
+ import { verifyOcToken } from '@orangecheck/me-client/server';
57
+
58
+ export default async function session(req, res) {
59
+ const token = String(req.body?.token ?? '');
60
+ const s = await verifyOcToken(token, { audience: 'https://your-site.example' });
61
+ if (!s) return res.status(401).json({ ok: false });
62
+ const maxAge = (s.exp ?? 0) - Math.floor(Date.now() / 1000);
63
+ res.setHeader('Set-Cookie', `oc_session=${token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`);
64
+ res.status(200).json({ ok: true });
46
65
  }
47
66
  ```
48
67
 
@@ -228,6 +247,10 @@ app.get('/api/me', (c) => {
228
247
 
229
248
  `getOcSession(headers)` is the framework-agnostic primitive: pass a Web `Headers` object or a plain `{ cookie, authorization }` object. Cookie auth (family `*.ochk.io`) and Bearer auth (cross-domain) are handled identically.
230
249
 
250
+ ### Sessions ended everywhere
251
+
252
+ Set `OC_PROJECT_KEY` and `OC_SIGNING_SECRET` (from ochk.app/me/projects) and `getOcSession`, `withOcAuth`, `ocAuthExpress` and `ocAuthHono` refuse a session the person ended everywhere: sign out everywhere, a passkey removed, the account deleted. They read those account cutoffs from me.ochk.io once a minute, with a request signed by your secret, so a refusal lands within about two minutes. If me.ochk.io can't be reached, reads go on and writes are refused until it can. Without the two variables, a token is checked by its signature alone and stays valid until it expires.
253
+
231
254
  ## Browser popup signin
232
255
 
233
256
  ```tsx
@@ -236,10 +259,14 @@ import { signInWithOc } from '@orangecheck/me-client/popup';
236
259
  button.addEventListener('click', async () => {
237
260
  const result = await signInWithOc();
238
261
  if (!result) return; // user cancelled or popup blocked
239
- // Cross-domain integrators only — same-origin family integrators
240
- // (.ochk.io) skip this; the cookie rides automatically.
241
- localStorage.setItem('oc-token', result.token);
242
- location.assign('/dashboard');
262
+ // Your server verifies it and keeps it in an httpOnly cookie. Not
263
+ // localStorage: any script on the page could read it there.
264
+ const r = await fetch('/api/auth/session', {
265
+ method: 'POST',
266
+ headers: { 'Content-Type': 'application/json' },
267
+ body: JSON.stringify({ token: result.token }),
268
+ });
269
+ if (r.ok) location.assign('/dashboard');
243
270
  });
244
271
  ```
245
272
 
package/dist/popup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/popup.ts"],"names":[],"mappings":"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","file":"popup.js","sourcesContent":["/**\n * @orangecheck/me-client/popup\n *\n * Browser-side helper that opens the OC sign-in popup, listens for the\n * postMessage from me.ochk.io, and resolves with the verified session.\n *\n * import { signInWithOc } from '@orangecheck/me-client/popup';\n *\n * document.querySelector('#oc-signin')!.addEventListener('click', async () => {\n * const result = await signInWithOc();\n * if (result) {\n * localStorage.setItem('oc-session-token', result.token);\n * location.assign('/app');\n * }\n * });\n *\n * Why this lives in a separate entry point: it's browser-only (uses\n * window.open / addEventListener) and integrators that only need the\n * server-side verifier shouldn't pull popup orchestration into their\n * server bundle.\n */\n\nconst DEFAULT_POPUP_ORIGIN = 'https://me.ochk.io';\nconst DEFAULT_POPUP_PATH = '/popup/signin';\nconst DEFAULT_FEATURES = 'width=480,height=720,resizable=yes,scrollbars=yes';\n\nexport interface OcPopupAccount {\n id?: string;\n /** Opaque public identity · `did:oc:<32-hex>`. Stable across linking\n * events. The canonical identifier post auth-refactor (Dec 2025\n * +). */\n did_oc?: string;\n /** Primary linked Bitcoin address, if the user has one and is\n * surfacing it (same-origin family integrators get it inline;\n * cross-domain integrators need the bitcoin_address scope). */\n primary_btc?: string | null;\n /** Whether the user has a primary email linked (plaintext stays\n * on auth host; only the boolean reaches integrators). */\n has_email?: boolean;\n /** Legacy fields kept for back-compat with pre-refactor consumers.\n * Prefer did_oc + primary_btc. Removed in a future major. */\n address?: string;\n btc_address?: string;\n display_name?: string | null;\n nostr_npub?: string | null;\n wallet_provisioned?: boolean;\n email?: string;\n}\n\nexport interface OcPopupResult {\n /** Account payload from the auth host, unsigned: trust only the\n * verified token. `did_oc` is the account's opaque identifier, the\n * same at every site; the email and Bitcoin address stay private\n * unless the user grants a scope. */\n account: OcPopupAccount;\n /** The session JWT. Cross-domain integrators store this and send\n * it as `Authorization: Bearer <token>` on subsequent calls.\n * Family integrators (*.ochk.io) can ignore it — the cookie set\n * Domain=.ochk.io rides home automatically. */\n token: string;\n}\n\nexport interface SignInWithOcOptions {\n /** Origin of the OC popup page. Defaults to https://me.ochk.io.\n * Override only for staging / preview environments. */\n popupOrigin?: string;\n /** Path on the popup origin. Defaults to /popup/signin. */\n popupPath?: string;\n /** window.open features string. Defaults to a 480x720 resizable\n * popup with scrollbars. */\n features?: string;\n /** AbortSignal that cancels the wait and closes the popup. */\n signal?: AbortSignal;\n}\n\n/**\n * Open the OC signin popup and resolve with the session result. Returns\n * `null` if the user closed the popup without completing signin, or if\n * the AbortSignal fired.\n *\n * MUST be called inside a user-gesture handler (e.g. a button onClick).\n * Browsers block `window.open` outside of user gestures.\n */\nexport function signInWithOc(options: SignInWithOcOptions = {}): Promise<OcPopupResult | null> {\n const popupOrigin = options.popupOrigin ?? DEFAULT_POPUP_ORIGIN;\n const popupPath = options.popupPath ?? DEFAULT_POPUP_PATH;\n const features = options.features ?? DEFAULT_FEATURES;\n\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null);\n return;\n }\n\n const url = new URL(popupPath, popupOrigin);\n url.searchParams.set('opener_origin', window.location.origin);\n const popup = window.open(url.toString(), 'oc-signin', features);\n if (!popup) {\n // Popup blocked. Caller can detect a null result and fall\n // back to a full-page redirect if desired.\n resolve(null);\n return;\n }\n popup.focus();\n\n let settled = false;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearInterval(closedTimer);\n if (options.signal) options.signal.removeEventListener('abort', onAbort);\n };\n\n function settle(value: OcPopupResult | null) {\n if (settled) return;\n settled = true;\n cleanup();\n try {\n if (popup && !popup.closed) popup.close();\n } catch {\n /* popup may have closed itself first */\n }\n resolve(value);\n }\n\n function onMessage(event: MessageEvent) {\n // Origin pinning is necessary but not sufficient — same-origin\n // pages other than our popup can post the same shape. Pin both\n // origin AND source window so only the popup we opened can\n // settle this promise.\n if (event.origin !== popupOrigin) return;\n if (event.source !== popup) return;\n const data = event.data as\n | { type?: string; account?: OcPopupAccount; token?: string }\n | null;\n if (!data || data.type !== 'oc-signin-success') return;\n if (!data.account || !data.token) {\n // Malformed payload — treat as not-signed-in.\n settle(null);\n return;\n }\n settle({ account: data.account, token: data.token });\n }\n\n function onAbort() {\n settle(null);\n }\n\n window.addEventListener('message', onMessage);\n if (options.signal) {\n if (options.signal.aborted) {\n settle(null);\n return;\n }\n options.signal.addEventListener('abort', onAbort);\n }\n\n // Detect the user closing the popup without completing signin.\n const closedTimer = setInterval(() => {\n if (popup && popup.closed) settle(null);\n }, 500);\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/popup.ts"],"names":[],"mappings":"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","file":"popup.js","sourcesContent":["/**\n * @orangecheck/me-client/popup\n *\n * Browser-side helper that opens the OC sign-in popup, listens for the\n * postMessage from me.ochk.io, and resolves with the verified session.\n *\n * import { signInWithOc } from '@orangecheck/me-client/popup';\n *\n * document.querySelector('#oc-signin')!.addEventListener('click', async () => {\n * const result = await signInWithOc();\n * if (!result) return;\n * // Your server verifies it and keeps it in an httpOnly cookie.\n * const r = await fetch('/api/auth/session', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ token: result.token }),\n * });\n * if (r.ok) location.assign('/app');\n * });\n *\n * Why this lives in a separate entry point: it's browser-only (uses\n * window.open / addEventListener) and integrators that only need the\n * server-side verifier shouldn't pull popup orchestration into their\n * server bundle.\n */\n\nconst DEFAULT_POPUP_ORIGIN = 'https://me.ochk.io';\nconst DEFAULT_POPUP_PATH = '/popup/signin';\nconst DEFAULT_FEATURES = 'width=480,height=720,resizable=yes,scrollbars=yes';\n\nexport interface OcPopupAccount {\n id?: string;\n /** Opaque public identity · `did:oc:<32-hex>`. Stable across linking\n * events. The canonical identifier post auth-refactor (Dec 2025\n * +). */\n did_oc?: string;\n /** Primary linked Bitcoin address, if the user has one and is\n * surfacing it (same-origin family integrators get it inline;\n * cross-domain integrators need the bitcoin_address scope). */\n primary_btc?: string | null;\n /** Whether the user has a primary email linked (plaintext stays\n * on auth host; only the boolean reaches integrators). */\n has_email?: boolean;\n /** Legacy fields kept for back-compat with pre-refactor consumers.\n * Prefer did_oc + primary_btc. Removed in a future major. */\n address?: string;\n btc_address?: string;\n display_name?: string | null;\n nostr_npub?: string | null;\n wallet_provisioned?: boolean;\n email?: string;\n}\n\nexport interface OcPopupResult {\n /** Account payload from the auth host, unsigned: trust only the\n * verified token. `did_oc` is the account's opaque identifier, the\n * same at every site; the email and Bitcoin address stay private\n * unless the user grants a scope. */\n account: OcPopupAccount;\n /** The session JWT, bound to your origin. Hand it once to your own\n * server, which verifies it and keeps it in an httpOnly cookie; never\n * in localStorage, where any script on the page can read it. Family\n * sites (*.ochk.io) can ignore it: the Domain=.ochk.io cookie rides\n * home on its own. */\n token: string;\n}\n\nexport interface SignInWithOcOptions {\n /** Origin of the OC popup page. Defaults to https://me.ochk.io.\n * Override only for staging / preview environments. */\n popupOrigin?: string;\n /** Path on the popup origin. Defaults to /popup/signin. */\n popupPath?: string;\n /** window.open features string. Defaults to a 480x720 resizable\n * popup with scrollbars. */\n features?: string;\n /** AbortSignal that cancels the wait and closes the popup. */\n signal?: AbortSignal;\n}\n\n/**\n * Open the OC signin popup and resolve with the session result. Returns\n * `null` if the user closed the popup without completing signin, or if\n * the AbortSignal fired.\n *\n * MUST be called inside a user-gesture handler (e.g. a button onClick).\n * Browsers block `window.open` outside of user gestures.\n */\nexport function signInWithOc(options: SignInWithOcOptions = {}): Promise<OcPopupResult | null> {\n const popupOrigin = options.popupOrigin ?? DEFAULT_POPUP_ORIGIN;\n const popupPath = options.popupPath ?? DEFAULT_POPUP_PATH;\n const features = options.features ?? DEFAULT_FEATURES;\n\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null);\n return;\n }\n\n const url = new URL(popupPath, popupOrigin);\n url.searchParams.set('opener_origin', window.location.origin);\n const popup = window.open(url.toString(), 'oc-signin', features);\n if (!popup) {\n // Popup blocked. Caller can detect a null result and fall\n // back to a full-page redirect if desired.\n resolve(null);\n return;\n }\n popup.focus();\n\n let settled = false;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearInterval(closedTimer);\n if (options.signal) options.signal.removeEventListener('abort', onAbort);\n };\n\n function settle(value: OcPopupResult | null) {\n if (settled) return;\n settled = true;\n cleanup();\n try {\n if (popup && !popup.closed) popup.close();\n } catch {\n /* popup may have closed itself first */\n }\n resolve(value);\n }\n\n function onMessage(event: MessageEvent) {\n // Origin pinning is necessary but not sufficient — same-origin\n // pages other than our popup can post the same shape. Pin both\n // origin AND source window so only the popup we opened can\n // settle this promise.\n if (event.origin !== popupOrigin) return;\n if (event.source !== popup) return;\n const data = event.data as\n | { type?: string; account?: OcPopupAccount; token?: string }\n | null;\n if (!data || data.type !== 'oc-signin-success') return;\n if (!data.account || !data.token) {\n // Malformed payload — treat as not-signed-in.\n settle(null);\n return;\n }\n settle({ account: data.account, token: data.token });\n }\n\n function onAbort() {\n settle(null);\n }\n\n window.addEventListener('message', onMessage);\n if (options.signal) {\n if (options.signal.aborted) {\n settle(null);\n return;\n }\n options.signal.addEventListener('abort', onAbort);\n }\n\n // Detect the user closing the popup without completing signin.\n const closedTimer = setInterval(() => {\n if (popup && popup.closed) settle(null);\n }, 500);\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/popup.ts"],"names":[],"mappings":"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","file":"popup.mjs","sourcesContent":["/**\n * @orangecheck/me-client/popup\n *\n * Browser-side helper that opens the OC sign-in popup, listens for the\n * postMessage from me.ochk.io, and resolves with the verified session.\n *\n * import { signInWithOc } from '@orangecheck/me-client/popup';\n *\n * document.querySelector('#oc-signin')!.addEventListener('click', async () => {\n * const result = await signInWithOc();\n * if (result) {\n * localStorage.setItem('oc-session-token', result.token);\n * location.assign('/app');\n * }\n * });\n *\n * Why this lives in a separate entry point: it's browser-only (uses\n * window.open / addEventListener) and integrators that only need the\n * server-side verifier shouldn't pull popup orchestration into their\n * server bundle.\n */\n\nconst DEFAULT_POPUP_ORIGIN = 'https://me.ochk.io';\nconst DEFAULT_POPUP_PATH = '/popup/signin';\nconst DEFAULT_FEATURES = 'width=480,height=720,resizable=yes,scrollbars=yes';\n\nexport interface OcPopupAccount {\n id?: string;\n /** Opaque public identity · `did:oc:<32-hex>`. Stable across linking\n * events. The canonical identifier post auth-refactor (Dec 2025\n * +). */\n did_oc?: string;\n /** Primary linked Bitcoin address, if the user has one and is\n * surfacing it (same-origin family integrators get it inline;\n * cross-domain integrators need the bitcoin_address scope). */\n primary_btc?: string | null;\n /** Whether the user has a primary email linked (plaintext stays\n * on auth host; only the boolean reaches integrators). */\n has_email?: boolean;\n /** Legacy fields kept for back-compat with pre-refactor consumers.\n * Prefer did_oc + primary_btc. Removed in a future major. */\n address?: string;\n btc_address?: string;\n display_name?: string | null;\n nostr_npub?: string | null;\n wallet_provisioned?: boolean;\n email?: string;\n}\n\nexport interface OcPopupResult {\n /** Account payload from the auth host, unsigned: trust only the\n * verified token. `did_oc` is the account's opaque identifier, the\n * same at every site; the email and Bitcoin address stay private\n * unless the user grants a scope. */\n account: OcPopupAccount;\n /** The session JWT. Cross-domain integrators store this and send\n * it as `Authorization: Bearer <token>` on subsequent calls.\n * Family integrators (*.ochk.io) can ignore it — the cookie set\n * Domain=.ochk.io rides home automatically. */\n token: string;\n}\n\nexport interface SignInWithOcOptions {\n /** Origin of the OC popup page. Defaults to https://me.ochk.io.\n * Override only for staging / preview environments. */\n popupOrigin?: string;\n /** Path on the popup origin. Defaults to /popup/signin. */\n popupPath?: string;\n /** window.open features string. Defaults to a 480x720 resizable\n * popup with scrollbars. */\n features?: string;\n /** AbortSignal that cancels the wait and closes the popup. */\n signal?: AbortSignal;\n}\n\n/**\n * Open the OC signin popup and resolve with the session result. Returns\n * `null` if the user closed the popup without completing signin, or if\n * the AbortSignal fired.\n *\n * MUST be called inside a user-gesture handler (e.g. a button onClick).\n * Browsers block `window.open` outside of user gestures.\n */\nexport function signInWithOc(options: SignInWithOcOptions = {}): Promise<OcPopupResult | null> {\n const popupOrigin = options.popupOrigin ?? DEFAULT_POPUP_ORIGIN;\n const popupPath = options.popupPath ?? DEFAULT_POPUP_PATH;\n const features = options.features ?? DEFAULT_FEATURES;\n\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null);\n return;\n }\n\n const url = new URL(popupPath, popupOrigin);\n url.searchParams.set('opener_origin', window.location.origin);\n const popup = window.open(url.toString(), 'oc-signin', features);\n if (!popup) {\n // Popup blocked. Caller can detect a null result and fall\n // back to a full-page redirect if desired.\n resolve(null);\n return;\n }\n popup.focus();\n\n let settled = false;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearInterval(closedTimer);\n if (options.signal) options.signal.removeEventListener('abort', onAbort);\n };\n\n function settle(value: OcPopupResult | null) {\n if (settled) return;\n settled = true;\n cleanup();\n try {\n if (popup && !popup.closed) popup.close();\n } catch {\n /* popup may have closed itself first */\n }\n resolve(value);\n }\n\n function onMessage(event: MessageEvent) {\n // Origin pinning is necessary but not sufficient — same-origin\n // pages other than our popup can post the same shape. Pin both\n // origin AND source window so only the popup we opened can\n // settle this promise.\n if (event.origin !== popupOrigin) return;\n if (event.source !== popup) return;\n const data = event.data as\n | { type?: string; account?: OcPopupAccount; token?: string }\n | null;\n if (!data || data.type !== 'oc-signin-success') return;\n if (!data.account || !data.token) {\n // Malformed payload — treat as not-signed-in.\n settle(null);\n return;\n }\n settle({ account: data.account, token: data.token });\n }\n\n function onAbort() {\n settle(null);\n }\n\n window.addEventListener('message', onMessage);\n if (options.signal) {\n if (options.signal.aborted) {\n settle(null);\n return;\n }\n options.signal.addEventListener('abort', onAbort);\n }\n\n // Detect the user closing the popup without completing signin.\n const closedTimer = setInterval(() => {\n if (popup && popup.closed) settle(null);\n }, 500);\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/popup.ts"],"names":[],"mappings":"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","file":"popup.mjs","sourcesContent":["/**\n * @orangecheck/me-client/popup\n *\n * Browser-side helper that opens the OC sign-in popup, listens for the\n * postMessage from me.ochk.io, and resolves with the verified session.\n *\n * import { signInWithOc } from '@orangecheck/me-client/popup';\n *\n * document.querySelector('#oc-signin')!.addEventListener('click', async () => {\n * const result = await signInWithOc();\n * if (!result) return;\n * // Your server verifies it and keeps it in an httpOnly cookie.\n * const r = await fetch('/api/auth/session', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ token: result.token }),\n * });\n * if (r.ok) location.assign('/app');\n * });\n *\n * Why this lives in a separate entry point: it's browser-only (uses\n * window.open / addEventListener) and integrators that only need the\n * server-side verifier shouldn't pull popup orchestration into their\n * server bundle.\n */\n\nconst DEFAULT_POPUP_ORIGIN = 'https://me.ochk.io';\nconst DEFAULT_POPUP_PATH = '/popup/signin';\nconst DEFAULT_FEATURES = 'width=480,height=720,resizable=yes,scrollbars=yes';\n\nexport interface OcPopupAccount {\n id?: string;\n /** Opaque public identity · `did:oc:<32-hex>`. Stable across linking\n * events. The canonical identifier post auth-refactor (Dec 2025\n * +). */\n did_oc?: string;\n /** Primary linked Bitcoin address, if the user has one and is\n * surfacing it (same-origin family integrators get it inline;\n * cross-domain integrators need the bitcoin_address scope). */\n primary_btc?: string | null;\n /** Whether the user has a primary email linked (plaintext stays\n * on auth host; only the boolean reaches integrators). */\n has_email?: boolean;\n /** Legacy fields kept for back-compat with pre-refactor consumers.\n * Prefer did_oc + primary_btc. Removed in a future major. */\n address?: string;\n btc_address?: string;\n display_name?: string | null;\n nostr_npub?: string | null;\n wallet_provisioned?: boolean;\n email?: string;\n}\n\nexport interface OcPopupResult {\n /** Account payload from the auth host, unsigned: trust only the\n * verified token. `did_oc` is the account's opaque identifier, the\n * same at every site; the email and Bitcoin address stay private\n * unless the user grants a scope. */\n account: OcPopupAccount;\n /** The session JWT, bound to your origin. Hand it once to your own\n * server, which verifies it and keeps it in an httpOnly cookie; never\n * in localStorage, where any script on the page can read it. Family\n * sites (*.ochk.io) can ignore it: the Domain=.ochk.io cookie rides\n * home on its own. */\n token: string;\n}\n\nexport interface SignInWithOcOptions {\n /** Origin of the OC popup page. Defaults to https://me.ochk.io.\n * Override only for staging / preview environments. */\n popupOrigin?: string;\n /** Path on the popup origin. Defaults to /popup/signin. */\n popupPath?: string;\n /** window.open features string. Defaults to a 480x720 resizable\n * popup with scrollbars. */\n features?: string;\n /** AbortSignal that cancels the wait and closes the popup. */\n signal?: AbortSignal;\n}\n\n/**\n * Open the OC signin popup and resolve with the session result. Returns\n * `null` if the user closed the popup without completing signin, or if\n * the AbortSignal fired.\n *\n * MUST be called inside a user-gesture handler (e.g. a button onClick).\n * Browsers block `window.open` outside of user gestures.\n */\nexport function signInWithOc(options: SignInWithOcOptions = {}): Promise<OcPopupResult | null> {\n const popupOrigin = options.popupOrigin ?? DEFAULT_POPUP_ORIGIN;\n const popupPath = options.popupPath ?? DEFAULT_POPUP_PATH;\n const features = options.features ?? DEFAULT_FEATURES;\n\n return new Promise((resolve) => {\n if (typeof window === 'undefined') {\n resolve(null);\n return;\n }\n\n const url = new URL(popupPath, popupOrigin);\n url.searchParams.set('opener_origin', window.location.origin);\n const popup = window.open(url.toString(), 'oc-signin', features);\n if (!popup) {\n // Popup blocked. Caller can detect a null result and fall\n // back to a full-page redirect if desired.\n resolve(null);\n return;\n }\n popup.focus();\n\n let settled = false;\n const cleanup = () => {\n window.removeEventListener('message', onMessage);\n clearInterval(closedTimer);\n if (options.signal) options.signal.removeEventListener('abort', onAbort);\n };\n\n function settle(value: OcPopupResult | null) {\n if (settled) return;\n settled = true;\n cleanup();\n try {\n if (popup && !popup.closed) popup.close();\n } catch {\n /* popup may have closed itself first */\n }\n resolve(value);\n }\n\n function onMessage(event: MessageEvent) {\n // Origin pinning is necessary but not sufficient — same-origin\n // pages other than our popup can post the same shape. Pin both\n // origin AND source window so only the popup we opened can\n // settle this promise.\n if (event.origin !== popupOrigin) return;\n if (event.source !== popup) return;\n const data = event.data as\n | { type?: string; account?: OcPopupAccount; token?: string }\n | null;\n if (!data || data.type !== 'oc-signin-success') return;\n if (!data.account || !data.token) {\n // Malformed payload — treat as not-signed-in.\n settle(null);\n return;\n }\n settle({ account: data.account, token: data.token });\n }\n\n function onAbort() {\n settle(null);\n }\n\n window.addEventListener('message', onMessage);\n if (options.signal) {\n if (options.signal.aborted) {\n settle(null);\n return;\n }\n options.signal.addEventListener('abort', onAbort);\n }\n\n // Detect the user closing the popup without completing signin.\n const closedTimer = setInterval(() => {\n if (popup && popup.closed) settle(null);\n }, 500);\n });\n}\n"]}
package/dist/server.d.mts CHANGED
@@ -1,9 +1,16 @@
1
- import { SessionPayload, VerifyOcOptions, SessionRequestHeaders } from '@orangecheck/auth-core';
1
+ import { SessionPayload, VerifyOcOptions, RevocationCheck, SessionRequestHeaders } from '@orangecheck/auth-core';
2
2
  export { BondClaim, SessionPayload, VerifyOcOptions, bondMeets, readBondClaim, verifyOcToken } from '@orangecheck/auth-core';
3
3
  export { A as ActivityAttestation, a as ActivityAttestationBundle, b as ActivityVerifyCheck, c as ActivityVerifyResult, V as VerifyActivityOptions, i as identity, r as refreshEnvelopeJwks, v as verifyActivityAttestation } from './identity-DfxCvxuH.mjs';
4
4
 
5
- declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: VerifyOcOptions): Promise<SessionPayload | null>;
5
+ interface OcSessionOptions extends VerifyOcOptions {
6
+ method?: string;
7
+ projectKey?: string;
8
+ revocation?: RevocationCheck | false;
9
+ }
10
+ declare function _resetSessionChecksForTests(): void;
11
+ declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: OcSessionOptions): Promise<SessionPayload | null>;
6
12
  interface NextApiRequestLike {
13
+ method?: string;
7
14
  headers: {
8
15
  cookie?: string;
9
16
  authorization?: string;
@@ -19,7 +26,7 @@ interface NextApiResponseLike {
19
26
  type WithOcSession<Req> = Req & {
20
27
  ocSession?: SessionPayload | null;
21
28
  };
22
- interface WithOcAuthOptions extends VerifyOcOptions {
29
+ interface WithOcAuthOptions extends OcSessionOptions {
23
30
  required?: boolean;
24
31
  requireBond?: {
25
32
  minSats?: number;
@@ -44,6 +51,7 @@ type OcHonoEnv = {
44
51
  };
45
52
  };
46
53
  interface ExpressRequestLike {
54
+ method?: string;
47
55
  headers: {
48
56
  cookie?: string;
49
57
  authorization?: string;
@@ -62,6 +70,7 @@ interface HonoContextLike {
62
70
  req: {
63
71
  raw: {
64
72
  headers: Headers;
73
+ method?: string;
65
74
  };
66
75
  };
67
76
  set(key: string, value: unknown): void;
@@ -71,4 +80,4 @@ interface HonoContextLike {
71
80
  type HonoNextLike = () => Promise<void>;
72
81
  declare function ocAuthHono(options?: WithOcAuthOptions): (c: HonoContextLike, next: HonoNextLike) => Promise<unknown>;
73
82
 
74
- export { BACK_ACCOUNT_URL, type OcHonoEnv, type WithOcAuthOptions, type WithOcSession, getOcSession, ocAuthExpress, ocAuthHono, withOcAuth };
83
+ export { BACK_ACCOUNT_URL, type OcHonoEnv, type OcSessionOptions, type WithOcAuthOptions, type WithOcSession, _resetSessionChecksForTests, getOcSession, ocAuthExpress, ocAuthHono, withOcAuth };
package/dist/server.d.ts CHANGED
@@ -1,9 +1,16 @@
1
- import { SessionPayload, VerifyOcOptions, SessionRequestHeaders } from '@orangecheck/auth-core';
1
+ import { SessionPayload, VerifyOcOptions, RevocationCheck, SessionRequestHeaders } from '@orangecheck/auth-core';
2
2
  export { BondClaim, SessionPayload, VerifyOcOptions, bondMeets, readBondClaim, verifyOcToken } from '@orangecheck/auth-core';
3
3
  export { A as ActivityAttestation, a as ActivityAttestationBundle, b as ActivityVerifyCheck, c as ActivityVerifyResult, V as VerifyActivityOptions, i as identity, r as refreshEnvelopeJwks, v as verifyActivityAttestation } from './identity-DfxCvxuH.js';
4
4
 
5
- declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: VerifyOcOptions): Promise<SessionPayload | null>;
5
+ interface OcSessionOptions extends VerifyOcOptions {
6
+ method?: string;
7
+ projectKey?: string;
8
+ revocation?: RevocationCheck | false;
9
+ }
10
+ declare function _resetSessionChecksForTests(): void;
11
+ declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: OcSessionOptions): Promise<SessionPayload | null>;
6
12
  interface NextApiRequestLike {
13
+ method?: string;
7
14
  headers: {
8
15
  cookie?: string;
9
16
  authorization?: string;
@@ -19,7 +26,7 @@ interface NextApiResponseLike {
19
26
  type WithOcSession<Req> = Req & {
20
27
  ocSession?: SessionPayload | null;
21
28
  };
22
- interface WithOcAuthOptions extends VerifyOcOptions {
29
+ interface WithOcAuthOptions extends OcSessionOptions {
23
30
  required?: boolean;
24
31
  requireBond?: {
25
32
  minSats?: number;
@@ -44,6 +51,7 @@ type OcHonoEnv = {
44
51
  };
45
52
  };
46
53
  interface ExpressRequestLike {
54
+ method?: string;
47
55
  headers: {
48
56
  cookie?: string;
49
57
  authorization?: string;
@@ -62,6 +70,7 @@ interface HonoContextLike {
62
70
  req: {
63
71
  raw: {
64
72
  headers: Headers;
73
+ method?: string;
65
74
  };
66
75
  };
67
76
  set(key: string, value: unknown): void;
@@ -71,4 +80,4 @@ interface HonoContextLike {
71
80
  type HonoNextLike = () => Promise<void>;
72
81
  declare function ocAuthHono(options?: WithOcAuthOptions): (c: HonoContextLike, next: HonoNextLike) => Promise<unknown>;
73
82
 
74
- export { BACK_ACCOUNT_URL, type OcHonoEnv, type WithOcAuthOptions, type WithOcSession, getOcSession, ocAuthExpress, ocAuthHono, withOcAuth };
83
+ export { BACK_ACCOUNT_URL, type OcHonoEnv, type OcSessionOptions, type WithOcAuthOptions, type WithOcSession, _resetSessionChecksForTests, getOcSession, ocAuthExpress, ocAuthHono, withOcAuth };
package/dist/server.js CHANGED
@@ -570,9 +570,40 @@ var identity = {
570
570
  };
571
571
 
572
572
  // src/server.ts
573
+ var sessionChecks = /* @__PURE__ */ new Map();
574
+ var warnedUnchecked = false;
575
+ function revocationFor(options) {
576
+ if (options.revocation === false) return null;
577
+ if (options.revocation) return options.revocation;
578
+ const key = options.projectKey ?? "";
579
+ if (!sessionChecks.has(key)) {
580
+ sessionChecks.set(
581
+ key,
582
+ authCore.integratorRevocationCheck(options.projectKey ? { projectKey: options.projectKey } : {})
583
+ );
584
+ }
585
+ const check = sessionChecks.get(key);
586
+ const production = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
587
+ if (!check && !warnedUnchecked && production) {
588
+ warnedUnchecked = true;
589
+ console.warn(
590
+ "[@orangecheck/me-client] sessions are checked by signature only, so one the person ended everywhere stays valid here until it expires. Set OC_PROJECT_KEY and OC_SIGNING_SECRET to refuse it."
591
+ );
592
+ }
593
+ return check;
594
+ }
595
+ function _resetSessionChecksForTests() {
596
+ sessionChecks.clear();
597
+ warnedUnchecked = false;
598
+ }
573
599
  async function getOcSession(headers, options = {}) {
574
600
  const session = await authCore.getOcSession(headers, options);
575
- if (!session && options.audience === void 0) warnIfAudienceMissing(headers);
601
+ if (!session) {
602
+ if (options.audience === void 0) warnIfAudienceMissing(headers);
603
+ return null;
604
+ }
605
+ const check = revocationFor(options);
606
+ if (check && await check.refuses(session, options.method)) return null;
576
607
  return session;
577
608
  }
578
609
  var warnedAudience = false;
@@ -686,7 +717,7 @@ function withOcAuth(handler, options = {}) {
686
717
  cookie: typeof req.headers.cookie === "string" ? req.headers.cookie : null,
687
718
  authorization: typeof req.headers.authorization === "string" ? req.headers.authorization : null
688
719
  },
689
- options
720
+ { ...options, method: req.method }
690
721
  );
691
722
  const withSession = req;
692
723
  withSession.ocSession = session;
@@ -705,7 +736,7 @@ function ocAuthExpress(options = {}) {
705
736
  cookie: typeof req.headers.cookie === "string" ? req.headers.cookie : null,
706
737
  authorization: typeof req.headers.authorization === "string" ? req.headers.authorization : null
707
738
  },
708
- options
739
+ { ...options, method: req.method }
709
740
  );
710
741
  req.ocSession = session;
711
742
  const refused = await refusal(session, options);
@@ -723,7 +754,10 @@ function ocAuthHono(options = {}) {
723
754
  return async (c, next) => {
724
755
  c.header("Cache-Control", "no-store, private");
725
756
  c.header("Vary", "Cookie, Authorization");
726
- const session = await getOcSession(c.req.raw.headers, options);
757
+ const session = await getOcSession(c.req.raw.headers, {
758
+ ...options,
759
+ method: c.req.raw.method
760
+ });
727
761
  c.set("ocSession", session);
728
762
  const refused = await refusal(session, options);
729
763
  if (refused) return c.json(refused.body, refused.status);
@@ -744,6 +778,7 @@ Object.defineProperty(exports, "verifyOcToken", {
744
778
  get: function () { return authCore.verifyOcToken; }
745
779
  });
746
780
  exports.BACK_ACCOUNT_URL = BACK_ACCOUNT_URL;
781
+ exports._resetSessionChecksForTests = _resetSessionChecksForTests;
747
782
  exports.getOcSession = getOcSession;
748
783
  exports.identity = identity;
749
784
  exports.ocAuthExpress = ocAuthExpress;