@gaballahtech/seba 0.0.0-stage → 0.2.1

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.
Files changed (86) hide show
  1. package/ARCHITECTURE.md +242 -0
  2. package/BUILD_REPORT.md +195 -0
  3. package/CHANGELOG.md +86 -0
  4. package/LICENSE +21 -0
  5. package/README.md +282 -2
  6. package/RESEARCH.md +180 -0
  7. package/SECURITY.md +224 -0
  8. package/VALIDATION_REPORT.md +187 -0
  9. package/dist/browser/index.d.ts +9 -0
  10. package/dist/browser/index.d.ts.map +1 -0
  11. package/dist/browser/index.js +9 -0
  12. package/dist/browser/index.js.map +1 -0
  13. package/dist/browser/platform.d.ts +14 -0
  14. package/dist/browser/platform.d.ts.map +1 -0
  15. package/dist/browser/platform.js +60 -0
  16. package/dist/browser/platform.js.map +1 -0
  17. package/dist/browser/provider-modal.d.ts +15 -0
  18. package/dist/browser/provider-modal.d.ts.map +1 -0
  19. package/dist/browser/provider-modal.js +126 -0
  20. package/dist/browser/provider-modal.js.map +1 -0
  21. package/dist/browser/storage.d.ts +32 -0
  22. package/dist/browser/storage.d.ts.map +1 -0
  23. package/dist/browser/storage.js +121 -0
  24. package/dist/browser/storage.js.map +1 -0
  25. package/dist/core/client.d.ts +49 -0
  26. package/dist/core/client.d.ts.map +1 -0
  27. package/dist/core/client.js +371 -0
  28. package/dist/core/client.js.map +1 -0
  29. package/dist/core/discovery.d.ts +82 -0
  30. package/dist/core/discovery.d.ts.map +1 -0
  31. package/dist/core/discovery.js +243 -0
  32. package/dist/core/discovery.js.map +1 -0
  33. package/dist/core/dpop.d.ts +67 -0
  34. package/dist/core/dpop.d.ts.map +1 -0
  35. package/dist/core/dpop.js +176 -0
  36. package/dist/core/dpop.js.map +1 -0
  37. package/dist/core/errors.d.ts +86 -0
  38. package/dist/core/errors.d.ts.map +1 -0
  39. package/dist/core/errors.js +104 -0
  40. package/dist/core/errors.js.map +1 -0
  41. package/dist/core/http.d.ts +68 -0
  42. package/dist/core/http.d.ts.map +1 -0
  43. package/dist/core/http.js +128 -0
  44. package/dist/core/http.js.map +1 -0
  45. package/dist/core/identity.d.ts +145 -0
  46. package/dist/core/identity.d.ts.map +1 -0
  47. package/dist/core/identity.js +735 -0
  48. package/dist/core/identity.js.map +1 -0
  49. package/dist/core/oidc.d.ts +175 -0
  50. package/dist/core/oidc.d.ts.map +1 -0
  51. package/dist/core/oidc.js +413 -0
  52. package/dist/core/oidc.js.map +1 -0
  53. package/dist/core/session.d.ts +116 -0
  54. package/dist/core/session.d.ts.map +1 -0
  55. package/dist/core/session.js +350 -0
  56. package/dist/core/session.js.map +1 -0
  57. package/dist/core/storage.d.ts +22 -0
  58. package/dist/core/storage.d.ts.map +1 -0
  59. package/dist/core/storage.js +48 -0
  60. package/dist/core/storage.js.map +1 -0
  61. package/dist/index.d.ts +18 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +3 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/node/index.d.ts +9 -0
  66. package/dist/node/index.d.ts.map +1 -0
  67. package/dist/node/index.js +9 -0
  68. package/dist/node/index.js.map +1 -0
  69. package/dist/node/platform.d.ts +25 -0
  70. package/dist/node/platform.d.ts.map +1 -0
  71. package/dist/node/platform.js +161 -0
  72. package/dist/node/platform.js.map +1 -0
  73. package/dist/seba.d.ts +39 -0
  74. package/dist/seba.d.ts.map +1 -0
  75. package/dist/seba.js +58 -0
  76. package/dist/seba.js.map +1 -0
  77. package/dist/types.d.ts +279 -0
  78. package/dist/types.d.ts.map +1 -0
  79. package/dist/types.js +8 -0
  80. package/dist/types.js.map +1 -0
  81. package/examples/node/index.ts +61 -0
  82. package/examples/react/App.tsx +127 -0
  83. package/examples/react/index.html +12 -0
  84. package/examples/react/main.tsx +14 -0
  85. package/examples/react/vite.config.ts +27 -0
  86. package/package.json +83 -4
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Node.js example: restore or start a login, read the user's profile, sign out.
3
+ *
4
+ * Prerequisites for the local Community Solid Server flow:
5
+ *
6
+ * 1. `npm run build` (the example imports the package through its self-reference).
7
+ * 2. A CSS instance on http://localhost:3000 with an account and a pod.
8
+ * 3. A Client ID Document served over http on loopback that publishes the redirect URI.
9
+ *
10
+ * Run:
11
+ *
12
+ * ```bash
13
+ * SEBA_CLIENT_ID=http://localhost:8443/client-id.jsonld \
14
+ * SEBA_REDIRECT_URI=http://localhost:3001/auth/callback \
15
+ * SEBA_ISSUER=http://localhost:3000 \
16
+ * WEBID=http://localhost:3000/alice/profile/card#me \
17
+ * node examples/node/index.ts
18
+ * ```
19
+ *
20
+ * The redirect URI must be published verbatim in the Client ID Document and must use
21
+ * plain http on loopback (`127.0.0.1`, `::1` or `localhost`) because SEBA starts a
22
+ * temporary callback server there.
23
+ */
24
+
25
+ import { exec } from "node:child_process";
26
+ import { Seba } from "@gaballahtech/seba/node";
27
+
28
+ const issuer = process.env["SEBA_ISSUER"] ?? "http://localhost:3000";
29
+ const clientId = process.env["SEBA_CLIENT_ID"] ?? "http://localhost:8443/client-id.jsonld";
30
+ const redirectUri = process.env["SEBA_REDIRECT_URI"] ?? "http://localhost:3001/auth/callback";
31
+ const webId = process.env["WEBID"];
32
+
33
+ const seba = new Seba({
34
+ clientId,
35
+ redirectUri,
36
+ openAuthorizationUrl(url) {
37
+ // Replace with the opener for your platform: `open` (macOS), `start` (Windows).
38
+ exec(`xdg-open "${url}"`, (error) => {
39
+ if (error !== null) {
40
+ console.error("Open this URL in a browser to continue:");
41
+ console.error(url);
42
+ }
43
+ });
44
+ },
45
+ debug: process.env["SEBA_DEBUG"] === "1",
46
+ });
47
+
48
+ const session = (await seba.restoreSession()) ?? (await seba.login(webId === undefined ? { issuer } : { issuer, webId }));
49
+
50
+ console.log(`Signed in as ${session.webId} (provider ${session.provider}).`);
51
+
52
+ const response = await seba.fetch(session.webId, {
53
+ headers: { accept: "text/turtle" },
54
+ });
55
+ if (!response.ok) {
56
+ throw new Error(`Reading the profile failed with HTTP ${String(response.status)}.`);
57
+ }
58
+ console.log(await response.text());
59
+
60
+ await seba.logout();
61
+ console.log("Signed out.");
@@ -0,0 +1,127 @@
1
+ /**
2
+ * React example.
3
+ *
4
+ * The same page starts the login, receives the callback and shows the profile. Keep the
5
+ * component mounted on the redirect URI; `login()` detects the authorization response and
6
+ * finishes the flow.
7
+ */
8
+
9
+ import { useCallback, useEffect, useState } from "react";
10
+ import { Seba, isSebaError, type Session } from "@gaballahtech/seba";
11
+
12
+ const seba = new Seba({
13
+ clientId: import.meta.env.VITE_SEBA_CLIENT_ID ?? "https://app.example/client-id.jsonld",
14
+ redirectUri: `${window.location.origin}/auth/callback`,
15
+ });
16
+
17
+ export default function App() {
18
+ const [session, setSession] = useState<Session | null>(null);
19
+ const [profile, setProfile] = useState<string | null>(null);
20
+ const [error, setError] = useState<string | null>(null);
21
+ const [busy, setBusy] = useState(false);
22
+
23
+ useEffect(() => {
24
+ let cancelled = false;
25
+ const hasCallback = new URL(window.location.href).searchParams.has("code");
26
+
27
+ async function boot(): Promise<void> {
28
+ try {
29
+ // On the callback route this completes the pending login; elsewhere it restores.
30
+ const restored = hasCallback ? await seba.login() : await seba.restoreSession();
31
+ if (!cancelled) {
32
+ setSession(restored);
33
+ }
34
+ } catch (caught) {
35
+ if (!cancelled) {
36
+ setError(describe(caught));
37
+ }
38
+ }
39
+ }
40
+
41
+ void boot();
42
+ return () => {
43
+ cancelled = true;
44
+ };
45
+ }, []);
46
+
47
+ const signIn = useCallback(async (): Promise<void> => {
48
+ setBusy(true);
49
+ setError(null);
50
+ try {
51
+ // Without a WebID the browser dialog asks for one. The page navigates away, so this
52
+ // promise normally never resolves.
53
+ await seba.login();
54
+ } catch (caught) {
55
+ setError(describe(caught));
56
+ setBusy(false);
57
+ }
58
+ }, []);
59
+
60
+ const loadProfile = useCallback(async (): Promise<void> => {
61
+ if (session === null) {
62
+ return;
63
+ }
64
+ setBusy(true);
65
+ setError(null);
66
+ try {
67
+ const response = await seba.fetch(session.webId, { headers: { accept: "text/turtle" } });
68
+ setProfile(response.ok ? await response.text() : `HTTP ${String(response.status)}`);
69
+ } catch (caught) {
70
+ setError(describe(caught));
71
+ } finally {
72
+ setBusy(false);
73
+ }
74
+ }, [session]);
75
+
76
+ const signOut = useCallback(async (): Promise<void> => {
77
+ setBusy(true);
78
+ try {
79
+ await seba.logout();
80
+ setSession(null);
81
+ setProfile(null);
82
+ } catch (caught) {
83
+ setError(describe(caught));
84
+ } finally {
85
+ setBusy(false);
86
+ }
87
+ }, []);
88
+
89
+ return (
90
+ <main style={{ fontFamily: "system-ui, sans-serif", maxWidth: 720, margin: "3rem auto", padding: "0 1rem" }}>
91
+ <h1>SEBA + React</h1>
92
+
93
+ {session === null ? (
94
+ <button type="button" onClick={() => void signIn()} disabled={busy}>
95
+ Sign in with Solid
96
+ </button>
97
+ ) : (
98
+ <section>
99
+ <p>
100
+ Signed in as <strong>{session.webId}</strong>
101
+ </p>
102
+ <p>Provider: {session.provider}</p>
103
+ <button type="button" onClick={() => void loadProfile()} disabled={busy}>
104
+ Read my profile
105
+ </button>{" "}
106
+ <button type="button" onClick={() => void signOut()} disabled={busy}>
107
+ Sign out
108
+ </button>
109
+ {profile !== null && <pre style={{ whiteSpace: "pre-wrap", background: "#f1f5f9", padding: 12 }}>{profile}</pre>}
110
+ </section>
111
+ )}
112
+
113
+ {error !== null && (
114
+ <p role="alert" style={{ color: "#b91c1c" }}>
115
+ {error}
116
+ </p>
117
+ )}
118
+ </main>
119
+ );
120
+ }
121
+
122
+ function describe(error: unknown): string {
123
+ if (isSebaError(error)) {
124
+ return `${error.code}: ${error.message}`;
125
+ }
126
+ return error instanceof Error ? error.message : String(error);
127
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>SEBA React example</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,14 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+
5
+ const container = document.getElementById("root");
6
+ if (container === null) {
7
+ throw new Error("The page has no #root element.");
8
+ }
9
+
10
+ createRoot(container).render(
11
+ <StrictMode>
12
+ <App />
13
+ </StrictMode>,
14
+ );
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Vite config for the React example.
3
+ *
4
+ * The `seba` alias points at the TypeScript sources so the example can be built from a
5
+ * checkout without publishing the package first.
6
+ */
7
+
8
+ import { fileURLToPath } from "node:url";
9
+ import react from "@vitejs/plugin-react";
10
+ import { defineConfig } from "vite";
11
+
12
+ const root = fileURLToPath(new URL(".", import.meta.url));
13
+ const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
14
+
15
+ export default defineConfig({
16
+ root,
17
+ plugins: [react()],
18
+ resolve: {
19
+ alias: {
20
+ seba: `${packageRoot}src/index.ts`,
21
+ },
22
+ },
23
+ build: {
24
+ outDir: "dist",
25
+ emptyOutDir: true,
26
+ },
27
+ });
package/package.json CHANGED
@@ -1,6 +1,85 @@
1
1
  {
2
2
  "name": "@gaballahtech/seba",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "Developer-friendly Solid-OIDC authentication: login, session restoration and DPoP-signed fetch in four methods.",
5
+ "license": "MIT",
6
+ "author": "gaballahtech",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/gaballahtech/seba.git"
10
+ },
11
+ "homepage": "https://github.com/gaballahtech/seba#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/gaballahtech/seba/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "sideEffects": false,
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./browser": {
26
+ "types": "./dist/browser/index.d.ts",
27
+ "default": "./dist/browser/index.js"
28
+ },
29
+ "./node": {
30
+ "types": "./dist/node/index.d.ts",
31
+ "default": "./dist/node/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "examples",
38
+ "README.md",
39
+ "CHANGELOG.md",
40
+ "BUILD_REPORT.md",
41
+ "VALIDATION_REPORT.md",
42
+ "LICENSE",
43
+ "RESEARCH.md",
44
+ "ARCHITECTURE.md",
45
+ "SECURITY.md"
46
+ ],
47
+ "engines": {
48
+ "node": ">=20"
49
+ },
50
+ "keywords": [
51
+ "solid",
52
+ "solid-oidc",
53
+ "oidc",
54
+ "oauth2",
55
+ "dpop",
56
+ "pkce",
57
+ "webid",
58
+ "authentication"
59
+ ],
60
+ "scripts": {
61
+ "build": "tsc -p tsconfig.build.json",
62
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.examples.json",
63
+ "typecheck:examples": "tsc -p tsconfig.examples.json",
64
+ "example:react:build": "vite build --config examples/react/vite.config.ts",
65
+ "validate:discovery": "npm run build && node scripts/validation/public-discovery.mjs",
66
+ "test": "vitest run",
67
+ "test:watch": "vitest"
68
+ },
69
+ "dependencies": {
70
+ "idb": "^8.0.3",
71
+ "jose": "^6.1.0"
72
+ },
73
+ "devDependencies": {
74
+ "@types/node": "^24.7.0",
75
+ "@types/react": "^19.3.0",
76
+ "@types/react-dom": "^19.3.0",
77
+ "@vitejs/plugin-react": "^6.2.1",
78
+ "fake-indexeddb": "^6.2.2",
79
+ "react": "^19.3.1",
80
+ "react-dom": "^19.3.1",
81
+ "typescript": "^5.9.3",
82
+ "vite": "^8.3.6",
83
+ "vitest": "^5.1.0"
84
+ }
85
+ }