@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/SECURITY.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# SECURITY
|
|
2
|
+
|
|
3
|
+
## 1. Threat model
|
|
4
|
+
|
|
5
|
+
| Threat | Assets at risk | Mitigation |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Authorization code interception / mix-up | Access + refresh tokens | PKCE S256, random `state`, RFC 9207 `iss` checked against the issuer the request was built for. |
|
|
8
|
+
| ID token forgery or substitution | User identity (`WebID`) | Signature verified with the provider's JWKS; `iss`, `aud`, `azp`, `exp`, `iat`, `nonce` validated; algorithm taken from the key metadata, not the token's preference. |
|
|
9
|
+
| DPoP proof replay | Pod access | A new proof (new `jti`, new signature) for every attempt; no proof cache, no TTL, no reuse option. |
|
|
10
|
+
| Token theft from the browser profile | Refresh token, DPoP key | Only the refresh token and key are persisted (IndexedDB); access token stays in memory; no `localStorage`; no cookies set by the library. |
|
|
11
|
+
| XSS on the application origin | Everything the origin can reach | Out of scope to *prevent*; the library minimises the window (no tokens in DOM, no globals, no logging) and documents the residual risk. |
|
|
12
|
+
| Credential leak via redirects | Access token, DPoP proof | `redirect: "manual"`; cross-origin redirect responses are returned to the caller, never re-issued with credentials. |
|
|
13
|
+
| Duplicate side effects from retries | Application state | Automatic replay is limited to replayable requests (`GET`/`HEAD`/`OPTIONS`, or a re-readable body), at most one nonce retry and one refresh retry. |
|
|
14
|
+
| Malicious/compromised provider metadata | Redirection of tokens | Issuer must match the discovery document exactly; endpoints must be `https` (loopback `http` only); redirect URI must be published verbatim by the Client ID Document. |
|
|
15
|
+
| Secret leakage through logs | Tokens, codes, keys | Debug logging is structured and excludes credentials by construction; errors carry codes and messages, never payloads. |
|
|
16
|
+
| Stolen database copy | Refresh token, key | IndexedDB is origin-bound and not encrypted at rest by SEBA; at-rest protection is the OS/browser profile's job (documented limitation). |
|
|
17
|
+
|
|
18
|
+
Trust boundaries: (1) the browser page and its origin, (2) the provider's HTTPS endpoints,
|
|
19
|
+
(3) the Solid resource server, (4) the published Client ID Document. Everything crossing
|
|
20
|
+
those boundaries is validated; nothing is trusted because it "looks like" a token.
|
|
21
|
+
|
|
22
|
+
## 2. Token storage policy
|
|
23
|
+
|
|
24
|
+
| Value | Where | Lifetime |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Access token | A private field in `SebaClient` (memory) | Until refresh, logout, or process end |
|
|
27
|
+
| Refresh token | IndexedDB (`seba`/`session`, record key `current`) or memory | Until logout / rejection |
|
|
28
|
+
| DPoP private key | IndexedDB, structured-cloned `CryptoKey` (non-extractable) or exported JWK fallback | Same as the refresh token |
|
|
29
|
+
| DPoP public JWK + thumbprint | With the session record | Non-secret |
|
|
30
|
+
| `state`, `nonce`, `code_verifier` | `sessionStorage` (browser) / memory (Node), single transaction record | Deleted after the callback, stale after 10 minutes |
|
|
31
|
+
| ID token | Memory, inside the validated token set | Session lifetime in memory |
|
|
32
|
+
| Authorization code | Memory, only between callback parsing and exchange | Seconds |
|
|
33
|
+
|
|
34
|
+
`PersistedSession` has **no access-token field** — the type makes accidental persistence a
|
|
35
|
+
compile error. `localStorage`, cookies, `window.name` and URL fragments are never used for
|
|
36
|
+
secrets. If IndexedDB is unavailable and `storage` was left at its default, SEBA throws
|
|
37
|
+
`SebaConfigError`; it never silently downgrades to a weaker store.
|
|
38
|
+
|
|
39
|
+
## 3. DPoP implementation requirements (RFC 9449)
|
|
40
|
+
|
|
41
|
+
- `typ` is `dpop+jwt`, `alg` is `ES256`; the `jwk` header contains only `kty`, `crv`, `x`,
|
|
42
|
+
`y` — never `d`.
|
|
43
|
+
- `jti`: 32 bytes from `crypto.getRandomValues`, base64url encoded. Unique per proof.
|
|
44
|
+
- `htm`: uppercase HTTP method actually sent.
|
|
45
|
+
- `htu`: the request URL with query and fragment removed, normalized by `URL`
|
|
46
|
+
(lowercased scheme/host, default port removed).
|
|
47
|
+
- `iat`: seconds since the epoch, from the local clock at signing time.
|
|
48
|
+
- `ath`: `base64url(SHA-256(ASCII(access_token)))`, **only** on protected-resource requests.
|
|
49
|
+
Token-endpoint requests carry no `ath` (there is no access token being presented).
|
|
50
|
+
- `nonce`: included only when the server supplied a `DPoP-Nonce`; the nonce is cached per
|
|
51
|
+
endpoint, the proof is always regenerated.
|
|
52
|
+
- One proof per HTTP attempt. Retries — nonce challenge, token refresh, redirect hop —
|
|
53
|
+
each call `createDPoPProof()` again.
|
|
54
|
+
|
|
55
|
+
`createDPoPProof()` is a pure function of `(key, method, url, accessToken?, nonce?)`
|
|
56
|
+
returning a compact JWS. There is no module-level proof state, so no code path can return
|
|
57
|
+
a previously signed proof. `tests/security/dpop-security.test.ts` asserts this directly:
|
|
58
|
+
100 sequential proofs for an identical `(htm, htu)` yield 100 distinct JWTs and 100
|
|
59
|
+
distinct `jti` values, and a recorded proof never reappears.
|
|
60
|
+
|
|
61
|
+
## 4. Key management
|
|
62
|
+
|
|
63
|
+
- Keys are generated with `jose.generateKeyPair("ES256", { extractable: false })` and
|
|
64
|
+
never leave the runtime.
|
|
65
|
+
- `structuredClone(privateKey)` is probed once at login. When it succeeds, the
|
|
66
|
+
non-extractable `CryptoKey` is stored and the key material is never exportable
|
|
67
|
+
(`exportKey` throws for the private key). The public JWK is stored alongside it because a
|
|
68
|
+
non-extractable private key cannot reproduce its public half later.
|
|
69
|
+
- When the engine cannot clone `CryptoKey`s, SEBA generates an extractable key, exports the
|
|
70
|
+
private JWK and stores it. This is a documented downgrade: the key is readable by
|
|
71
|
+
same-origin script either way, but it is no longer protected from export. The probe
|
|
72
|
+
result is not configurable, and index-key persistence is never used as a silent
|
|
73
|
+
substitute.
|
|
74
|
+
- Only one key pair exists per stored session. A new login replaces the record atomically;
|
|
75
|
+
logout deletes it. Keys are never logged, never returned from the public API and never
|
|
76
|
+
included in error messages.
|
|
77
|
+
- The public JWK's SHA-256 thumbprint (`jkt`) is what the ID token's `cnf.jkt` is compared
|
|
78
|
+
against; the access token is therefore bound to the same key used for its proofs.
|
|
79
|
+
|
|
80
|
+
## 5. PKCE and OAuth state
|
|
81
|
+
|
|
82
|
+
- `code_verifier`: 43 characters from the RFC 7636 alphabet, each from `crypto.getRandomValues`
|
|
83
|
+
(~258 bits of entropy), never shorter than the RFC minimum.
|
|
84
|
+
- `code_challenge`: `base64url(SHA-256(ASCII(verifier)))`, `code_challenge_method=S256`.
|
|
85
|
+
`plain` is never sent or accepted.
|
|
86
|
+
- `state` and `nonce`: 32 random bytes each, base64url, independent values.
|
|
87
|
+
- All three are stored **before** the navigation and consumed exactly once. A callback
|
|
88
|
+
without a pending transaction, with a different `state`, or older than 10 minutes is
|
|
89
|
+
rejected with `SebaAuthError` before the code is exchanged.
|
|
90
|
+
- The authorization response's `iss` (RFC 9207) must equal the issuer the pending
|
|
91
|
+
transaction was created for; otherwise `SebaAuthError` (mix-up protection).
|
|
92
|
+
- The code is exchanged exactly once. Pending state is removed as soon as the exchange has
|
|
93
|
+
succeeded, so a replayed callback finds no transaction and fails closed. A response that
|
|
94
|
+
fails validation *before* the exchange leaves the transaction in place (until the
|
|
95
|
+
10-minute expiry) so the user can retry; the code itself is never exchanged twice.
|
|
96
|
+
|
|
97
|
+
## 6. ID token validation
|
|
98
|
+
|
|
99
|
+
Performed by `validateIdToken()` before any identity value is read:
|
|
100
|
+
|
|
101
|
+
1. Compact JWS parsed; signature verified with `createRemoteJWKSet(jwksUri)` via `jose`,
|
|
102
|
+
restricted to asymmetric algorithms (`ES256/384/512`, `RS256/384/512`, `PS256/384/512`).
|
|
103
|
+
2. `iss` must equal the discovered issuer (exact string).
|
|
104
|
+
3. `aud` must contain `client_id`; `azp`, when present, must equal `client_id`; `azp` is
|
|
105
|
+
required when `aud` has multiple values.
|
|
106
|
+
4. When the provider advertises the `webid` scope, `aud` must also contain `"solid"`.
|
|
107
|
+
5. `exp` must be in the future (60 s clock tolerance), `iat` must not be in the future
|
|
108
|
+
(60 s tolerance), `nbf` is honoured when present.
|
|
109
|
+
6. `nonce` must equal the pending transaction's nonce (login only).
|
|
110
|
+
7. `sub` is required. `webid`, when present, must be an `https` URL (loopback `http`
|
|
111
|
+
allowed for development); otherwise `sub` is used when it is an `https` URL.
|
|
112
|
+
8. `cnf.jkt`, when present, must equal the local DPoP key thumbprint, and is **required**
|
|
113
|
+
when the provider advertises `webid`.
|
|
114
|
+
9. The resulting WebID must equal `options.webId` when the caller supplied one
|
|
115
|
+
(`SebaAuthError` on mismatch).
|
|
116
|
+
|
|
117
|
+
Decoding without verification is never used to make a decision. Failed validation is a
|
|
118
|
+
hard `SebaAuthError`; there is no "accept anyway" path.
|
|
119
|
+
|
|
120
|
+
## 7. Refresh-token handling
|
|
121
|
+
|
|
122
|
+
- Refresh requests use `grant_type=refresh_token`, `client_id`, the stored refresh token and
|
|
123
|
+
a freshly signed DPoP proof for the token endpoint (no `ath`).
|
|
124
|
+
- The response must be `token_type: "DPoP"`; anything else is discarded (`SebaAuthError`)
|
|
125
|
+
and the previous record is left untouched.
|
|
126
|
+
- Rotation: a returned `refresh_token` replaces the stored one in a single IndexedDB
|
|
127
|
+
`put`. If the provider omits it, the existing token is kept (RFC 6749 §6 allows both).
|
|
128
|
+
- `invalid_grant` / `invalid_token` / `invalid_request` / `unauthorized_client` → storage
|
|
129
|
+
cleared, then `SebaSessionExpiredError`. The application learns the user must sign in
|
|
130
|
+
again. Other OAuth errors surface as `SebaAuthError` and the record is kept.
|
|
131
|
+
- Network failures and 5xx responses → `SebaNetworkError`, record **kept**, so a provider
|
|
132
|
+
outage is never reported as a logout.
|
|
133
|
+
- An ID token returned by a refresh is validated (signature, `iss`, `aud`, lifetime) and
|
|
134
|
+
its WebID must match the stored one; a nonce is not expected on refresh.
|
|
135
|
+
- Concurrent refreshes are collapsed into one in-flight promise to avoid rotation races.
|
|
136
|
+
|
|
137
|
+
## 8. Replay protection
|
|
138
|
+
|
|
139
|
+
- DPoP: see §3 — no cache, no TTL, fresh `jti` per attempt.
|
|
140
|
+
- OAuth: `state` is single-use and removed before the token request; `nonce` is single-use
|
|
141
|
+
per transaction; PKCE ties the code to this client instance.
|
|
142
|
+
- Retries in `Seba.fetch`: at most one `use_dpop_nonce` retry and at most one
|
|
143
|
+
refresh-then-retry, never nested unbounded. Each retry signs a new proof.
|
|
144
|
+
- Redirects: at most 5 same-origin hops. `303` switches to `GET`; `301`/`302` on a `POST`
|
|
145
|
+
switch to `GET`; `307`/`308` keep the method and body; other method/status combinations
|
|
146
|
+
are only followed when the body is replayable. Cross-origin targets are never followed
|
|
147
|
+
with credentials; the redirect response is returned instead.
|
|
148
|
+
- Requests with a non-replayable body (`ReadableStream`) are never retried after a 401:
|
|
149
|
+
the challenge response is returned unchanged.
|
|
150
|
+
|
|
151
|
+
## 9. Redirect and request security
|
|
152
|
+
|
|
153
|
+
- The configured `redirectUri` must appear **verbatim** in the Client ID Document's
|
|
154
|
+
`redirect_uris`; no scheme/host/port/trailing-slash normalization is applied to either
|
|
155
|
+
side, so `https://app.example/cb` never matches `https://app.example/cb/`.
|
|
156
|
+
- Node.js callback servers bind only to loopback (`127.0.0.1`, `::1`, `localhost`) and only
|
|
157
|
+
answer the registered path with the expected `state`; anything else gets `404`/`400`. The
|
|
158
|
+
listener is fully closed before `login()` settles, and after a five-minute timeout.
|
|
159
|
+
- The authorization URL is built from the discovered endpoints only, over `https`.
|
|
160
|
+
- `Authorization`/`DPoP` headers are attached only to requests the caller initiated (plus
|
|
161
|
+
same-origin redirect hops). They are never attached to a different origin, and token
|
|
162
|
+
endpoint requests never carry `Authorization`.
|
|
163
|
+
- The provider selection UI validates user input as an `https` URL before use and never
|
|
164
|
+
collects credentials; there is no password field anywhere in the library.
|
|
165
|
+
|
|
166
|
+
## 10. Logging restrictions
|
|
167
|
+
|
|
168
|
+
`debug: true` logs only: operation names, issuer/client identifiers, endpoint URLs
|
|
169
|
+
(without query strings), status codes, retry reasons and error codes. It never logs access
|
|
170
|
+
tokens, refresh tokens, ID tokens, authorization codes, PKCE verifiers/challenges, `state`,
|
|
171
|
+
`nonce`, public or private JWKs, or DPoP proofs. Errors contain codes and human-readable
|
|
172
|
+
messages; `cause` objects are the original network/JOSE errors and contain no credentials.
|
|
173
|
+
|
|
174
|
+
## 11. Known limitations
|
|
175
|
+
|
|
176
|
+
1. **XSS defeats browser storage.** Same-origin script can use the stored refresh token and
|
|
177
|
+
key while the page is open. SEBA reduces exposure but cannot prevent this.
|
|
178
|
+
2. **IndexedDB is not encrypted at rest** by SEBA. Anyone with the browser profile can read
|
|
179
|
+
the refresh token and (fallback mode) the exported key.
|
|
180
|
+
3. **Opaque browser redirects.** With `redirect: "manual"`, browsers hide `Location` and the
|
|
181
|
+
status of a redirect, so SEBA returns the redirect response instead of following it.
|
|
182
|
+
4. **No token introspection.** SEBA validates ID tokens and relies on the resource server
|
|
183
|
+
for access-token validity; revocation is best-effort.
|
|
184
|
+
5. **Clock skew tolerance (60 s)** is fixed; a badly skewed device can fail login and
|
|
185
|
+
receives an actionable `SebaAuthError`.
|
|
186
|
+
6. **No live-provider verification.** Only the hand-written mock provider and unit-level
|
|
187
|
+
protocol tests were executed in this environment.
|
|
188
|
+
7. **`cnf.jkt` requirement gated on advertised `webid` scope.** A provider that omits the
|
|
189
|
+
scope but issues unbound tokens will pass ID token validation; the resource server still
|
|
190
|
+
rejects unbound tokens.
|
|
191
|
+
8. **IndexedDB at-rest exposure.** Anyone with the browser profile can read the refresh
|
|
192
|
+
token and — on engines that cannot structured-clone `CryptoKey` — the exported private
|
|
193
|
+
JWK. Mitigation available **today**: pass `storage: "memory"` to keep the refresh token
|
|
194
|
+
and key out of IndexedDB for the lifetime of the page; the session then does not survive
|
|
195
|
+
a reload. No `sessionStorage` session backend is implemented, so it is not offered as a
|
|
196
|
+
mitigation here.
|
|
197
|
+
|
|
198
|
+
## 12. External Validation Findings (v0.2.0)
|
|
199
|
+
|
|
200
|
+
Audit of `src/core/dpop.ts`, `src/core/session.ts` and `src/core/client.ts`, plus the real
|
|
201
|
+
provider run documented in `VALIDATION_REPORT.md`.
|
|
202
|
+
|
|
203
|
+
| ID | Severity | Finding | Resolution |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| F1 | Medium (interop) | Real providers (solidcommunity.net, solidweb.me, CSS 7.3.0) publish a path-less issuer with a trailing slash. Exact string comparison rejected the discovery document when the caller typed the host without `/`. | **Fixed.** `issuerEquivalent()` accepts only the empty-path canonical variant and keeps the provider's spelling for every later comparison; paths/ports/schemes must still match exactly. Unit-tested. |
|
|
206
|
+
| F2 | Medium | A refresh whose ID token failed validation (e.g. `webid` for a different user) left the unusable grant in storage and threw `SebaAuthError`, so every subsequent restore repeated the failure. | **Fixed, stricter.** `SessionManager` now clears storage and throws `SebaSessionExpiredError` when refresh-time ID token validation fails. Transport/JWKS errors still keep the record. Regression test added. |
|
|
207
|
+
| F3 | Low (accepted) | A callback with a mismatched `state` throws `SebaAuthError` but leaves the pending transaction intact until its 10-minute expiry. | **Accepted.** The code is never exchanged, and the legitimate transaction stays usable. Clearing it would allow a page that can navigate the user to cancel an in-flight login. No token or identity is exposed either way. |
|
|
208
|
+
| F4 | Informational | CSS 7.3.0 issues an ID token with string `aud` (no `"solid"`) and no `cnf`, contrary to Solid-OIDC §8.1. | **Not worked around.** Relaxing the audience or `cnf` requirement would remove Solid-specific audience restriction and proof-of-possession binding. The provider is non-conformant; SEBA refuses the session. |
|
|
209
|
+
|
|
210
|
+
Audit checks that passed, with the mechanism:
|
|
211
|
+
|
|
212
|
+
| Check | Result |
|
|
213
|
+
|---|---|
|
|
214
|
+
| New proof for every attempt | `SebaClient.fetch()` calls `createProof()` inside the attempt loop; `requestTokenResponse()` signs again on its nonce retry. No proof object is stored. |
|
|
215
|
+
| `ath` only with an access token | `createDPoPProof` adds `ath` only when `input.accessToken !== undefined`; token-endpoint and revocation proofs omit it. |
|
|
216
|
+
| `jti` uniqueness | 32 bytes from `crypto.getRandomValues` (256 bits), base64url, per proof. |
|
|
217
|
+
| `state` validated before exchange | `parseAuthorizationResponse()` runs before any token request; mismatch throws before the exchange. |
|
|
218
|
+
| `use_dpop_nonce` retry signs fresh | `requestTokenResponse()` loops and calls `signer.createProof({ nonce })` again; no cached proof. |
|
|
219
|
+
| Refresh-token rotation | Returned refresh token overwrites the stored record with one IndexedDB `put`; concurrent refreshes collapse into one in-flight promise. |
|
|
220
|
+
| `restoreSession()` fails closed on identity mismatch | Now clears storage and throws `SebaSessionExpiredError` (F2). |
|
|
221
|
+
| No secrets in logs | `grep -rn "console\." src/` returns only `core/client.ts`'s `console.debug` of caller-supplied diagnostic messages (issuer, endpoint, WebID after success) and doc comments. No tokens, codes, verifiers, keys or proofs are logged. |
|
|
222
|
+
|
|
223
|
+
Residual risk unchanged: XSS on the application origin can use the stored refresh token and
|
|
224
|
+
key while the page is open; SEBA cannot prevent that.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# VALIDATION_REPORT — SEBA v0.2.0
|
|
2
|
+
|
|
3
|
+
Environment: Linux (container), Node.js v26.12.1, npm 12.2.1, Chrome 141 binary present
|
|
4
|
+
(`/usr/bin/google-chrome-stable`), no Firefox/Safari binary. Network access to npm and the
|
|
5
|
+
public Solid providers was available.
|
|
6
|
+
|
|
7
|
+
This report records what was executed, not what was intended. Where a gate could not run,
|
|
8
|
+
it is marked **BLOCKED** with the concrete reason.
|
|
9
|
+
|
|
10
|
+
## Phase A — real provider validation
|
|
11
|
+
|
|
12
|
+
### A.1 Local Community Solid Server 7.3.0
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @solid/community-server@7.3.0 -c @css:config/file.json -f ./css-data -p 3000
|
|
18
|
+
node scripts/validation/css-setup.mjs # account + password login + pod
|
|
19
|
+
CSS_EMAIL=… CSS_PASSWORD=… WEBID=… node scripts/validation/css-flow.mjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The Client ID Document is served by the validation script at
|
|
23
|
+
`http://localhost:8443/client-id.jsonld` with `redirect_uris:
|
|
24
|
+
["http://localhost:3001/auth/callback"]`. Plain loopback HTTP is used instead of the
|
|
25
|
+
suggested `mkcert` HTTPS endpoint because SEBA's Node callback server intentionally binds
|
|
26
|
+
plain HTTP on loopback only; the document shape is otherwise the requested one (`@context`,
|
|
27
|
+
`client_id`, `redirect_uris`, `client_name`, `scope`, `grant_types`).
|
|
28
|
+
|
|
29
|
+
What passed:
|
|
30
|
+
|
|
31
|
+
| Step | Result |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Discovery | `GET /.well-known/openid-configuration` → 200; issuer `http://localhost:3000/` |
|
|
34
|
+
| Client ID Document fetch by CSS | Accepted; authorization request reached the account interaction |
|
|
35
|
+
| PKCE S256, `state`, `nonce` in authorization request | Accepted by CSS |
|
|
36
|
+
| Interactive login / WebID selection / consent (driven through CSS's account JSON API) | Completed; CSS redirected to the callback with `code`, `state`, `iss=http://localhost:3000/` |
|
|
37
|
+
| SEBA callback server | Received the callback, returned HTTP 200 |
|
|
38
|
+
| Token request with DPoP proof | Accepted by CSS (no `invalid_dpop_proof`, no nonce challenge needed) |
|
|
39
|
+
| Token response | `token_type: "DPoP"`, access token, refresh token, ID token |
|
|
40
|
+
|
|
41
|
+
The ID token CSS actually issued (non-secret claims only, captured by the validation
|
|
42
|
+
script):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"iss": "http://localhost:3000/",
|
|
47
|
+
"aud": "http://localhost:8443/client-id.jsonld",
|
|
48
|
+
"azp": "http://localhost:8443/client-id.jsonld",
|
|
49
|
+
"webid": "http://localhost:3000/seba018213/profile/card#me",
|
|
50
|
+
"sub": "http://localhost:3000/seba018213/profile/card#me"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
SEBA then failed the login with:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
SebaAuthError: The ID token audience does not include "solid".
|
|
58
|
+
Solid-OIDC requires it when the provider advertises the webid scope.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Root cause: CSS 7.3.0 deviates from [Solid-OIDC §8.1](https://solidproject.org/TR/oidc).
|
|
62
|
+
With the `webid` scope (which CSS itself advertises in `scopes_supported`), the spec
|
|
63
|
+
requires:
|
|
64
|
+
|
|
65
|
+
- `aud` to be an **array** that includes `azp` and the string `"solid"`;
|
|
66
|
+
- a `cnf` claim with `jkt`.
|
|
67
|
+
|
|
68
|
+
CSS emits a string `aud` containing only the client ID and emits no `cnf`. The same
|
|
69
|
+
observation was reported to the Solid authentication panel long ago
|
|
70
|
+
(solid/authentication-panel#135 discusses the strict audience requirement), but the current
|
|
71
|
+
normative text has not changed.
|
|
72
|
+
|
|
73
|
+
Decision: **do not relax the check.** Accepting an ID token without `"solid"` and `cnf`
|
|
74
|
+
would remove the Solid-specific audience restriction and the proof-of-possession binding
|
|
75
|
+
this library exists to enforce. The failure is in the provider, not in SEBA. Gate A.1 is
|
|
76
|
+
therefore **BLOCKED** at the last step (post-token, pre-session) by provider
|
|
77
|
+
non-conformance. Everything up to and including the DPoP token exchange was exercised
|
|
78
|
+
against real CSS.
|
|
79
|
+
|
|
80
|
+
Transcript files from the run: `/tmp/css-flow.out`, `/tmp/css-flow-noslash.out` (ephemeral,
|
|
81
|
+
environment-local).
|
|
82
|
+
|
|
83
|
+
### A.2 Public providers
|
|
84
|
+
|
|
85
|
+
No account could be created on `solidcommunity.net`, `solidweb.org` or `solidweb.me`
|
|
86
|
+
without an interactive browser session, an email address and (on some deployments) a
|
|
87
|
+
CAPTCHA. No credentials were available to this environment, so the full login → fetch →
|
|
88
|
+
restore → logout cycle did **not** run against any public provider. It is not marked as
|
|
89
|
+
tested anywhere in this repository.
|
|
90
|
+
|
|
91
|
+
What was executed instead, through SEBA's real discovery code
|
|
92
|
+
(`node scripts/validation/public-discovery.mjs`):
|
|
93
|
+
|
|
94
|
+
| Requested issuer | Discovery | Issuer returned | Revocation endpoint | `webid` advertised |
|
|
95
|
+
|---|---|---|---|---|
|
|
96
|
+
| `https://solidcommunity.net` | 200 | `https://solidcommunity.net/` | yes | yes |
|
|
97
|
+
| `https://solidcommunity.net/` | 200 | `https://solidcommunity.net/` | yes | yes |
|
|
98
|
+
| `https://solidweb.org` | 200 | `https://solidweb.org` | **no** | yes |
|
|
99
|
+
| `https://solidweb.me` | 200 | `https://solidweb.me/` | yes | yes |
|
|
100
|
+
| `http://localhost:3000` | 200 | `http://localhost:3000/` | yes | yes |
|
|
101
|
+
|
|
102
|
+
This surfaced a real interoperability problem: two of the three providers publish an issuer
|
|
103
|
+
with a trailing slash while users and documentation usually type the host without one, and
|
|
104
|
+
SEBA's exact-string comparison rejected the document. Fixed — see Phase D finding F1.
|
|
105
|
+
|
|
106
|
+
Gate A.2: **BLOCKED** (no public account; full cycle not executed). Discovery compatibility
|
|
107
|
+
is proven, not authentication.
|
|
108
|
+
|
|
109
|
+
## Phase B — browser validation
|
|
110
|
+
|
|
111
|
+
- Chrome is installed, but Firefox and Safari are not present in this environment.
|
|
112
|
+
- A browser end-to-end login against a real provider is blocked by A.1/A.2: the only local
|
|
113
|
+
real provider (CSS) fails ID token validation, and no public provider account exists.
|
|
114
|
+
- No Playwright/Puppeteer run was performed. The built React example (Vite) and the browser
|
|
115
|
+
storage adapter (`fake-indexeddb` unit tests) are the only browser-adjacent evidence.
|
|
116
|
+
|
|
117
|
+
Gate B.1: **BLOCKED** — Chrome + Firefox 5-step matrix not executed. Safari availability
|
|
118
|
+
was never reached.
|
|
119
|
+
|
|
120
|
+
### B.2 IndexedDB edge cases
|
|
121
|
+
|
|
122
|
+
Not executed in a real browser. What the code does today, verified by unit tests or
|
|
123
|
+
inspection:
|
|
124
|
+
|
|
125
|
+
| Case | Behaviour | Evidence |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| Storage quota exceeded | `storage.save()` rejection is caught in `SessionManager.adopt` and only logged; the in-memory session stays usable. Refresh-rotation save failure is likewise non-fatal. | `tests/unit/session.test.ts` "keeps the session usable when storage fails" |
|
|
128
|
+
| Corrupted pending-authorization JSON | Removed and treated as no pending login (`SebaAuthError` when a callback arrives). | `tests/unit/browser-storage.test.ts` |
|
|
129
|
+
| Stored session from another client/version | Cleared, then `SebaAuthError`. | `tests/unit/session.test.ts` |
|
|
130
|
+
| Unusable stored DPoP key | Cleared, then `SebaSessionExpiredError`. | `tests/unit/session.test.ts` |
|
|
131
|
+
| Private/incognito IndexedDB failure | `openDB` rejection propagates while saving; `adopt` downgrades it to a debug message and keeps the memory session. A later `restoreSession()` sees nothing. No hang, but no typed error is surfaced to the caller because storage failure is intentionally non-fatal. | code inspection; not browser-executed |
|
|
132
|
+
|
|
133
|
+
Gate B.2: **NOT EXECUTED in a browser**; no crash/hang was found by inspection or unit
|
|
134
|
+
tests, but this gate is not claimed green.
|
|
135
|
+
|
|
136
|
+
## Phase C — examples
|
|
137
|
+
|
|
138
|
+
- `tsconfig.examples.json` added; `npm run typecheck` now runs both projects.
|
|
139
|
+
- `npm run typecheck:examples` → 0 errors.
|
|
140
|
+
- `npm run example:react:build` (`vite build`) → success, 79 modules.
|
|
141
|
+
- Node example is env-driven and documented; React example has `index.html`, `main.tsx` and
|
|
142
|
+
`vite.config.ts`, with `clientId`/`redirectUri` documented in its comments and README.
|
|
143
|
+
- Runtime against CSS remains blocked by A.1.
|
|
144
|
+
|
|
145
|
+
Gate C.1: **PASS** (typechecked). Gate C.2: **PARTIAL** — build command works; the Node
|
|
146
|
+
example cannot complete a login against CSS for the provider reason above.
|
|
147
|
+
|
|
148
|
+
## Phase D — security audit
|
|
149
|
+
|
|
150
|
+
See `SECURITY.md` §12 for the full table and accepted risks. Summary:
|
|
151
|
+
|
|
152
|
+
- **F1 (real-provider incompatibility, fixed):** issuer with/without trailing slash.
|
|
153
|
+
`parseProviderConfiguration` now accepts only the empty-path canonical form and adopts the
|
|
154
|
+
provider's spelling; paths, ports and schemes still require exact equality. Not a
|
|
155
|
+
security weakening: the exact declared issuer is what is stored and compared afterwards.
|
|
156
|
+
- **F2 (audit finding, fixed, stricter):** a refresh whose ID token fails validation (e.g.
|
|
157
|
+
different WebID) previously left the unusable grant in storage and threw `SebaAuthError`.
|
|
158
|
+
It now clears storage and throws `SebaSessionExpiredError`. New regression test added.
|
|
159
|
+
- **F3 (audit finding, accepted):** a callback with a mismatched `state` throws
|
|
160
|
+
`SebaAuthError` without clearing the pending transaction. The code is never exchanged and
|
|
161
|
+
the legitimate transaction remains usable until its 10-minute expiry. Clearing it would
|
|
162
|
+
let any page that can navigate the user cancel an in-flight login; both behaviours fail
|
|
163
|
+
closed. Documented.
|
|
164
|
+
- No proof reuse, no cached proofs, fresh `jti` per attempt, `ath` only on resource
|
|
165
|
+
requests, no tokens/keys logged.
|
|
166
|
+
|
|
167
|
+
Gate D.1: zero unresolved high-severity findings; one medium fixed, one low accepted.
|
|
168
|
+
|
|
169
|
+
## Phase E — release
|
|
170
|
+
|
|
171
|
+
See `BUILD_REPORT.md` for the pasted gate output and package contents. Version 0.2.0 is a
|
|
172
|
+
validation-and-hardening release, **not** a claim of production readiness: the public
|
|
173
|
+
provider and browser gates are blocked, and no document in this repository says otherwise.
|
|
174
|
+
|
|
175
|
+
## Definition-of-done checklist
|
|
176
|
+
|
|
177
|
+
| Item | Status |
|
|
178
|
+
|---|---|
|
|
179
|
+
| Local CSS full cycle with transcript | **BLOCKED** — every step through DPoP token exchange succeeded; CSS 7.3.0 fails Solid-OIDC §8.1 ID token requirements |
|
|
180
|
+
| At least one public provider full cycle | **BLOCKED** — no account could be created; discovery verified for three providers |
|
|
181
|
+
| Chrome and Firefox 5-step browser check | **BLOCKED** — Firefox absent; real-provider login blocked; no browser automation run |
|
|
182
|
+
| IndexedDB edge cases typed errors | **PARTIAL** — unit-covered; browser execution not performed |
|
|
183
|
+
| Examples typechecked and documented | **PASS** |
|
|
184
|
+
| Manual security audit of `dpop.ts`, `session.ts`, `client.ts` | **PASS** (findings in SECURITY.md §12) |
|
|
185
|
+
| No document claims untested capability | **PASS** |
|
|
186
|
+
| `npm pack --dry-run` output pasted in BUILD_REPORT | **PASS** |
|
|
187
|
+
| `CHANGELOG.md` honest about validation | **PASS** |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry point (`seba/browser`).
|
|
3
|
+
*
|
|
4
|
+
* Identical to the root entry point; browsers resolve the platform themselves. The
|
|
5
|
+
* subpath exists so bundlers can target a browser-only module graph.
|
|
6
|
+
*/
|
|
7
|
+
export * from "../index.js";
|
|
8
|
+
export { createBrowserPlatform } from "./platform.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry point (`seba/browser`).
|
|
3
|
+
*
|
|
4
|
+
* Identical to the root entry point; browsers resolve the platform themselves. The
|
|
5
|
+
* subpath exists so bundlers can target a browser-only module graph.
|
|
6
|
+
*/
|
|
7
|
+
export * from "../index.js";
|
|
8
|
+
export { createBrowserPlatform } from "./platform.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser platform: navigation-based login, IDB session storage, `sessionStorage`
|
|
3
|
+
* authorization state.
|
|
4
|
+
*/
|
|
5
|
+
import type { LoginOptions, Platform } from "../types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create the platform used by `Seba` in browsers.
|
|
8
|
+
*
|
|
9
|
+
* @throws {@link SebaConfigError} when no browser `window`/`document` exists.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createBrowserPlatform(): Platform;
|
|
12
|
+
/** Re-exported for {@link Seba} type inference in browser-only builds. */
|
|
13
|
+
export type { LoginOptions };
|
|
14
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/browser/platform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAA2B,YAAY,EAAE,QAAQ,EAAsC,MAAM,aAAa,CAAC;AAUvH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAqDhD;AAED,0EAA0E;AAC1E,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser platform: navigation-based login, IDB session storage, `sessionStorage`
|
|
3
|
+
* authorization state.
|
|
4
|
+
*/
|
|
5
|
+
import { SebaConfigError } from "../core/errors.js";
|
|
6
|
+
import { memorySessionStorage } from "../core/storage.js";
|
|
7
|
+
import { askForIdentity } from "./provider-modal.js";
|
|
8
|
+
import { createIndexedDbStorage, createSessionStoragePendingAuthStore } from "./storage.js";
|
|
9
|
+
/** Query parameters produced by an authorization response. */
|
|
10
|
+
const CALLBACK_PARAMS = ["code", "state", "iss", "error", "error_description", "error_uri", "session_state"];
|
|
11
|
+
/** How long a browser waits for navigation before reporting that it did not happen. */
|
|
12
|
+
const NAVIGATION_GRACE_MS = 1_500;
|
|
13
|
+
/**
|
|
14
|
+
* Create the platform used by `Seba` in browsers.
|
|
15
|
+
*
|
|
16
|
+
* @throws {@link SebaConfigError} when no browser `window`/`document` exists.
|
|
17
|
+
*/
|
|
18
|
+
export function createBrowserPlatform() {
|
|
19
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
20
|
+
throw new SebaConfigError("The browser entry point requires a DOM. Import \"seba\" in a browser or \"seba/node\" in Node.js.");
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
kind: "browser",
|
|
24
|
+
defaultStorage: "indexeddb",
|
|
25
|
+
fetch: (input, init) => globalThis.fetch(input, init),
|
|
26
|
+
currentUrl: () => new URL(window.location.href),
|
|
27
|
+
pendingAuth: createSessionStoragePendingAuthStore(),
|
|
28
|
+
selectIdentity: () => askForIdentity(),
|
|
29
|
+
createStorage(kind) {
|
|
30
|
+
if (kind === "memory") {
|
|
31
|
+
return memorySessionStorage();
|
|
32
|
+
}
|
|
33
|
+
return createIndexedDbStorage();
|
|
34
|
+
},
|
|
35
|
+
async beginInteractiveLogin(request) {
|
|
36
|
+
const target = new URL(request.redirectUri);
|
|
37
|
+
const before = window.location.href;
|
|
38
|
+
window.location.assign(request.authorizationUrl);
|
|
39
|
+
return new Promise((_resolve, reject) => {
|
|
40
|
+
if (before.startsWith(target.origin) && window.location.href !== before) {
|
|
41
|
+
// Navigation already happened; the page is unloading.
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
window.setTimeout(() => {
|
|
45
|
+
reject(new SebaConfigError("The browser did not navigate to the identity provider. " +
|
|
46
|
+
"A popup blocker or a sandboxed iframe can prevent navigation; open the page in a top-level tab."));
|
|
47
|
+
}, NAVIGATION_GRACE_MS);
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
clearCallback() {
|
|
51
|
+
const url = new URL(window.location.href);
|
|
52
|
+
for (const name of CALLBACK_PARAMS) {
|
|
53
|
+
url.searchParams.delete(name);
|
|
54
|
+
}
|
|
55
|
+
const query = url.searchParams.toString();
|
|
56
|
+
window.history.replaceState({}, document.title, `${url.pathname}${query === "" ? "" : `?${query}`}${url.hash}`);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/browser/platform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,oCAAoC,EAAE,MAAM,cAAc,CAAC;AAE5F,8DAA8D;AAC9D,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;AAE7G,uFAAuF;AACvF,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACrE,MAAM,IAAI,eAAe,CACvB,mGAAmG,CACpG,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,SAAS;QACf,cAAc,EAAE,WAAW;QAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;QACrD,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC/C,WAAW,EAAE,oCAAoC,EAAE;QACnD,cAAc,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE;QACtC,aAAa,CAAC,IAAiB;YAC7B,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,OAAO,oBAAoB,EAAE,CAAC;YAChC,CAAC;YACD,OAAO,sBAAsB,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,qBAAqB,CAAC,OAAgC;YAC1D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YACpC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;YAEjD,OAAO,IAAI,OAAO,CAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;gBAC3C,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACxE,sDAAsD;oBACtD,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;oBACrB,MAAM,CACJ,IAAI,eAAe,CACjB,yDAAyD;wBACvD,iGAAiG,CACpG,CACF,CAAC;gBACJ,CAAC,EAAE,mBAAmB,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,aAAa;YACX,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC1C,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;gBACnC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;YACD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC1C,MAAM,CAAC,OAAO,CAAC,YAAY,CACzB,EAAE,EACF,QAAQ,CAAC,KAAK,EACd,GAAG,GAAG,CAAC,QAAQ,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,CAC/D,CAAC;QACJ,CAAC;KACiB,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free identity prompt for browsers.
|
|
3
|
+
*
|
|
4
|
+
* The dialog asks for a WebID by default and offers an identity-provider URL instead.
|
|
5
|
+
* It is only a hint: whatever the user enters is validated and discovered by the core.
|
|
6
|
+
*/
|
|
7
|
+
import type { LoginOptions } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Ask the user which WebID or identity provider to use.
|
|
10
|
+
*
|
|
11
|
+
* @returns Login options collected from the dialog.
|
|
12
|
+
* @throws {@link SebaAuthError} when the user cancels or the dialog cannot be shown.
|
|
13
|
+
*/
|
|
14
|
+
export declare function askForIdentity(): Promise<LoginOptions>;
|
|
15
|
+
//# sourceMappingURL=provider-modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-modal.d.ts","sourceRoot":"","sources":["../../src/browser/provider-modal.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CA4HtD"}
|