javascript-solid-server 0.0.11 → 0.0.13

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.
@@ -20,7 +20,13 @@
20
20
  "WebFetch(domain:solid-contrib.github.io)",
21
21
  "Bash(git clone:*)",
22
22
  "Bash(chmod:*)",
23
- "Bash(JSS_PORT=4000 JSS_CONNEG=true node bin/jss.js:*)"
23
+ "Bash(JSS_PORT=4000 JSS_CONNEG=true node bin/jss.js:*)",
24
+ "Bash(find:*)",
25
+ "Bash(timeout 5 node:*)",
26
+ "Bash(npm view:*)",
27
+ "Bash(npm ls:*)",
28
+ "Bash(timeout 10 node:*)",
29
+ "Bash(npm run test:cth:*)"
24
30
  ]
25
31
  }
26
32
  }
package/README.md CHANGED
@@ -54,7 +54,7 @@ npm run benchmark
54
54
 
55
55
  ## Features
56
56
 
57
- ### Implemented (v0.0.11)
57
+ ### Implemented (v0.0.13)
58
58
 
59
59
  - **LDP CRUD Operations** - GET, PUT, POST, DELETE, HEAD
60
60
  - **N3 Patch** - Solid's native patch format for RDF updates
@@ -67,6 +67,7 @@ npm run benchmark
67
67
  - **Multi-user Pods** - Create pods at `/<username>/`
68
68
  - **WebID Profiles** - JSON-LD structured data in HTML at pod root
69
69
  - **Web Access Control (WAC)** - `.acl` file-based authorization
70
+ - **Solid-OIDC Identity Provider** - Built-in IdP with DPoP, dynamic registration
70
71
  - **Solid-OIDC Resource Server** - Accept DPoP-bound access tokens from external IdPs
71
72
  - **Simple Auth Tokens** - Built-in token authentication for development
72
73
  - **Content Negotiation** - Optional Turtle <-> JSON-LD conversion
@@ -132,6 +133,8 @@ jss --help # Show help
132
133
  | `--ssl-cert <path>` | SSL certificate (PEM) | - |
133
134
  | `--conneg` | Enable Turtle support | false |
134
135
  | `--notifications` | Enable WebSocket | false |
136
+ | `--idp` | Enable built-in IdP | false |
137
+ | `--idp-issuer <url>` | IdP issuer URL | (auto) |
135
138
  | `-q, --quiet` | Suppress logs | false |
136
139
 
137
140
  ### Environment Variables
@@ -274,9 +277,60 @@ Use the token returned from pod creation:
274
277
  curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/alice/private/
275
278
  ```
276
279
 
277
- ### Solid-OIDC (Production)
280
+ ### Built-in Identity Provider (v0.0.12+)
278
281
 
279
- The server accepts DPoP-bound access tokens from external Solid identity providers:
282
+ Enable the built-in Solid-OIDC Identity Provider:
283
+
284
+ ```bash
285
+ jss start --idp
286
+ ```
287
+
288
+ With IdP enabled, pod creation requires email and password:
289
+
290
+ ```bash
291
+ curl -X POST http://localhost:3000/.pods \
292
+ -H "Content-Type: application/json" \
293
+ -d '{"name": "alice", "email": "alice@example.com", "password": "secret123"}'
294
+ ```
295
+
296
+ Response:
297
+ ```json
298
+ {
299
+ "name": "alice",
300
+ "webId": "http://localhost:3000/alice/#me",
301
+ "podUri": "http://localhost:3000/alice/",
302
+ "idpIssuer": "http://localhost:3000",
303
+ "loginUrl": "http://localhost:3000/idp/auth"
304
+ }
305
+ ```
306
+
307
+ OIDC Discovery: `/.well-known/openid-configuration`
308
+
309
+ ### Programmatic Login (CTH Compatible)
310
+
311
+ For automated testing and scripts, use the credentials endpoint:
312
+
313
+ ```bash
314
+ curl -X POST http://localhost:3000/idp/credentials \
315
+ -H "Content-Type: application/json" \
316
+ -d '{"email": "alice@example.com", "password": "secret123"}'
317
+ ```
318
+
319
+ Response:
320
+ ```json
321
+ {
322
+ "access_token": "...",
323
+ "token_type": "Bearer",
324
+ "expires_in": 3600,
325
+ "webid": "http://localhost:3000/alice/#me"
326
+ }
327
+ ```
328
+
329
+ For DPoP-bound tokens (Solid-OIDC compliant), include a DPoP proof header.
330
+
331
+ ### Solid-OIDC (External IdP)
332
+
333
+ The server also accepts DPoP-bound access tokens from external Solid identity providers:
280
334
 
281
335
  ```bash
