@witnium-tech/witniumchain 0.3.0 → 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/FRONTEND-INTEGRATION.md +265 -0
- package/README.md +95 -19
- package/dist/index.d.mts +4439 -62
- package/dist/index.d.ts +4439 -62
- package/dist/index.js +812 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +808 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -7
- package/README.md.bak +0 -160
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# WitniumChain — Frontend Integration Guide
|
|
2
|
+
|
|
3
|
+
Audience: the team building the WitniumChain dashboard frontend. This is the
|
|
4
|
+
contract between the backend (accounts + chain-api) and your UI. It covers the
|
|
5
|
+
end-to-end user flows, where keys live, the recovery-file format, and the
|
|
6
|
+
known sharp edges discovered during backend validation.
|
|
7
|
+
|
|
8
|
+
The SDK that backs all of this is **`@witnium-tech/witniumchain`** (npm). Every
|
|
9
|
+
flow below has a typed method on it.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. The custody model — read this first
|
|
14
|
+
|
|
15
|
+
Two ed25519 keys are generated **client-side** at signup and never leave the
|
|
16
|
+
user's device in plaintext:
|
|
17
|
+
|
|
18
|
+
| Key | Controls | Used for |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| **Owner key** | The contract's signing-key set (add/revoke), pause/unpause | Authorizing changes. Never signs witnesses. |
|
|
21
|
+
| **Signing key** | — | Signing witnesses day-to-day |
|
|
22
|
+
|
|
23
|
+
Witnium only ever receives the **public** halves. The backend enforces
|
|
24
|
+
`ownerPublicKey !== initialSigningPublicKey`.
|
|
25
|
+
|
|
26
|
+
**Your job on the frontend:** generate these keys, store them encrypted, let
|
|
27
|
+
the user export/import them, and produce owner-key signatures when the backend
|
|
28
|
+
asks for them (the MCP-CUSTODY ceremony, §6). You are the custody surface. If
|
|
29
|
+
you lose the keys with no recovery file, the account's contract is
|
|
30
|
+
unrecoverable — there is no server-side escrow by design.
|
|
31
|
+
|
|
32
|
+
### Recommended storage (production)
|
|
33
|
+
|
|
34
|
+
The throwaway test frontend stored keys as plain hex in `localStorage`. **Do
|
|
35
|
+
not ship that.** The intended production design:
|
|
36
|
+
|
|
37
|
+
- Wrap the two private keys with a key derived from a **WebAuthn PRF** assertion
|
|
38
|
+
(passkey). The user authenticates with their passkey to decrypt.
|
|
39
|
+
- Issue **recovery codes** at signup (one-time display) as the fallback when no
|
|
40
|
+
passkey is available (new device, lost authenticator).
|
|
41
|
+
- Persist the wrapped blob in IndexedDB.
|
|
42
|
+
|
|
43
|
+
The backend does not need to know how you wrap the keys — it only sees public
|
|
44
|
+
keys + signatures. But see §7 for the **same-origin requirement**, which is a
|
|
45
|
+
hard constraint on where this storage lives.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @witnium-tech/witniumchain
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { WitniumchainClient } from '@witnium-tech/witniumchain';
|
|
57
|
+
|
|
58
|
+
const client = new WitniumchainClient({
|
|
59
|
+
baseUrl: 'https://auth.witniumchain.com', // accounts
|
|
60
|
+
chainBaseUrl: 'https://api.witniumchain.com', // chain-api (read methods)
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Generate keys with any ed25519 lib. The backend + SDK speak 64-hex-char
|
|
65
|
+
public keys (no `0x`). Example with `@noble/ed25519`:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import * as ed from '@noble/ed25519';
|
|
69
|
+
const priv = ed.utils.randomPrivateKey();
|
|
70
|
+
const pub = await ed.getPublicKeyAsync(priv); // hex-encode both halves
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. Signup → org → contract (one call)
|
|
76
|
+
|
|
77
|
+
`createOrg` is the self-serve path. It creates the admin user, the
|
|
78
|
+
organization, the org-admin membership, **and deploys the contract** with the
|
|
79
|
+
client-generated keys — all in one request.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const result = await client.createOrg({
|
|
83
|
+
orgName: 'Acme Inc.',
|
|
84
|
+
adminEmail: 'you@example.com',
|
|
85
|
+
adminPassword: '≥12 chars',
|
|
86
|
+
ownerPublicKey: ownerPubHex,
|
|
87
|
+
initialSigningPublicKey: signingPubHex,
|
|
88
|
+
});
|
|
89
|
+
// → { orgId, userId, contractAddress, contractVersion, emailVerifyToken }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Persist the two private keys + `contractAddress` locally (wrapped, per §1) and
|
|
93
|
+
prompt a recovery-file download (§5) before doing anything else.
|
|
94
|
+
|
|
95
|
+
`emailVerifyToken` is also emailed to the admin. It's echoed in the response so
|
|
96
|
+
you can render a "click to verify" link inline if you prefer. **The email is
|
|
97
|
+
the login gate, not the contract-deploy gate** — the contract is already live
|
|
98
|
+
when this returns.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. Auth flows
|
|
103
|
+
|
|
104
|
+
### Email verify
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
await client.verifyEmail(token); // GET /v1/auth/verify?token=…
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> ⚠️ This is a **GET**, not POST. (Caught in testing — easy to get wrong.)
|
|
111
|
+
|
|
112
|
+
### Password login
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
await client.login({ email, password }); // sets wac_session cookie
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
In a browser the `wac_session` cookie is set automatically (the SDK uses
|
|
119
|
+
`credentials: 'include'`). Server-side callers must capture + replay the cookie.
|
|
120
|
+
|
|
121
|
+
### Magic-link sign-in
|
|
122
|
+
|
|
123
|
+
`beginOAuthLogin` / passwordless paths exist; see the SDK's `OauthNamespace`
|
|
124
|
+
and the accounts OpenAPI for `/v1/auth/magic/*`.
|
|
125
|
+
|
|
126
|
+
### MFA (TOTP)
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const { secret, otpauthUrl } = await client.mfa.totp.enroll(); // render QR from otpauthUrl
|
|
130
|
+
const { recoveryCodes } = await client.mfa.totp.confirm({ code }); // show ONCE
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### OAuth 2.1 + PKCE (for when your dashboard is itself an OAuth client)
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const { authorizationUrl } = await client.beginOAuthLogin({ … });
|
|
137
|
+
// …redirect, then on callback:
|
|
138
|
+
await client.completeOAuthLogin({ … }); // PKCE verifier from sessionStorage
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Access token held in memory; refresh token delivered as an HttpOnly
|
|
142
|
+
`wac_refresh` cookie scoped to `/token` (JS never touches it). The SDK does
|
|
143
|
+
transparent single-flight refresh on 401.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 5. Recovery-file format
|
|
148
|
+
|
|
149
|
+
The throwaway frontend used this shape. **Treat it as a starting point** —
|
|
150
|
+
production should wrap `record` with WebAuthn/recovery-code encryption rather
|
|
151
|
+
than storing cleartext keys.
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"version": 1,
|
|
156
|
+
"type": "witnium-recovery",
|
|
157
|
+
"created": "<ISO 8601>",
|
|
158
|
+
"record": {
|
|
159
|
+
"email": "you@example.com",
|
|
160
|
+
"orgId": "<uuid>",
|
|
161
|
+
"userId": "<uuid>",
|
|
162
|
+
"contractAddress": "0x… (lowercase)",
|
|
163
|
+
"contractVersion": "5.0.0",
|
|
164
|
+
"ownerPublicKey": "<64 hex>",
|
|
165
|
+
"ownerPrivateKey": "<64 hex — ENCRYPT THIS>",
|
|
166
|
+
"signingPublicKey": "<64 hex>",
|
|
167
|
+
"signingPrivateKey": "<64 hex — ENCRYPT THIS>"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Import = restore `record` into local storage. The owner private key must be
|
|
173
|
+
retrievable for the MCP-CUSTODY ceremony (§6).
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 6. MCP-CUSTODY consent ceremony
|
|
178
|
+
|
|
179
|
+
When a user connects an AI agent (claude.ai's MCP connector, or a self-hosted
|
|
180
|
+
one) the backend runs a ceremony at OAuth-consent time that mints a **fresh
|
|
181
|
+
ephemeral signing key per connection** and asks the user to sign two owner-key
|
|
182
|
+
operations:
|
|
183
|
+
|
|
184
|
+
1. `addSigningKey(<ephemeral key>)` — registers it on the contract (submitted now)
|
|
185
|
+
2. `revokeSigningKey(<same key>)` — pre-signed, stored, fired automatically on disconnect
|
|
186
|
+
|
|
187
|
+
**What the frontend must provide:** the page that collects these two
|
|
188
|
+
signatures is currently *server-rendered by accounts* at
|
|
189
|
+
`/interaction/:uid/sign-keys`. It runs inline JS that reads the owner key from
|
|
190
|
+
`localStorage['witnium.ownerPrivateKey:<contractAddress>']`, signs both
|
|
191
|
+
messages, and form-POSTs them back.
|
|
192
|
+
|
|
193
|
+
You have two choices:
|
|
194
|
+
|
|
195
|
+
- **Let accounts render it** (current behavior). Then your only job is to make
|
|
196
|
+
sure the owner key is in `localStorage` under that exact key, **on the
|
|
197
|
+
accounts origin** (see §7).
|
|
198
|
+
- **Render it yourself** and POST the two signatures to
|
|
199
|
+
`/interaction/:uid/sign-keys` (`addOwnerSignature`, `revokeOwnerSignature`,
|
|
200
|
+
`revokeNonce`). Gives you full UX control. The signed message formats are:
|
|
201
|
+
- add: `{"op":1,"contract":"<addr>","nonce":<ownerNonce+1>,"newKey":"<pub>"}`
|
|
202
|
+
- revoke: `{"op":2,"contract":"<addr>","nonce":<ownerNonce+2>,"key":"<pub>"}`
|
|
203
|
+
(canonical JSON, signed as UTF-8 bytes with the owner key)
|
|
204
|
+
|
|
205
|
+
To get refresh tokens (required for the agent to stay connected + for the
|
|
206
|
+
disconnect-on-revoke cascade), the OAuth client **must request
|
|
207
|
+
`prompt=consent`** and include `offline_access` in scope. Without
|
|
208
|
+
`prompt=consent` the AS silently drops `offline_access` and no refresh token is
|
|
209
|
+
issued — this is standard OIDC, not a bug.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 7. Same-origin requirement (architectural — needs a decision)
|
|
214
|
+
|
|
215
|
+
**The dashboard that stores keys and the accounts OIDC interaction pages must
|
|
216
|
+
share an origin**, because `localStorage` is origin-scoped. Discovered in
|
|
217
|
+
testing: keys saved on `test-frontend.witniumchain.com` were invisible to the
|
|
218
|
+
sign-keys page rendered at `auth.witniumchain.com` → the ceremony couldn't find
|
|
219
|
+
the owner key.
|
|
220
|
+
|
|
221
|
+
Pick one:
|
|
222
|
+
|
|
223
|
+
- **(Recommended) Serve the dashboard from `auth.witniumchain.com`** (a path,
|
|
224
|
+
or accounts serving the SPA). Owner key in localStorage is naturally visible
|
|
225
|
+
to the OIDC interaction pages. Simplest, no cross-origin plumbing.
|
|
226
|
+
- **Cross-origin with `postMessage`** — dashboard on its own domain passes the
|
|
227
|
+
owner-key signature to the accounts interaction page via a `postMessage`
|
|
228
|
+
handshake. More moving parts; partially erodes the "keys never leave your
|
|
229
|
+
page" story. Only do this if a separate dashboard domain is a hard
|
|
230
|
+
requirement.
|
|
231
|
+
|
|
232
|
+
This decision blocks the key-storage implementation, so settle it early.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 8. Known limitations / sharp edges
|
|
237
|
+
|
|
238
|
+
| Issue | Impact | Status |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| **Pre-signed revoke nonce drift** | If a user opens several agent connections in a row without disconnecting, older connections' on-chain `revokeSigningKey` fails when finally fired (the pre-signed sig is bound to a stale ownerNonce). The Vault key is still destroyed — Witnium genuinely loses signing ability — but the on-chain key stays listed as active. | Known. On-chain cleanup is best-effort under serial grants. Mitigation TBD (revoke-then-add atomic, or periodic re-sign). |
|
|
241
|
+
| **`prompt=consent` required for refresh tokens** | Omitting it silently drops `offline_access`. | Working as designed (OIDC). Document in your OAuth client. |
|
|
242
|
+
| **Email verify is GET** | POST returns "Cannot POST". | Working as designed. |
|
|
243
|
+
| **Same-origin localStorage** | §7. | Architectural decision required. |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 9. API reference
|
|
248
|
+
|
|
249
|
+
- **accounts OpenAPI**: `https://auth.witniumchain.com/openapi.json` (or the
|
|
250
|
+
committed `specs/accounts.openapi.json` in the SDK repo)
|
|
251
|
+
- **chain-api OpenAPI**: `specs/chain-api.openapi.json` in the SDK repo
|
|
252
|
+
- Every SDK method's request/response type is derived from these specs via
|
|
253
|
+
`openapi-typescript`, so they stay in lockstep. Re-export aliases (e.g.
|
|
254
|
+
`CreateOrgRequest`, `SignupResponse`) are in `src/types.ts`.
|
|
255
|
+
|
|
256
|
+
## 10. SDK clients at a glance
|
|
257
|
+
|
|
258
|
+
| Client | For | Auth |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `WitniumchainClient` | end-users (signup, login, OAuth, MFA, delegated keys, witness reads) | session cookie / access token |
|
|
261
|
+
| `WitniumchainOrgClient` | org admins managing members | org API key |
|
|
262
|
+
| `WitniumchainAdminClient` | sysadmin org lifecycle | admin token |
|
|
263
|
+
| `WitniumchainChainAdminClient` | **service-to-service only** — not for frontend use | admin token / service-principal JWT |
|
|
264
|
+
|
|
265
|
+
The frontend lives almost entirely in `WitniumchainClient`.
|
package/README.md
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
# @witnium-tech/witniumchain
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
TypeScript SDK for WitniumChain — the canonical single client for both the
|
|
4
|
+
accounts service (identity, billing, OAuth, delegated keys, witness writes)
|
|
5
|
+
and the chain-api read surfaces (contract info, witness lookup, wallet
|
|
6
|
+
balance, dashboards). The two-service backend is hidden behind one
|
|
7
|
+
`WitniumchainClient` class; you give it one OAuth access token and it knows
|
|
8
|
+
where each call belongs.
|
|
6
9
|
|
|
7
10
|
## Status
|
|
8
11
|
|
|
9
|
-
**v0.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
- `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
**v0.5 — adds OAuth 2.1 + PKCE login helpers** on top of the v0.4 surface:
|
|
13
|
+
|
|
14
|
+
- All 41 accounts routes (five auth modes: session / OAuth / org / admin / signed)
|
|
15
|
+
- 9 chain-api read methods, routed to `chainBaseUrl`, reusing the same OAuth
|
|
16
|
+
`accessToken` (accounts mints tokens with `aud=https://api.witniumchain.com`,
|
|
17
|
+
so they're valid against both services unchanged)
|
|
18
|
+
- Three layered clients on top: `WitniumchainClient` (end-user),
|
|
19
|
+
`WitniumchainOrgClient` (org admin), `WitniumchainAdminClient` (sysadmin)
|
|
20
|
+
- **`beginOAuthLogin` / `completeOAuthLogin` / `refreshAccessToken` / `signOut`** —
|
|
21
|
+
browser-side Authorization-Code + PKCE login flow. Discovery-driven
|
|
22
|
+
endpoint resolution (`/.well-known/openid-configuration`), PKCE verifier
|
|
23
|
+
in `sessionStorage`, access token held in memory only, transparent 401-retry
|
|
24
|
+
with single-flight refresh-token rotation. Refresh tokens are delivered as
|
|
25
|
+
an HttpOnly `wac_refresh` cookie scoped to `Path=/token` — JavaScript never
|
|
26
|
+
touches them. Non-browser callers (Node SSR, native apps without a cookie
|
|
27
|
+
jar) fall back to in-band refresh-token delivery; the SDK handles both.
|
|
28
|
+
- **`client.mfa.totp.enroll/confirm/disable` + `client.mfa.recoveryCodes.regenerate`** —
|
|
29
|
+
TOTP MFA self-management. `enroll` returns the secret + otpauth URL (render
|
|
30
|
+
as a QR code in your dashboard with any QR lib); `confirm` validates the
|
|
31
|
+
first code and returns 10 single-use recovery codes (shown ONCE).
|
|
32
|
+
|
|
33
|
+
What's NOT exposed (by design): chain-api v5 writes — those are proxied by
|
|
34
|
+
accounts' v5 surface which adds credit reservation + idempotency + scope
|
|
35
|
+
checks. Calling chain-api writes directly would burn credits without
|
|
36
|
+
billing them. See `docs/PLAN-PHASE-SDK-UNIFIED.md` for the routing rules.
|
|
37
|
+
|
|
38
|
+
Every type comes from the published OpenAPI specs of both services via
|
|
39
|
+
`openapi-typescript`. `npm run openapi:check` regenerates and fails on
|
|
40
|
+
diff; it runs ahead of every `npm publish`.
|
|
23
41
|
|
|
24
42
|
## Install
|
|
25
43
|
|
|
@@ -29,14 +47,14 @@ npm install @witnium-tech/witniumchain
|
|
|
29
47
|
|
|
30
48
|
## Auth model
|
|
31
49
|
|
|
32
|
-
The
|
|
50
|
+
The SDK accepts five distinct credentials. Configure whichever
|
|
33
51
|
you need on the client; methods that require a credential you didn't supply
|
|
34
52
|
throw at call time.
|
|
35
53
|
|
|
36
54
|
| Credential | Header / Cookie | Used by |
|
|
37
55
|
|---|---|---|
|
|
38
56
|
| `sessionCookie` | `Cookie: wac_session=…` | `/v1/auth/logout`, `/v1/account/*`, `/v1/billing/*`, `/v1/keys/*`, `/v1/contracts/{pause,unpause}`, `/v1/oauth/sessions*` |
|
|
39
|
-
| `accessToken` | `Authorization: Bearer <JWT>` | `/v1/users/me/delegated-keys/*`, `/v1/sign
|
|
57
|
+
| `accessToken` | `Authorization: Bearer <JWT>` | `/v1/users/me/delegated-keys/*`, `/v1/sign`, **all chain-api reads** |
|
|
40
58
|
| `orgApiKey` | `Authorization: Bearer wcorg_live_…` | `/v1/orgs/me/*` |
|
|
41
59
|
| `adminToken` | `Authorization: Bearer <ADMIN_TOKEN>` | `/v1/admin/*` |
|
|
42
60
|
| `signedRequest` | `X-Witnium-Key/Timestamp/Signature` | `/v1/contracts/{addr}/witnesses/{propose,sign,finalize,revoke}` |
|
|
@@ -45,6 +63,30 @@ Public routes need no credential (`/v1/auth/{signup,verify,login,…}`,
|
|
|
45
63
|
`/v1/contracts/provision`, `GET /v1/contracts/{addr}/witnesses/{id}`,
|
|
46
64
|
`/health/*`).
|
|
47
65
|
|
|
66
|
+
## Two services, one client
|
|
67
|
+
|
|
68
|
+
Accounts and chain-api are separate services today (consolidation is a
|
|
69
|
+
later phase). The SDK hides that:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const client = new WitniumchainClient({
|
|
73
|
+
baseUrl: 'https://auth.witniumchain.com', // accounts
|
|
74
|
+
chainBaseUrl: 'https://api.witniumchain.com', // chain-api (omit if you only need accounts)
|
|
75
|
+
accessToken: oauthBearerJwt,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Writes / billing / auth → accounts (baseUrl)
|
|
79
|
+
await client.proposeWitnessV5('0xabc', { ... });
|
|
80
|
+
|
|
81
|
+
// Reads of on-chain state → chain-api (chainBaseUrl)
|
|
82
|
+
const witness = await client.getWitnessV5('0xabc', witnessId);
|
|
83
|
+
const balance = await client.getWalletBalance('0xabc');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If you call a chain-api method without `chainBaseUrl` configured, the SDK
|
|
87
|
+
throws a clear `WitniumchainApiError` at call time naming the missing
|
|
88
|
+
config — no silent fallback to a wrong base URL.
|
|
89
|
+
|
|
48
90
|
## Examples
|
|
49
91
|
|
|
50
92
|
### End-user signup + login
|
|
@@ -97,6 +139,40 @@ const { organization, apiKey } = await sys.createOrganization({
|
|
|
97
139
|
// `apiKey` is shown ONCE — store it now.
|
|
98
140
|
```
|
|
99
141
|
|
|
142
|
+
### Sign in with Witnium (Authorization Code + PKCE)
|
|
143
|
+
|
|
144
|
+
For a browser SPA (e.g. a Lovable app) that wants to authenticate end-users
|
|
145
|
+
against a Witnium org. No backend required.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const client = new WitniumchainClient({
|
|
149
|
+
baseUrl: 'https://auth.witniumchain.com',
|
|
150
|
+
oauthClientId: 'your-registered-client-id',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 1. Kick off login. The SDK generates a PKCE pair, stashes the verifier in
|
|
154
|
+
// sessionStorage, and returns the URL to send the user to.
|
|
155
|
+
const { authorizationUrl } = await client.beginOAuthLogin({
|
|
156
|
+
redirectUri: 'https://your-app.example/callback',
|
|
157
|
+
});
|
|
158
|
+
window.location.assign(authorizationUrl);
|
|
159
|
+
|
|
160
|
+
// 2. On the callback page, complete the exchange. Returns the access token
|
|
161
|
+
// (the SDK ALSO holds it in memory; subsequent BearerJWT calls Just Work).
|
|
162
|
+
const { accessToken, expiresAt } = await client.completeOAuthLogin(
|
|
163
|
+
window.location.href,
|
|
164
|
+
);
|
|
165
|
+
window.history.replaceState({}, '', window.location.pathname); // strip ?code=…
|
|
166
|
+
|
|
167
|
+
// 3. Use the SDK normally. When the access token expires, the SDK refreshes
|
|
168
|
+
// transparently on the next call. You only need to redirect to login if
|
|
169
|
+
// refresh itself fails (refresh token revoked or expired).
|
|
170
|
+
const { keys } = await client.listDelegatedKeys();
|
|
171
|
+
|
|
172
|
+
// 4. Sign-out clears in-memory tokens.
|
|
173
|
+
client.signOut();
|
|
174
|
+
```
|
|
175
|
+
|
|
100
176
|
### OAuth API — delegated-key + sign
|
|
101
177
|
|
|
102
178
|
```ts
|