@productcraft/platform-auth 0.2.0 → 1.0.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 +123 -217
- package/api.md +1716 -0
- package/dist/index.cjs +1809 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1973 -19
- package/dist/index.d.ts +1973 -19
- package/dist/index.js +1744 -8
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +691 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +32 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.js +689 -0
- package/dist/testing.js.map +1 -0
- package/manifest.json +1115 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,283 +1,189 @@
|
|
|
1
1
|
# @productcraft/platform-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Node.js / TypeScript client for the **ProductCraft Platform API**: workspaces, members and invites, roles and IAM policies, workspace API keys, service activation, audit logs, and the introspection endpoints every ProductCraft backend uses to resolve permissions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Every method, type, error class and mock is generated from the production OpenAPI spec. Upgrading from 0.x? See the [1.0 migration guide](https://github.com/clauderanelagh/productcraft-node/blob/main/docs/migrations/platform-auth-1.0.0.md).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
6
10
|
npm install @productcraft/platform-auth
|
|
7
11
|
```
|
|
8
12
|
|
|
9
|
-
Server-side
|
|
13
|
+
Node 20.3 or later, Bun, Deno and edge runtimes. Server-side: it sends a workspace key or a platform session. Or install the umbrella package for every product: `npm install productcraft`.
|
|
10
14
|
|
|
11
15
|
## Which SDK do I want?
|
|
12
16
|
|
|
13
17
|
| If your goal is to … | Reach for |
|
|
14
18
|
|---|---|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
19
|
+
| Sign in **your own end-users** in a product you build on ProductCraft | [`@productcraft/auth`](https://www.npmjs.com/package/@productcraft/auth) |
|
|
20
|
+
| Manage **your ProductCraft account and workspaces**: keys, members, roles, policies, services | **this package** |
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
Both have "auth" in the name. Platform owns the ProductCraft account you use to sign in to the console; Auth owns the end-users of the products you build. They are separate databases with separate signing keys.
|
|
19
23
|
|
|
20
24
|
## Quick start
|
|
21
25
|
|
|
22
26
|
```ts
|
|
23
27
|
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
24
28
|
|
|
25
|
-
// Reads PRODUCTCRAFT_API_KEY from the environment
|
|
26
|
-
const
|
|
29
|
+
// Reads PRODUCTCRAFT_API_KEY from the environment.
|
|
30
|
+
const platform = new PlatformAuth();
|
|
27
31
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
{ params: { path: { workspace_id: "<workspace-uuid>" } } },
|
|
32
|
-
);
|
|
32
|
+
// Which workspace and permissions does this key carry?
|
|
33
|
+
const me = await platform.apiKeys.self();
|
|
34
|
+
console.log(me.workspace_id, me.name, me.allowed);
|
|
33
35
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
+
// Workspace routes take the workspace slug as the first argument.
|
|
37
|
+
const ws = await platform.workspaces.retrieve("acme");
|
|
38
|
+
|
|
39
|
+
// Pages are fetched as you iterate.
|
|
40
|
+
for await (const key of platform.workspaces.apiKeys.list(ws.slug, { include_revoked: "false" })) {
|
|
41
|
+
console.log(key.id, key.name, key.status);
|
|
42
|
+
}
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
Platform routes name the workspace by **slug**, positionally (`platform.workspaces.members.list("acme")`). The one exception is `platform.stats.retrieve()`, which uses the workspace id: pass `workspaceId`, or let a workspace key resolve it once per process.
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
## Credentials
|
|
41
48
|
|
|
42
|
-
|
|
|
49
|
+
| Credential | Pass | Use it for |
|
|
43
50
|
|---|---|---|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
| `cookie` (`auth_token`) | Server-side route handlers running behind the console session | ✅ |
|
|
47
|
-
|
|
48
|
-
PAKs are workspace-scoped credentials — they read + introspect freely against the workspace they're bound to but cannot mint another PAK or reshape the human-admin surface. Cross-workspace probes by a PAK return the uniform non-member shape (no slug → UUID oracle).
|
|
51
|
+
| Workspace API key (`pcft_live_…`) | `apiKey`, or `PRODUCTCRAFT_API_KEY` in the environment | Backend reads and introspection for the workspace the key belongs to |
|
|
52
|
+
| Platform session (a signed-in person) | `auth: { type: "bearer", token }` | Administration: creating workspaces, minting and revoking keys, roles, policies, invites |
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
```ts
|
|
55
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
const anon = new PlatformAuth();
|
|
58
|
+
const { access_token } = await anon.auth.signin({ identifier: "ada@example.com", password: process.env.PASSWORD!, session_duration: 3600 });
|
|
59
|
+
const admin = new PlatformAuth({ auth: { type: "bearer", token: access_token } });
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const minted = await admin.workspaces.apiKeys.create("acme", { name: "Production mail", preset: "mail-send", expires_in_days: 90 });
|
|
62
|
+
console.log(minted.token); // shown once: store it in your secret manager
|
|
63
|
+
await admin.auth.logoutCurrent();
|
|
57
64
|
```
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
| { type: "cookie", value: "auth_token=..." },
|
|
71
|
-
baseUrl: "https://api.auth.example.test", // optional override
|
|
72
|
-
fetch: customFetch, // optional
|
|
73
|
-
});
|
|
66
|
+
A workspace key cannot mint another key or reshape roles and policies; those calls need a person's session. An explicit `auth` always wins over the environment. The environment fallback is server-only: never put a key in `NEXT_PUBLIC_*` or any variable a bundler inlines into browser code. A keyed client constructed in a browser throws unless you set `dangerouslyAllowBrowser: true`.
|
|
67
|
+
|
|
68
|
+
## Introspection (for backends that accept workspace keys)
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
72
|
+
|
|
73
|
+
// Resolve the key a request carried to its workspace and policy.
|
|
74
|
+
declare const incomingKey: string;
|
|
75
|
+
const caller = new PlatformAuth({ apiKey: incomingKey });
|
|
76
|
+
const { is_valid, workspace_id, policy } = await caller.introspect.apiKey();
|
|
74
77
|
```
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
<!-- generated:start (scripts/gen-sdk.mjs; edits between the markers are overwritten) -->
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
## Resources
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
// Sign in
|
|
82
|
-
await platformAuth.client.POST(
|
|
83
|
-
"/v1/auth/signin",
|
|
84
|
-
{ body: { identifier: "you@example.com", password: "..." } },
|
|
85
|
-
);
|
|
83
|
+
Full reference: [api.md](./api.md).
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
- `platform.account`: `erase`
|
|
86
|
+
- `platform.apiKeys`: `self`
|
|
87
|
+
- `platform.auth`: `logout`, `logoutCurrent`, `password.requestReset`, `password.reset`, `refresh`, `signin`, `signup`
|
|
88
|
+
- `platform.introspect`: `apiKey`, `retrieve`, `workspaces.retrieve`
|
|
89
|
+
- `platform.me`: `avatar.clear`, `avatar.upload`, `hiddenGuides.hide`, `hiddenGuides.list`, `hiddenGuides.reset`, `hiddenGuides.unhide`, `profile.update`, `retrieve`
|
|
90
|
+
- `platform.oauth`: `connections.delete`, `connections.list`, `workspaces.connections.delete`, `workspaces.connections.list`, `workspaces.connections.restore`
|
|
91
|
+
- `platform.sessions`: `delete`, `list`, `revokeOthers`
|
|
92
|
+
- `platform.stats`: `retrieve`
|
|
93
|
+
- `platform.verification`: `resend`, `verify`
|
|
94
|
+
- `platform.wellKnown`: `jwks`, `oauthAuthorizationServer`
|
|
95
|
+
- `platform.workspaces`: `apiKeys.create`, `apiKeys.delete`, `apiKeys.list`, `apiKeys.presets.list`, `apiKeys.preview`, `apiKeys.retrieve`, `apiKeys.rotate`, `apiKeys.update`, `apiKeys.updateBindings`, `auditFeed.list`, `auditLogs.export`, `auditLogs.list`, `avatar.clear`, `avatar.upload`, `create`, `delete`, `invites.accept`, `invites.create`, `invites.delete`, `invites.list`, `list`, `members.delete`, `members.list`, `members.updateRole`, `policies.actions.catalog`, `policies.create`, `policies.delete`, `policies.list`, `policies.retrieve`, `policies.update`, `retrieve`, `roles.create`, `roles.delete`, `roles.list`, `roles.retrieve`, `roles.update`, `services.disable`, `services.enable`, `services.list`, `services.updateSettings`, `update`
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
await platformAuth.client.POST("/v1/auth/logout-current", { body: { refresh_token } });
|
|
97
|
+
## Pagination
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
await platformAuth.client.POST(
|
|
95
|
-
"/v1/auth/password/request-reset",
|
|
96
|
-
{ body: { email: "you@example.com" } },
|
|
97
|
-
);
|
|
99
|
+
List methods return a `PagePromise`: `for await` walks every item across pages, `await` gives one page.
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
101
|
+
```ts
|
|
102
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
103
|
+
const platform = new PlatformAuth({ apiKey: process.env.PRODUCTCRAFT_API_KEY });
|
|
104
|
+
for await (const item of platform.oauth.connections.list({ limit: 50 })) console.log(item);
|
|
105
|
+
const page = await platform.oauth.connections.list();
|
|
106
|
+
if (page.hasNextPage()) await page.getNextPage();
|
|
107
|
+
const all = await platform.oauth.connections.list().toArray({ maxItems: 500 });
|
|
103
108
|
```
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```ts before
|
|
108
|
-
// Resolve the bearer of a token: returns account + email_verified_at + workspaces.
|
|
109
|
-
// Accepts a `pcft_live_*` PAK too — the response carries a synthetic verified
|
|
110
|
-
// account block so downstream `RequireEmailVerifiedGuard`-style gates pass.
|
|
111
|
-
await platformAuth.client.GET("/v1/introspect");
|
|
110
|
+
## Errors
|
|
112
111
|
|
|
113
|
-
|
|
114
|
-
await platformAuth.client.GET(
|
|
115
|
-
"/v1/introspect/workspaces/{workspace_id}",
|
|
116
|
-
{ params: { path: { workspace_id: "<workspace-uuid>" } } },
|
|
117
|
-
);
|
|
112
|
+
Every failure is a class you can catch. `APIError` carries `status`, `code`, `message`, `errors[]`, `requestId` and `docUrl`.
|
|
118
113
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
```ts
|
|
115
|
+
import { PlatformAuth, NotFoundError, RateLimitError, APIError } from "@productcraft/platform-auth";
|
|
116
|
+
const platform = new PlatformAuth();
|
|
117
|
+
try {
|
|
118
|
+
await platform.introspect.workspaces.retrieve("00000000-0000-0000-0000-000000000000");
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (err instanceof NotFoundError) console.log(err.code, err.requestId, err.docUrl);
|
|
121
|
+
else if (err instanceof RateLimitError) console.log(err.retryAfter);
|
|
122
|
+
else if (err instanceof APIError) console.log(err.status, err.errors);
|
|
123
|
+
else throw err;
|
|
124
|
+
}
|
|
123
125
|
```
|
|
124
126
|
|
|
125
|
-
|
|
127
|
+
## Retries and timeouts
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
Network errors, 408, 429, 5xx and `409 IDEMPOTENCY_IN_PROGRESS` are retried twice by default with exponential backoff and jitter, honouring `Retry-After` up to 60 s. A POST or PATCH without an idempotency key is never retried. Each attempt times out after 60 s, so the worst case with defaults is about 3 minutes; pass `signal: AbortSignal.timeout(ms)` for an overall deadline.
|
|
128
130
|
|
|
129
|
-
```ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
await
|
|
131
|
+
```ts
|
|
132
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
133
|
+
const platform = new PlatformAuth({ maxRetries: 3, timeout: 20_000 });
|
|
134
|
+
await platform.oauth.connections.list({}, { maxRetries: 0, signal: AbortSignal.timeout(5_000) });
|
|
135
|
+
```
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
await platformAuth.client.POST(
|
|
136
|
-
"/v1/workspaces",
|
|
137
|
-
{ body: { display_name: "Acme", slug: "acme" } },
|
|
138
|
-
);
|
|
137
|
+
## Idempotency
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
await platformAuth.client.GET("/v1/workspaces/{workspace_slug}", { ... });
|
|
139
|
+
Writes that support it send an `Idempotency-Key` automatically and reuse it on every retry. Pass your own to make a retry across processes safe. If the API's idempotency store is unavailable it runs the request without it (fail-open), so a retry during that window can execute twice.
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
```ts
|
|
142
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
143
|
+
const platform = new PlatformAuth({ apiKey: process.env.PRODUCTCRAFT_API_KEY });
|
|
144
|
+
const workspace = await platform.workspaces.create({ display_name: "Acme Corp", slug: "acme-corp" }, { idempotencyKey: "order-1234" });
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
```ts before
|
|
150
|
-
// Members
|
|
151
|
-
await platformAuth.client.GET("/v1/workspaces/{workspace_slug}/members", { ... });
|
|
152
|
-
await platformAuth.client.PATCH(
|
|
153
|
-
"/v1/workspaces/{workspace_slug}/members/{account_id}/role",
|
|
154
|
-
{ params: { ... }, body: { role_id: "role_..." } },
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Invites
|
|
158
|
-
await platformAuth.client.POST(
|
|
159
|
-
"/v1/workspaces/{workspace_slug}/invites",
|
|
160
|
-
{ params: { ... }, body: { email: "...", role_id: "..." } },
|
|
161
|
-
);
|
|
162
|
-
await platformAuth.client.POST("/v1/workspaces/invites/accept", { body: { code } });
|
|
163
|
-
|
|
164
|
-
// Roles
|
|
165
|
-
await platformAuth.client.POST(
|
|
166
|
-
"/v1/workspaces/{workspace_slug}/roles",
|
|
167
|
-
{ params: { ... }, body: { name: "billing-admin", policy_ids: ["pol_..."] } },
|
|
168
|
-
);
|
|
169
|
-
```
|
|
147
|
+
## Raw responses
|
|
170
148
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```ts before
|
|
174
|
-
// List the canonical permission catalogue (what verbs exist for each service)
|
|
175
|
-
await platformAuth.client.GET(
|
|
176
|
-
"/v1/workspaces/{workspace_slug}/policies/actions/catalog",
|
|
177
|
-
{ ... },
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Create a managed policy. Cookie/JWT only.
|
|
181
|
-
await platformAuth.client.POST(
|
|
182
|
-
"/v1/workspaces/{workspace_slug}/policies",
|
|
183
|
-
{
|
|
184
|
-
params: { ... },
|
|
185
|
-
body: {
|
|
186
|
-
name: "mail-read-only",
|
|
187
|
-
description: "Read-only access to all Mail resources",
|
|
188
|
-
policy: [
|
|
189
|
-
{ effect: "allow", actions: ["mail.read", "mail.list"], resources: ["*"] },
|
|
190
|
-
],
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
```
|
|
149
|
+
Every returned object has a non-enumerable `lastResponse` (`requestId`, `status`, `headers`). `.withResponse()` adds the `Response` and parsed rate-limit headers.
|
|
195
150
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const { data: policy } = await platformAuth.client.POST(
|
|
205
|
-
"/v1/workspaces/{workspace_slug}/policies",
|
|
206
|
-
{
|
|
207
|
-
params: { path: { workspace_slug: "acme" } },
|
|
208
|
-
body: {
|
|
209
|
-
name: "ci-staging-deploy",
|
|
210
|
-
description: "Auth + Mail for the staging pipeline",
|
|
211
|
-
policy: [
|
|
212
|
-
// Permission action prefixes rebranded (heimdall.* → auth.*, envoi.* → mail.*),
|
|
213
|
-
// but resource-URN namespaces intentionally stay pcft:heimdall: / pcft:envoi: —
|
|
214
|
-
// the services still emit the old resource prefixes.
|
|
215
|
-
{ effect: "allow", actions: ["auth.create", "auth.read", "auth.update"], resources: ["pcft:heimdall:app/*"] },
|
|
216
|
-
{ effect: "allow", actions: ["mail.read", "mail.send"], resources: ["*"] },
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
// 2. Mint a PAT that binds to that policy
|
|
223
|
-
const { data: pak } = await platformAuth.client.POST(
|
|
224
|
-
"/v1/workspaces/{workspace_slug}/api-keys",
|
|
225
|
-
{
|
|
226
|
-
params: { path: { workspace_slug: "acme" } },
|
|
227
|
-
body: {
|
|
228
|
-
name: "github-actions-staging-deploy",
|
|
229
|
-
description: "Deploys acme-staging app from CI",
|
|
230
|
-
policy_ids: [policy!.id],
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
);
|
|
234
|
-
// pak.token — the plaintext `pcft_live_*` value, returned exactly once
|
|
235
|
-
// pak.record — public metadata (id, prefix, policies, last_used_at, …)
|
|
236
|
-
|
|
237
|
-
// Replace the bound policies on an existing PAK (re-runs caller-narrowing
|
|
238
|
-
// against the merged statement set)
|
|
239
|
-
await platformAuth.client.PATCH(
|
|
240
|
-
"/v1/workspaces/{workspace_slug}/api-keys/{id}/bindings",
|
|
241
|
-
{ params: { ... }, body: { policy_ids: ["<new-policy-uuid>"] } },
|
|
242
|
-
);
|
|
151
|
+
```ts
|
|
152
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
153
|
+
const platform = new PlatformAuth({ apiKey: process.env.PRODUCTCRAFT_API_KEY });
|
|
154
|
+
const id = "9b2f6c1e-0d6a-4a63-9a43-2f1c3e4d5a6b";
|
|
155
|
+
const obj = await platform.introspect.workspaces.retrieve(id);
|
|
156
|
+
console.log(obj.lastResponse.requestId);
|
|
157
|
+
const { data, response, requestId, rateLimit } = await platform.introspect.workspaces.retrieve(id).withResponse();
|
|
158
|
+
console.log(data, response.status, requestId, rateLimit?.remaining);
|
|
243
159
|
```
|
|
244
160
|
|
|
245
|
-
|
|
161
|
+
## Testing
|
|
246
162
|
|
|
247
|
-
|
|
248
|
-
// Enable / disable a service for the workspace (mail, waitlist, social, auth, …)
|
|
249
|
-
await platformAuth.client.PUT(
|
|
250
|
-
"/v1/workspaces/{workspace_slug}/services/{service}",
|
|
251
|
-
{ params: { ... }, body: { enabled: true } },
|
|
252
|
-
);
|
|
163
|
+
`@productcraft/platform-auth/testing` exports `createMockFetch()`, a `fetch` that answers every operation with its spec example. Override one operation by name.
|
|
253
164
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
);
|
|
165
|
+
```ts
|
|
166
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
167
|
+
import { createMockFetch } from "@productcraft/platform-auth/testing";
|
|
168
|
+
const fetch = createMockFetch();
|
|
169
|
+
const platform = new PlatformAuth({ apiKey: "test", workspaceId: "9b2f6c1e-0d6a-4a63-9a43-2f1c3e4d5a6b", fetch });
|
|
170
|
+
await platform.oauth.connections.list();
|
|
171
|
+
console.log(fetch.calls.length);
|
|
259
172
|
```
|
|
260
173
|
|
|
261
|
-
|
|
174
|
+
## Escape hatch
|
|
262
175
|
|
|
263
|
-
|
|
264
|
-
// Read the workspace audit log
|
|
265
|
-
await platformAuth.client.GET(
|
|
266
|
-
"/v1/workspaces/{workspace_slug}/audit-logs",
|
|
267
|
-
{ params: { ... }, query: { limit: 100 } },
|
|
268
|
-
);
|
|
176
|
+
`.client` is the typed `openapi-fetch` client over the same credential and base URL (no retries). Use it for anything the resource methods do not cover.
|
|
269
177
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
);
|
|
178
|
+
```ts
|
|
179
|
+
import { PlatformAuth } from "@productcraft/platform-auth";
|
|
180
|
+
const platform = new PlatformAuth({ apiKey: process.env.PRODUCTCRAFT_API_KEY });
|
|
181
|
+
const { data, error } = await platform.client.GET("/.well-known/jwks.json");
|
|
182
|
+
console.log(data, error);
|
|
275
183
|
```
|
|
276
184
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Generated from the live OpenAPI spec at `https://api.auth.productcraft.co/docs-json` via [`openapi-typescript`](https://openapi-ts.dev/) + [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/). The nightly `spec-refresh` workflow opens a PR whenever the spec changes.
|
|
185
|
+
<!-- generated:end -->
|
|
280
186
|
|
|
281
187
|
## License
|
|
282
188
|
|
|
283
|
-
|
|
189
|
+
MIT
|