@productcraft/auth 0.5.1 → 0.6.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
@@ -90,8 +90,11 @@ await consumer.auth.signup({
90
90
 
91
91
  await consumer.auth.refresh({ refresh_token });
92
92
  await consumer.auth.logout({ refresh_token });
93
- await consumer.auth.requestReset({ email });
94
- await consumer.auth.resetPassword({ token, new_password: "..." });
93
+
94
+ // Forgot password. `sendPasswordResetEmail` mints the code AND emails it
95
+ // via Mail — this is what you want for a normal reset flow.
96
+ await consumer.auth.sendPasswordResetEmail({ email });
97
+ await consumer.auth.resetPassword({ code, new_password: "..." });
95
98
 
96
99
  // Sign in with Apple (native iOS flow). See "Federated sign-in" below.
97
100
  await consumer.auth.signinWithProvider({
@@ -116,6 +119,27 @@ await consumer.oauth.clientCredentials({ clientId, clientSecret, scope });
116
119
 
117
120
  > **Wire-shape note.** Auth's JSON wire is snake_case, and the SDK returns response objects as-is (e.g. `access_token`, `refresh_token`, `token_type`, `expires_in`). Request DTOs follow the same convention; some convenience fields (`identifier`, `password`, `nonce`) are unaffected, but anywhere the spec uses an underscore the SDK does too.
118
121
 
122
+ ### One-time codes: who sends the email?
123
+
124
+ Verification and password-reset each come in two flavours. **Picking the wrong one is silent** — you get a `2xx` and no email is ever sent — so choose deliberately:
125
+
126
+ | Method | Mints a code | Sends the email | Returns |
127
+ | --- | --- | --- | --- |
128
+ | `auth.requestVerification()` | ✅ | ❌ | `{ code, expires_at }` |
129
+ | `auth.sendVerificationEmail()` | ✅ | ✅ via Mail | `{ expires_at }` |
130
+ | `auth.requestReset()` | ✅ | ❌ | `{ code, expires_at }` |
131
+ | `auth.sendPasswordResetEmail()` | ✅ | ✅ via Mail | `{ expires_at }` |
132
+
133
+ **Use `send*` for a normal flow.** Mail delivers the code using your app's configured sender and template, and you never handle the plaintext code.
134
+
135
+ **Use `request*` only when you are delivering the code yourself** — your own SMTP, an SMS provider, or consuming it in-process (e.g. provisioning a test account). If you call `request*` and drop the returned `code` on the floor, your users get nothing and nothing is logged anywhere, because no send was ever attempted.
136
+
137
+ Each pair is gated by a *separate* PAK permission (`auth.user.verify.create` vs `auth.user.verify.send-email`, and likewise for `password-reset`), so a workspace can enable minting without enabling outbound mail.
138
+
139
+ The `send*` methods fail loudly with a typed `412` when Mail isn't ready — `ENVOI_NOT_ENABLED`, `ENVOI_SENDER_NOT_CONFIGURED`, `ENVOI_TEMPLATE_NOT_CONFIGURED`, `ENVOI_APP_NOT_BOUND_TO_WORKSPACE` — or `503 ENVOI_DISPATCH_FAILED`. If your endpoint returns a uniform response to prevent account enumeration, catch and **log** these rather than discarding them; a swallowed 412 is indistinguishable from the silent failure above.
140
+
141
+ Both `request*` and `send*` return `{}` when the contact doesn't match (or is already verified / unverified as appropriate). That's the anti-enumeration contract, not an error — there is nothing to branch on.
142
+
119
143
  ## Federated sign-in (Apple, Google)
120
144
 
121
145
  `consumer.auth.signinWithProvider` lets a BFF exchange a provider-issued identity token for an Auth `{ access_token, refresh_token }` pair. The endpoint creates the EndUser on first sign-in and returns the same account on subsequent ones — the SDK is a thin wrapper over Auth's `POST /{appSlug}/v1/auth/oauth/{provider}` which does the heavy lifting (Apple JWKS verification, issuer pinning, audience validation against the app's configured native client ids, server-side nonce replay protection, account creation / linking).
package/dist/index.cjs CHANGED
@@ -1361,6 +1361,36 @@ async function consumerAuthControllerRequestPasswordReset({
1361
1361
  return res.data;
1362
1362
  }
1363
1363
 
1364
+ // src/_generated/clients/consumerAuth/consumerAuthControllerSendPasswordResetEmail.ts
1365
+ function getConsumerAuthControllerSendPasswordResetEmailUrl({
1366
+ appSlug
1367
+ }) {
1368
+ const app_slug = appSlug;
1369
+ const res = {
1370
+ method: "POST",
1371
+ url: `/${app_slug}/v1/auth/send-password-reset-email`
1372
+ };
1373
+ return res;
1374
+ }
1375
+ async function consumerAuthControllerSendPasswordResetEmail({
1376
+ appSlug,
1377
+ data,
1378
+ headers
1379
+ }, config = {}) {
1380
+ const { client: request = client, ...requestConfig } = config;
1381
+ const requestData = data;
1382
+ const res = await request({
1383
+ method: "POST",
1384
+ url: getConsumerAuthControllerSendPasswordResetEmailUrl({
1385
+ appSlug
1386
+ }).url.toString(),
1387
+ data: requestData,
1388
+ ...requestConfig,
1389
+ headers: { ...headers, ...requestConfig.headers }
1390
+ });
1391
+ return res.data;
1392
+ }
1393
+
1364
1394
  // src/_generated/clients/consumerAuth/consumerAuthControllerResetPassword.ts
1365
1395
  function getConsumerAuthControllerResetPasswordUrl({
1366
1396
  appSlug
@@ -1887,7 +1917,17 @@ var ConsumerScope = class {
1887
1917
  { client: this.client }
1888
1918
  ),
1889
1919
  /**
1890
- * PAK-required: caller must instantiate `Auth` with
1920
+ * Mint a 6-digit password-reset code. **Sends no email.** Returns
1921
+ * `{ code, expires_at }` for delivery via your own channel — or
1922
+ * `{}` when the contact doesn't match a verified account (uniform
1923
+ * shape — no account enumeration).
1924
+ *
1925
+ * If you want Mail to deliver the code for you, call
1926
+ * {@link sendPasswordResetEmail} instead. Calling this one and
1927
+ * discarding the `code` means the user never hears anything.
1928
+ *
1929
+ * PAK-required with `auth.user.password-reset.create`: caller must
1930
+ * instantiate `Auth` with
1891
1931
  * `auth: { type: "apiKey", key: "pcft_live_..." }`. The
1892
1932
  * kubb-generated `headers.authorization` slot is a stub — the
1893
1933
  * HTTP client's auth middleware overrides whatever's passed.
@@ -1896,6 +1936,31 @@ var ConsumerScope = class {
1896
1936
  { appSlug: this.appSlug, data, headers: { authorization: "" } },
1897
1937
  { client: this.client }
1898
1938
  ),
1939
+ /**
1940
+ * Mint + dispatch a password-reset email via Mail in one call.
1941
+ * Returns `{ expires_at }` on success; the plaintext code is NOT
1942
+ * returned. Returns `{}` when the contact is missing or unverified
1943
+ * (same enumeration defence as {@link requestReset}).
1944
+ *
1945
+ * This is the counterpart to {@link sendVerificationEmail} for the
1946
+ * reset slot, and the method you want for a plain "forgot
1947
+ * password" flow — {@link requestReset} only mints, it does not
1948
+ * email.
1949
+ *
1950
+ * PAK-required with `auth.user.password-reset.send-email` —
1951
+ * distinct permission from the bare mint so customers can enable
1952
+ * mint without enabling outbound Mail traffic. Surfaces typed 412
1953
+ * precondition errors (e.g. `ENVOI_NOT_ENABLED`,
1954
+ * `ENVOI_TEMPLATE_NOT_CONFIGURED`) rather than failing silently.
1955
+ */
1956
+ sendPasswordResetEmail: (data) => consumerAuthControllerSendPasswordResetEmail(
1957
+ {
1958
+ appSlug: this.appSlug,
1959
+ data,
1960
+ headers: { authorization: "" }
1961
+ },
1962
+ { client: this.client }
1963
+ ),
1899
1964
  resetPassword: (data) => consumerAuthControllerResetPassword(
1900
1965
  { appSlug: this.appSlug, data },
1901
1966
  { client: this.client }