@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class of every error thrown by SEBA.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* try {
|
|
7
|
+
* await seba.login();
|
|
8
|
+
* } catch (error) {
|
|
9
|
+
* if (error instanceof SebaError) console.error(error.code, error.message);
|
|
10
|
+
* }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export class SebaError extends Error {
|
|
14
|
+
/** Stable, machine-readable error code. */
|
|
15
|
+
code;
|
|
16
|
+
constructor(code, message, options) {
|
|
17
|
+
super(message, options?.cause === undefined ? undefined : { cause: options.cause });
|
|
18
|
+
this.name = new.target.name;
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Invalid, missing or contradictory library configuration. */
|
|
23
|
+
export class SebaConfigError extends SebaError {
|
|
24
|
+
constructor(message, options) {
|
|
25
|
+
super("SEBA_ERR_CONFIG", message, options);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** WebID, Client ID Document or OpenID Provider discovery failed. */
|
|
29
|
+
export class SebaDiscoveryError extends SebaError {
|
|
30
|
+
constructor(message, options) {
|
|
31
|
+
super("SEBA_ERR_DISCOVERY", message, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The configured redirect URI is not published by the Client ID Document.
|
|
36
|
+
*
|
|
37
|
+
* The message lists both the requested URI and the published URIs so the
|
|
38
|
+
* mismatch can be fixed without inspecting network traffic.
|
|
39
|
+
*/
|
|
40
|
+
export class SebaRedirectMismatchError extends SebaError {
|
|
41
|
+
/** Redirect URI requested by the application. */
|
|
42
|
+
requestedRedirectUri;
|
|
43
|
+
/** Redirect URIs published in the Client ID Document. */
|
|
44
|
+
publishedRedirectUris;
|
|
45
|
+
constructor(requestedRedirectUri, publishedRedirectUris, options) {
|
|
46
|
+
super("SEBA_ERR_REDIRECT_MISMATCH", `Redirect URI "${requestedRedirectUri}" is not published by the Client ID Document. ` +
|
|
47
|
+
`Published redirect_uris: ${formatList(publishedRedirectUris)}. ` +
|
|
48
|
+
`Add the exact string "${requestedRedirectUri}" to the document's redirect_uris array. ` +
|
|
49
|
+
"No dynamic client registration is performed by SEBA.", options);
|
|
50
|
+
this.requestedRedirectUri = requestedRedirectUri;
|
|
51
|
+
this.publishedRedirectUris = [...publishedRedirectUris];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The authorization response, token response or identity token was rejected.
|
|
56
|
+
*
|
|
57
|
+
* Always indicates a security validation failure (state, `iss`, nonce, PKCE,
|
|
58
|
+
* issuer, audience, signature or expiration), never a transient failure.
|
|
59
|
+
*/
|
|
60
|
+
export class SebaAuthError extends SebaError {
|
|
61
|
+
constructor(message, options) {
|
|
62
|
+
super("SEBA_ERR_AUTH", message, options);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The stored session can no longer be used (refresh token revoked, expired or rejected).
|
|
67
|
+
*
|
|
68
|
+
* The local session state has already been cleared when this is thrown.
|
|
69
|
+
*/
|
|
70
|
+
export class SebaSessionExpiredError extends SebaError {
|
|
71
|
+
constructor(message, options) {
|
|
72
|
+
super("SEBA_ERR_SESSION_EXPIRED", message, options);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** A DPoP proof could not be created, or the provider rejected the DPoP key binding. */
|
|
76
|
+
export class SebaDPoPError extends SebaError {
|
|
77
|
+
constructor(message, options) {
|
|
78
|
+
super("SEBA_ERR_DPOP", message, options);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** A network request failed (offline, DNS, TLS, connection reset, non-HTTP response). */
|
|
82
|
+
export class SebaNetworkError extends SebaError {
|
|
83
|
+
constructor(message, options) {
|
|
84
|
+
super("SEBA_ERR_NETWORK", message, options);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Interactive login is impossible in the current environment. */
|
|
88
|
+
export class SebaNoUIError extends SebaError {
|
|
89
|
+
constructor(message, options) {
|
|
90
|
+
super("SEBA_ERR_NO_UI", message, options);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Type guard for {@link SebaError}.
|
|
95
|
+
*
|
|
96
|
+
* @param value - Any caught value.
|
|
97
|
+
*/
|
|
98
|
+
export function isSebaError(value) {
|
|
99
|
+
return value instanceof SebaError;
|
|
100
|
+
}
|
|
101
|
+
function formatList(values) {
|
|
102
|
+
return values.length === 0 ? "(none)" : values.map((value) => `"${value}"`).join(", ");
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal HTTP helpers shared by discovery, token and revocation requests.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the single implementation of "make a request, turn transport
|
|
5
|
+
* failures into `SebaNetworkError`, and decode a JSON body safely". It never adds
|
|
6
|
+
* credentials: callers attach `Authorization`/`DPoP` themselves.
|
|
7
|
+
*/
|
|
8
|
+
import { SebaError } from "./errors.js";
|
|
9
|
+
/** Default timeout for provider and resource requests, in milliseconds. */
|
|
10
|
+
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
11
|
+
/** Request options accepted by {@link httpFetch}. */
|
|
12
|
+
export interface HttpOptions {
|
|
13
|
+
/** `fetch` implementation supplied by the runtime platform. */
|
|
14
|
+
readonly fetchImpl: typeof fetch;
|
|
15
|
+
/** Abort the request after this many milliseconds. Defaults to 10 seconds. */
|
|
16
|
+
readonly timeoutMs?: number;
|
|
17
|
+
/** Caller cancellation signal, if any. */
|
|
18
|
+
readonly signal?: AbortSignal | null;
|
|
19
|
+
}
|
|
20
|
+
/** JSON content types accepted from SEBA providers. */
|
|
21
|
+
export declare const JSON_ACCEPT = "application/json";
|
|
22
|
+
/**
|
|
23
|
+
* Perform a single attempt of an HTTP request.
|
|
24
|
+
*
|
|
25
|
+
* Transport failures (offline, DNS, TLS, connection reset, unsupported scheme) become
|
|
26
|
+
* {@link SebaNetworkError}; HTTP status handling is left to the caller because a 400 is
|
|
27
|
+
* meaningful to a token endpoint and meaningless to a JWKS fetch.
|
|
28
|
+
*
|
|
29
|
+
* @param url - Absolute request URL.
|
|
30
|
+
* @param init - Standard fetch options; `signal` is replaced with an internal one.
|
|
31
|
+
* @param options - Runtime fetch implementation and timeout.
|
|
32
|
+
*/
|
|
33
|
+
export declare function httpFetch(url: string | URL, init: RequestInit, options: HttpOptions): Promise<Response>;
|
|
34
|
+
/**
|
|
35
|
+
* Parse a JSON response body.
|
|
36
|
+
*
|
|
37
|
+
* @param text - Raw response body.
|
|
38
|
+
* @param context - Human-readable description used in the error message.
|
|
39
|
+
* @param createError - Error constructor for the calling module (`SebaDiscoveryError`,
|
|
40
|
+
* `SebaAuthError`, …) so the failure carries the right code.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseJson<T>(text: string, context: string, createError?: (message: string) => SebaError): T;
|
|
43
|
+
/**
|
|
44
|
+
* Read a response body as text, mapping a broken stream to a network error.
|
|
45
|
+
*
|
|
46
|
+
* @param response - Response whose body should be read.
|
|
47
|
+
* @param context - Human-readable description used in the error message.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readText(response: Response, context: string): Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a response is a successful HTTP status.
|
|
52
|
+
*
|
|
53
|
+
* @param response - Response to inspect.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isOk(response: Response): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Strip query and fragment from a URL for safe inclusion in error messages and logs.
|
|
58
|
+
*
|
|
59
|
+
* @param value - Absolute URL, possibly unparseable.
|
|
60
|
+
*/
|
|
61
|
+
export declare function describeUrl(value: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Join `fetch`-style input into a string URL without losing the base for `URL` objects.
|
|
64
|
+
*
|
|
65
|
+
* @param input - Request or string/URL input.
|
|
66
|
+
*/
|
|
67
|
+
export declare function inputToString(input: RequestInfo | URL): string;
|
|
68
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAsB,SAAS,EAAoB,MAAM,aAAa,CAAC;AAE9E,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,OAAO,KAAK,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACtC;AAED,uDAAuD;AACvD,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuC7G;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,SAAwD,GACzF,CAAC,CAMH;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMnF;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,GAAG,MAAM,CAQ9D"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal HTTP helpers shared by discovery, token and revocation requests.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the single implementation of "make a request, turn transport
|
|
5
|
+
* failures into `SebaNetworkError`, and decode a JSON body safely". It never adds
|
|
6
|
+
* credentials: callers attach `Authorization`/`DPoP` themselves.
|
|
7
|
+
*/
|
|
8
|
+
import { SebaDiscoveryError, SebaError, SebaNetworkError } from "./errors.js";
|
|
9
|
+
/** Default timeout for provider and resource requests, in milliseconds. */
|
|
10
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
|
+
/** JSON content types accepted from SEBA providers. */
|
|
12
|
+
export const JSON_ACCEPT = "application/json";
|
|
13
|
+
/**
|
|
14
|
+
* Perform a single attempt of an HTTP request.
|
|
15
|
+
*
|
|
16
|
+
* Transport failures (offline, DNS, TLS, connection reset, unsupported scheme) become
|
|
17
|
+
* {@link SebaNetworkError}; HTTP status handling is left to the caller because a 400 is
|
|
18
|
+
* meaningful to a token endpoint and meaningless to a JWKS fetch.
|
|
19
|
+
*
|
|
20
|
+
* @param url - Absolute request URL.
|
|
21
|
+
* @param init - Standard fetch options; `signal` is replaced with an internal one.
|
|
22
|
+
* @param options - Runtime fetch implementation and timeout.
|
|
23
|
+
*/
|
|
24
|
+
export async function httpFetch(url, init, options) {
|
|
25
|
+
const target = typeof url === "string" ? url : url.href;
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
28
|
+
let timedOut = false;
|
|
29
|
+
const timer = setTimeout(() => {
|
|
30
|
+
timedOut = true;
|
|
31
|
+
controller.abort();
|
|
32
|
+
}, timeoutMs);
|
|
33
|
+
const external = options.signal ?? null;
|
|
34
|
+
const onAbort = () => {
|
|
35
|
+
controller.abort();
|
|
36
|
+
};
|
|
37
|
+
if (external !== null) {
|
|
38
|
+
if (external.aborted) {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
throw new SebaNetworkError(`The request to ${describeUrl(target)} was aborted before it started.`, {
|
|
41
|
+
cause: external.reason,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
external.addEventListener("abort", onAbort, { once: true });
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return await options.fetchImpl(url, { ...init, signal: controller.signal });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (external?.aborted === true) {
|
|
51
|
+
throw new SebaNetworkError(`The request to ${describeUrl(target)} was aborted.`, { cause: error });
|
|
52
|
+
}
|
|
53
|
+
const reason = timedOut ? `did not complete within ${String(timeoutMs)} ms` : "could not be completed";
|
|
54
|
+
throw new SebaNetworkError(`Network request to ${describeUrl(target)} ${reason}. ` +
|
|
55
|
+
"Check the network connection, the provider URL and (in Node.js) any proxy configuration.", { cause: error });
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
external?.removeEventListener("abort", onAbort);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a JSON response body.
|
|
64
|
+
*
|
|
65
|
+
* @param text - Raw response body.
|
|
66
|
+
* @param context - Human-readable description used in the error message.
|
|
67
|
+
* @param createError - Error constructor for the calling module (`SebaDiscoveryError`,
|
|
68
|
+
* `SebaAuthError`, …) so the failure carries the right code.
|
|
69
|
+
*/
|
|
70
|
+
export function parseJson(text, context, createError = (message) => new SebaDiscoveryError(message)) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(text);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw createError(`${context} is not valid JSON. The provider returned a non-JSON response body.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read a response body as text, mapping a broken stream to a network error.
|
|
80
|
+
*
|
|
81
|
+
* @param response - Response whose body should be read.
|
|
82
|
+
* @param context - Human-readable description used in the error message.
|
|
83
|
+
*/
|
|
84
|
+
export async function readText(response, context) {
|
|
85
|
+
try {
|
|
86
|
+
return await response.text();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new SebaNetworkError(`Unable to read the ${context} response body.`, { cause: error });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check whether a response is a successful HTTP status.
|
|
94
|
+
*
|
|
95
|
+
* @param response - Response to inspect.
|
|
96
|
+
*/
|
|
97
|
+
export function isOk(response) {
|
|
98
|
+
return response.status >= 200 && response.status < 300;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Strip query and fragment from a URL for safe inclusion in error messages and logs.
|
|
102
|
+
*
|
|
103
|
+
* @param value - Absolute URL, possibly unparseable.
|
|
104
|
+
*/
|
|
105
|
+
export function describeUrl(value) {
|
|
106
|
+
try {
|
|
107
|
+
const url = new URL(value);
|
|
108
|
+
return `${url.origin}${url.pathname}`;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return "the configured URL";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Join `fetch`-style input into a string URL without losing the base for `URL` objects.
|
|
116
|
+
*
|
|
117
|
+
* @param input - Request or string/URL input.
|
|
118
|
+
*/
|
|
119
|
+
export function inputToString(input) {
|
|
120
|
+
if (typeof input === "string") {
|
|
121
|
+
return input;
|
|
122
|
+
}
|
|
123
|
+
if (input instanceof URL) {
|
|
124
|
+
return input.href;
|
|
125
|
+
}
|
|
126
|
+
return input.url;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client identification and WebID-based issuer discovery.
|
|
3
|
+
*
|
|
4
|
+
* SEBA uses a static Solid Client ID Document as its `client_id`: the URL of the
|
|
5
|
+
* document *is* the client identifier. No dynamic client registration is performed and
|
|
6
|
+
* `registration_endpoint` is never called.
|
|
7
|
+
*
|
|
8
|
+
* WebID profiles are read as Turtle (documented subset), JSON-LD or, as a last resort,
|
|
9
|
+
* an HTML `Link` header. The parsed issuer is then validated as an issuer URL before it
|
|
10
|
+
* is trusted.
|
|
11
|
+
*/
|
|
12
|
+
import type { ClientIDDocument } from "../types.js";
|
|
13
|
+
/** JSON-LD context every Solid Client ID Document must declare. */
|
|
14
|
+
export declare const SOLID_OIDC_CONTEXT = "https://www.w3.org/ns/solid/oidc-context.jsonld";
|
|
15
|
+
/** Fully expanded predicate of `solid:oidcIssuer`. */
|
|
16
|
+
export declare const OIDC_ISSUER_PREDICATE = "http://www.w3.org/ns/solid/terms#oidcIssuer";
|
|
17
|
+
/** Result of validating a WebID: the normalized WebID and its profile URL. */
|
|
18
|
+
export interface WebIdLocation {
|
|
19
|
+
/** Normalized WebID (absolute IRI, fragment preserved). */
|
|
20
|
+
readonly webId: string;
|
|
21
|
+
/** Profile document URL (the WebID without its fragment). */
|
|
22
|
+
readonly documentUrl: string;
|
|
23
|
+
}
|
|
24
|
+
/** A `(subject, solid:oidcIssuer, issuer)` statement extracted from a profile. */
|
|
25
|
+
export interface IssuerStatement {
|
|
26
|
+
readonly subject: string;
|
|
27
|
+
readonly issuer: string;
|
|
28
|
+
}
|
|
29
|
+
/** An RDF term parsed from Turtle. */
|
|
30
|
+
type Term = {
|
|
31
|
+
readonly type: "iri";
|
|
32
|
+
readonly value: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "literal";
|
|
35
|
+
readonly value: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "bnode";
|
|
38
|
+
readonly id: string;
|
|
39
|
+
};
|
|
40
|
+
/** A parsed Turtle triple. */
|
|
41
|
+
export interface Triple {
|
|
42
|
+
readonly subject: string;
|
|
43
|
+
readonly predicate: string;
|
|
44
|
+
readonly object: Term;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate and normalize a WebID.
|
|
48
|
+
*
|
|
49
|
+
* @param webId - WebID supplied by the application or the user.
|
|
50
|
+
* @throws {@link SebaConfigError} when the value is not an absolute https (or loopback
|
|
51
|
+
* http) IRI without embedded credentials.
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateWebId(webId: string): WebIdLocation;
|
|
54
|
+
/**
|
|
55
|
+
* Validate a Client ID Document URL taken from configuration.
|
|
56
|
+
*
|
|
57
|
+
* @param clientId - Configured `client_id`.
|
|
58
|
+
* @throws {@link SebaConfigError} when the value cannot be a Client ID Document URL.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateClientIdUrl(clientId: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Parse and validate a Solid Client ID Document.
|
|
63
|
+
*
|
|
64
|
+
* @param raw - Parsed JSON body.
|
|
65
|
+
* @param documentUrl - URL the document was fetched from; `client_id` must equal it.
|
|
66
|
+
* @throws {@link SebaDiscoveryError} when the document is not a valid Client ID Document.
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseClientIdDocument(raw: unknown, documentUrl: string): ClientIDDocument;
|
|
69
|
+
/**
|
|
70
|
+
* Fetch and validate the Client ID Document.
|
|
71
|
+
*
|
|
72
|
+
* @param clientId - Configured Client ID Document URL.
|
|
73
|
+
* @param fetchImpl - Runtime `fetch` implementation.
|
|
74
|
+
*/
|
|
75
|
+
export declare function loadClientIdDocument(clientId: string, fetchImpl: typeof fetch): Promise<ClientIDDocument>;
|
|
76
|
+
/**
|
|
77
|
+
* Assert that a redirect URI is published verbatim by the Client ID Document.
|
|
78
|
+
*
|
|
79
|
+
* Membership is compared as exact strings: no normalization is applied to either side.
|
|
80
|
+
*
|
|
81
|
+
* @param document - Validated Client ID Document.
|
|
82
|
+
* @param redirectUri - Configured redirect URI.
|
|
83
|
+
* @throws {@link SebaRedirectMismatchError} when the URI is not published.
|
|
84
|
+
*/
|
|
85
|
+
export declare function assertRedirectUriPublished(document: ClientIDDocument, redirectUri: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Parse a Turtle document and return its triples.
|
|
88
|
+
*
|
|
89
|
+
* Supported subset: `@prefix`/`PREFIX`, `@base`/`BASE`, IRIs (relative IRIs resolve
|
|
90
|
+
* against the base), prefixed names, `a` as `rdf:type`, subject/predicate/object lists
|
|
91
|
+
* with `;` and `,`, comments, blank node labels, empty `[]` blank nodes, and literals with
|
|
92
|
+
* language tags or datatypes. Collections `( … )` and property lists `[ … ]` are rejected
|
|
93
|
+
* with a descriptive error instead of being mis-parsed.
|
|
94
|
+
*
|
|
95
|
+
* @param text - Turtle source.
|
|
96
|
+
* @param baseUrl - Base IRI for relative references (the document URL).
|
|
97
|
+
*/
|
|
98
|
+
export declare function parseTurtle(text: string, baseUrl: string): Triple[];
|
|
99
|
+
/**
|
|
100
|
+
* Extract `solid:oidcIssuer` statements from parsed triples.
|
|
101
|
+
*
|
|
102
|
+
* @param triples - Parsed triples.
|
|
103
|
+
*/
|
|
104
|
+
export declare function extractIssuerStatements(triples: readonly Triple[]): IssuerStatement[];
|
|
105
|
+
/**
|
|
106
|
+
* Extract `solid:oidcIssuer` statements from a JSON-LD document.
|
|
107
|
+
*
|
|
108
|
+
* Accepts the expanded predicate and the compacted `solid:oidcIssuer` form, string or
|
|
109
|
+
* `{ "@id": … }` values, and nested `@graph` containers.
|
|
110
|
+
*
|
|
111
|
+
* @param raw - Parsed JSON-LD document.
|
|
112
|
+
* @param documentUrl - Base URL for relative `@id` values.
|
|
113
|
+
*/
|
|
114
|
+
export declare function extractJsonLdIssuers(raw: unknown, documentUrl: string): IssuerStatement[];
|
|
115
|
+
/**
|
|
116
|
+
* Resolve an IRI against a base while preserving already-absolute IRIs byte for byte.
|
|
117
|
+
*
|
|
118
|
+
* `new URL("https://idp.example").href` appends a trailing slash, which would silently
|
|
119
|
+
* change an issuer identifier. Responding issuer identifiers are compared exactly, so
|
|
120
|
+
* absolute IRIs are returned unchanged.
|
|
121
|
+
*
|
|
122
|
+
* @param value - IRI reference from the document.
|
|
123
|
+
* @param base - Base IRI for relative references.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resolveAbsoluteIri(value: string, base: string): string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Parse an HTTP `Link` header and return the targets with the requested relation.
|
|
128
|
+
*
|
|
129
|
+
* @param header - Raw `Link` header value, when present.
|
|
130
|
+
* @param relation - Relation URI to match.
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseLinkHeader(header: string | null, relation: string): string[];
|
|
133
|
+
/**
|
|
134
|
+
* Resolve the OpenID Provider issuer for a WebID by reading its profile.
|
|
135
|
+
*
|
|
136
|
+
* Order: `solid:oidcIssuer` triple for the exact WebID (Turtle or JSON-LD), then a
|
|
137
|
+
* `rel="http://www.w3.org/ns/solid/terms#oidcIssuer"` `Link` header. Failures are
|
|
138
|
+
* discovered errors with actionable messages.
|
|
139
|
+
*
|
|
140
|
+
* @param webId - WebID supplied by the application or the user.
|
|
141
|
+
* @param fetchImpl - Runtime `fetch` implementation.
|
|
142
|
+
*/
|
|
143
|
+
export declare function discoverIssuerForWebId(webId: string, fetchImpl: typeof fetch): Promise<string>;
|
|
144
|
+
export {};
|
|
145
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/core/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,oDAAoD,CAAC;AAKpF,sDAAsD;AACtD,eAAO,MAAM,qBAAqB,gDAA6B,CAAC;AAKhE,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,sCAAsC;AACtC,KAAK,IAAI,GACL;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAqB1D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CA+DzF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgB/G;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAIhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEnE;AAoZD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAQrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,eAAe,EAAE,CA6CzF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc7E;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BjF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA4DpG"}
|