@productcraft/platform-auth 0.0.3 → 0.0.5
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 +222 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
# @productcraft/platform-auth
|
|
2
2
|
|
|
3
|
-
Typed Node.js SDK for [ProductCraft Platform-Auth](https://productcraft.co) —
|
|
3
|
+
Typed Node.js SDK for [ProductCraft Platform-Auth](https://productcraft.co) — PlatformUser sign-in / sign-up / password reset, workspaces, members + invites, workspace roles + IAM policies, workspace API keys, service activation, and the `/v1/introspect` endpoint every other ProductCraft backend uses to resolve permissions.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @productcraft/platform-auth
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Server-side only. The SDK ships a Platform API Key
|
|
9
|
+
Server-side only. The SDK ships a Platform API Key in the `Authorization` header — never embed it in a browser bundle.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Which SDK do I want?
|
|
12
|
+
|
|
13
|
+
| If your goal is to … | Reach for |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Authenticate **your own end-users** in a product you're building (Heimdall apps) | [`@productcraft/heimdall`](https://www.npmjs.com/package/@productcraft/heimdall) |
|
|
16
|
+
| Authenticate **your team** into the ProductCraft console + manage workspaces | **this package** |
|
|
17
|
+
|
|
18
|
+
The two are easy to confuse because both have "auth" in the name. Platform-Auth owns *the ProductCraft account you use to log into productcraft.co*. Heimdall owns *the end-users of products you build on the platform*. Distinct databases, distinct signing keys.
|
|
12
19
|
|
|
13
20
|
## Quick start
|
|
14
21
|
|
|
@@ -21,22 +28,26 @@ const platformAuth = new PlatformAuth({
|
|
|
21
28
|
|
|
22
29
|
// Resolve the caller's effective policy + enabled services for a workspace
|
|
23
30
|
const { data } = await platformAuth.client.GET(
|
|
24
|
-
"/v1/introspect/workspaces/{
|
|
25
|
-
{ params: { path: {
|
|
31
|
+
"/v1/introspect/workspaces/{workspace_id}",
|
|
32
|
+
{ params: { path: { workspace_id: "<workspace-uuid>" } } },
|
|
26
33
|
);
|
|
27
34
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
"/v1/workspaces/{slug}/api-keys",
|
|
31
|
-
{
|
|
32
|
-
params: { path: { slug: "my-workspace" } },
|
|
33
|
-
body: { name: "ci-bot", permissions: ["envoi.read", "rally.read"] },
|
|
34
|
-
},
|
|
35
|
-
);
|
|
35
|
+
// data.policy — flat array of IAM-style { effect, actions, resources } statements
|
|
36
|
+
// data.services — string[] of enabled services (envoi / rally / agora / heimdall / …)
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
The `client` is an [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/) instance bound to `https://api.auth.productcraft.co` and your auth credential.
|
|
39
40
|
|
|
41
|
+
### Auth modes
|
|
42
|
+
|
|
43
|
+
| Mode | When to use | Mint/rotate/delete surfaces |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `apiKey` (`pcft_live_*`) | Backend-to-backend reads + introspect calls | ❌ — mint requires a human session |
|
|
46
|
+
| `bearer` (PlatformUser JWT) | Anything administrative: workspace create/delete, role/policy authoring, PAK minting, invite mutations | ✅ |
|
|
47
|
+
| `cookie` (`auth_token`) | Server-side route handlers running behind the console session | ✅ |
|
|
48
|
+
|
|
49
|
+
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).
|
|
50
|
+
|
|
40
51
|
## Configuration
|
|
41
52
|
|
|
42
53
|
```ts
|
|
@@ -49,6 +60,204 @@ new PlatformAuth({
|
|
|
49
60
|
});
|
|
50
61
|
```
|
|
51
62
|
|
|
63
|
+
## Common operations
|
|
64
|
+
|
|
65
|
+
### Authentication (PlatformUser sign-in)
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// Sign in
|
|
69
|
+
await platformAuth.client.POST(
|
|
70
|
+
"/v1/auth/signin",
|
|
71
|
+
{ body: { identifier: "you@example.com", password: "..." } },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Refresh tokens
|
|
75
|
+
await platformAuth.client.POST("/v1/auth/refresh", { body: { refresh_token } });
|
|
76
|
+
|
|
77
|
+
// Logout (single session) — or /v1/auth/logout to revoke every refresh token
|
|
78
|
+
await platformAuth.client.POST("/v1/auth/logout-current", { body: { refresh_token } });
|
|
79
|
+
|
|
80
|
+
// Password reset
|
|
81
|
+
await platformAuth.client.POST(
|
|
82
|
+
"/v1/auth/password/request-reset",
|
|
83
|
+
{ body: { email: "you@example.com" } },
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
await platformAuth.client.POST(
|
|
87
|
+
"/v1/auth/password/reset",
|
|
88
|
+
{ body: { token, new_password: "..." } },
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Introspect — used by every other ProductCraft backend
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// Resolve the bearer of a token: returns account + email_verified_at + workspaces.
|
|
96
|
+
// Accepts a `pcft_live_*` PAK too — the response carries a synthetic verified
|
|
97
|
+
// account block so downstream `RequireEmailVerifiedGuard`-style gates pass.
|
|
98
|
+
await platformAuth.client.GET("/v1/introspect");
|
|
99
|
+
|
|
100
|
+
// Resolve effective policy + enabled services for one workspace
|
|
101
|
+
await platformAuth.client.GET(
|
|
102
|
+
"/v1/introspect/workspaces/{workspace_id}",
|
|
103
|
+
{ params: { path: { workspace_id: "<workspace-uuid>" } } },
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Resolve a PAK — POST, not GET. Used internally by services that accept
|
|
107
|
+
// `pcft_live_*` on routes. Caller's bearer is the PAK being introspected;
|
|
108
|
+
// the body is unused (sent as `{}`).
|
|
109
|
+
await platformAuth.client.POST("/v1/introspect/api-key");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The latter two are cached by every consuming service for ~60 seconds. Reach for them from a backend gate; never from a hot request path.
|
|
113
|
+
|
|
114
|
+
### Workspaces
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// List — for a PAK caller, this returns exactly the workspace the PAK is
|
|
118
|
+
// bound to (member_role / member_role_id are null on that lane).
|
|
119
|
+
await platformAuth.client.GET("/v1/workspaces");
|
|
120
|
+
|
|
121
|
+
// Create — cookie/JWT only. PAKs cannot create workspaces.
|
|
122
|
+
await platformAuth.client.POST(
|
|
123
|
+
"/v1/workspaces",
|
|
124
|
+
{ body: { display_name: "Acme", slug: "acme" } },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Get / update by slug
|
|
128
|
+
await platformAuth.client.GET("/v1/workspaces/{workspace_slug}", { ... });
|
|
129
|
+
|
|
130
|
+
// Delete — cookie/JWT only. PAKs cannot delete workspaces.
|
|
131
|
+
await platformAuth.client.DELETE("/v1/workspaces/{workspace_slug}", { ... });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Members, invites, roles
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// Members
|
|
138
|
+
await platformAuth.client.GET("/v1/workspaces/{workspace_slug}/members", { ... });
|
|
139
|
+
await platformAuth.client.PATCH(
|
|
140
|
+
"/v1/workspaces/{workspace_slug}/members/{account_id}/role",
|
|
141
|
+
{ params: { ... }, body: { role_id: "role_..." } },
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Invites
|
|
145
|
+
await platformAuth.client.POST(
|
|
146
|
+
"/v1/workspaces/{workspace_slug}/invites",
|
|
147
|
+
{ params: { ... }, body: { email: "...", role_id: "..." } },
|
|
148
|
+
);
|
|
149
|
+
await platformAuth.client.POST("/v1/workspaces/invites/accept", { body: { code } });
|
|
150
|
+
|
|
151
|
+
// Roles
|
|
152
|
+
await platformAuth.client.POST(
|
|
153
|
+
"/v1/workspaces/{workspace_slug}/roles",
|
|
154
|
+
{ params: { ... }, body: { name: "billing-admin", policy_ids: ["pol_..."] } },
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### IAM policies
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// List the canonical permission catalogue (what verbs exist for each service)
|
|
162
|
+
await platformAuth.client.GET(
|
|
163
|
+
"/v1/workspaces/{workspace_slug}/policies/actions/catalog",
|
|
164
|
+
{ ... },
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Create a managed policy. Cookie/JWT only.
|
|
168
|
+
await platformAuth.client.POST(
|
|
169
|
+
"/v1/workspaces/{workspace_slug}/policies",
|
|
170
|
+
{
|
|
171
|
+
params: { ... },
|
|
172
|
+
body: {
|
|
173
|
+
name: "envoi-read-only",
|
|
174
|
+
description: "Read-only access to all Envoi resources",
|
|
175
|
+
policy: [
|
|
176
|
+
{ effect: "allow", actions: ["envoi.read", "envoi.list"], resources: ["*"] },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Workspace API keys (PATs)
|
|
184
|
+
|
|
185
|
+
PATs bind to one or more managed `workspace_policy` rows. Author the policy first, then mint the key with its id. The inline `policy: [...]` shape is **not** accepted — `policy_ids: [<uuid>]` is required.
|
|
186
|
+
|
|
187
|
+
Mint / rotate / revoke require a cookie or JWT — a PAK cannot mint another PAK.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// 1. Author (or reuse) a managed policy
|
|
191
|
+
const { data: policy } = await platformAuth.client.POST(
|
|
192
|
+
"/v1/workspaces/{workspace_slug}/policies",
|
|
193
|
+
{
|
|
194
|
+
params: { path: { workspace_slug: "acme" } },
|
|
195
|
+
body: {
|
|
196
|
+
name: "ci-staging-deploy",
|
|
197
|
+
description: "Heimdall + Envoi for the staging pipeline",
|
|
198
|
+
policy: [
|
|
199
|
+
{ effect: "allow", actions: ["heimdall.create", "heimdall.read", "heimdall.update"], resources: ["pcft:heimdall:app/*"] },
|
|
200
|
+
{ effect: "allow", actions: ["envoi.template.read", "envoi.template.send"], resources: ["*"] },
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// 2. Mint a PAT that binds to that policy
|
|
207
|
+
const { data: pak } = await platformAuth.client.POST(
|
|
208
|
+
"/v1/workspaces/{workspace_slug}/api-keys",
|
|
209
|
+
{
|
|
210
|
+
params: { path: { workspace_slug: "acme" } },
|
|
211
|
+
body: {
|
|
212
|
+
name: "github-actions-staging-deploy",
|
|
213
|
+
description: "Deploys acme-staging app from CI",
|
|
214
|
+
policy_ids: [policy!.id],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
// pak.token — the plaintext `pcft_live_*` value, returned exactly once
|
|
219
|
+
// pak.record — public metadata (id, prefix, policies, last_used_at, …)
|
|
220
|
+
|
|
221
|
+
// Replace the bound policies on an existing PAK (re-runs caller-narrowing
|
|
222
|
+
// against the merged statement set)
|
|
223
|
+
await platformAuth.client.PATCH(
|
|
224
|
+
"/v1/workspaces/{workspace_slug}/api-keys/{id}/bindings",
|
|
225
|
+
{ params: { ... }, body: { policy_ids: ["<new-policy-uuid>"] } },
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Service activation
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// Enable / disable a service for the workspace (envoi, rally, agora, heimdall, …)
|
|
233
|
+
await platformAuth.client.PUT(
|
|
234
|
+
"/v1/workspaces/{workspace_slug}/services/{service}",
|
|
235
|
+
{ params: { ... }, body: { enabled: true } },
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Per-service workspace settings
|
|
239
|
+
await platformAuth.client.PUT(
|
|
240
|
+
"/v1/workspaces/{workspace_slug}/services/{service}/settings",
|
|
241
|
+
{ ... },
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Audit
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// Read the workspace audit log
|
|
249
|
+
await platformAuth.client.GET(
|
|
250
|
+
"/v1/workspaces/{workspace_slug}/audit-logs",
|
|
251
|
+
{ params: { ... }, query: { limit: 100 } },
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Streaming-friendly "recent activity" feed (for the console UI)
|
|
255
|
+
await platformAuth.client.GET(
|
|
256
|
+
"/v1/workspaces/{workspace_slug}/audit-feed",
|
|
257
|
+
{ ... },
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
52
261
|
## How this SDK is built
|
|
53
262
|
|
|
54
263
|
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.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@productcraft/platform-auth",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"description": "PlatformUser auth, workspaces, members, invites, roles + IAM policies, API keys, service activation, and the introspect endpoint every ProductCraft backend uses to resolve permissions. Generated from the production OpenAPI spec.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|