282
336
  curl -H "Authorization: DPoP ACCESS_TOKEN" \
@@ -323,7 +377,7 @@ Server: pub http://localhost:3000/alice/public/data.json (on change)
323
377
  npm test
324
378
  ```
325
379
 
326
- Currently passing: **163 tests** (including 27 conformance tests)
380
+ Currently passing: **182 tests** (including 27 conformance tests)
327
381
 
328
382
  ## Project Structure
329
383
 
@@ -355,6 +409,14 @@ src/
355
409
  │ ├── index.js # WebSocket plugin
356
410
  │ ├── events.js # Event emitter
357
411
  │ └── websocket.js # solid-0.1 protocol
412
+ ├── idp/
413
+ │ ├── index.js # Identity Provider plugin
414
+ │ ├── provider.js # oidc-provider config
415
+ │ ├── adapter.js # Filesystem adapter
416
+ │ ├── accounts.js # User account management
417
+ │ ├── keys.js # JWKS key management
418
+ │ ├── interactions.js # Login/consent handlers
419
+ │ └── views.js # HTML templates
358
420
  ├── rdf/
359
421
  │ ├── turtle.js # Turtle <-> JSON-LD
360
422
  │ └── conneg.js # Content negotiation
@@ -372,6 +434,8 @@ Minimal dependencies for a fast, secure server:
372
434
  - **fs-extra** - Enhanced file operations
373
435
  - **jose** - JWT/JWK handling for Solid-OIDC
374
436
  - **n3** - Turtle parsing (only used when conneg enabled)
437
+ - **oidc-provider** - OpenID Connect Identity Provider (only when IdP enabled)
438
+ - **bcrypt** - Password hashing (only when IdP enabled)
375
439
 
376
440
  ## License
377
441
 
package/bin/jss.js CHANGED
@@ -44,6 +44,9 @@ program
44
44
  .option('--no-conneg', 'Disable content negotiation')
45
45
  .option('--notifications', 'Enable WebSocket notifications')
46
46
  .option('--no-notifications', 'Disable WebSocket notifications')
47
+ .option('--idp', 'Enable built-in Identity Provider')
48
+ .option('--no-idp', 'Disable built-in Identity Provider')
49
+ .option('--idp-issuer <url>', 'IdP issuer URL (defaults to server URL)')
47
50
  .option('-q, --quiet', 'Suppress log output')
48
51
  .option('--print-config', 'Print configuration and exit')
49
52
  .action(async (options) => {
@@ -55,11 +58,19 @@ program
55
58
  process.exit(0);
56
59
  }
57
60
 
61
+ // Determine IdP issuer URL
62
+ const protocol = config.ssl ? 'https' : 'http';
63
+ const serverHost = config.host === '0.0.0.0' ? 'localhost' : config.host;
64
+ const baseUrl = `${protocol}://${serverHost}:${config.port}`;
65
+ const idpIssuer = config.idpIssuer || baseUrl;
66
+
58
67
  // Create and start server
