@productcraft/auth 0.7.1 → 0.8.1
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 +21 -3
- package/dist/index.cjs +40 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -4
- package/dist/index.d.ts +163 -4
- package/dist/index.js +40 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,9 +13,8 @@ Server-side only. For React / Next.js apps, call this from your backend (BFF pat
|
|
|
13
13
|
```ts
|
|
14
14
|
import { Auth } from "@productcraft/auth";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
16
|
+
// Reads PRODUCTCRAFT_API_KEY (and PRODUCTCRAFT_AUTH_APP) from the environment
|
|
17
|
+
const auth = new Auth();
|
|
19
18
|
```
|
|
20
19
|
|
|
21
20
|
The SDK splits into three caller contexts.
|
|
@@ -310,6 +309,8 @@ try {
|
|
|
310
309
|
|
|
311
310
|
Behind the scenes: JWKS fetched once, cached in-memory (10 min TTL by default), singleflighted so 100 concurrent verifies do 1 fetch, auto-refetched if a token's `kid` isn't in the cached JWKS (rotation handling). Issuer + audience are checked against `scope.acceptedIssuers` + `scope.expectedAudience` automatically.
|
|
312
311
|
|
|
312
|
+
**Refresh tokens are refused.** A token carrying `token_use: "refresh"` (or the older `type: "refresh"`) throws `JwtRefreshTokenError` (code `ERR_JWT_REFRESH_TOKEN`) even when its signature, issuer and audience are valid: a refresh token is only good at the refresh endpoint, never as an access token. `createAuthJwtStrategy` from `@productcraft/auth-passport` refuses them too. If you call `jwtVerify` yourself (below), check `isRefreshToken(payload)` and reject.
|
|
313
|
+
|
|
313
314
|
### Building blocks for `jose`, NestJS guards, Hono, etc.
|
|
314
315
|
|
|
315
316
|
`scope.jwks.getKey` is a [jose-compatible](https://github.com/panva/jose) key resolver — pass it anywhere a `GetKeyFunction` is expected.
|
|
@@ -368,11 +369,28 @@ import {
|
|
|
368
369
|
} from "@productcraft/auth";
|
|
369
370
|
```
|
|
370
371
|
|
|
372
|
+
## Credentials from the environment
|
|
373
|
+
|
|
374
|
+
Server-side, omit `auth` and the client reads your workspace key from the environment:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
# .env
|
|
378
|
+
PRODUCTCRAFT_API_KEY=pcft_live_...
|
|
379
|
+
PRODUCTCRAFT_AUTH_APP=acme # default slug for auth.consumer()
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- An explicit `auth` always wins over the environment.
|
|
383
|
+
- Hosts are never read from the environment. Override them in code with `baseUrl`.
|
|
384
|
+
- This is a server-only fallback. Never put a key in `NEXT_PUBLIC_*` or any other variable a bundler inlines into browser code.
|
|
385
|
+
- `auth.app(appId)` and the workspace methods (`apps`, `stats`) use the explicit `auth`, else `PRODUCTCRAFT_API_KEY`.
|
|
386
|
+
- `auth.consumer()` takes its slug from `PRODUCTCRAFT_AUTH_APP` when you omit it, and **never** reads a credential from the environment: consumer routes act for an end user, so only an explicit `auth` is sent. PAK-only consumer routes (`signup_requires_pak` sign-ups, tenants, code mint, factors) therefore need `new Auth({ auth: { type: "apiKey", key: process.env.MY_APP_PAK! } })`.
|
|
387
|
+
|
|
371
388
|
## Configuration
|
|
372
389
|
|
|
373
390
|
```ts
|
|
374
391
|
new Auth({
|
|
375
392
|
// Auth credential the SDK presents to Auth
|
|
393
|
+
// Omit to use PRODUCTCRAFT_API_KEY from the environment
|
|
376
394
|
auth: { type: "apiKey", key: "pcft_live_..." }
|
|
377
395
|
| { type: "bearer", token: "eyJ..." }
|
|
378
396
|
| { type: "cookie", value: "auth_token=..." },
|
package/dist/index.cjs
CHANGED
|
@@ -2070,6 +2070,12 @@ var JwtNotYetValidError = class extends JwtVerifyError {
|
|
|
2070
2070
|
this.name = "JwtNotYetValidError";
|
|
2071
2071
|
}
|
|
2072
2072
|
};
|
|
2073
|
+
var JwtRefreshTokenError = class extends JwtVerifyError {
|
|
2074
|
+
constructor(message = "Refresh tokens cannot be used as access tokens", options) {
|
|
2075
|
+
super("ERR_JWT_REFRESH_TOKEN", message, options);
|
|
2076
|
+
this.name = "JwtRefreshTokenError";
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2073
2079
|
var JwtIssuerMismatchError = class extends JwtVerifyError {
|
|
2074
2080
|
constructor(message, options) {
|
|
2075
2081
|
super("ERR_JWT_ISS_MISMATCH", message, options);
|
|
@@ -2150,6 +2156,10 @@ var JwksCache = class {
|
|
|
2150
2156
|
refresh() {
|
|
2151
2157
|
}
|
|
2152
2158
|
};
|
|
2159
|
+
function isRefreshToken(claims) {
|
|
2160
|
+
if (!claims) return false;
|
|
2161
|
+
return claims.token_use === "refresh" || claims.type === "refresh";
|
|
2162
|
+
}
|
|
2153
2163
|
async function verifyAuthToken(token, getKey, opts = {}) {
|
|
2154
2164
|
try {
|
|
2155
2165
|
const { payload } = await jose.jwtVerify(token, getKey, {
|
|
@@ -2161,6 +2171,7 @@ async function verifyAuthToken(token, getKey, opts = {}) {
|
|
|
2161
2171
|
currentDate: opts.currentDate,
|
|
2162
2172
|
maxTokenAge: opts.maxTokenAge
|
|
2163
2173
|
});
|
|
2174
|
+
if (isRefreshToken(payload)) throw new JwtRefreshTokenError();
|
|
2164
2175
|
return payload;
|
|
2165
2176
|
} catch (err) {
|
|
2166
2177
|
throw translateJoseError(err);
|
|
@@ -2702,7 +2713,15 @@ async function statsControllerGetMyStats({ params } = {}, config = {}) {
|
|
|
2702
2713
|
|
|
2703
2714
|
// src/index.ts
|
|
2704
2715
|
var Auth = class {
|
|
2716
|
+
/** Admin client: explicit `auth`, else `PRODUCTCRAFT_API_KEY`. */
|
|
2705
2717
|
client;
|
|
2718
|
+
/**
|
|
2719
|
+
* Consumer client: explicit `auth` only. Consumer routes act for an
|
|
2720
|
+
* end user, so a workspace key from the environment must never leak
|
|
2721
|
+
* onto them. Pass `auth` explicitly for PAK-only consumer routes
|
|
2722
|
+
* (`signup_requires_pak`, tenants, code mint, factors).
|
|
2723
|
+
*/
|
|
2724
|
+
consumerClient;
|
|
2706
2725
|
baseUrl;
|
|
2707
2726
|
fetch;
|
|
2708
2727
|
jwtConfig;
|
|
@@ -2710,6 +2729,11 @@ var Auth = class {
|
|
|
2710
2729
|
this.baseUrl = config.baseUrl ?? core.PC_BASE_URL.auth;
|
|
2711
2730
|
this.fetch = config.fetch;
|
|
2712
2731
|
this.client = makeAuthHttpClient({
|
|
2732
|
+
baseUrl: this.baseUrl,
|
|
2733
|
+
auth: core.resolveAuth(config.auth),
|
|
2734
|
+
fetch: this.fetch
|
|
2735
|
+
});
|
|
2736
|
+
this.consumerClient = makeAuthHttpClient({
|
|
2713
2737
|
baseUrl: this.baseUrl,
|
|
2714
2738
|
auth: config.auth,
|
|
2715
2739
|
fetch: this.fetch
|
|
@@ -2721,7 +2745,8 @@ var Auth = class {
|
|
|
2721
2745
|
/**
|
|
2722
2746
|
* Returns an `AppScope` bound to the given appId. All operations under
|
|
2723
2747
|
* `/v1/apps/{appId}/...` are exposed as resource namespaces on the
|
|
2724
|
-
* returned object.
|
|
2748
|
+
* returned object. Uses the explicit `auth`, else
|
|
2749
|
+
* `PRODUCTCRAFT_API_KEY` from the environment.
|
|
2725
2750
|
*/
|
|
2726
2751
|
app(appId) {
|
|
2727
2752
|
return new AppScope(appId, this.client);
|
|
@@ -2730,11 +2755,21 @@ var Auth = class {
|
|
|
2730
2755
|
* Returns a `ConsumerScope` bound to the given appSlug. Exposes the
|
|
2731
2756
|
* `/{appSlug}/v1/...` surface (sign-in, sign-up, me, verify, oauth)
|
|
2732
2757
|
* and the JWT-verify helper for tokens issued by this app.
|
|
2758
|
+
*
|
|
2759
|
+
* `appSlug` defaults to `PRODUCTCRAFT_AUTH_APP` from the environment.
|
|
2760
|
+
* No credential is ever read from the environment for consumer
|
|
2761
|
+
* routes; only an explicit `auth` is sent.
|
|
2733
2762
|
*/
|
|
2734
2763
|
consumer(appSlug) {
|
|
2764
|
+
const slug = appSlug ?? core.readEnv(core.PC_AUTH_APP_ENV);
|
|
2765
|
+
if (!slug) {
|
|
2766
|
+
throw new Error(
|
|
2767
|
+
`@productcraft/auth: consumer() needs an app slug. Pass one, or set ${core.PC_AUTH_APP_ENV}.`
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2735
2770
|
return new ConsumerScope(
|
|
2736
|
-
|
|
2737
|
-
{ client: this.
|
|
2771
|
+
slug,
|
|
2772
|
+
{ client: this.consumerClient, baseUrl: this.baseUrl, fetch: this.fetch },
|
|
2738
2773
|
this.jwtConfig
|
|
2739
2774
|
);
|
|
2740
2775
|
}
|
|
@@ -2786,6 +2821,7 @@ exports.JwtExpiredError = JwtExpiredError;
|
|
|
2786
2821
|
exports.JwtInvalidError = JwtInvalidError;
|
|
2787
2822
|
exports.JwtIssuerMismatchError = JwtIssuerMismatchError;
|
|
2788
2823
|
exports.JwtNotYetValidError = JwtNotYetValidError;
|
|
2824
|
+
exports.JwtRefreshTokenError = JwtRefreshTokenError;
|
|
2789
2825
|
exports.JwtVerifyError = JwtVerifyError;
|
|
2790
2826
|
exports.PasskeyError = PasskeyError;
|
|
2791
2827
|
exports.createPasskeyCredential = createPasskeyCredential;
|
|
@@ -2794,6 +2830,7 @@ exports.encodeCredential = encodeCredential;
|
|
|
2794
2830
|
exports.fromB64u = fromB64u;
|
|
2795
2831
|
exports.getPasskeyCredential = getPasskeyCredential;
|
|
2796
2832
|
exports.isPasskeySupported = isPasskeySupported;
|
|
2833
|
+
exports.isRefreshToken = isRefreshToken;
|
|
2797
2834
|
exports.toB64u = toB64u;
|
|
2798
2835
|
exports.verifyAuthToken = verifyAuthToken;
|
|
2799
2836
|
//# sourceMappingURL=index.cjs.map
|