@gaballahtech/seba 0.0.0-stage → 0.2.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/ARCHITECTURE.md +242 -0
- package/BUILD_REPORT.md +195 -0
- package/CHANGELOG.md +59 -0
- package/LICENSE +21 -0
- package/README.md +282 -2
- package/RESEARCH.md +180 -0
- package/SECURITY.md +224 -0
- package/VALIDATION_REPORT.md +187 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/platform.d.ts +14 -0
- package/dist/browser/platform.d.ts.map +1 -0
- package/dist/browser/platform.js +60 -0
- package/dist/browser/platform.js.map +1 -0
- package/dist/browser/provider-modal.d.ts +15 -0
- package/dist/browser/provider-modal.d.ts.map +1 -0
- package/dist/browser/provider-modal.js +126 -0
- package/dist/browser/provider-modal.js.map +1 -0
- package/dist/browser/storage.d.ts +32 -0
- package/dist/browser/storage.d.ts.map +1 -0
- package/dist/browser/storage.js +121 -0
- package/dist/browser/storage.js.map +1 -0
- package/dist/core/client.d.ts +49 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +371 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/discovery.d.ts +82 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +243 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/dpop.d.ts +67 -0
- package/dist/core/dpop.d.ts.map +1 -0
- package/dist/core/dpop.js +176 -0
- package/dist/core/dpop.js.map +1 -0
- package/dist/core/errors.d.ts +86 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +104 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/http.d.ts +68 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +128 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/identity.d.ts +145 -0
- package/dist/core/identity.d.ts.map +1 -0
- package/dist/core/identity.js +735 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/oidc.d.ts +175 -0
- package/dist/core/oidc.d.ts.map +1 -0
- package/dist/core/oidc.js +413 -0
- package/dist/core/oidc.js.map +1 -0
- package/dist/core/session.d.ts +116 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +350 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/storage.d.ts +22 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +48 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.d.ts +9 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +9 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/platform.d.ts +25 -0
- package/dist/node/platform.d.ts.map +1 -0
- package/dist/node/platform.js +161 -0
- package/dist/node/platform.js.map +1 -0
- package/dist/seba.d.ts +39 -0
- package/dist/seba.d.ts.map +1 -0
- package/dist/seba.js +58 -0
- package/dist/seba.js.map +1 -0
- package/dist/types.d.ts +279 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/examples/node/index.ts +61 -0
- package/examples/react/App.tsx +127 -0
- package/examples/react/index.html +12 -0
- package/examples/react/main.tsx +14 -0
- package/examples/react/vite.config.ts +27 -0
- package/package.json +83 -4
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# ARCHITECTURE
|
|
2
|
+
|
|
3
|
+
## 1. System overview
|
|
4
|
+
|
|
5
|
+
SEBA is a single npm package with three entry points over one implementation:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────┐
|
|
9
|
+
seba │ src/index.ts (public API) │
|
|
10
|
+
└──────────────┬───────────────┘
|
|
11
|
+
┌───────────┴────────────┐
|
|
12
|
+
│ src/seba.ts (Seba) │ 4 public methods, lazy runtime binding
|
|
13
|
+
└───────────┬────────────┘
|
|
14
|
+
│
|
|
15
|
+
┌──────────────▼───────────────┐
|
|
16
|
+
│ src/core/client.ts │ protocol orchestration, platform-agnostic
|
|
17
|
+
└──┬────────┬────────┬─────────┘
|
|
18
|
+
┌───────────┘ │ └─────────────┐
|
|
19
|
+
┌───────▼──────┐ ┌──────────▼───────┐ ┌──────────▼─────────┐
|
|
20
|
+
│ identity.ts │ │ discovery.ts │ │ oidc.ts │
|
|
21
|
+
│ Client ID │ │ OP metadata │ │ PKCE, state, nonce │
|
|
22
|
+
│ WebID │ │ issuer checks │ │ callback, tokens │
|
|
23
|
+
└──────────────┘ └──────────────────┘ └──────────┬─────────┘
|
|
24
|
+
│
|
|
25
|
+
┌───────────────▼──────────┐
|
|
26
|
+
│ dpop.ts session.ts │
|
|
27
|
+
│ proofs storage/refresh│
|
|
28
|
+
└───────────────┬──────────┘
|
|
29
|
+
│ Platform interface
|
|
30
|
+
┌──────────────────────────┴─────────────────────────┐
|
|
31
|
+
┌─────────▼──────────┐ ┌────────────▼─────────┐
|
|
32
|
+
│ browser/platform │ │ node/platform │
|
|
33
|
+
│ sessionStorage, │ │ memory pending auth, │
|
|
34
|
+
│ IndexedDB, DOM │ │ loopback HTTP server │
|
|
35
|
+
└────────────────────┘ └──────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`src/core/client.ts` contains **all** protocol sequencing exactly once. Browser and Node
|
|
39
|
+
differences are confined to the `Platform` interface (`src/types.ts`): how a callback URL
|
|
40
|
+
is obtained, where pending authorization state lives, and which storage backend is the
|
|
41
|
+
default. There is no second code path for "Node login"; the same validation runs for both.
|
|
42
|
+
|
|
43
|
+
## 2. Core module responsibilities
|
|
44
|
+
|
|
45
|
+
| Module | Responsibility | Deliberately not responsible for |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `core/errors.ts` | Error hierarchy with stable `code` values. | Deciding retry policy. |
|
|
48
|
+
| `core/http.ts` | JSON/text fetching with content-type checks, `SebaNetworkError` mapping, non-2xx handling, timeout. | Auth headers (callers add them). |
|
|
49
|
+
| `core/identity.ts` | Client ID Document fetch/validation, exact redirect-URI membership, WebID URL handling, Turtle-subset + JSON-LD + `Link` issuer extraction. | Trusting any value it parsed (issuer is re-validated). |
|
|
50
|
+
| `core/discovery.ts` | Issuer validation, well-known lookup, OP metadata validation. | Assuming optional metadata exists. |
|
|
51
|
+
| `core/oidc.ts` | PKCE, `state`, `nonce`, authorization URL, callback parsing/validation, code exchange, token-response parsing, ID token validation, WebID extraction. | Storing anything. |
|
|
52
|
+
| `core/dpop.ts` | ES256 key generation/import, proof signing, `jti`, `htu`, `ath`, thumbprints, nonce plumbing. | Caching proofs (forbidden). |
|
|
53
|
+
| `core/session.ts` | The active session record, refresh with rotation, revocation, logout cleanup, restore. | Deciding whether to redirect the user, storage engines. |
|
|
54
|
+
| `core/storage.ts`, `browser/storage.ts` | Memory adapters (Node.js, tests) and IndexedDB/`sessionStorage` adapters (browser). | Protocol decisions. |
|
|
55
|
+
| `core/client.ts` | The four operations, fetch retry/redirect policy, in-memory access token, last-seen DPoP nonce, debug logging. | Any DOM or `node:` API. |
|
|
56
|
+
| `seba.ts` | Public `Seba` class with the four documented methods; binds a runtime platform lazily on first use. | Protocol details. |
|
|
57
|
+
|
|
58
|
+
## 3. Browser architecture
|
|
59
|
+
|
|
60
|
+
`src/browser/platform.ts` supplies:
|
|
61
|
+
|
|
62
|
+
- `currentUrl()` → `new URL(window.location.href)`.
|
|
63
|
+
- `pendingAuth` → one `sessionStorage` record under `seba.pending-authorization`
|
|
64
|
+
(the record itself carries `clientId` and `redirectUri`, which are re-checked on the
|
|
65
|
+
callback). The PKCE verifier and expected nonce never leave the tab and never appear in
|
|
66
|
+
a URL.
|
|
67
|
+
- `beginInteractiveLogin()` → `window.location.assign(authorizationUrl)`; the returned
|
|
68
|
+
promise intentionally never settles, because the document is being replaced.
|
|
69
|
+
- `selectIdentity()` → the vanilla DOM provider selector in `browser/provider-modal.ts`
|
|
70
|
+
(focus trap, `Esc` to close, `Enter` to submit, WebID or custom issuer input, no password
|
|
71
|
+
field, no network calls).
|
|
72
|
+
- `createStorage("indexeddb")` → `idb`-backed adapter; `"memory"` → process memory.
|
|
73
|
+
- `clearCallback()` → `history.replaceState` to remove `code`/`state`/`iss` from the
|
|
74
|
+
address bar after the callback has been consumed.
|
|
75
|
+
|
|
76
|
+
Login lifecycle in a browser (documented in `README.md`):
|
|
77
|
+
|
|
78
|
+
1. Application calls `login()`. There is no callback in the URL, so SEBA discovers the
|
|
79
|
+
provider, stores pending state in `sessionStorage`, and navigates away.
|
|
80
|
+
2. The provider authenticates the user and redirects to `redirectUri?code=…&state=…&iss=…`.
|
|
81
|
+
3. The application calls `login()` again on that page. SEBA recognises the callback,
|
|
82
|
+
validates `state`, `iss`, `nonce`, PKCE and the ID token, stores the refresh token and
|
|
83
|
+
key, keeps the access token in memory, returns the `Session`.
|
|
84
|
+
|
|
85
|
+
Because step 1 unloads the page, its promise never resolves; this is documented rather
|
|
86
|
+
than papered over with polling or `BroadcastChannel` tricks.
|
|
87
|
+
|
|
88
|
+
`src/browser/index.ts` is the `@gaballahtech/seba/browser` entry point: it re-exports the public API and
|
|
89
|
+
`createBrowserPlatform`.
|
|
90
|
+
|
|
91
|
+
## 4. Node.js architecture
|
|
92
|
+
|
|
93
|
+
`src/node/platform.ts`:
|
|
94
|
+
|
|
95
|
+
- `currentUrl()` → `null` (no document).
|
|
96
|
+
- `pendingAuth` → an in-process record; no browser storage API is referenced anywhere in
|
|
97
|
+
`src/node/`.
|
|
98
|
+
- `beginInteractiveLogin()` →
|
|
99
|
+
1. parse the configured `redirectUri`; require `http:` and a loopback host
|
|
100
|
+
(`127.0.0.1`, `::1`, `localhost`) — never bind `0.0.0.0`;
|
|
101
|
+
2. `node:http` server on that host and port, only answering the registered path;
|
|
102
|
+
3. call `openAuthorizationUrl(authorizationUrl)` (required; otherwise
|
|
103
|
+
`SebaNoUIError` — thrown *before* the server is started);
|
|
104
|
+
4. respond with a small HTML page that contains no secrets and close the listener
|
|
105
|
+
completely before resolving the login promise, so the port is free for the next login
|
|
106
|
+
and no late request can reach a closed transaction;
|
|
107
|
+
5. reject after a five-minute timeout and close the listener the same way.
|
|
108
|
+
- `createStorage()` → memory only; `"indexeddb"` throws `SebaConfigError` with an
|
|
109
|
+
actionable message instead of pretending a browser store exists.
|
|
110
|
+
|
|
111
|
+
Provider and resource calls use the runtime's global `fetch` through `core/http.ts`, which
|
|
112
|
+
adds an abort-based 10-second timeout and maps transport failures to `SebaNetworkError`.
|
|
113
|
+
`src/node/index.ts` is the `@gaballahtech/seba/node` entry point: it re-exports the public API and
|
|
114
|
+
`createNodePlatform`.
|
|
115
|
+
|
|
116
|
+
The configured `redirectUri` must still be published by the Client ID Document, so a
|
|
117
|
+
loopback URI must appear in `redirect_uris`; otherwise `SebaRedirectMismatchError` is
|
|
118
|
+
thrown before any listener is opened.
|
|
119
|
+
|
|
120
|
+
## 5. Authentication flow
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
login(options)
|
|
124
|
+
│
|
|
125
|
+
├─ callback present & pending state found? ──yes──► completeLogin()
|
|
126
|
+
│ ├ state exact match
|
|
127
|
+
│ ├ RFC 9207 `iss` == pending issuer
|
|
128
|
+
│ ├ error param? → SebaAuthError
|
|
129
|
+
│ ├ POST token endpoint (fresh DPoP proof, no ath)
|
|
130
|
+
│ ├ token_type == DPoP
|
|
131
|
+
│ ├ validate ID token (JWKS, iss, aud, azp, exp, iat, nonce)
|
|
132
|
+
│ ├ cnf.jkt == our thumbprint (and required for `webid`)
|
|
133
|
+
│ ├ webId == requested webId (when given)
|
|
134
|
+
│ ├ persist {refresh_token, key, webId, issuer, clientId}
|
|
135
|
+
│ └ keep access token in memory → Session
|
|
136
|
+
│
|
|
137
|
+
└─ no callback
|
|
138
|
+
├ issuer = options.issuer ?? discoverIssuerForWebId(options.webId) ?? platform.selectIdentity()
|
|
139
|
+
├ loadClientIdDocument(clientId) → redirect URI must be listed exactly
|
|
140
|
+
├ discoverProvider(issuer)
|
|
141
|
+
├ create PKCE verifier/challenge, state, nonce → PendingAuthorization
|
|
142
|
+
├ build authorization URL (response_type=code, scope=openid webid offline_access,
|
|
143
|
+
│ code_challenge_method=S256, nonce, state, client_id)
|
|
144
|
+
├ platform.beginInteractiveLogin(...) → browser: navigate away
|
|
145
|
+
│ → node: loopback callback URL
|
|
146
|
+
└ completeLogin(callbackUrl)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 6. Session lifecycle
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
(no session) ──login──► { accessToken (memory), refreshToken (storage), key (storage) }
|
|
153
|
+
│
|
|
154
|
+
access token expires / 401 invalid_token
|
|
155
|
+
▼
|
|
156
|
+
refresh (grant_type=refresh_token, fresh proof)
|
|
157
|
+
│ rotation: persist new refresh token atomically
|
|
158
|
+
▼
|
|
159
|
+
new access token in memory
|
|
160
|
+
│
|
|
161
|
+
restoreSession() on reload: load record → refresh → Session
|
|
162
|
+
│ record missing → null
|
|
163
|
+
│ invalid_grant → clear storage + SebaSessionExpiredError
|
|
164
|
+
│ 5xx / offline → SebaNetworkError, record kept
|
|
165
|
+
▼
|
|
166
|
+
logout(): clear memory → revoke (best effort) → delete record
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The persisted record (`PersistedSession`) is the only durable state:
|
|
170
|
+
`version`, `webId`, `provider`, `clientId`, `refreshToken`, `key`
|
|
171
|
+
(`CryptoKey` + public JWK, or exported JWK), optional `scope`, `createdAt`.
|
|
172
|
+
**No access token field exists in the type**, so it cannot be persisted by accident.
|
|
173
|
+
|
|
174
|
+
Refresh is serialized through a single in-flight promise in `core/session.ts`, so ten
|
|
175
|
+
concurrent `fetch()` calls that all see an expired token perform one refresh, not ten
|
|
176
|
+
(rotating refresh tokens would otherwise invalidate each other).
|
|
177
|
+
|
|
178
|
+
## 7. DPoP lifecycle
|
|
179
|
+
|
|
180
|
+
1. **Generation** at login: ES256/P-256 via `jose.generateKeyPair`. Non-extractable private
|
|
181
|
+
key when the runtime can structured-clone `CryptoKey` (probed with `structuredClone`);
|
|
182
|
+
otherwise an extractable key exported to JWK and stored as such (reported in
|
|
183
|
+
`SECURITY.md`).
|
|
184
|
+
2. **Signing** per attempt: `{typ: "dpop+jwt", alg: "ES256", jwk: publicJwk}` with
|
|
185
|
+
`{jti, htm, htu, iat}` plus `ath` only when an access token is presented, plus `nonce`
|
|
186
|
+
when the server supplied one.
|
|
187
|
+
3. **No persistence of proofs.** `createDPoPProof()` is pure: inputs in, one JWT out. It is
|
|
188
|
+
called once per HTTP attempt, including retries (`nonce` challenge, token refresh,
|
|
189
|
+
redirect hop).
|
|
190
|
+
4. **Nonce handling**: `core/client.ts` keeps the most recent `DPoP-Nonce` returned by a
|
|
191
|
+
protected resource and sends it with subsequent proofs. A nonce is server state, not a
|
|
192
|
+
proof; caching it is explicitly allowed by RFC 9449. Token requests handle their own
|
|
193
|
+
`use_dpop_nonce` challenge in `core/oidc.ts`.
|
|
194
|
+
5. **Binding**: the key pair stays with the session record; the ID token's `cnf.jkt` and
|
|
195
|
+
the access token's binding are checked against its thumbprint.
|
|
196
|
+
|
|
197
|
+
## 8. Storage architecture
|
|
198
|
+
|
|
199
|
+
| Runtime | Default | Record location | Access token |
|
|
200
|
+
|---|---|---|---|
|
|
201
|
+
| Browser | `indexeddb` | DB `seba`, object store `session`, single record key `current` | memory only |
|
|
202
|
+
| Browser (`storage: "memory"`) | opt-in | module-level map | memory only |
|
|
203
|
+
| Node.js | `memory` | process memory | memory only |
|
|
204
|
+
|
|
205
|
+
`sessionStorage` holds **only** the pending authorization transaction
|
|
206
|
+
(`state`, `code_verifier`, `nonce`, `issuer`, `clientId`, `redirectUri`, `createdAt`,
|
|
207
|
+
optional `webId`) and is deleted as soon as the callback is processed. `localStorage` is
|
|
208
|
+
never used. IndexedDB writes are whole-record `put`s, which makes refresh-token rotation a
|
|
209
|
+
single atomic overwrite.
|
|
210
|
+
|
|
211
|
+
## 9. Public API design
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
class Seba {
|
|
215
|
+
constructor(config: SebaConfig)
|
|
216
|
+
login(options?: LoginOptions): Promise<Session>
|
|
217
|
+
restoreSession(): Promise<Session | null>
|
|
218
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
|
|
219
|
+
logout(): Promise<void>
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Exactly four public methods. `Session` carries `webId`, `isLoggedIn: true`, `provider` and
|
|
224
|
+
no tokens. Tokens, keys and proofs are never exported from the package root. Configuration
|
|
225
|
+
is validated when the first operation runs (`SebaConfigError`); there are no
|
|
226
|
+
proof-caching or TTL knobs.
|
|
227
|
+
|
|
228
|
+
`Seba` binds its platform on first use (dynamic import of `browser/platform.js` when
|
|
229
|
+
`window`/`document` exist, otherwise `node/platform.js`), which keeps `node:http` out of
|
|
230
|
+
browser bundles and DOM code out of Node.
|
|
231
|
+
|
|
232
|
+
## 10. Architectural trade-offs
|
|
233
|
+
|
|
234
|
+
| Trade-off | Choice | Consequence |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| Redirect lifecycle vs. promise ergonomics | `login()`'s promise does not settle when navigation happens | Applications must call `login()` again on the callback page; documented with code. |
|
|
237
|
+
| No proof cache | Re-sign every attempt | Slight CPU cost per request (one ECDSA signature); replay protection stays intact. |
|
|
238
|
+
| `redirect: "manual"` everywhere | Never re-attach credentials on redirects | Browsers surface redirects to the app instead of following them. |
|
|
239
|
+
| Custom Turtle subset | ~200 lines instead of an RDF dependency | Documented, tested subset + JSON-LD + `Link` fallback; unsupported syntax fails loudly. |
|
|
240
|
+
| Static client identity | No DCR | One published JSON-LD file must list every redirect URI. |
|
|
241
|
+
| Loopback-only Node login | Real HTTP callback | Requires `openAuthorizationUrl`; impossible flows fail with `SebaNoUIError`. |
|
|
242
|
+
| `cnf.jkt` requirement gated on advertised `webid` scope | Interoperable and strict | Providers that do not advertise `webid` get standard OIDC validation only. |
|
package/BUILD_REPORT.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# BUILD_REPORT — seba 0.2.0
|
|
2
|
+
|
|
3
|
+
Date: 2026-11-05
|
|
4
|
+
Environment: Linux container, Node.js v26.12.1, npm 12.2.1, TypeScript 5.9.3, Vitest 5.1.0,
|
|
5
|
+
Vite 8.3.6. Chrome binary present; no Firefox/Safari.
|
|
6
|
+
|
|
7
|
+
## 1. Gate results (actual, in the required order)
|
|
8
|
+
|
|
9
|
+
| Gate | Command | Exit | Result |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| Install | `npm install` | 0 | 0 vulnerabilities |
|
|
12
|
+
| Typecheck | `npm run typecheck` = `tsc --noEmit && tsc -p tsconfig.examples.json` | 0 | No errors in `src/`, `tests/` or `examples/` |
|
|
13
|
+
| Tests | `npm test` | 0 | 9 files, **123/123** tests passed |
|
|
14
|
+
| Build | `npm run build` | 0 | `dist/` emitted |
|
|
15
|
+
| Pack | `npm pack --dry-run` | 0 | `seba-0.2.0.tgz`, 86 files, 97.8 kB packed, 373.7 kB unpacked |
|
|
16
|
+
|
|
17
|
+
Test summary as printed:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Test Files 9 passed (9)
|
|
21
|
+
Tests 123 passed (123)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Coverage changed since 0.1.0: discovery gained three issuer-canonicalization tests and the
|
|
25
|
+
session suite's "different WebID on refresh" test now expects the stricter
|
|
26
|
+
`SebaSessionExpiredError` + cleared storage behaviour.
|
|
27
|
+
|
|
28
|
+
## 2. `npm pack --dry-run` output
|
|
29
|
+
|
|
30
|
+
Captured from the final gate run (the listing was taken immediately before this report's
|
|
31
|
+
last wording change, so this file's own byte count differs by less than 1 kB; the pack was
|
|
32
|
+
re-run after writing it and again included `BUILD_REPORT.md` and exited 0):
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
npm notice 📦 seba@0.2.0
|
|
36
|
+
npm notice Tarball Contents
|
|
37
|
+
npm notice 15.6kB ARCHITECTURE.md
|
|
38
|
+
npm notice 6.1kB BUILD_REPORT.md
|
|
39
|
+
npm notice 3.1kB CHANGELOG.md
|
|
40
|
+
npm notice 1.1kB LICENSE
|
|
41
|
+
npm notice 10.7kB README.md
|
|
42
|
+
npm notice 11.7kB RESEARCH.md
|
|
43
|
+
npm notice 16.9kB SECURITY.md
|
|
44
|
+
npm notice 10.1kB VALIDATION_REPORT.md
|
|
45
|
+
npm notice 325B dist/browser/index.d.ts
|
|
46
|
+
npm notice 191B dist/browser/index.d.ts.map
|
|
47
|
+
npm notice 323B dist/browser/index.js
|
|
48
|
+
npm notice 189B dist/browser/index.js.map
|
|
49
|
+
npm notice 517B dist/browser/platform.d.ts
|
|
50
|
+
npm notice 282B dist/browser/platform.d.ts.map
|
|
51
|
+
npm notice 2.8kB dist/browser/platform.js
|
|
52
|
+
npm notice 2.1kB dist/browser/platform.js.map
|
|
53
|
+
npm notice 609B dist/browser/provider-modal.d.ts
|
|
54
|
+
npm notice 255B dist/browser/provider-modal.d.ts.map
|
|
55
|
+
npm notice 5.6kB dist/browser/provider-modal.js
|
|
56
|
+
npm notice 4.1kB dist/browser/provider-modal.js.map
|
|
57
|
+
npm notice 1.4kB dist/browser/storage.d.ts
|
|
58
|
+
npm notice 526B dist/browser/storage.d.ts.map
|
|
59
|
+
npm notice 4.3kB dist/browser/storage.js
|
|
60
|
+
npm notice 3.2kB dist/browser/storage.js.map
|
|
61
|
+
npm notice 2.1kB dist/core/client.d.ts
|
|
62
|
+
npm notice 630B dist/core/client.d.ts.map
|
|
63
|
+
npm notice 15.3kB dist/core/client.js
|
|
64
|
+
npm notice 12.3kB dist/core/client.js.map
|
|
65
|
+
npm notice 3.8kB dist/core/discovery.d.ts
|
|
66
|
+
npm notice 900B dist/core/discovery.d.ts.map
|
|
67
|
+
npm notice 11.5kB dist/core/discovery.js
|
|
68
|
+
npm notice 7.0kB dist/core/discovery.js.map
|
|
69
|
+
npm notice 2.4kB dist/core/dpop.d.ts
|
|
70
|
+
npm notice 979B dist/core/dpop.d.ts.map
|
|
71
|
+
npm notice 5.8kB dist/core/dpop.js
|
|
72
|
+
npm notice 4.7kB dist/core/dpop.js.map
|
|
73
|
+
npm notice 3.5kB dist/core/errors.d.ts
|
|
74
|
+
npm notice 1.5kB dist/core/errors.d.ts.map
|
|
75
|
+
npm notice 3.7kB dist/core/errors.js
|
|
76
|
+
npm notice 2.1kB dist/core/errors.js.map
|
|
77
|
+
npm notice 3.0kB dist/core/http.d.ts
|
|
78
|
+
npm notice 1.1kB dist/core/http.d.ts.map
|
|
79
|
+
npm notice 4.6kB dist/core/http.js
|
|
80
|
+
npm notice 3.0kB dist/core/http.js.map
|
|
81
|
+
npm notice 6.1kB dist/core/identity.d.ts
|
|
82
|
+
npm notice 2.0kB dist/core/identity.d.ts.map
|
|
83
|
+
npm notice 28.3kB dist/core/identity.js
|
|
84
|
+
npm notice 23.5kB dist/core/identity.js.map
|
|
85
|
+
npm notice 6.4kB dist/core/oidc.d.ts
|
|
86
|
+
npm notice 3.1kB dist/core/oidc.d.ts.map
|
|
87
|
+
npm notice 18.3kB dist/core/oidc.js
|
|
88
|
+
npm notice 14.1kB dist/core/oidc.js.map
|
|
89
|
+
npm notice 4.8kB dist/core/session.d.ts
|
|
90
|
+
npm notice 1.9kB dist/core/session.d.ts.map
|
|
91
|
+
npm notice 13.7kB dist/core/session.js
|
|
92
|
+
npm notice 9.7kB dist/core/session.js.map
|
|
93
|
+
npm notice 1.0kB dist/core/storage.d.ts
|
|
94
|
+
npm notice 401B dist/core/storage.d.ts.map
|
|
95
|
+
npm notice 1.4kB dist/core/storage.js
|
|
96
|
+
npm notice 891B dist/core/storage.js.map
|
|
97
|
+
npm notice 706B dist/index.d.ts
|
|
98
|
+
npm notice 443B dist/index.d.ts.map
|
|
99
|
+
npm notice 279B dist/index.js
|
|
100
|
+
npm notice 286B dist/index.js.map
|
|
101
|
+
npm notice 317B dist/node/index.d.ts
|
|
102
|
+
npm notice 188B dist/node/index.d.ts.map
|
|
103
|
+
npm notice 315B dist/node/index.js
|
|
104
|
+
npm notice 186B dist/node/index.js.map
|
|
105
|
+
npm notice 1.2kB dist/node/platform.d.ts
|
|
106
|
+
npm notice 331B dist/node/platform.d.ts.map
|
|
107
|
+
npm notice 7.2kB dist/node/platform.js
|
|
108
|
+
npm notice 4.7kB dist/node/platform.js.map
|
|
109
|
+
npm notice 1.6kB dist/seba.d.ts
|
|
110
|
+
npm notice 542B dist/seba.d.ts.map
|
|
111
|
+
npm notice 2.1kB dist/seba.js
|
|
112
|
+
npm notice 1.2kB dist/seba.js.map
|
|
113
|
+
npm notice 10.8kB dist/types.d.ts
|
|
114
|
+
npm notice 4.3kB dist/types.d.ts.map
|
|
115
|
+
npm notice 274B dist/types.js
|
|
116
|
+
npm notice 115B dist/types.js.map
|
|
117
|
+
npm notice 2.2kB examples/node/index.ts
|
|
118
|
+
npm notice 3.7kB examples/react/App.tsx
|
|
119
|
+
npm notice 299B examples/react/index.html
|
|
120
|
+
npm notice 319B examples/react/main.tsx
|
|
121
|
+
npm notice 644B examples/react/vite.config.ts
|
|
122
|
+
npm notice 1.9kB package.json
|
|
123
|
+
npm notice Tarball Details
|
|
124
|
+
npm notice name: seba
|
|
125
|
+
npm notice version: 0.2.0
|
|
126
|
+
npm notice filename: seba-0.2.0.tgz
|
|
127
|
+
npm notice package size: 97.8 kB
|
|
128
|
+
npm notice unpacked size: 373.7 kB
|
|
129
|
+
npm notice total files: 86
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The tarball contains `README.md`, `CHANGELOG.md`, `BUILD_REPORT.md`, `SECURITY.md`,
|
|
133
|
+
`VALIDATION_REPORT.md`, `dist/` and `examples/`. It contains **no** `node_modules`, no test
|
|
134
|
+
files or fixtures, and no `css-data` directory. The generated `examples/react/dist` bundle
|
|
135
|
+
was removed before this run; the React example ships as sources plus its Vite config.
|
|
136
|
+
|
|
137
|
+
## 2b. v0.2.0 external validation status
|
|
138
|
+
|
|
139
|
+
> **v0.2.0 external validation status:**
|
|
140
|
+
> - Local CSS 7.3.0: login **fails at ID token validation** due to CSS non-compliance (see CSS_ISSUE.md). Seba's rejection is correct per Solid-OIDC §8.1.
|
|
141
|
+
> - Public providers: discovery tested against `solidcommunity.net`, `solidweb.org`, `solidweb.me`. Full cycle not completed (account creation requires interactive browser). Trailing-slash issuer bug fixed.
|
|
142
|
+
> - Browsers: unit tests for browser storage pass; no real-browser E2E performed.
|
|
143
|
+
> - This release is **not** claimed production-ready. See `VALIDATION_REPORT.md`.
|
|
144
|
+
|
|
145
|
+
Upstream report: https://github.com/CommunitySolidServer/CommunitySolidServer/issues/2295
|
|
146
|
+
|
|
147
|
+
## 3. External validation summary
|
|
148
|
+
|
|
149
|
+
Full transcripts, error messages and blockers are in `VALIDATION_REPORT.md`.
|
|
150
|
+
|
|
151
|
+
- **Community Solid Server 7.3.0 (real):** discovery, Client ID Document fetch,
|
|
152
|
+
PKCE/state/nonce, account/WebID/consent interaction, callback, and the DPoP token request
|
|
153
|
+
all succeeded. The login is rejected at ID token validation because CSS issues
|
|
154
|
+
`aud` as a string containing only the client ID (no `"solid"`) and no `cnf` claim;
|
|
155
|
+
Solid-OIDC §8.1 requires an array with `"solid"` and a `cnf` binding. The library was not
|
|
156
|
+
weakened to accommodate a non-conformant provider.
|
|
157
|
+
- **Public providers:** discovery through SEBA's real code succeeded for
|
|
158
|
+
`solidcommunity.net` (issuer `https://solidcommunity.net/`), `solidweb.org` (issuer
|
|
159
|
+
`https://solidweb.org`, no revocation endpoint) and `solidweb.me` (issuer
|
|
160
|
+
`https://solidweb.me/`). No account could be created, so no public full cycle is claimed.
|
|
161
|
+
- **Browser:** not executed. Chrome is installed but Firefox/Safari are not, and a real
|
|
162
|
+
provider login is blocked as above. No browser matrix is claimed green.
|
|
163
|
+
- **Examples:** `tsc -p tsconfig.examples.json` and `vite build` succeed; runtime login is
|
|
164
|
+
blocked by the provider issue.
|
|
165
|
+
|
|
166
|
+
## 4. Changes made because of validation
|
|
167
|
+
|
|
168
|
+
1. **Issuer canonicalization** (`src/core/discovery.ts`): accept the empty-path
|
|
169
|
+
trailing-slash form of a path-less issuer and adopt the provider's exact spelling for
|
|
170
|
+
all later comparisons. Motivated by real providers (solidcommunity.net, solidweb.me,
|
|
171
|
+
CSS). Paths, ports, schemes and query strings still require exact equality. Three tests
|
|
172
|
+
added.
|
|
173
|
+
2. **Refresh identity mismatch is terminal** (`src/core/session.ts`): refresh-time ID token
|
|
174
|
+
validation failure now clears storage and throws `SebaSessionExpiredError` instead of
|
|
175
|
+
leaving an unusable grant. Stricter; regression test updated.
|
|
176
|
+
3. **Examples in the typecheck graph** (`tsconfig.examples.json`, package scripts), plus
|
|
177
|
+
runnable Vite scaffolding for the React example and an env-driven Node example.
|
|
178
|
+
4. Docs reconciled with reality: provider compatibility table, security audit findings,
|
|
179
|
+
validation report, changelog, version 0.2.0.
|
|
180
|
+
|
|
181
|
+
## 5. Still unvalidated (explicit)
|
|
182
|
+
|
|
183
|
+
- Full login → fetch → restore → logout against any public provider (no account).
|
|
184
|
+
- Any real-browser run (no Firefox; no provider login that reaches a session).
|
|
185
|
+
- Safari `CryptoKey` persistence and private-browsing IndexedDB behaviour.
|
|
186
|
+
- IndexedDB quota-exceeded and corrupted-record handling in a real browser (unit-covered,
|
|
187
|
+
not browser-executed).
|
|
188
|
+
- Real pod resource access with a provider-issued access token (blocked at ID token).
|
|
189
|
+
- No automated accessibility audit of the browser dialog.
|
|
190
|
+
|
|
191
|
+
## 6. Release notes
|
|
192
|
+
|
|
193
|
+
Version bumped to `0.2.0`. This is a validation release with one interop fix and one
|
|
194
|
+
security hardening change; it is **not** “production ready” in the sense of having passed
|
|
195
|
+
real-provider and browser gates, and no document here claims that.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] — 2026-11-05
|
|
4
|
+
|
|
5
|
+
External validation and hardening release. This is not a claim of production readiness; the
|
|
6
|
+
gates that could not run are listed explicitly.
|
|
7
|
+
|
|
8
|
+
### Fixed because of external validation
|
|
9
|
+
|
|
10
|
+
- **Issuer canonicalization.** Real providers publish path-less issuers with a trailing
|
|
11
|
+
slash (`https://solidcommunity.net/`, `https://solidweb.me/`, Community Solid Server's
|
|
12
|
+
`http://localhost:3000/`). SEBA required byte-identical strings and rejected the document
|
|
13
|
+
when the caller typed the host without the slash. Discovery now accepts that one
|
|
14
|
+
canonical form and adopts the provider's exact spelling for all later comparisons;
|
|
15
|
+
paths, ports, schemes and query strings still require exact equality. Verified against
|
|
16
|
+
solidcommunity.net, solidweb.org, solidweb.me and CSS 7.3.0.
|
|
17
|
+
|
|
18
|
+
### Fixed from the manual security audit
|
|
19
|
+
|
|
20
|
+
- A refresh whose ID token fails validation (including a WebID that differs from the stored
|
|
21
|
+
session) now clears the stored grant and throws `SebaSessionExpiredError`. It previously
|
|
22
|
+
kept the unusable record and threw `SebaAuthError`, which would repeat the failure on
|
|
23
|
+
every restore. Stricter, covered by a regression test.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `tsconfig.examples.json`; `npm run typecheck` now typechecks `examples/` as well.
|
|
28
|
+
- `npm run typecheck:examples`, `npm run example:react:build`, `npm run validate:discovery`.
|
|
29
|
+
- Runnable React example scaffolding (`examples/react/index.html`, `main.tsx`,
|
|
30
|
+
`vite.config.ts`) and an env-driven Node example.
|
|
31
|
+
- `scripts/validation/css-setup.mjs` and `css-flow.mjs`: a reproducible real-provider flow
|
|
32
|
+
against a local Community Solid Server, including the account/pod setup and the browser
|
|
33
|
+
interaction driven through CSS's JSON API.
|
|
34
|
+
- `scripts/validation/public-discovery.mjs`: discovery checks against public providers.
|
|
35
|
+
- `VALIDATION_REPORT.md`, this changelog, and a "Provider Compatibility" table in README.
|
|
36
|
+
|
|
37
|
+
### Validated externally
|
|
38
|
+
|
|
39
|
+
- Community Solid Server 7.3.0: discovery, Client ID Document fetch, PKCE/state/nonce
|
|
40
|
+
authorization, consent, callback, DPoP token request and token response all succeeded.
|
|
41
|
+
Login stops at ID token validation because CSS emits a string `aud` without `"solid"` and
|
|
42
|
+
no `cnf` claim, which Solid-OIDC §8.1 requires.
|
|
43
|
+
- solidcommunity.net, solidweb.org and solidweb.me: discovery and issuer handling verified
|
|
44
|
+
through SEBA's real discovery code; the first two publish trailing-slash issuers, which
|
|
45
|
+
exposed the normalization bug fixed above.
|
|
46
|
+
- One real CSS failure was caused by **provider non-compliance**, not by Seba: CSS 7.3.0
|
|
47
|
+
emits `id_token.aud` as a string without `"solid"` and omits `cnf.jkt`, contrary to
|
|
48
|
+
Solid-OIDC §8.1. The failure is documented in `CSS_ISSUE.md` and reported upstream.
|
|
49
|
+
|
|
50
|
+
### Not validated (explicitly)
|
|
51
|
+
|
|
52
|
+
- No full login → fetch → restore → logout cycle against any public provider: creating an
|
|
53
|
+
account requires an interactive browser, email and, on some deployments, a CAPTCHA.
|
|
54
|
+
- No browser end-to-end run. Chrome is installed, Firefox/Safari are not. The 5-step browser
|
|
55
|
+
matrix was not executed.
|
|
56
|
+
- Safari `CryptoKey` persistence and IndexedDB private-browsing behaviour were not executed.
|
|
57
|
+
- IndexedDB quota-exceeded and corrupted-record behaviour in a real browser was not
|
|
58
|
+
executed; the unit tests cover the adapter and the failure paths.
|
|
59
|
+
- No real pod resource was fetched with a CSS-issued token (blocked by the ID token issue).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SEBA contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|