59
68
  const server = createServer({
60
69
  logger: config.logger,
61
70
  conneg: config.conneg,
62
71
  notifications: config.notifications,
72
+ idp: config.idp,
73
+ idpIssuer: idpIssuer,
63
74
  ssl: config.ssl ? {
64
75
  key: await fs.readFile(config.sslKey),
65
76
  cert: await fs.readFile(config.sslCert),
@@ -69,16 +80,14 @@ program
69
80
 
70
81
  await server.listen({ port: config.port, host: config.host });
71
82
 
72
- const protocol = config.ssl ? 'https' : 'http';
73
- const address = config.host === '0.0.0.0' ? 'localhost' : config.host;
74
-
75
83
  if (!config.quiet) {
76
84
  console.log(`\n JavaScript Solid Server v${pkg.version}`);
77
- console.log(` ${protocol}://${address}:${config.port}/`);
85
+ console.log(` ${baseUrl}/`);
78
86
  console.log(`\n Data: ${path.resolve(config.root)}`);
79
87
  if (config.ssl) console.log(' SSL: enabled');
80
88
  if (config.conneg) console.log(' Conneg: enabled');
81
89
  if (config.notifications) console.log(' WebSocket: enabled');
90
+ if (config.idp) console.log(` IdP: ${idpIssuer}`);
82
91
  console.log('\n Press Ctrl+C to stop\n');
83
92
  }
84
93
 
@@ -142,6 +151,15 @@ program
142
151
  config.sslCert = await prompt('SSL certificate path', './ssl/cert.pem');
143
152
  }
144
153
 
154
+ // Ask about IdP
155
+ config.idp = await confirm('Enable built-in Identity Provider?', false);
156
+ if (config.idp) {
157
+ const customIssuer = await confirm('Use custom issuer URL?', false);
158
+ if (customIssuer) {
159
+ config.idpIssuer = await prompt('IdP issuer URL', 'https://example.com');
160
+ }
161
+ }
162
+
145
163
  console.log('');
146
164
  }
147
165
 
@@ -0,0 +1,50 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/alice/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/alice/"
15
+ },
16
+ "acl:mode": [
17
+ {
18
+ "@id": "acl:Read"
19
+ },
20
+ {
21
+ "@id": "acl:Write"
22
+ },
23
+ {
24
+ "@id": "acl:Control"
25
+ }
26
+ ],
27
+ "acl:default": {
28
+ "@id": "http://localhost:3457/alice/"
29
+ }
30
+ },
31
+ {
32
+ "@id": "#public",
33
+ "@type": "acl:Authorization",
34
+ "acl:agentClass": {
35
+ "@id": "foaf:Agent"
36
+ },
37
+ "acl:accessTo": {
38
+ "@id": "http://localhost:3457/alice/"
39
+ },
40
+ "acl:mode": [
41
+ {
42
+ "@id": "acl:Read"
43
+ }
44
+ ],
45
+ "acl:default": {
46
+ "@id": "http://localhost:3457/alice/"
47
+ }
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/alice/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/alice/inbox/"
15
+ },
16
+ "acl:default": {
17
+ "@id": "http://localhost:3457/alice/inbox/"
18
+ },
19
+ "acl:mode": [
20
+ {
21
+ "@id": "acl:Read"
22
+ },
23
+ {
24
+ "@id": "acl:Write"
25
+ },
26
+ {
27
+ "@id": "acl:Control"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "@id": "#public",
33
+ "@type": "acl:Authorization",
34
+ "acl:agentClass": {
35
+ "@id": "foaf:Agent"
36
+ },
37
+ "acl:accessTo": {
38
+ "@id": "http://localhost:3457/alice/inbox/"
39
+ },
40
+ "acl:default": {
41
+ "@id": "http://localhost:3457/alice/inbox/"
42
+ },
43
+ "acl:mode": [
44
+ {
45
+ "@id": "acl:Append"
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,80 @@
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>alice's Profile</title>
7
+ <script type="application/ld+json">
8
+ {
9
+ "@context": {
10
+ "foaf": "http://xmlns.com/foaf/0.1/",
11
+ "solid": "http://www.w3.org/ns/solid/terms#",
12
+ "schema": "http://schema.org/",
13
+ "pim": "http://www.w3.org/ns/pim/space#",
14
+ "ldp": "http://www.w3.org/ns/ldp#",
15
+ "inbox": {
16
+ "@id": "ldp:inbox",
17
+ "@type": "@id"
18
+ },
19
+ "storage": {
20
+ "@id": "pim:storage",
21
+ "@type": "@id"
22
+ },
23
+ "oidcIssuer": {
24
+ "@id": "solid:oidcIssuer",
25
+ "@type": "@id"
26
+ },
27
+ "preferencesFile": {
28
+ "@id": "pim:preferencesFile",
29
+ "@type": "@id"
30
+ }
31
+ },
32
+ "@graph": [
33
+ {
34
+ "@id": "http://localhost:3457/alice/",
35
+ "@type": "foaf:PersonalProfileDocument",
36
+ "foaf:maker": {
37
+ "@id": "http://localhost:3457/alice/#me"
38
+ },
39
+ "foaf:primaryTopic": {
40
+ "@id": "http://localhost:3457/alice/#me"
41
+ }
42
+ },
43
+ {
44
+ "@id": "http://localhost:3457/alice/#me",
45
+ "@type": [
46
+ "foaf:Person",
47
+ "schema:Person"
48
+ ],
49
+ "foaf:name": "alice",
50
+ "inbox": "http://localhost:3457/alice/inbox/",
51
+ "storage": "http://localhost:3457/alice/",
52
+ "oidcIssuer": "http://localhost:3457",
53
+ "preferencesFile": "http://localhost:3457/alice/settings/prefs"
54
+ }
55
+ ]
56
+ }
57
+ </script>
58
+ <style>
59
+ body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
60
+ h1 { color: #333; }
61
+ .card { background: #f5f5f5; padding: 1.5rem; border-radius: 8px; }
62
+ dt { font-weight: bold; margin-top: 1rem; }
63
+ dd { margin-left: 0; color: #666; }
64
+ a { color: #7c4dff; }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="card">
69
+ <h1>alice</h1>
70
+ <dl>
71
+ <dt>WebID</dt>
72
+ <dd><a href="http://localhost:3457/alice/#me">http://localhost:3457/alice/#me</a></dd>
73
+ <dt>Storage</dt>
74
+ <dd><a href="http://localhost:3457/alice/">http://localhost:3457/alice/</a></dd>
75
+ <dt>Inbox</dt>
76
+ <dd><a href="http://localhost:3457/alice/inbox/">http://localhost:3457/alice/inbox/</a></dd>
77
+ </dl>
78
+ </div>
79
+ </body>
80
+ </html>
@@ -0,0 +1,32 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/alice/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/alice/private/"
15
+ },
16
+ "acl:mode": [
17
+ {
18
+ "@id": "acl:Read"
19
+ },
20
+ {
21
+ "@id": "acl:Write"
22
+ },
23
+ {
24
+ "@id": "acl:Control"
25
+ }
26
+ ],
27
+ "acl:default": {
28
+ "@id": "http://localhost:3457/alice/private/"
29
+ }
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1 @@
1
+ {"@id":"#test","http://example.org/value":42}
@@ -0,0 +1,32 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/alice/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/alice/settings/"
15
+ },
16
+ "acl:mode": [
17
+ {
18
+ "@id": "acl:Read"
19
+ },
20
+ {
21
+ "@id": "acl:Write"
22
+ },
23
+ {
24
+ "@id": "acl:Control"
25
+ }
26
+ ],
27
+ "acl:default": {
28
+ "@id": "http://localhost:3457/alice/settings/"
29
+ }
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "@context": {
3
+ "solid": "http://www.w3.org/ns/solid/terms#",
4
+ "pim": "http://www.w3.org/ns/pim/space#",
5
+ "publicTypeIndex": {
6
+ "@id": "solid:publicTypeIndex",
7
+ "@type": "@id"
8
+ },
9
+ "privateTypeIndex": {
10
+ "@id": "solid:privateTypeIndex",
11
+ "@type": "@id"
12
+ }
13
+ },
14
+ "@id": "http://localhost:3457/alice/settings/prefs",
15
+ "publicTypeIndex": "http://localhost:3457/alice/settings/publicTypeIndex",
16
+ "privateTypeIndex": "http://localhost:3457/alice/settings/privateTypeIndex"
17
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "@context": {
3
+ "solid": "http://www.w3.org/ns/solid/terms#"
4
+ },
5
+ "@id": "http://localhost:3457/alice/settings/privateTypeIndex",
6
+ "@type": "solid:TypeIndex"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "@context": {
3
+ "solid": "http://www.w3.org/ns/solid/terms#"
4
+ },
5
+ "@id": "http://localhost:3457/alice/settings/publicTypeIndex",
6
+ "@type": "solid:TypeIndex"
7
+ }
package/data/bob/.acl ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/bob/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/bob/"
15
+ },
16
+ "acl:mode": [
17
+ {
18
+ "@id": "acl:Read"
19
+ },
20
+ {
21
+ "@id": "acl:Write"
22
+ },
23
+ {
24
+ "@id": "acl:Control"
25
+ }
26
+ ],
27
+ "acl:default": {
28
+ "@id": "http://localhost:3457/bob/"
29
+ }
30
+ },
31
+ {
32
+ "@id": "#public",
33
+ "@type": "acl:Authorization",
34
+ "acl:agentClass": {
35
+ "@id": "foaf:Agent"
36
+ },
37
+ "acl:accessTo": {
38
+ "@id": "http://localhost:3457/bob/"
39
+ },
40
+ "acl:mode": [
41
+ {
42
+ "@id": "acl:Read"
43
+ }
44
+ ],
45
+ "acl:default": {
46
+ "@id": "http://localhost:3457/bob/"
47
+ }
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "@context": {
3
+ "acl": "http://www.w3.org/ns/auth/acl#",
4
+ "foaf": "http://xmlns.com/foaf/0.1/"
5
+ },
6
+ "@graph": [
7
+ {
8
+ "@id": "#owner",
9
+ "@type": "acl:Authorization",
10
+ "acl:agent": {
11
+ "@id": "http://localhost:3457/bob/#me"
12
+ },
13
+ "acl:accessTo": {
14
+ "@id": "http://localhost:3457/bob/inbox/"
15
+ },
16
+ "acl:default": {
17
+ "@id": "http://localhost:3457/bob/inbox/"
18
+ },
19
+ "acl:mode": [
20
+ {
21
+ "@id": "acl:Read"
22
+ },
23
+ {
24
+ "@id": "acl:Write"
25
+ },
26
+ {
27
+ "@id": "acl:Control"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "@id": "#public",
33
+ "@type": "acl:Authorization",
34
+ "acl:agentClass": {
35
+ "@id": "foaf:Agent"
36
+ },
37
+ "acl:accessTo": {
38
+ "@id": "http://localhost:3457/bob/inbox/"
39
+ },
40
+ "acl:default": {
41
+ "@id": "http://localhost:3457/bob/inbox/"
42
+ },
43
+ "acl:mode": [
44
+ {
45
+ "@id": "acl:Append"
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,80 @@
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>bob's Profile</title>
7
+ <script type="application/ld+json">
8
+ {
9
+ "@context": {
10
+ "foaf": "http://xmlns.com/foaf/0.1/",
11
+ "solid": "http://www.w3.org/ns/solid/terms#",
12
+ "schema": "http://schema.org/",
13
+ "pim": "http://www.w3.org/ns/pim/space#",
14
+ "ldp": "http://www.w3.org/ns/ldp#",
15
+ "inbox": {
16
+ "@id": "ldp:inbox",
17
+ "@type": "@id"
18
+ },
19
+ "storage": {
20
+ "@id": "pim:storage",
21
+ "@type": "@id"
22
+ },
23
+ "oidcIssuer": {
24
+ "@id": "solid:oidcIssuer",
25
+ "@type": "@id"
26
+ },
27
+ "preferencesFile": {
28
+ "@id": "pim:preferencesFile",
29
+ "@type": "@id"
30
+ }
31
+ },
32
+ "@graph": [
33
+ {
34
+ "@id": "http://localhost:3457/bob/",
35
+ "@type": "foaf:PersonalProfileDocument",
36
+ "foaf:maker": {
37
+ "@id": "http://localhost:3457/bob/#me"
38
+ },
39
+ "foaf:primaryTopic": {
40
+ "@id": "http://localhost:3457/bob/#me"
41
+ }
42
+ },
43
+ {
44
+ "@id": "http://localhost:3457/bob/#me",
45
+ "@type": [
46
+ "foaf:Person",
47
+ "schema:Person"
48
+ ],
49
+ "foaf:name": "bob",
50
+ "inbox": "http://localhost:3457/bob/inbox/",
51
+ "storage": "http://localhost:3457/bob/",
52
+ "oidcIssuer": "http://localhost:3457",
53
+ "preferencesFile": "http://localhost:3457/bob/settings/prefs"
54
+ }
55
+ ]
56
+ }
57
+ </script>
58
+ <style>
59
+ body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
60
+ h1 { color: #333; }
61
+ .card { background: #f5f5f5; padding: 1.5rem; border-radius: 8px; }
62
+ dt { font-weight: bold; margin-top: 1rem; }
63
+ dd { margin-left: 0; color: #666; }
64
+ a { color: #7c4dff; }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="card">
69
+ <h1>bob</h1>
70
+ <dl>
71
+ <dt>WebID</dt>
72
+ <dd><a href="http://localhost:3457/bob/#me">http://localhost:3457/bob/#me</a></dd>
73
+ <dt>Storage</dt>
74
+ <dd><a href="http://localhost:3457/bob/">http://localhost:3457/bob/</a></dd>
75
+ <dt>Inbox</dt>
76
+ <dd><a href="http://localhost:3457/bob/inbox/">http://localhost:3457/bob/inbox/</a></dd>
77
+ </dl>
78
+ </div>
79
+ </body>
80
+ </html>