@productcraft/auth 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { PC_BASE_URL } from '@productcraft/core';
1
+ import { PC_BASE_URL, resolveAuth, readEnv, PC_AUTH_APP_ENV } from '@productcraft/core';
2
2
  import { createRemoteJWKSet, errors, jwtVerify, customFetch } from 'jose';
3
3
 
4
4
  // src/index.ts
@@ -763,11 +763,15 @@ function getApiKeyControllerListApiKeysUrl({
763
763
  const res = { method: "GET", url: `/v1/apps/${app_id}/api-keys` };
764
764
  return res;
765
765
  }
766
- async function apiKeyControllerListApiKeys({ appId }, config = {}) {
766
+ async function apiKeyControllerListApiKeys({
767
+ appId,
768
+ params
769
+ }, config = {}) {
767
770
  const { client: request = client, ...requestConfig } = config;
768
771
  const res = await request({
769
772
  method: "GET",
770
773
  url: getApiKeyControllerListApiKeysUrl({ appId }).url.toString(),
774
+ params,
771
775
  ...requestConfig
772
776
  });
773
777
  return res.data;
@@ -1029,7 +1033,9 @@ async function appAuditControllerGetAuditLogs({
1029
1033
  limit: params.limit,
1030
1034
  cursor: params.cursor,
1031
1035
  action: params.action,
1032
- actor_id: params.actorId
1036
+ actor_id: params.actorId,
1037
+ since: params.since,
1038
+ until: params.until
1033
1039
  } : void 0;
1034
1040
  const res = await request({
1035
1041
  method: "GET",
@@ -1555,6 +1561,220 @@ async function consumerIdpControllerNativeSignIn({
1555
1561
  return res.data;
1556
1562
  }
1557
1563
 
1564
+ // src/_generated/clients/consumerAuth/consumerAuthControllerPasskeyOptions.ts
1565
+ function getConsumerAuthControllerPasskeyOptionsUrl({
1566
+ appSlug
1567
+ }) {
1568
+ const app_slug = appSlug;
1569
+ const res = {
1570
+ method: "POST",
1571
+ url: `/${app_slug}/v1/auth/passkey/options`
1572
+ };
1573
+ return res;
1574
+ }
1575
+ async function consumerAuthControllerPasskeyOptions({
1576
+ appSlug,
1577
+ data
1578
+ }, config = {}) {
1579
+ const { client: request = client, ...requestConfig } = config;
1580
+ const requestData = data;
1581
+ const res = await request({
1582
+ method: "POST",
1583
+ url: getConsumerAuthControllerPasskeyOptionsUrl({ appSlug }).url.toString(),
1584
+ data: requestData,
1585
+ ...requestConfig
1586
+ });
1587
+ return res.data;
1588
+ }
1589
+
1590
+ // src/_generated/clients/consumerAuth/consumerAuthControllerPasskeyVerify.ts
1591
+ function getConsumerAuthControllerPasskeyVerifyUrl({
1592
+ appSlug
1593
+ }) {
1594
+ const app_slug = appSlug;
1595
+ const res = {
1596
+ method: "POST",
1597
+ url: `/${app_slug}/v1/auth/passkey/verify`
1598
+ };
1599
+ return res;
1600
+ }
1601
+ async function consumerAuthControllerPasskeyVerify({
1602
+ appSlug,
1603
+ data
1604
+ }, config = {}) {
1605
+ const { client: request = client, ...requestConfig } = config;
1606
+ const requestData = data;
1607
+ const res = await request({
1608
+ method: "POST",
1609
+ url: getConsumerAuthControllerPasskeyVerifyUrl({ appSlug }).url.toString(),
1610
+ data: requestData,
1611
+ ...requestConfig
1612
+ });
1613
+ return res.data;
1614
+ }
1615
+
1616
+ // src/_generated/clients/consumerAuth/consumerAuthControllerMfaWebauthnOptions.ts
1617
+ function getConsumerAuthControllerMfaWebauthnOptionsUrl({
1618
+ appSlug
1619
+ }) {
1620
+ const app_slug = appSlug;
1621
+ const res = {
1622
+ method: "POST",
1623
+ url: `/${app_slug}/v1/auth/mfa/webauthn/options`
1624
+ };
1625
+ return res;
1626
+ }
1627
+ async function consumerAuthControllerMfaWebauthnOptions({
1628
+ appSlug,
1629
+ data
1630
+ }, config = {}) {
1631
+ const { client: request = client, ...requestConfig } = config;
1632
+ const requestData = data;
1633
+ const res = await request({
1634
+ method: "POST",
1635
+ url: getConsumerAuthControllerMfaWebauthnOptionsUrl({
1636
+ appSlug
1637
+ }).url.toString(),
1638
+ data: requestData,
1639
+ ...requestConfig
1640
+ });
1641
+ return res.data;
1642
+ }
1643
+
1644
+ // src/_generated/clients/consumerAuth/consumerAuthControllerMfaVerify.ts
1645
+ function getConsumerAuthControllerMfaVerifyUrl({
1646
+ appSlug
1647
+ }) {
1648
+ const app_slug = appSlug;
1649
+ const res = {
1650
+ method: "POST",
1651
+ url: `/${app_slug}/v1/auth/mfa/verify`
1652
+ };
1653
+ return res;
1654
+ }
1655
+ async function consumerAuthControllerMfaVerify({
1656
+ appSlug,
1657
+ data
1658
+ }, config = {}) {
1659
+ const { client: request = client, ...requestConfig } = config;
1660
+ const requestData = data;
1661
+ const res = await request({
1662
+ method: "POST",
1663
+ url: getConsumerAuthControllerMfaVerifyUrl({ appSlug }).url.toString(),
1664
+ data: requestData,
1665
+ ...requestConfig
1666
+ });
1667
+ return res.data;
1668
+ }
1669
+
1670
+ // src/_generated/clients/consumerMfa/consumerMfaControllerStartEnrollment.ts
1671
+ function getConsumerMfaControllerStartEnrollmentUrl({
1672
+ appSlug
1673
+ }) {
1674
+ const app_slug = appSlug;
1675
+ const res = {
1676
+ method: "POST",
1677
+ url: `/${app_slug}/v1/me/mfa/factors`
1678
+ };
1679
+ return res;
1680
+ }
1681
+ async function consumerMfaControllerStartEnrollment({
1682
+ appSlug,
1683
+ data
1684
+ }, config = {}) {
1685
+ const { client: request = client, ...requestConfig } = config;
1686
+ const requestData = data;
1687
+ const res = await request({
1688
+ method: "POST",
1689
+ url: getConsumerMfaControllerStartEnrollmentUrl({ appSlug }).url.toString(),
1690
+ data: requestData,
1691
+ ...requestConfig
1692
+ });
1693
+ return res.data;
1694
+ }
1695
+
1696
+ // src/_generated/clients/consumerMfa/consumerMfaControllerConfirmEnrollment.ts
1697
+ function getConsumerMfaControllerConfirmEnrollmentUrl({
1698
+ id,
1699
+ appSlug
1700
+ }) {
1701
+ const app_slug = appSlug;
1702
+ const res = {
1703
+ method: "POST",
1704
+ url: `/${app_slug}/v1/me/mfa/factors/${id}/verify`
1705
+ };
1706
+ return res;
1707
+ }
1708
+ async function consumerMfaControllerConfirmEnrollment({
1709
+ id,
1710
+ appSlug,
1711
+ data
1712
+ }, config = {}) {
1713
+ const { client: request = client, ...requestConfig } = config;
1714
+ const requestData = data;
1715
+ const res = await request({
1716
+ method: "POST",
1717
+ url: getConsumerMfaControllerConfirmEnrollmentUrl({
1718
+ id,
1719
+ appSlug
1720
+ }).url.toString(),
1721
+ data: requestData,
1722
+ ...requestConfig
1723
+ });
1724
+ return res.data;
1725
+ }
1726
+
1727
+ // src/_generated/clients/consumerMfa/consumerMfaControllerStepUpWebauthnOptions.ts
1728
+ function getConsumerMfaControllerStepUpWebauthnOptionsUrl({
1729
+ appSlug
1730
+ }) {
1731
+ const app_slug = appSlug;
1732
+ const res = {
1733
+ method: "POST",
1734
+ url: `/${app_slug}/v1/me/mfa/step-up/webauthn/options`
1735
+ };
1736
+ return res;
1737
+ }
1738
+ async function consumerMfaControllerStepUpWebauthnOptions({
1739
+ appSlug
1740
+ }, config = {}) {
1741
+ const { client: request = client, ...requestConfig } = config;
1742
+ const res = await request({
1743
+ method: "POST",
1744
+ url: getConsumerMfaControllerStepUpWebauthnOptionsUrl({
1745
+ appSlug
1746
+ }).url.toString(),
1747
+ ...requestConfig
1748
+ });
1749
+ return res.data;
1750
+ }
1751
+
1752
+ // src/_generated/clients/consumerMfa/consumerMfaControllerStepUp.ts
1753
+ function getConsumerMfaControllerStepUpUrl({
1754
+ appSlug
1755
+ }) {
1756
+ const app_slug = appSlug;
1757
+ const res = {
1758
+ method: "POST",
1759
+ url: `/${app_slug}/v1/me/mfa/step-up`
1760
+ };
1761
+ return res;
1762
+ }
1763
+ async function consumerMfaControllerStepUp({
1764
+ appSlug,
1765
+ data
1766
+ }, config = {}) {
1767
+ const { client: request = client, ...requestConfig } = config;
1768
+ const requestData = data;
1769
+ const res = await request({
1770
+ method: "POST",
1771
+ url: getConsumerMfaControllerStepUpUrl({ appSlug }).url.toString(),
1772
+ data: requestData,
1773
+ ...requestConfig
1774
+ });
1775
+ return res.data;
1776
+ }
1777
+
1558
1778
  // src/_generated/clients/consumerVerify/consumerVerifyControllerVerify.ts
1559
1779
  function getConsumerVerifyControllerVerifyUrl({
1560
1780
  appSlug
@@ -1660,6 +1880,165 @@ async function consumerOAuthControllerClientCredentials({
1660
1880
  return res.data;
1661
1881
  }
1662
1882
 
1883
+ // src/passkeys.ts
1884
+ function toB64u(buf) {
1885
+ const bytes = buf instanceof ArrayBuffer ? new Uint8Array(buf) : new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
1886
+ let bin = "";
1887
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
1888
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1889
+ }
1890
+ function fromB64u(s) {
1891
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
1892
+ const bin = atob(b64.padEnd(Math.ceil(b64.length / 4) * 4, "="));
1893
+ return Uint8Array.from(bin, (c) => c.charCodeAt(0));
1894
+ }
1895
+ var snakeToCamel = (k) => k.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
1896
+ function camelizeKeys(value) {
1897
+ if (Array.isArray(value)) return value.map(camelizeKeys);
1898
+ if (value && typeof value === "object" && !(value instanceof Uint8Array)) {
1899
+ const out = {};
1900
+ for (const [k, v] of Object.entries(value)) {
1901
+ out[snakeToCamel(k)] = camelizeKeys(v);
1902
+ }
1903
+ return out;
1904
+ }
1905
+ return value;
1906
+ }
1907
+ function decodeOptions(publicKey) {
1908
+ const pk = camelizeKeys(publicKey);
1909
+ const out = {
1910
+ ...pk,
1911
+ challenge: fromB64u(pk.challenge)
1912
+ };
1913
+ if (pk.user) {
1914
+ out.user = { ...pk.user, id: fromB64u(pk.user.id) };
1915
+ }
1916
+ for (const list of ["excludeCredentials", "allowCredentials"]) {
1917
+ const entries = pk[list];
1918
+ if (entries) {
1919
+ out[list] = entries.map((c) => ({ ...c, id: fromB64u(c.id) }));
1920
+ }
1921
+ }
1922
+ return out;
1923
+ }
1924
+ function encodeCredential(cred) {
1925
+ const r = cred.response;
1926
+ const response = {
1927
+ clientDataJSON: toB64u(r.clientDataJSON)
1928
+ };
1929
+ if (r.attestationObject) response.attestationObject = toB64u(r.attestationObject);
1930
+ if (r.authenticatorData) response.authenticatorData = toB64u(r.authenticatorData);
1931
+ if (r.signature) response.signature = toB64u(r.signature);
1932
+ if (r.userHandle) response.userHandle = toB64u(r.userHandle);
1933
+ return { id: cred.id, response };
1934
+ }
1935
+ var PasskeyError = class extends Error {
1936
+ code;
1937
+ cause;
1938
+ constructor(code, message, cause) {
1939
+ super(message);
1940
+ this.name = "PasskeyError";
1941
+ this.code = code;
1942
+ this.cause = cause;
1943
+ }
1944
+ };
1945
+ function browserCredentials() {
1946
+ const nav = globalThis.navigator;
1947
+ const creds = nav?.credentials;
1948
+ if (!creds || typeof creds.create !== "function" || typeof creds.get !== "function") {
1949
+ return void 0;
1950
+ }
1951
+ return creds;
1952
+ }
1953
+ function isPasskeySupported() {
1954
+ const g = globalThis;
1955
+ if (typeof g.window === "undefined") return false;
1956
+ if (typeof g.PublicKeyCredential !== "function") return false;
1957
+ if (g.isSecureContext === false) return false;
1958
+ return browserCredentials() !== void 0;
1959
+ }
1960
+ function mapDomError(err) {
1961
+ const name = err?.name;
1962
+ const message = err?.message ?? String(err);
1963
+ switch (name) {
1964
+ case "NotAllowedError":
1965
+ return new PasskeyError(
1966
+ "cancelled",
1967
+ `Passkey prompt was cancelled, timed out, or no passkey matched: ${message}`,
1968
+ err
1969
+ );
1970
+ case "InvalidStateError":
1971
+ return new PasskeyError("invalid_state", `Authenticator refused: ${message}`, err);
1972
+ case "SecurityError":
1973
+ return new PasskeyError(
1974
+ "security",
1975
+ `Page origin does not match the app's rp_id: ${message}`,
1976
+ err
1977
+ );
1978
+ case "AbortError":
1979
+ return new PasskeyError("cancelled", `Passkey ceremony aborted: ${message}`, err);
1980
+ case "TimeoutError":
1981
+ return new PasskeyError(
1982
+ "timeout",
1983
+ `Passkey prompt timed out before a credential was produced: ${message}`,
1984
+ err
1985
+ );
1986
+ default:
1987
+ return new PasskeyError("unknown", `navigator.credentials failed: ${message}`, err);
1988
+ }
1989
+ }
1990
+ function resolveCredentials(opts = {}) {
1991
+ const creds = opts.credentials ?? browserCredentials();
1992
+ if (!creds) {
1993
+ throw new PasskeyError(
1994
+ "unsupported",
1995
+ "navigator.credentials is not available \u2014 passkeys need a browser with WebAuthn (check isPasskeySupported())"
1996
+ );
1997
+ }
1998
+ return creds;
1999
+ }
2000
+ function isCredentialLike(value) {
2001
+ if (!value || typeof value !== "object") return false;
2002
+ const v = value;
2003
+ return typeof v.id === "string" && !!v.response && typeof v.response === "object";
2004
+ }
2005
+ async function createPasskeyCredential(publicKey, opts = {}) {
2006
+ const creds = resolveCredentials(opts);
2007
+ let cred;
2008
+ try {
2009
+ cred = await creds.create({ publicKey: decodeOptions(publicKey), signal: opts.signal });
2010
+ } catch (err) {
2011
+ throw mapDomError(err);
2012
+ }
2013
+ if (!isCredentialLike(cred)) {
2014
+ throw new PasskeyError(
2015
+ "no_credential",
2016
+ "navigator.credentials.create() returned no usable credential"
2017
+ );
2018
+ }
2019
+ return encodeCredential(cred);
2020
+ }
2021
+ async function getPasskeyCredential(publicKey, opts = {}) {
2022
+ const creds = resolveCredentials(opts);
2023
+ let cred;
2024
+ try {
2025
+ cred = await creds.get({
2026
+ publicKey: decodeOptions(publicKey),
2027
+ mediation: opts.mediation,
2028
+ signal: opts.signal
2029
+ });
2030
+ } catch (err) {
2031
+ throw mapDomError(err);
2032
+ }
2033
+ if (!isCredentialLike(cred)) {
2034
+ throw new PasskeyError(
2035
+ "no_credential",
2036
+ "navigator.credentials.get() returned no usable credential"
2037
+ );
2038
+ }
2039
+ return encodeCredential(cred);
2040
+ }
2041
+
1663
2042
  // src/jwt/errors.ts
1664
2043
  var JwtVerifyError = class extends Error {
1665
2044
  code;
@@ -1689,6 +2068,12 @@ var JwtNotYetValidError = class extends JwtVerifyError {
1689
2068
  this.name = "JwtNotYetValidError";
1690
2069
  }
1691
2070
  };
2071
+ var JwtRefreshTokenError = class extends JwtVerifyError {
2072
+ constructor(message = "Refresh tokens cannot be used as access tokens", options) {
2073
+ super("ERR_JWT_REFRESH_TOKEN", message, options);
2074
+ this.name = "JwtRefreshTokenError";
2075
+ }
2076
+ };
1692
2077
  var JwtIssuerMismatchError = class extends JwtVerifyError {
1693
2078
  constructor(message, options) {
1694
2079
  super("ERR_JWT_ISS_MISMATCH", message, options);
@@ -1769,6 +2154,10 @@ var JwksCache = class {
1769
2154
  refresh() {
1770
2155
  }
1771
2156
  };
2157
+ function isRefreshToken(claims) {
2158
+ if (!claims) return false;
2159
+ return claims.token_use === "refresh" || claims.type === "refresh";
2160
+ }
1772
2161
  async function verifyAuthToken(token, getKey, opts = {}) {
1773
2162
  try {
1774
2163
  const { payload } = await jwtVerify(token, getKey, {
@@ -1780,6 +2169,7 @@ async function verifyAuthToken(token, getKey, opts = {}) {
1780
2169
  currentDate: opts.currentDate,
1781
2170
  maxTokenAge: opts.maxTokenAge
1782
2171
  });
2172
+ if (isRefreshToken(payload)) throw new JwtRefreshTokenError();
1783
2173
  return payload;
1784
2174
  } catch (err) {
1785
2175
  throw translateJoseError(err);
@@ -2114,6 +2504,141 @@ var ConsumerScope = class {
2114
2504
  )
2115
2505
  };
2116
2506
  // ─────────────────────────────────────────────────────────────
2507
+ // Passkeys — WebAuthn ceremonies, one call each. BROWSER ONLY at
2508
+ // call time (they drive navigator.credentials); importing is safe
2509
+ // anywhere. Base64url in both directions is handled here; the
2510
+ // wire stays snake_case except the spec-fixed response members
2511
+ // (clientDataJSON, attestationObject, authenticatorData, signature,
2512
+ // userHandle), which travel verbatim.
2513
+ // ─────────────────────────────────────────────────────────────
2514
+ passkeys = {
2515
+ /**
2516
+ * Enrol a passkey as an MFA factor for the signed-in end-user.
2517
+ * Requires the scope to carry the user's access token
2518
+ * (`new Auth({ auth: { type: "bearer", token } })`).
2519
+ *
2520
+ * `POST /me/mfa/factors { type: "webauthn" }` →
2521
+ * `navigator.credentials.create()` →
2522
+ * `POST /me/mfa/factors/:id/verify { credential }`.
2523
+ *
2524
+ * Resolves with the now-enabled factor and the user's recovery
2525
+ * codes — show those once, they are not retrievable later. The
2526
+ * factor stays disabled until the verify call succeeds, so an
2527
+ * abandoned prompt leaves nothing usable behind.
2528
+ */
2529
+ enroll: async (opts = {}) => {
2530
+ const credentials = resolveCredentials(opts);
2531
+ const start = await consumerMfaControllerStartEnrollment(
2532
+ {
2533
+ appSlug: this.appSlug,
2534
+ data: { type: "webauthn", ...opts.label ? { label: opts.label } : {} }
2535
+ },
2536
+ { client: this.client }
2537
+ );
2538
+ const publicKey = start.enrollment.public_key;
2539
+ const credential = await createPasskeyCredential(publicKey, { ...opts, credentials });
2540
+ return await consumerMfaControllerConfirmEnrollment(
2541
+ { appSlug: this.appSlug, id: start.factor.id, data: { credential } },
2542
+ { client: this.client }
2543
+ );
2544
+ },
2545
+ /**
2546
+ * Passwordless sign-in with a discoverable passkey. No password,
2547
+ * no email address: the authenticator finds its own passkey for
2548
+ * the app's rp_id.
2549
+ *
2550
+ * `POST /auth/passkey/options {}` → `navigator.credentials.get()`
2551
+ * → `POST /auth/passkey/verify { credential }`.
2552
+ *
2553
+ * Resolves with the session (`access_token`, `refresh_token`,
2554
+ * …; `amr: ["webauthn"]`). Rejects with `PasskeyError`
2555
+ * (`code: "cancelled"`) when the user dismisses the prompt or no
2556
+ * passkey matches, and with `AuthHttpError` 401 for a forged,
2557
+ * unknown, replayed or expired assertion — the server answers
2558
+ * those uniformly on purpose.
2559
+ *
2560
+ * The options call deliberately takes no identifier (it would be
2561
+ * an account-existence oracle); `tenant_id` is the only
2562
+ * accepted body member and only matters for multi-tenant apps.
2563
+ * Pass `mediation: "conditional"` for autofill-style UI.
2564
+ */
2565
+ signIn: async (opts = {}) => {
2566
+ const credentials = resolveCredentials(opts);
2567
+ const { public_key } = await consumerAuthControllerPasskeyOptions(
2568
+ {
2569
+ appSlug: this.appSlug,
2570
+ data: opts.tenant_id ? { tenant_id: opts.tenant_id } : {}
2571
+ },
2572
+ { client: this.client }
2573
+ );
2574
+ const credential = await getPasskeyCredential(
2575
+ public_key,
2576
+ { ...opts, credentials }
2577
+ );
2578
+ return await consumerAuthControllerPasskeyVerify(
2579
+ { appSlug: this.appSlug, data: { credential } },
2580
+ { client: this.client }
2581
+ );
2582
+ },
2583
+ /**
2584
+ * Satisfy the MFA step of a password sign-in with a passkey.
2585
+ * Call it when `auth.signin()` answered
2586
+ * `{ mfa_required: true, mfa_token, factors }` and `factors`
2587
+ * contains a `webauthn` entry.
2588
+ *
2589
+ * `POST /auth/mfa/webauthn/options { mfa_token }` →
2590
+ * `navigator.credentials.get()` →
2591
+ * `POST /auth/mfa/verify { mfa_token, credential }`.
2592
+ *
2593
+ * Resolves with the session (`amr: ["pwd", "webauthn"]`, fresh
2594
+ * `mfa_at`). Possession of the `mfa_token` — a correct password
2595
+ * — is the authority for the options call.
2596
+ */
2597
+ completeMfaChallenge: async (args) => {
2598
+ const { mfa_token, ...browserOpts } = args;
2599
+ const credentials = resolveCredentials(browserOpts);
2600
+ const { public_key } = await consumerAuthControllerMfaWebauthnOptions(
2601
+ { appSlug: this.appSlug, data: { mfa_token } },
2602
+ { client: this.client }
2603
+ );
2604
+ const credential = await getPasskeyCredential(
2605
+ public_key,
2606
+ { ...browserOpts, credentials }
2607
+ );
2608
+ return await consumerAuthControllerMfaVerify(
2609
+ { appSlug: this.appSlug, data: { mfa_token, credential } },
2610
+ { client: this.client }
2611
+ );
2612
+ },
2613
+ /**
2614
+ * Re-confirm the signed-in user's identity inside a live session
2615
+ * (before a sensitive action). Requires the scope to carry the
2616
+ * user's access token.
2617
+ *
2618
+ * `POST /me/mfa/step-up/webauthn/options` →
2619
+ * `navigator.credentials.get()` →
2620
+ * `POST /me/mfa/step-up { credential }`.
2621
+ *
2622
+ * Resolves with `{ amr, mfa_at }`. A passkey assertion is never
2623
+ * tried as a recovery code, so a failed step-up never burns one.
2624
+ */
2625
+ stepUp: async (opts = {}) => {
2626
+ const credentials = resolveCredentials(opts);
2627
+ const { public_key } = await consumerMfaControllerStepUpWebauthnOptions(
2628
+ { appSlug: this.appSlug },
2629
+ { client: this.client }
2630
+ );
2631
+ const credential = await getPasskeyCredential(
2632
+ public_key,
2633
+ { ...opts, credentials }
2634
+ );
2635
+ return await consumerMfaControllerStepUp(
2636
+ { appSlug: this.appSlug, data: { credential } },
2637
+ { client: this.client }
2638
+ );
2639
+ }
2640
+ };
2641
+ // ─────────────────────────────────────────────────────────────
2117
2642
  // OAuth M2M — client_credentials grant for service-to-service tokens
2118
2643
  // ─────────────────────────────────────────────────────────────
2119
2644
  oauth = {
@@ -2132,9 +2657,9 @@ function getAppControllerListMyAppsUrl() {
2132
2657
  async function appControllerListMyApps({ params } = {}, config = {}) {
2133
2658
  const { client: request = client, ...requestConfig } = config;
2134
2659
  const mappedParams = params ? {
2660
+ workspace_id: params.workspaceId,
2135
2661
  limit: params.limit,
2136
- cursor: params.cursor,
2137
- workspace_id: params.workspaceId
2662
+ cursor: params.cursor
2138
2663
  } : void 0;
2139
2664
  const res = await request({
2140
2665
  method: "GET",
@@ -2186,7 +2711,15 @@ async function statsControllerGetMyStats({ params } = {}, config = {}) {
2186
2711
 
2187
2712
  // src/index.ts
2188
2713
  var Auth = class {
2714
+ /** Admin client: explicit `auth`, else `PRODUCTCRAFT_API_KEY`. */
2189
2715
  client;
2716
+ /**
2717
+ * Consumer client: explicit `auth` only. Consumer routes act for an
2718
+ * end user, so a workspace key from the environment must never leak
2719
+ * onto them. Pass `auth` explicitly for PAK-only consumer routes
2720
+ * (`signup_requires_pak`, tenants, code mint, factors).
2721
+ */
2722
+ consumerClient;
2190
2723
  baseUrl;
2191
2724
  fetch;
2192
2725
  jwtConfig;
@@ -2194,6 +2727,11 @@ var Auth = class {
2194
2727
  this.baseUrl = config.baseUrl ?? PC_BASE_URL.auth;
2195
2728
  this.fetch = config.fetch;
2196
2729
  this.client = makeAuthHttpClient({
2730
+ baseUrl: this.baseUrl,
2731
+ auth: resolveAuth(config.auth),
2732
+ fetch: this.fetch
2733
+ });
2734
+ this.consumerClient = makeAuthHttpClient({
2197
2735
  baseUrl: this.baseUrl,
2198
2736
  auth: config.auth,
2199
2737
  fetch: this.fetch
@@ -2205,7 +2743,8 @@ var Auth = class {
2205
2743
  /**
2206
2744
  * Returns an `AppScope` bound to the given appId. All operations under
2207
2745
  * `/v1/apps/{appId}/...` are exposed as resource namespaces on the
2208
- * returned object.
2746
+ * returned object. Uses the explicit `auth`, else
2747
+ * `PRODUCTCRAFT_API_KEY` from the environment.
2209
2748
  */
2210
2749
  app(appId) {
2211
2750
  return new AppScope(appId, this.client);
@@ -2214,11 +2753,21 @@ var Auth = class {
2214
2753
  * Returns a `ConsumerScope` bound to the given appSlug. Exposes the
2215
2754
  * `/{appSlug}/v1/...` surface (sign-in, sign-up, me, verify, oauth)
2216
2755
  * and the JWT-verify helper for tokens issued by this app.
2756
+ *
2757
+ * `appSlug` defaults to `PRODUCTCRAFT_AUTH_APP` from the environment.
2758
+ * No credential is ever read from the environment for consumer
2759
+ * routes; only an explicit `auth` is sent.
2217
2760
  */
2218
2761
  consumer(appSlug) {
2762
+ const slug = appSlug ?? readEnv(PC_AUTH_APP_ENV);
2763
+ if (!slug) {
2764
+ throw new Error(
2765
+ `@productcraft/auth: consumer() needs an app slug. Pass one, or set ${PC_AUTH_APP_ENV}.`
2766
+ );
2767
+ }
2219
2768
  return new ConsumerScope(
2220
- appSlug,
2221
- { client: this.client, baseUrl: this.baseUrl, fetch: this.fetch },
2769
+ slug,
2770
+ { client: this.consumerClient, baseUrl: this.baseUrl, fetch: this.fetch },
2222
2771
  this.jwtConfig
2223
2772
  );
2224
2773
  }
@@ -2257,6 +2806,6 @@ var Auth = class {
2257
2806
  };
2258
2807
  };
2259
2808
 
2260
- export { AUTH_LEGACY_ISSUER, AppScope, Auth, AuthHttpError, ConsumerScope, JwksCache, JwksFetchError, JwksKeyNotFoundError, JwtAudienceMismatchError, JwtExpiredError, JwtInvalidError, JwtIssuerMismatchError, JwtNotYetValidError, JwtVerifyError, verifyAuthToken };
2809
+ export { AUTH_LEGACY_ISSUER, AppScope, Auth, AuthHttpError, ConsumerScope, JwksCache, JwksFetchError, JwksKeyNotFoundError, JwtAudienceMismatchError, JwtExpiredError, JwtInvalidError, JwtIssuerMismatchError, JwtNotYetValidError, JwtRefreshTokenError, JwtVerifyError, PasskeyError, createPasskeyCredential, decodeOptions, encodeCredential, fromB64u, getPasskeyCredential, isPasskeySupported, isRefreshToken, toB64u, verifyAuthToken };
2261
2810
  //# sourceMappingURL=index.js.map
2262
2811
  //# sourceMappingURL=index.js.map