@faable/auth-sdk 2.5.96 → 2.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/.github/workflows/release.yml +4 -0
- package/.releaserc +21 -0
- package/README.md +91 -4
- package/dist/FaableAuthApi.js +16 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/version.d.ts +3 -2
- package/dist/version.js +23 -9
- package/package.json +6 -3
- package/tests/pack/verify-pack.mjs +100 -0
|
@@ -24,6 +24,10 @@ jobs:
|
|
|
24
24
|
node-version: lts/*
|
|
25
25
|
- run: npm ci
|
|
26
26
|
- run: npm run build
|
|
27
|
+
# Fails the build if the version sentinels are not in dist/ verbatim
|
|
28
|
+
# (the release replacement would miss them and the SDK would ship
|
|
29
|
+
# anonymous) or if anything in dist/ imports `node:*` (browser-unsafe).
|
|
30
|
+
- run: npm run test:pack
|
|
27
31
|
- run: npm run release
|
|
28
32
|
env:
|
|
29
33
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/.releaserc
CHANGED
|
@@ -18,6 +18,27 @@
|
|
|
18
18
|
"repositoryUrl": "https://x-access-token:${GITHUB_TOKEN}@github.com/faablecloud/auth-sdk.git",
|
|
19
19
|
"plugins": [
|
|
20
20
|
"@semantic-release/commit-analyzer",
|
|
21
|
+
[
|
|
22
|
+
"semantic-release-replace-plugin",
|
|
23
|
+
{
|
|
24
|
+
"replacements": [
|
|
25
|
+
{
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/*.js"
|
|
28
|
+
],
|
|
29
|
+
"from": "0.0.0-dev",
|
|
30
|
+
"to": "${nextRelease.version}"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/*.js"
|
|
35
|
+
],
|
|
36
|
+
"from": "0000000dev",
|
|
37
|
+
"to": "${nextRelease.gitHead.slice(0, 7)}"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
21
42
|
"@semantic-release/release-notes-generator",
|
|
22
43
|
[
|
|
23
44
|
"@semantic-release/npm",
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a href="https://faable.com">
|
|
3
3
|
<h1 align="center">Faable Auth SDK</h1>
|
|
4
4
|
</a>
|
|
5
|
-
<p align="center">
|
|
5
|
+
<p align="center">Client for the FaableAuth REST API — management from a server, session and login flows from a browser or CLI.</p>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
@@ -11,9 +11,22 @@
|
|
|
11
11
|
</a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
Programmatically manage FaableAuth users, teams, connections and clients through the FaableAuth REST API.
|
|
14
|
+
Programmatically manage FaableAuth users, teams, connections and clients through the FaableAuth REST API — and, with the right strategy, call the tenant's session and login endpoints from a browser or a CLI.
|
|
15
15
|
|
|
16
|
-
> ⚠️ **
|
|
16
|
+
> ⚠️ **Where it can run depends on the credential, not on the package.** The
|
|
17
|
+
> SDK itself has no Node-only dependencies and bundles for the browser. What
|
|
18
|
+
> you must never ship to a browser or any untrusted runtime is a
|
|
19
|
+
> **`client_secret`** or an **API key** — those grant management scopes over
|
|
20
|
+
> the whole tenant. A user's own bearer token or the browser's session cookie
|
|
21
|
+
> only grant what that user can do, and those are fine client-side.
|
|
22
|
+
|
|
23
|
+
| Strategy | Credential | Grants | Where |
|
|
24
|
+
| ----------------------- | --------------------------------- | ----------------------------------------------------------------- | --------------------------------------- |
|
|
25
|
+
| `authClientCredentials` | `client_id` + **`client_secret`** | The management scopes of the client (full catalog unless narrowed) | **Server only** |
|
|
26
|
+
| `authApikey` | **API key** (`fak_…`) | The management scopes of the key | **Server only** |
|
|
27
|
+
| `authBearer` | A token the caller already holds | Whatever that token's scopes and subject allow | Anywhere the token itself may live |
|
|
28
|
+
| `authCookie` | The browser's session cookie | The signed-in user's own session (`/me`, MFA, passkeys…) | Browser, same-origin with the auth host |
|
|
29
|
+
| _(none)_ | — | Public endpoints only (login, passwordless start, device flow…) | Anywhere |
|
|
17
30
|
|
|
18
31
|
---
|
|
19
32
|
|
|
@@ -76,15 +89,89 @@ const api = FaableAuthApi.create({
|
|
|
76
89
|
});
|
|
77
90
|
```
|
|
78
91
|
|
|
92
|
+
### Bearer token
|
|
93
|
+
|
|
94
|
+
For a token you already hold — a user's access token in a CLI, a token minted
|
|
95
|
+
elsewhere. The SDK does not mint or refresh it; whoever owns the token does.
|
|
96
|
+
`token` may be a getter (sync or async): it is consulted on **every** request,
|
|
97
|
+
so a getter that returns the current token keeps a long-lived client correct
|
|
98
|
+
when the token rotates.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { FaableAuthApi, authBearer } from "@faable/auth-sdk";
|
|
102
|
+
|
|
103
|
+
const api = FaableAuthApi.create({
|
|
104
|
+
domain: "https://<your-account>.auth.faable.link",
|
|
105
|
+
authStrategy: authBearer,
|
|
106
|
+
auth: { token: () => session.accessToken },
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
What the calls may do is whatever the token's subject and scopes allow — a
|
|
111
|
+
user token reaches the user's own resources, a management token reaches the
|
|
112
|
+
management API.
|
|
113
|
+
|
|
114
|
+
### Session cookie (browser)
|
|
115
|
+
|
|
116
|
+
For pages served under the tenant's auth host that authenticate with the
|
|
117
|
+
browser's session cookie (the hosted login pages: `/me`, MFA, passkeys). No
|
|
118
|
+
config: the browser attaches the cookie itself; the strategy makes the request
|
|
119
|
+
`withCredentials` so it also does cross-origin, where the auth server must
|
|
120
|
+
allow the page's origin for credentials.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { FaableAuthApi, authCookie } from "@faable/auth-sdk";
|
|
124
|
+
|
|
125
|
+
const api = FaableAuthApi.create({
|
|
126
|
+
domain: "https://<your-account>.auth.faable.link",
|
|
127
|
+
authStrategy: authCookie,
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### No credentials
|
|
132
|
+
|
|
133
|
+
Omit `auth` and `authStrategy` and the client is anonymous — right for the
|
|
134
|
+
public endpoints (login, `/passwordless/start`, the device flow, OIDC
|
|
135
|
+
discovery). Those are not on the generated client; call them through the
|
|
136
|
+
fetcher:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const api = FaableAuthApi.create({ domain: "https://<your-account>.auth.faable.link" });
|
|
140
|
+
await api.fetcher.post("/passwordless/start", { email, client_id });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Identifying your integration
|
|
144
|
+
|
|
145
|
+
Every request carries `x-faable-client: auth-sdk/<version>+<sha>` so the
|
|
146
|
+
auth server can attribute traffic. An app that wraps the SDK can report as
|
|
147
|
+
itself instead — the value you pass replaces the default, on data calls and on
|
|
148
|
+
the token request alike:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
FaableAuthApi.create({ domain, clientInfo: { client: "my-app/1.4.0", name: "my-app" } });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Per-call options
|
|
155
|
+
|
|
156
|
+
Every fetcher method takes `timeout`, `responseType` and `retry` for that one
|
|
157
|
+
call. Retries only ever apply to GET (a replayed write could duplicate a
|
|
158
|
+
resource), and `timeout` and `retry` multiply — a short deadline on a hot path
|
|
159
|
+
wants `retry: false` next to it:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
await api.fetcher.get(`/user/${id}`, { timeout: 800, retry: false });
|
|
163
|
+
```
|
|
164
|
+
|
|
79
165
|
### Constructor options
|
|
80
166
|
|
|
81
167
|
| Option | Type | Description |
|
|
82
168
|
| -------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
83
169
|
| `domain` | string | **Required.** The account host to target, e.g. `https://<your-account>.auth.faable.link`. No default. The protocol is optional — `<your-account>.auth.faable.link` and `https://<your-account>.auth.faable.link` are equivalent. |
|
|
84
|
-
| `authStrategy` | strategy builder | Auth strategy (`authClientCredentials` \| `authApikey`), re-exported from `@faable/auth-sdk`. Defaults to `authClientCredentials
|
|
170
|
+
| `authStrategy` | strategy builder | Auth strategy (`authClientCredentials` \| `authApikey` \| `authBearer` \| `authCookie`), re-exported from `@faable/auth-sdk`. Defaults to `authClientCredentials` when `auth` is given; anonymous when neither is. |
|
|
85
171
|
| `auth` | config | Config for the chosen strategy. Its shape is inferred from `authStrategy` (e.g. `{ client_id, client_secret }`). |
|
|
86
172
|
| `debug` | boolean | Enables verbose logging in the underlying fetcher. |
|
|
87
173
|
| `headers` | `{ team_id?; account_id? }` | Advanced / testing escape hatch — target `domain` but have the server resolve a different team/account (sent as `x-faable-team` / `x-faableauth-account`). |
|
|
174
|
+
| `clientInfo` | `{ client?; name?; instance? }` | How this integration identifies itself (`x-faable-client` / `x-faable-instance`). `client` replaces the SDK's own `auth-sdk/<version>+<sha>`. |
|
|
88
175
|
|
|
89
176
|
---
|
|
90
177
|
|
package/dist/FaableAuthApi.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getDomain, isPlainObject, requireId } from "./helpers.js";
|
|
|
2
2
|
import { FaableApiError } from "./error_handler.js";
|
|
3
3
|
import { GeneratedFaableAuthApi } from "./api/generated-client.js";
|
|
4
4
|
import { authClientCredentials } from "@faable/sdk-base";
|
|
5
|
-
import {
|
|
5
|
+
import { SDK_CLIENT } from "./version.js";
|
|
6
6
|
// auth-sdk exists to drive the Faable management API, so a client_credentials
|
|
7
7
|
// client should "just work" against it from `domain` alone. The management API
|
|
8
8
|
// audience is `faable:management:<account_id>`, where `<account_id>` is this
|
|
@@ -67,23 +67,27 @@ export class FaableAuthApi extends GeneratedFaableAuthApi {
|
|
|
67
67
|
...params,
|
|
68
68
|
auth,
|
|
69
69
|
// Identify ourselves as a first-party client (`x-faable-client:
|
|
70
|
-
// auth-sdk/<version>`, surfaced as the `client`/`client_version
|
|
71
|
-
// and forward any consumer-supplied `name`/`instance`
|
|
72
|
-
// attribution. Routing this through `clientInfo` makes
|
|
73
|
-
// `/oauth/token` call too (sdk-base stamps both fetchers
|
|
74
|
-
// identity). A caller-set `clientInfo.client`
|
|
70
|
+
// auth-sdk/<version>+<sha>`, surfaced as the `client`/`client_version`/
|
|
71
|
+
// `client_sha` tags), and forward any consumer-supplied `name`/`instance`
|
|
72
|
+
// for per-replica attribution. Routing this through `clientInfo` makes
|
|
73
|
+
// it ride on the `/oauth/token` call too (sdk-base stamps both fetchers
|
|
74
|
+
// from the same identity). A caller-set `clientInfo.client` wins — the
|
|
75
|
+
// hosted login pages and the CLI wrap this SDK and must report as
|
|
76
|
+
// themselves, not as auth-sdk.
|
|
77
|
+
//
|
|
78
|
+
// Nothing is set as a static fetcher header any more: sdk-base gives
|
|
79
|
+
// explicit `fetcher.headers` precedence over `clientInfo`, so the old
|
|
80
|
+
// backward-compat copy of this value silently overrode every consumer's
|
|
81
|
+
// `clientInfo.client` (and left `/oauth/token` reporting a different
|
|
82
|
+
// client than the data calls). The sdk-base this package requires has
|
|
83
|
+
// honoured `clientInfo` since 1.5.x, so the fallback had no job left.
|
|
75
84
|
clientInfo: {
|
|
76
|
-
client:
|
|
85
|
+
client: SDK_CLIENT,
|
|
77
86
|
...params?.clientInfo,
|
|
78
87
|
},
|
|
79
88
|
fetcher: {
|
|
80
89
|
...params?.fetcher,
|
|
81
90
|
headers: {
|
|
82
|
-
// Also set `x-faable-client` as a static fetcher header for
|
|
83
|
-
// backward-compat: if this runs against an older @faable/sdk-base
|
|
84
|
-
// that ignores `clientInfo`, data requests still carry it (only the
|
|
85
|
-
// new `/oauth/token` attribution needs the newer sdk-base).
|
|
86
|
-
"x-faable-client": `auth-sdk/${version}`,
|
|
87
91
|
...params?.fetcher?.headers,
|
|
88
92
|
...(params?.headers?.team_id && {
|
|
89
93
|
"x-faable-team": params.headers.team_id,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./FaableAuthApi.js";
|
|
2
|
+
export { SDK_CLIENT, commit, version } from "./version.js";
|
|
2
3
|
export * from "./api/generated-client.js";
|
|
3
4
|
export * from "./api/api-types.js";
|
|
4
5
|
export * from "./api/types.js";
|
|
5
|
-
export { authClientCredentials, authApikey } from "@faable/sdk-base";
|
|
6
|
-
export type { ApiParams, ClientCredentialsConfig, ApikeyConfig, AuthStrategy, AuthStrategyBuilder, AuthStrategyContext, } from "@faable/sdk-base";
|
|
6
|
+
export { authClientCredentials, authApikey, authBearer, authCookie, } from "@faable/sdk-base";
|
|
7
|
+
export type { ApiParams, ClientCredentialsConfig, ApikeyConfig, BearerConfig, RetryConfig, Fetcher, FetcherConfig, FetcherRequestParams, AuthStrategy, AuthStrategyBuilder, AuthStrategyContext, } from "@faable/sdk-base";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from "./FaableAuthApi.js";
|
|
2
|
+
export { SDK_CLIENT, commit, version } from "./version.js";
|
|
2
3
|
export * from "./api/generated-client.js";
|
|
3
4
|
export * from "./api/api-types.js";
|
|
4
5
|
export * from "./api/types.js";
|
|
5
6
|
// Re-export the auth strategies (and their config/types) from
|
|
6
7
|
// @faable/sdk-base so consumers depend on @faable/auth-sdk ONLY — sdk-base
|
|
7
8
|
// stays a transitive dependency and no app needs to import it directly.
|
|
8
|
-
export { authClientCredentials, authApikey } from "@faable/sdk-base";
|
|
9
|
+
export { authClientCredentials, authApikey, authBearer, authCookie, } from "@faable/sdk-base";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
declare const version
|
|
2
|
-
export
|
|
1
|
+
export declare const version = "0.0.0-dev";
|
|
2
|
+
export declare const commit = "0000000dev";
|
|
3
|
+
export declare const SDK_CLIENT = "auth-sdk/0.0.0-dev+0000000dev";
|
package/dist/version.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// Both constants are replaced at release time by the
|
|
2
|
+
// `semantic-release-replace-plugin` entries in `.releaserc`, which rewrite
|
|
3
|
+
// every occurrence in `dist/*.js` with the version and commit being published.
|
|
4
|
+
// `tests/pack/verify-pack.mjs` fails the build if either sentinel is missing
|
|
5
|
+
// from the packed output, so a build can never ship anonymous.
|
|
6
|
+
//
|
|
7
|
+
// Why not read package.json at runtime: the previous `createRequire` from
|
|
8
|
+
// `node:module` kept this package out of every browser bundle — the hosted
|
|
9
|
+
// login pages could not import the SDK at all. Same pattern as auth-js.
|
|
10
|
+
//
|
|
11
|
+
// The sentinels MUST stay byte-identical to the `from` values in `.releaserc`.
|
|
12
|
+
export const version = "0.0.0-dev";
|
|
13
|
+
// Short git SHA of the released commit. The version dates a build; this names
|
|
14
|
+
// the exact tree, so a canonical log line or an audit entry leads straight to
|
|
15
|
+
// `git show <sha>`. Deliberately NOT hex: an unreleased build (dev, a local
|
|
16
|
+
// link) cannot be mistaken for a real commit — auth only records values that
|
|
17
|
+
// look like a SHA, and this one never will.
|
|
18
|
+
export const commit = "0000000dev";
|
|
19
|
+
// What this SDK writes in `x-faable-client`. Exported for a consumer that
|
|
20
|
+
// builds a strategy on its own (outside `FaableAuthApi`) and still wants the
|
|
21
|
+
// token request attributed — a bare `authClientCredentials` stamps only what
|
|
22
|
+
// it is given.
|
|
23
|
+
export const SDK_CLIENT = `auth-sdk/${version}+${commit}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faable/auth-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"author": "Marc Pomar <marc@faable.com>",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@faable/sdk-base": "^1.
|
|
13
|
+
"@faable/sdk-base": "^1.6.0"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@ava/typescript": "^5.0.0",
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"@types/node": "^20.10.5",
|
|
19
19
|
"ava": "^6.1.3",
|
|
20
20
|
"dotenv": "^16.4.5",
|
|
21
|
+
"nock": "^13.5.5",
|
|
21
22
|
"openapi-typescript": "^7.13.0",
|
|
22
23
|
"rimraf": "^6.0.1",
|
|
23
24
|
"semantic-release": "^25.0.3",
|
|
25
|
+
"semantic-release-replace-plugin": "^1.2.7",
|
|
24
26
|
"tsimp": "^2.0.11",
|
|
25
27
|
"typescript": "^5.3.3"
|
|
26
28
|
},
|
|
@@ -30,7 +32,8 @@
|
|
|
30
32
|
"prebuild": "npm run gentypes",
|
|
31
33
|
"prepublishOnly": "npm run build",
|
|
32
34
|
"test": "ava",
|
|
33
|
-
"release": "semantic-release"
|
|
35
|
+
"release": "semantic-release",
|
|
36
|
+
"test:pack": "node tests/pack/verify-pack.mjs"
|
|
34
37
|
},
|
|
35
38
|
"resolutions": {
|
|
36
39
|
"npm/chalk": "^4.1.2"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Guard on the PACKED build, run in CI between `npm run build` and the
|
|
3
|
+
// release. Three things that a green unit suite cannot see:
|
|
4
|
+
//
|
|
5
|
+
// 1. The version sentinels are in dist/ byte-identical to `.releaserc`, so
|
|
6
|
+
// semantic-release's replacement actually hits them. auth-js shipped as
|
|
7
|
+
// `auth-js/0.0.0` for months because its sentinel and its `.releaserc`
|
|
8
|
+
// disagreed — the rewrite hit a banner comment and nothing else.
|
|
9
|
+
// 2. Nothing in dist/ imports `node:*`. This package has to load in a browser
|
|
10
|
+
// bundle (the hosted login pages); a `createRequire` from `node:module`
|
|
11
|
+
// is exactly what kept it out until now.
|
|
12
|
+
// 3. The identity header on the wire: the default is `auth-sdk/<sentinels>`,
|
|
13
|
+
// and a consumer's `clientInfo.client` REPLACES it (the old static header
|
|
14
|
+
// used to override it silently).
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
|
|
21
|
+
const ROOT = resolve(fileURLToPath(import.meta.url), "../../..");
|
|
22
|
+
const run = (cmd, opts = {}) => execSync(cmd, { stdio: "inherit", ...opts });
|
|
23
|
+
const capture = (cmd, opts = {}) => execSync(cmd, { encoding: "utf8", ...opts }).trim();
|
|
24
|
+
const fail = (msg, ...rest) => {
|
|
25
|
+
console.error("✖", msg, ...rest);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.log("→ Building dist/");
|
|
30
|
+
run("npm run build", { cwd: ROOT });
|
|
31
|
+
|
|
32
|
+
// 1. Sentinels, verbatim, straight from .releaserc so the two cannot drift.
|
|
33
|
+
const releaserc = JSON.parse(readFileSync(join(ROOT, ".releaserc"), "utf8"));
|
|
34
|
+
const replacePlugin = releaserc.plugins.find(
|
|
35
|
+
(p) => Array.isArray(p) && p[0] === "semantic-release-replace-plugin",
|
|
36
|
+
);
|
|
37
|
+
if (!replacePlugin) fail(".releaserc has no semantic-release-replace-plugin entry");
|
|
38
|
+
const versionJs = readFileSync(join(ROOT, "dist/version.js"), "utf8");
|
|
39
|
+
for (const { from } of replacePlugin[1].replacements) {
|
|
40
|
+
if (!versionJs.includes(from)) {
|
|
41
|
+
fail(`dist/version.js does not contain the sentinel "${from}" that .releaserc replaces`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.log("✓ sentinels present in dist/version.js");
|
|
45
|
+
|
|
46
|
+
// 2. Browser-safety: no node: specifiers anywhere in the published JS.
|
|
47
|
+
const jsFiles = (dir) =>
|
|
48
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
49
|
+
e.isDirectory() ? jsFiles(join(dir, e.name)) : e.name.endsWith(".js") ? [join(dir, e.name)] : [],
|
|
50
|
+
);
|
|
51
|
+
for (const file of jsFiles(join(ROOT, "dist"))) {
|
|
52
|
+
const src = readFileSync(file, "utf8");
|
|
53
|
+
const hit = src.match(/from\s+["']node:[^"']+["']|require\(["']node:|createRequire\(/);
|
|
54
|
+
if (hit) fail(`${file.replace(ROOT + "/", "")} is not browser-safe: ${hit[0]}`);
|
|
55
|
+
}
|
|
56
|
+
console.log("✓ dist/ has no node: imports");
|
|
57
|
+
|
|
58
|
+
// 3. Pack, install into a clean consumer, look at the wire.
|
|
59
|
+
const tmp = mkdtempSync(join(tmpdir(), "faable-auth-sdk-pack-"));
|
|
60
|
+
try {
|
|
61
|
+
const tarball = join(ROOT, capture("npm pack --silent --pack-destination .", { cwd: ROOT }));
|
|
62
|
+
writeFileSync(
|
|
63
|
+
join(tmp, "package.json"),
|
|
64
|
+
JSON.stringify({ name: "consumer", version: "0.0.0", type: "module", private: true }, null, 2),
|
|
65
|
+
);
|
|
66
|
+
run(`npm install --silent --no-save "${tarball}"`, { cwd: tmp });
|
|
67
|
+
rmSync(tarball, { force: true });
|
|
68
|
+
|
|
69
|
+
const probe = `
|
|
70
|
+
const mod = await import("@faable/auth-sdk");
|
|
71
|
+
for (const name of ["FaableAuthApi", "authClientCredentials", "authApikey", "authBearer", "authCookie"]) {
|
|
72
|
+
if (typeof mod[name] === "undefined") { console.error("Missing export:", name); process.exit(1); }
|
|
73
|
+
}
|
|
74
|
+
const seen = [];
|
|
75
|
+
const adapter = async (config) => { seen.push(config); return { data: {}, status: 200, statusText: "OK", headers: {}, config }; };
|
|
76
|
+
const header = (i) => seen[i].headers["x-faable-client"] ?? seen[i].headers["X-Faable-Client"];
|
|
77
|
+
|
|
78
|
+
const dflt = mod.FaableAuthApi.create({ domain: "https://t.auth.faable.link", fetcher: { adapter } });
|
|
79
|
+
await dflt.fetcher.get("/me");
|
|
80
|
+
const expected = "auth-sdk/" + ${JSON.stringify(
|
|
81
|
+
replacePlugin[1].replacements.map((r) => r.from).join("+"),
|
|
82
|
+
)};
|
|
83
|
+
if (header(0) !== expected) { console.error("default x-faable-client must be", expected, "got", header(0)); process.exit(1); }
|
|
84
|
+
if (header(0).slice("auth-sdk/".length).length > 32) { console.error("version+sha exceeds auth's 32-char budget:", header(0)); process.exit(1); }
|
|
85
|
+
|
|
86
|
+
const wrapped = mod.FaableAuthApi.create({ domain: "https://t.auth.faable.link", fetcher: { adapter }, clientInfo: { client: "probe-ui/1.2.3" } });
|
|
87
|
+
await wrapped.fetcher.get("/me");
|
|
88
|
+
if (header(1) !== "probe-ui/1.2.3") { console.error("clientInfo.client must replace the default, got", header(1)); process.exit(1); }
|
|
89
|
+
|
|
90
|
+
const cookie = mod.FaableAuthApi.create({ domain: "https://t.auth.faable.link", fetcher: { adapter }, authStrategy: mod.authCookie });
|
|
91
|
+
await cookie.fetcher.get("/me");
|
|
92
|
+
if (seen[2].withCredentials !== true) { console.error("authCookie must send withCredentials"); process.exit(1); }
|
|
93
|
+
console.log("✓ wire identity: default", header(0), "| wrapped", header(1), "| cookie withCredentials");
|
|
94
|
+
`;
|
|
95
|
+
writeFileSync(join(tmp, "probe.mjs"), probe);
|
|
96
|
+
run("node probe.mjs", { cwd: tmp });
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
console.log("✓ pack verified");
|