@orangecheck/me-client 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/popup.d.mts CHANGED
@@ -1,5 +1,8 @@
1
1
  interface OcPopupAccount {
2
2
  id?: string;
3
+ did_oc?: string;
4
+ primary_btc?: string | null;
5
+ has_email?: boolean;
3
6
  address?: string;
4
7
  btc_address?: string;
5
8
  display_name?: string | null;
package/dist/popup.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  interface OcPopupAccount {
2
2
  id?: string;
3
+ did_oc?: string;
4
+ primary_btc?: string | null;
5
+ has_email?: boolean;
3
6
  address?: string;
4
7
  btc_address?: string;
5
8
  display_name?: string | null;
package/dist/popup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/popup.ts"],"names":[],"mappings":";;;AAsBA,IAAM,oBAAA,GAAuB,oBAAA;AAC7B,IAAM,kBAAA,GAAqB,eAAA;AAC3B,IAAM,gBAAA,GAAmB,mDAAA;AA6ClB,SAAS,YAAA,CAAa,OAAA,GAA+B,EAAC,EAAkC;AAC3F,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,oBAAA;AAC3C,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,gBAAA;AAErC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC5B,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,SAAA,EAAW,WAAW,CAAA;AAC1C,IAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,eAAA,EAAiB,MAAA,CAAO,SAAS,MAAM,CAAA;AAC5D,IAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,QAAA,EAAS,EAAG,aAAa,QAAQ,CAAA;AAC/D,IAAA,IAAI,CAAC,KAAA,EAAO;AAGR,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA;AAAA,IACJ;AACA,IAAA,KAAA,CAAM,KAAA,EAAM;AAEZ,IAAA,IAAI,OAAA,GAAU,KAAA;AACd,IAAA,MAAM,UAAU,MAAM;AAClB,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAC/C,MAAA,aAAA,CAAc,WAAW,CAAA;AACzB,MAAA,IAAI,QAAQ,MAAA,EAAQ,OAAA,CAAQ,MAAA,CAAO,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAAA,IAC3E,CAAA;AAEA,IAAA,SAAS,OAAO,KAAA,EAA6B;AACzC,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,OAAA,EAAQ;AACR,MAAA,IAAI;AACA,QAAA,IAAI,KAAA,IAAS,CAAC,KAAA,CAAM,MAAA,QAAc,KAAA,EAAM;AAAA,MAC5C,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACjB;AAEA,IAAA,SAAS,UAAU,KAAA,EAAqB;AAKpC,MAAA,IAAI,KAAA,CAAM,WAAW,WAAA,EAAa;AAClC,MAAA,IAAI,KAAA,CAAM,WAAW,KAAA,EAAO;AAC5B,MAAA,MAAM,OAAO,KAAA,CAAM,IAAA;AAGnB,MAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,IAAA,KAAS,mBAAA,EAAqB;AAChD,MAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,KAAK,KAAA,EAAO;AAE9B,QAAA,MAAA,CAAO,IAAI,CAAA;AACX,QAAA;AAAA,MACJ;AACA,MAAA,MAAA,CAAO,EAAE,OAAA,EAAS,IAAA,CAAK,SAAS,KAAA,EAAO,IAAA,CAAK,OAAO,CAAA;AAAA,IACvD;AAEA,IAAA,SAAS,OAAA,GAAU;AACf,MAAA,MAAA,CAAO,IAAI,CAAA;AAAA,IACf;AAEA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC5C,IAAA,IAAI,QAAQ,MAAA,EAAQ;AAChB,MAAA,IAAI,OAAA,CAAQ,OAAO,OAAA,EAAS;AACxB,QAAA,MAAA,CAAO,IAAI,CAAA;AACX,QAAA;AAAA,MACJ;AACA,MAAA,OAAA,CAAQ,MAAA,CAAO,gBAAA,CAAiB,OAAA,EAAS,OAAO,CAAA;AAAA,IACpD;AAGA,IAAA,MAAM,WAAA,GAAc,YAAY,MAAM;AAClC,MAAA,IAAI,KAAA,IAAS,KAAA,CAAM,MAAA,EAAQ,MAAA,CAAO,IAAI,CAAA;AAAA,IAC1C,GAAG,GAAG,CAAA;AAAA,EACV,CAAC,CAAA;AACL","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 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. `address` is the canonical\n * identifier (Bitcoin address for BIP-322 users, did:email:<hash>\n * for email-OTP users). */\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) {\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. `did_oc` is the canonical\n * opaque identifier post auth-refactor (Dec 2025). Underlying\n * email + BTC stay private; integrators get a per-project\n * anonymous identifier unless they explicitly request 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 +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 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. `address` is the canonical\n * identifier (Bitcoin address for BIP-322 users, did:email:<hash>\n * for email-OTP users). */\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) {\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. `did_oc` is the canonical\n * opaque identifier post auth-refactor (Dec 2025). Underlying\n * email + BTC stay private; integrators get a per-project\n * anonymous identifier unless they explicitly request 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"]}
package/dist/server.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { VerifyOcOptions, SessionRequestHeaders, SessionPayload } from '@orangecheck/auth-core';
2
2
  export { SessionPayload, VerifyOcOptions, verifyOcToken } from '@orangecheck/auth-core';
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-1VYMOhzV.mjs';
3
4
 
4
5
  declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: VerifyOcOptions): Promise<SessionPayload | null>;
5
6
  interface NextApiRequestLike {
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { VerifyOcOptions, SessionRequestHeaders, SessionPayload } from '@orangecheck/auth-core';
2
2
  export { SessionPayload, VerifyOcOptions, verifyOcToken } from '@orangecheck/auth-core';
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-1VYMOhzV.js';
3
4
 
4
5
  declare function getOcSession(headers: SessionRequestHeaders | Headers, options?: VerifyOcOptions): Promise<SessionPayload | null>;
5
6
  interface NextApiRequestLike {
package/dist/server.js CHANGED
@@ -2,6 +2,445 @@
2
2
 
3
3
  var authCore = require('@orangecheck/auth-core');
4
4
 
5
+ var __defProp = Object.defineProperty;
6
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
+
9
+ // node_modules/jose/dist/webapi/lib/buffer_utils.js
10
+ new TextEncoder();
11
+ var decoder = new TextDecoder();
12
+
13
+ // node_modules/jose/dist/webapi/lib/base64.js
14
+ function decodeBase64(encoded) {
15
+ if (Uint8Array.fromBase64) {
16
+ return Uint8Array.fromBase64(encoded);
17
+ }
18
+ const binary = atob(encoded);
19
+ const bytes = new Uint8Array(binary.length);
20
+ for (let i = 0; i < binary.length; i++) {
21
+ bytes[i] = binary.charCodeAt(i);
22
+ }
23
+ return bytes;
24
+ }
25
+
26
+ // node_modules/jose/dist/webapi/util/base64url.js
27
+ function decode(input) {
28
+ if (Uint8Array.fromBase64) {
29
+ return Uint8Array.fromBase64(typeof input === "string" ? input : decoder.decode(input), {
30
+ alphabet: "base64url"
31
+ });
32
+ }
33
+ let encoded = input;
34
+ if (encoded instanceof Uint8Array) {
35
+ encoded = decoder.decode(encoded);
36
+ }
37
+ encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
38
+ try {
39
+ return decodeBase64(encoded);
40
+ } catch {
41
+ throw new TypeError("The input to be decoded is not correctly encoded.");
42
+ }
43
+ }
44
+
45
+ // node_modules/jose/dist/webapi/util/errors.js
46
+ var JOSEError = class extends Error {
47
+ constructor(message, options) {
48
+ super(message, options);
49
+ __publicField(this, "code", "ERR_JOSE_GENERIC");
50
+ this.name = this.constructor.name;
51
+ Error.captureStackTrace?.(this, this.constructor);
52
+ }
53
+ };
54
+ __publicField(JOSEError, "code", "ERR_JOSE_GENERIC");
55
+ var JOSENotSupported = class extends JOSEError {
56
+ constructor() {
57
+ super(...arguments);
58
+ __publicField(this, "code", "ERR_JOSE_NOT_SUPPORTED");
59
+ }
60
+ };
61
+ __publicField(JOSENotSupported, "code", "ERR_JOSE_NOT_SUPPORTED");
62
+ var _a, _b;
63
+ var JWKSMultipleMatchingKeys = class extends (_b = JOSEError, _a = Symbol.asyncIterator, _b) {
64
+ constructor(message = "multiple matching keys found in the JSON Web Key Set", options) {
65
+ super(message, options);
66
+ __publicField(this, _a);
67
+ __publicField(this, "code", "ERR_JWKS_MULTIPLE_MATCHING_KEYS");
68
+ }
69
+ };
70
+ __publicField(JWKSMultipleMatchingKeys, "code", "ERR_JWKS_MULTIPLE_MATCHING_KEYS");
71
+
72
+ // node_modules/jose/dist/webapi/lib/type_checks.js
73
+ var isObjectLike = (value) => typeof value === "object" && value !== null;
74
+ function isObject(input) {
75
+ if (!isObjectLike(input) || Object.prototype.toString.call(input) !== "[object Object]") {
76
+ return false;
77
+ }
78
+ if (Object.getPrototypeOf(input) === null) {
79
+ return true;
80
+ }
81
+ let proto = input;
82
+ while (Object.getPrototypeOf(proto) !== null) {
83
+ proto = Object.getPrototypeOf(proto);
84
+ }
85
+ return Object.getPrototypeOf(input) === proto;
86
+ }
87
+
88
+ // node_modules/jose/dist/webapi/lib/jwk_to_key.js
89
+ var unsupportedAlg = 'Invalid or unsupported JWK "alg" (Algorithm) Parameter value';
90
+ function subtleMapping(jwk) {
91
+ let algorithm;
92
+ let keyUsages;
93
+ switch (jwk.kty) {
94
+ case "AKP": {
95
+ switch (jwk.alg) {
96
+ case "ML-DSA-44":
97
+ case "ML-DSA-65":
98
+ case "ML-DSA-87":
99
+ algorithm = { name: jwk.alg };
100
+ keyUsages = jwk.priv ? ["sign"] : ["verify"];
101
+ break;
102
+ default:
103
+ throw new JOSENotSupported(unsupportedAlg);
104
+ }
105
+ break;
106
+ }
107
+ case "RSA": {
108
+ switch (jwk.alg) {
109
+ case "PS256":
110
+ case "PS384":
111
+ case "PS512":
112
+ algorithm = { name: "RSA-PSS", hash: `SHA-${jwk.alg.slice(-3)}` };
113
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
114
+ break;
115
+ case "RS256":
116
+ case "RS384":
117
+ case "RS512":
118
+ algorithm = { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${jwk.alg.slice(-3)}` };
119
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
120
+ break;
121
+ case "RSA-OAEP":
122
+ case "RSA-OAEP-256":
123
+ case "RSA-OAEP-384":
124
+ case "RSA-OAEP-512":
125
+ algorithm = {
126
+ name: "RSA-OAEP",
127
+ hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`
128
+ };
129
+ keyUsages = jwk.d ? ["decrypt", "unwrapKey"] : ["encrypt", "wrapKey"];
130
+ break;
131
+ default:
132
+ throw new JOSENotSupported(unsupportedAlg);
133
+ }
134
+ break;
135
+ }
136
+ case "EC": {
137
+ switch (jwk.alg) {
138
+ case "ES256":
139
+ case "ES384":
140
+ case "ES512":
141
+ algorithm = {
142
+ name: "ECDSA",
143
+ namedCurve: { ES256: "P-256", ES384: "P-384", ES512: "P-521" }[jwk.alg]
144
+ };
145
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
146
+ break;
147
+ case "ECDH-ES":
148
+ case "ECDH-ES+A128KW":
149
+ case "ECDH-ES+A192KW":
150
+ case "ECDH-ES+A256KW":
151
+ algorithm = { name: "ECDH", namedCurve: jwk.crv };
152
+ keyUsages = jwk.d ? ["deriveBits"] : [];
153
+ break;
154
+ default:
155
+ throw new JOSENotSupported(unsupportedAlg);
156
+ }
157
+ break;
158
+ }
159
+ case "OKP": {
160
+ switch (jwk.alg) {
161
+ case "Ed25519":
162
+ case "EdDSA":
163
+ algorithm = { name: "Ed25519" };
164
+ keyUsages = jwk.d ? ["sign"] : ["verify"];
165
+ break;
166
+ case "ECDH-ES":
167
+ case "ECDH-ES+A128KW":
168
+ case "ECDH-ES+A192KW":
169
+ case "ECDH-ES+A256KW":
170
+ algorithm = { name: jwk.crv };
171
+ keyUsages = jwk.d ? ["deriveBits"] : [];
172
+ break;
173
+ default:
174
+ throw new JOSENotSupported(unsupportedAlg);
175
+ }
176
+ break;
177
+ }
178
+ default:
179
+ throw new JOSENotSupported('Invalid or unsupported JWK "kty" (Key Type) Parameter value');
180
+ }
181
+ return { algorithm, keyUsages };
182
+ }
183
+ async function jwkToKey(jwk) {
184
+ if (!jwk.alg) {
185
+ throw new TypeError('"alg" argument is required when "jwk.alg" is not present');
186
+ }
187
+ const { algorithm, keyUsages } = subtleMapping(jwk);
188
+ const keyData = { ...jwk };
189
+ if (keyData.kty !== "AKP") {
190
+ delete keyData.alg;
191
+ }
192
+ delete keyData.use;
193
+ return crypto.subtle.importKey("jwk", keyData, algorithm, jwk.ext ?? (jwk.d || jwk.priv ? false : true), jwk.key_ops ?? keyUsages);
194
+ }
195
+
196
+ // node_modules/jose/dist/webapi/key/import.js
197
+ async function importJWK(jwk, alg, options) {
198
+ if (!isObject(jwk)) {
199
+ throw new TypeError("JWK must be an object");
200
+ }
201
+ let ext;
202
+ ext ?? (ext = jwk.ext);
203
+ switch (jwk.kty) {
204
+ case "oct":
205
+ if (typeof jwk.k !== "string" || !jwk.k) {
206
+ throw new TypeError('missing "k" (Key Value) Parameter value');
207
+ }
208
+ return decode(jwk.k);
209
+ case "RSA":
210
+ if ("oth" in jwk && jwk.oth !== void 0) {
211
+ throw new JOSENotSupported('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');
212
+ }
213
+ return jwkToKey({ ...jwk, alg, ext });
214
+ case "AKP": {
215
+ if (typeof jwk.alg !== "string" || !jwk.alg) {
216
+ throw new TypeError('missing "alg" (Algorithm) Parameter value');
217
+ }
218
+ if (alg !== jwk.alg) {
219
+ throw new TypeError("JWK alg and alg option value mismatch");
220
+ }
221
+ return jwkToKey({ ...jwk, ext });
222
+ }
223
+ case "EC":
224
+ case "OKP":
225
+ return jwkToKey({ ...jwk, alg, ext });
226
+ default:
227
+ throw new JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');
228
+ }
229
+ }
230
+
231
+ // src/identity.ts
232
+ var DEFAULT_ISSUER = "https://me.ochk.io";
233
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
234
+ var ENVELOPE_JWKS_PATH = "/.well-known/oc-envelope-jwks.json";
235
+ var jwksCache = /* @__PURE__ */ new Map();
236
+ var inflight = /* @__PURE__ */ new Map();
237
+ async function fetchEnvelopeJwks(issuer, ttlMs) {
238
+ const cached = jwksCache.get(issuer);
239
+ if (cached && Date.now() - cached.fetchedAt < ttlMs) return cached;
240
+ const existing = inflight.get(issuer);
241
+ if (existing) return existing;
242
+ const p = (async () => {
243
+ try {
244
+ const url = `${issuer.replace(/\/$/, "")}${ENVELOPE_JWKS_PATH}`;
245
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
246
+ if (!res.ok) throw new Error(`envelope JWKS fetch ${url} returned ${res.status}`);
247
+ const json = await res.json();
248
+ if (!json || !Array.isArray(json.keys)) {
249
+ throw new Error(`envelope JWKS at ${url} did not return {keys:[\u2026]}`);
250
+ }
251
+ const keys = {};
252
+ for (const k of json.keys) {
253
+ if (typeof k.kid !== "string") continue;
254
+ try {
255
+ keys[k.kid] = await importJWK(k, "EdDSA");
256
+ } catch {
257
+ }
258
+ }
259
+ const entry = { keys, fetchedAt: Date.now() };
260
+ jwksCache.set(issuer, entry);
261
+ return entry;
262
+ } catch (err) {
263
+ const stale = jwksCache.get(issuer);
264
+ if (stale) return stale;
265
+ throw err;
266
+ }
267
+ })().finally(() => {
268
+ inflight.delete(issuer);
269
+ });
270
+ inflight.set(issuer, p);
271
+ return p;
272
+ }
273
+ function hexToBytes(hex) {
274
+ if (typeof hex !== "string") return null;
275
+ const s = hex.trim();
276
+ if (s.length === 0 || s.length % 2 !== 0) return null;
277
+ const out = new Uint8Array(s.length / 2);
278
+ for (let i = 0; i < out.length; i++) {
279
+ const b = parseInt(s.slice(i * 2, i * 2 + 2), 16);
280
+ if (Number.isNaN(b)) return null;
281
+ out[i] = b;
282
+ }
283
+ return out;
284
+ }
285
+ function base64ToBytes(b64) {
286
+ try {
287
+ const bin = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("binary");
288
+ const out = new Uint8Array(bin.length);
289
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
290
+ return out;
291
+ } catch {
292
+ return null;
293
+ }
294
+ }
295
+ function bytesToHex(bytes) {
296
+ let out = "";
297
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
298
+ return out;
299
+ }
300
+ async function sha256(bytes) {
301
+ const subtle = typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle ? globalThis.crypto.subtle : null;
302
+ if (!subtle) {
303
+ throw new Error("crypto.subtle unavailable \xB7 need Node 20+ / modern browser / Edge runtime");
304
+ }
305
+ const buf = await subtle.digest("SHA-256", bytes);
306
+ return new Uint8Array(buf);
307
+ }
308
+ function toArrayBuffer(view) {
309
+ const buf = new ArrayBuffer(view.byteLength);
310
+ new Uint8Array(buf).set(view);
311
+ return buf;
312
+ }
313
+ async function verifyEd25519(key, sig, msg) {
314
+ const subtle = typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle ? globalThis.crypto.subtle : null;
315
+ if (!subtle) return false;
316
+ try {
317
+ return await subtle.verify(
318
+ "Ed25519",
319
+ key,
320
+ toArrayBuffer(sig),
321
+ toArrayBuffer(msg)
322
+ );
323
+ } catch {
324
+ return false;
325
+ }
326
+ }
327
+ async function verifyActivityAttestation(bundle, options = {}) {
328
+ const checks = [];
329
+ const shapeOk = !!bundle && typeof bundle === "object" && typeof bundle.sig === "string" && typeof bundle.kid === "string" && typeof bundle.canonical_b64 === "string" && !!bundle.attestation && typeof bundle.attestation.id === "string" && typeof bundle.attestation.expires_at === "string";
330
+ checks.push({
331
+ ok: shapeOk,
332
+ label: "bundle shape \xB7 sig / kid / canonical_b64 / attestation"
333
+ });
334
+ if (!shapeOk) {
335
+ return { ok: false, reason: "bundle shape invalid", checks };
336
+ }
337
+ const a = bundle.attestation;
338
+ const canonical = base64ToBytes(bundle.canonical_b64);
339
+ checks.push({
340
+ ok: canonical !== null,
341
+ label: "canonical_b64 decodes",
342
+ detail: canonical ? `${canonical.length} bytes` : "invalid base64"
343
+ });
344
+ if (!canonical) return { ok: false, reason: "canonical_b64 invalid base64", checks, attestation: a };
345
+ const digest = await sha256(canonical);
346
+ const computedId = bytesToHex(digest);
347
+ const idOk = computedId === a.id;
348
+ checks.push({
349
+ ok: idOk,
350
+ label: "sha256(canonical_bytes) === id",
351
+ detail: idOk ? a.id : `expected ${a.id}, got ${computedId}`
352
+ });
353
+ if (!idOk) return { ok: false, reason: "id does not match canonical-bytes hash", checks, attestation: a };
354
+ let key = null;
355
+ if (options.jwk) {
356
+ if (options.jwk.kid !== bundle.kid) {
357
+ checks.push({
358
+ ok: false,
359
+ label: "JWK with matching kid present",
360
+ detail: `provided jwk.kid=${options.jwk.kid} but bundle.kid=${bundle.kid}`
361
+ });
362
+ return { ok: false, reason: "kid mismatch with provided jwk", checks, attestation: a };
363
+ }
364
+ try {
365
+ key = await importJWK(options.jwk, "EdDSA");
366
+ checks.push({ ok: true, label: "JWK with matching kid present", detail: `kid ${bundle.kid} (provided)` });
367
+ } catch (err) {
368
+ checks.push({
369
+ ok: false,
370
+ label: "JWK with matching kid present",
371
+ detail: `provided jwk failed import: ${err.message}`
372
+ });
373
+ return { ok: false, reason: "provided jwk failed import", checks, attestation: a };
374
+ }
375
+ } else {
376
+ const issuer = options.issuer ?? DEFAULT_ISSUER;
377
+ const ttl = options.jwksCacheTtlMs ?? DEFAULT_TTL_MS;
378
+ try {
379
+ let entry = await fetchEnvelopeJwks(issuer, ttl);
380
+ key = entry.keys[bundle.kid] ?? null;
381
+ if (!key) {
382
+ jwksCache.delete(issuer);
383
+ entry = await fetchEnvelopeJwks(issuer, ttl);
384
+ key = entry.keys[bundle.kid] ?? null;
385
+ }
386
+ checks.push({
387
+ ok: !!key,
388
+ label: "JWK with matching kid present",
389
+ detail: key ? `kid ${bundle.kid}` : `no JWK with kid ${bundle.kid} at ${issuer}${ENVELOPE_JWKS_PATH}`
390
+ });
391
+ if (!key) {
392
+ return { ok: false, reason: `kid ${bundle.kid} not in envelope JWKS`, checks, attestation: a };
393
+ }
394
+ } catch (err) {
395
+ checks.push({
396
+ ok: false,
397
+ label: "JWK with matching kid present",
398
+ detail: `envelope JWKS unavailable: ${err.message}`
399
+ });
400
+ return {
401
+ ok: false,
402
+ reason: `envelope JWKS unavailable: ${err.message}`,
403
+ checks,
404
+ attestation: a
405
+ };
406
+ }
407
+ }
408
+ const sigBytes = hexToBytes(bundle.sig);
409
+ if (!sigBytes) {
410
+ checks.push({
411
+ ok: false,
412
+ label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
413
+ detail: "malformed sig (expected hex)"
414
+ });
415
+ return { ok: false, reason: "malformed sig", checks, attestation: a };
416
+ }
417
+ const sigOk = await verifyEd25519(key, sigBytes, canonical);
418
+ checks.push({
419
+ ok: sigOk,
420
+ label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
421
+ detail: sigOk ? "signature valid" : "signature does not match"
422
+ });
423
+ if (!sigOk) return { ok: false, reason: "signature does not match", checks, attestation: a };
424
+ const now = options.now ?? /* @__PURE__ */ new Date();
425
+ const expiresMs = Date.parse(a.expires_at);
426
+ const freshOk = Number.isFinite(expiresMs) && expiresMs > now.getTime();
427
+ checks.push({
428
+ ok: freshOk,
429
+ label: "now < attestation.expires_at",
430
+ detail: freshOk ? `expires ${new Date(expiresMs).toISOString()}` : "attestation has expired"
431
+ });
432
+ if (!freshOk) return { ok: false, reason: "attestation has expired", checks, attestation: a };
433
+ return { ok: true, attestation: a, checks };
434
+ }
435
+ async function refreshEnvelopeJwks(issuer = DEFAULT_ISSUER) {
436
+ jwksCache.delete(issuer);
437
+ await fetchEnvelopeJwks(issuer, DEFAULT_TTL_MS);
438
+ }
439
+ var identity = {
440
+ verifyActivityAttestation,
441
+ refreshEnvelopeJwks
442
+ };
443
+
5
444
  // src/server.ts
6
445
  async function getOcSession(headers, options = {}) {
7
446
  return authCore.getOcSession(headers, options);
@@ -66,8 +505,11 @@ Object.defineProperty(exports, "verifyOcToken", {
66
505
  get: function () { return authCore.verifyOcToken; }
67
506
  });
68
507
  exports.getOcSession = getOcSession;
508
+ exports.identity = identity;
69
509
  exports.ocAuthExpress = ocAuthExpress;
70
510
  exports.ocAuthHono = ocAuthHono;
511
+ exports.refreshEnvelopeJwks = refreshEnvelopeJwks;
512
+ exports.verifyActivityAttestation = verifyActivityAttestation;
71
513
  exports.withOcAuth = withOcAuth;
72
514
  //# sourceMappingURL=server.js.map
73
515
  //# sourceMappingURL=server.js.map