javascript-solid-server 0.0.45 → 0.0.47
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/.claude/settings.local.json +7 -1
- package/README.md +2 -0
- package/package.json +2 -2
- package/src/handlers/git.js +21 -9
- package/test/interop/README.md +43 -0
- package/test/interop/css-interop.js +102 -0
- package/test/interop/nss-discovery.js +82 -0
- package/test/interop/nss-local-fixed.js +50 -0
- package/test/interop/nss-local.js +53 -0
- package/test/interop/rdflib-discovery.js +44 -0
- package/test/interop/solidcommunity-interop.js +126 -0
- package/test/interop/webid-discovery.js +58 -0
|
@@ -139,7 +139,13 @@
|
|
|
139
139
|
"Bash(gh gist view:*)",
|
|
140
140
|
"Bash(./bin/git-credential-nostr acl:*)",
|
|
141
141
|
"Bash(DATA_ROOT=/tmp/jss-git-test/data node:*)",
|
|
142
|
-
"Bash(git remote set-url:*)"
|
|
142
|
+
"Bash(git remote set-url:*)",
|
|
143
|
+
"Bash(for:*)",
|
|
144
|
+
"Bash(^/**\" | head -1 | sed ''''s/.*\\* //'''')\")",
|
|
145
|
+
"Bash(if [ ! -d \"node-solid-server\" ])",
|
|
146
|
+
"Bash(then git clone --depth 1 https://github.com/nodeSolidServer/node-solid-server.git)",
|
|
147
|
+
"Bash(node test-local-nss2.js:*)",
|
|
148
|
+
"Bash(npm test)"
|
|
143
149
|
]
|
|
144
150
|
}
|
|
145
151
|
}
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "javascript-solid-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
4
4
|
"description": "A minimal, fast Solid server",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"start": "node bin/jss.js start",
|
|
20
20
|
"dev": "node --watch bin/jss.js start",
|
|
21
|
-
"test": "node --test --test-concurrency=1",
|
|
21
|
+
"test": "node --test --test-concurrency=1 'test/*.test.js'",
|
|
22
22
|
"test:cth": "node scripts/test-cth-compat.js",
|
|
23
23
|
"benchmark": "node benchmark.js"
|
|
24
24
|
},
|
package/src/handlers/git.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { existsSync, statSync } from 'fs';
|
|
3
|
-
import { join, resolve } from 'path';
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { existsSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join, resolve, dirname } from 'path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Check if a URL path is a Git protocol request
|
|
@@ -93,12 +93,24 @@ export async function handleGit(request, reply) {
|
|
|
93
93
|
return reply.code(404).send({ error: 'Not a git repository' });
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
// Auto-configure
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
// Auto-configure repos to accept pushes (check full URL for query string)
|
|
97
|
+
if (isGitWriteOperation(request.url)) {
|
|
98
|
+
try {
|
|
99
|
+
// Enable receive-pack for HTTP push
|
|
100
|
+
execSync('git config http.receivepack true', {
|
|
101
|
+
cwd: repoAbs,
|
|
102
|
+
env: { ...process.env, GIT_DIR: gitInfo.gitDir }
|
|
103
|
+
});
|
|
104
|
+
// For non-bare repos, auto-update working directory after push
|
|
105
|
+
if (gitInfo.isRegular) {
|
|
106
|
+
execSync('git config receive.denyCurrentBranch updateInstead', {
|
|
107
|
+
cwd: repoAbs,
|
|
108
|
+
env: { ...process.env, GIT_DIR: gitInfo.gitDir }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// Ignore config errors - repo may still work
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
// Build CGI environment
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Interop Tests
|
|
2
|
+
|
|
3
|
+
Manual tests for debugging cross-server authentication issues. These tests depend on external services and are not run as part of `npm test`.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd test/interop
|
|
9
|
+
node <test-file>.js
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Tests
|
|
13
|
+
|
|
14
|
+
| File | Description |
|
|
15
|
+
|------|-------------|
|
|
16
|
+
| `css-interop.js` | Test OIDC config against CSS and NSS servers |
|
|
17
|
+
| `solidcommunity-interop.js` | Test DPoP auth against solidcommunity.net (CSS) |
|
|
18
|
+
| `nss-local.js` | Test DPoP auth against local NSS instance |
|
|
19
|
+
| `nss-local-fixed.js` | Test with Accept header fix applied |
|
|
20
|
+
| `nss-discovery.js` | Debug NSS OIDC discovery process |
|
|
21
|
+
| `webid-discovery.js` | Test WebID profile discovery and content negotiation |
|
|
22
|
+
| `rdflib-discovery.js` | Test rdflib parsing of WebID profiles |
|
|
23
|
+
|
|
24
|
+
## Known Issues
|
|
25
|
+
|
|
26
|
+
- **NSS WebID Discovery Bug**: NSS's `oidc-auth-manager` doesn't send Accept headers when fetching WebID profiles, causing discovery to fail when servers return HTML instead of Turtle. See: https://github.com/nodeSolidServer/oidc-auth-manager/issues/79
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- `node-fetch`
|
|
31
|
+
- `jose`
|
|
32
|
+
- `rdflib` (for rdflib tests)
|
|
33
|
+
- Valid credentials for melvincarvalho.com IdP
|
|
34
|
+
|
|
35
|
+
## Note on WebID Formats
|
|
36
|
+
|
|
37
|
+
Two popular WebID locations exist:
|
|
38
|
+
- `https://example.com/#me` (root + fragment)
|
|
39
|
+
- `https://example.com/profile/card#me` (profile path)
|
|
40
|
+
|
|
41
|
+
JSS is currently configured for `/profile/card` but will support `/` in the future.
|
|
42
|
+
|
|
43
|
+
**Trade-offs**: When WebID is at root (`/#me`), the root container must be public for discovery. Care must be taken with child ACLs to ensure private resources remain protected.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test against Community Solid Server (CSS) - typically more permissive
|
|
3
|
+
* Also check solidweb.org configuration
|
|
4
|
+
*/
|
|
5
|
+
const fetch = require('node-fetch');
|
|
6
|
+
const jose = require('jose');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
const ISSUER = 'https://melvincarvalho.com/';
|
|
10
|
+
|
|
11
|
+
async function test() {
|
|
12
|
+
console.log('=== Checking OIDC Configuration Details ===\n');
|
|
13
|
+
|
|
14
|
+
// Check solidweb.org OIDC config in detail
|
|
15
|
+
console.log('1. solidweb.org OIDC configuration:');
|
|
16
|
+
const swConfig = await fetch('https://solidweb.org/.well-known/openid-configuration');
|
|
17
|
+
const swData = await swConfig.json();
|
|
18
|
+
console.log(' issuer:', swData.issuer);
|
|
19
|
+
console.log(' subject_types_supported:', swData.subject_types_supported);
|
|
20
|
+
console.log(' id_token_signing_alg_values_supported:', swData.id_token_signing_alg_values_supported);
|
|
21
|
+
console.log(' token_endpoint_auth_methods_supported:', swData.token_endpoint_auth_methods_supported);
|
|
22
|
+
console.log(' claims_supported:', swData.claims_supported);
|
|
23
|
+
|
|
24
|
+
// Check melvincarvalho.com for comparison
|
|
25
|
+
console.log('\n2. melvincarvalho.com OIDC configuration:');
|
|
26
|
+
const mcConfig = await fetch('https://melvincarvalho.com/.well-known/openid-configuration');
|
|
27
|
+
const mcData = await mcConfig.json();
|
|
28
|
+
console.log(' issuer:', mcData.issuer);
|
|
29
|
+
console.log(' id_token_signing_alg_values_supported:', mcData.id_token_signing_alg_values_supported);
|
|
30
|
+
|
|
31
|
+
// Find other known Solid servers to test
|
|
32
|
+
console.log('\n3. Testing other Solid servers...');
|
|
33
|
+
|
|
34
|
+
const otherServers = [
|
|
35
|
+
'https://solidcommunity.net/.well-known/openid-configuration',
|
|
36
|
+
'https://inrupt.net/.well-known/openid-configuration',
|
|
37
|
+
'https://login.inrupt.com/.well-known/openid-configuration',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const url of otherServers) {
|
|
41
|
+
try {
|
|
42
|
+
const resp = await fetch(url, { timeout: 5000 });
|
|
43
|
+
if (resp.ok) {
|
|
44
|
+
const data = await resp.json();
|
|
45
|
+
console.log(' ' + url.split('/')[2] + ':');
|
|
46
|
+
console.log(' issuer:', data.issuer);
|
|
47
|
+
console.log(' token_types:', data.token_types_supported);
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.log(' ' + url.split('/')[2] + ': ' + err.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Now test our token against solidcommunity.net (another NSS)
|
|
55
|
+
console.log('\n4. Testing DPoP token against solidcommunity.net...');
|
|
56
|
+
|
|
57
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
|
|
58
|
+
const publicJwk = await jose.exportJWK(publicKey);
|
|
59
|
+
|
|
60
|
+
const credDpopProof = await new jose.SignJWT({
|
|
61
|
+
htm: 'POST',
|
|
62
|
+
htu: ISSUER + 'idp/credentials',
|
|
63
|
+
iat: Math.floor(Date.now() / 1000),
|
|
64
|
+
jti: crypto.randomUUID(),
|
|
65
|
+
})
|
|
66
|
+
.setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
|
|
67
|
+
.sign(privateKey);
|
|
68
|
+
|
|
69
|
+
const tokenResp = await fetch(ISSUER + 'idp/credentials', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json', 'DPoP': credDpopProof },
|
|
72
|
+
body: JSON.stringify({ email: 'melvin', password: 'melvintest123' }),
|
|
73
|
+
});
|
|
74
|
+
const { access_token } = await tokenResp.json();
|
|
75
|
+
|
|
76
|
+
// Test on solidcommunity.net
|
|
77
|
+
const testUrl = 'https://solidcommunity.net/';
|
|
78
|
+
const dpopProof = await new jose.SignJWT({
|
|
79
|
+
htm: 'GET',
|
|
80
|
+
htu: testUrl,
|
|
81
|
+
iat: Math.floor(Date.now() / 1000),
|
|
82
|
+
jti: crypto.randomUUID(),
|
|
83
|
+
})
|
|
84
|
+
.setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
|
|
85
|
+
.sign(privateKey);
|
|
86
|
+
|
|
87
|
+
const testResp = await fetch(testUrl, {
|
|
88
|
+
headers: {
|
|
89
|
+
'Authorization': 'DPoP ' + access_token,
|
|
90
|
+
'DPoP': dpopProof,
|
|
91
|
+
'Accept': 'text/turtle',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log(' Status:', testResp.status);
|
|
96
|
+
const wwwAuth = testResp.headers.get('www-authenticate');
|
|
97
|
+
if (wwwAuth) {
|
|
98
|
+
console.log(' WWW-Auth:', wwwAuth);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulate what NSS does to verify our token
|
|
3
|
+
*/
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const jose = require('jose');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
const ISSUER = 'https://melvincarvalho.com/';
|
|
9
|
+
|
|
10
|
+
async function test() {
|
|
11
|
+
console.log('=== Simulating NSS Token Verification ===\n');
|
|
12
|
+
|
|
13
|
+
// Step 1: Get a token
|
|
14
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
|
|
15
|
+
const publicJwk = await jose.exportJWK(publicKey);
|
|
16
|
+
|
|
17
|
+
const credProof = await new jose.SignJWT({
|
|
18
|
+
htm: 'POST', htu: ISSUER + 'idp/credentials',
|
|
19
|
+
iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(),
|
|
20
|
+
}).setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk }).sign(privateKey);
|
|
21
|
+
|
|
22
|
+
const tokenResp = await fetch(ISSUER + 'idp/credentials', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json', 'DPoP': credProof },
|
|
25
|
+
body: JSON.stringify({ email: 'melvin', password: 'melvintest123' }),
|
|
26
|
+
});
|
|
27
|
+
const { access_token } = await tokenResp.json();
|
|
28
|
+
|
|
29
|
+
// Decode token
|
|
30
|
+
const parts = access_token.split('.');
|
|
31
|
+
const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString());
|
|
32
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
33
|
+
|
|
34
|
+
console.log('Token issuer:', payload.iss);
|
|
35
|
+
console.log('Token webid:', payload.webid);
|
|
36
|
+
console.log('Token kid:', header.kid);
|
|
37
|
+
|
|
38
|
+
// Step 2: NSS would fetch WebID to discover issuer
|
|
39
|
+
console.log('\n1. Fetching WebID profile...');
|
|
40
|
+
const webidUrl = payload.webid.split('#')[0];
|
|
41
|
+
const webidResp = await fetch(webidUrl, { headers: { Accept: 'text/turtle' } });
|
|
42
|
+
console.log(' Status:', webidResp.status);
|
|
43
|
+
const webidBody = await webidResp.text();
|
|
44
|
+
const issuerMatch = webidBody.match(/oidcIssuer[^<]*<([^>]+)>/);
|
|
45
|
+
console.log(' Found oidcIssuer:', issuerMatch ? issuerMatch[1] : 'NOT FOUND');
|
|
46
|
+
|
|
47
|
+
// Step 3: NSS would fetch OIDC config
|
|
48
|
+
console.log('\n2. Fetching OIDC config...');
|
|
49
|
+
const configResp = await fetch(payload.iss + '.well-known/openid-configuration');
|
|
50
|
+
console.log(' Status:', configResp.status);
|
|
51
|
+
const config = await configResp.json();
|
|
52
|
+
console.log(' jwks_uri:', config.jwks_uri);
|
|
53
|
+
|
|
54
|
+
// Step 4: NSS would fetch JWKS
|
|
55
|
+
console.log('\n3. Fetching JWKS...');
|
|
56
|
+
const jwksResp = await fetch(config.jwks_uri);
|
|
57
|
+
console.log(' Status:', jwksResp.status);
|
|
58
|
+
const jwks = await jwksResp.json();
|
|
59
|
+
console.log(' Keys:', jwks.keys.length);
|
|
60
|
+
|
|
61
|
+
// Step 5: Find matching key
|
|
62
|
+
console.log('\n4. Finding key for kid:', header.kid);
|
|
63
|
+
const key = jwks.keys.find(k => k.kid === header.kid);
|
|
64
|
+
if (key) {
|
|
65
|
+
console.log(' Found key, alg:', key.alg);
|
|
66
|
+
|
|
67
|
+
// Try to verify
|
|
68
|
+
console.log('\n5. Verifying token signature...');
|
|
69
|
+
try {
|
|
70
|
+
const jwksSet = jose.createRemoteJWKSet(new URL(config.jwks_uri));
|
|
71
|
+
const { payload: verified } = await jose.jwtVerify(access_token, jwksSet);
|
|
72
|
+
console.log(' ✓ Signature valid!');
|
|
73
|
+
console.log(' Verified webid:', verified.webid);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.log(' ✗ Verification failed:', err.message);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.log(' ✗ Key not found!');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fetch = require('node-fetch');
|
|
2
|
+
const jose = require('jose');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
const ISSUER = 'https://melvincarvalho.com/';
|
|
7
|
+
const NSS_URL = 'https://localhost:8444/';
|
|
8
|
+
|
|
9
|
+
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
10
|
+
|
|
11
|
+
async function test() {
|
|
12
|
+
console.log('=== Testing DPoP against local NSS (port 8444) ===\n');
|
|
13
|
+
|
|
14
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
|
|
15
|
+
const publicJwk = await jose.exportJWK(publicKey);
|
|
16
|
+
|
|
17
|
+
const credProof = await new jose.SignJWT({
|
|
18
|
+
htm: 'POST', htu: ISSUER + 'idp/credentials',
|
|
19
|
+
iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(),
|
|
20
|
+
}).setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk }).sign(privateKey);
|
|
21
|
+
|
|
22
|
+
const tokenResp = await fetch(ISSUER + 'idp/credentials', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json', 'DPoP': credProof },
|
|
25
|
+
body: JSON.stringify({ email: 'melvin', password: 'melvintest123' }),
|
|
26
|
+
});
|
|
27
|
+
const { access_token } = await tokenResp.json();
|
|
28
|
+
console.log('Got token from melvincarvalho.com');
|
|
29
|
+
|
|
30
|
+
const dpopProof = await new jose.SignJWT({
|
|
31
|
+
htm: 'GET', htu: NSS_URL,
|
|
32
|
+
iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(),
|
|
33
|
+
}).setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk }).sign(privateKey);
|
|
34
|
+
|
|
35
|
+
console.log('Testing against local NSS:', NSS_URL);
|
|
36
|
+
const resp = await fetch(NSS_URL, {
|
|
37
|
+
agent,
|
|
38
|
+
headers: {
|
|
39
|
+
'Authorization': 'DPoP ' + access_token,
|
|
40
|
+
'DPoP': dpopProof,
|
|
41
|
+
'Accept': 'text/turtle',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log('Status:', resp.status);
|
|
46
|
+
const wwwAuth = resp.headers.get('www-authenticate');
|
|
47
|
+
if (wwwAuth) console.log('WWW-Authenticate:', wwwAuth);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const fetch = require('node-fetch');
|
|
2
|
+
const jose = require('jose');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
const ISSUER = 'https://melvincarvalho.com/';
|
|
7
|
+
const NSS_URL = 'https://localhost:8443/';
|
|
8
|
+
|
|
9
|
+
// Allow self-signed cert
|
|
10
|
+
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
11
|
+
|
|
12
|
+
async function test() {
|
|
13
|
+
console.log('=== Testing DPoP against local NSS ===\n');
|
|
14
|
+
|
|
15
|
+
// Get DPoP token from our IdP
|
|
16
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
|
|
17
|
+
const publicJwk = await jose.exportJWK(publicKey);
|
|
18
|
+
|
|
19
|
+
const credProof = await new jose.SignJWT({
|
|
20
|
+
htm: 'POST', htu: ISSUER + 'idp/credentials',
|
|
21
|
+
iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(),
|
|
22
|
+
}).setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk }).sign(privateKey);
|
|
23
|
+
|
|
24
|
+
const tokenResp = await fetch(ISSUER + 'idp/credentials', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json', 'DPoP': credProof },
|
|
27
|
+
body: JSON.stringify({ email: 'melvin', password: 'melvintest123' }),
|
|
28
|
+
});
|
|
29
|
+
const { access_token } = await tokenResp.json();
|
|
30
|
+
console.log('Got token from melvincarvalho.com');
|
|
31
|
+
|
|
32
|
+
// Create DPoP proof for local NSS
|
|
33
|
+
const dpopProof = await new jose.SignJWT({
|
|
34
|
+
htm: 'GET', htu: NSS_URL,
|
|
35
|
+
iat: Math.floor(Date.now() / 1000), jti: crypto.randomUUID(),
|
|
36
|
+
}).setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk }).sign(privateKey);
|
|
37
|
+
|
|
38
|
+
console.log('Testing against local NSS:', NSS_URL);
|
|
39
|
+
const resp = await fetch(NSS_URL, {
|
|
40
|
+
agent,
|
|
41
|
+
headers: {
|
|
42
|
+
'Authorization': 'DPoP ' + access_token,
|
|
43
|
+
'DPoP': dpopProof,
|
|
44
|
+
'Accept': 'text/turtle',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log('Status:', resp.status);
|
|
49
|
+
const wwwAuth = resp.headers.get('www-authenticate');
|
|
50
|
+
if (wwwAuth) console.log('WWW-Authenticate:', wwwAuth);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const rdf = require('rdflib');
|
|
2
|
+
|
|
3
|
+
const webId = 'https://melvincarvalho.com/#me';
|
|
4
|
+
|
|
5
|
+
const store = rdf.graph();
|
|
6
|
+
const fetcher = rdf.fetcher(store);
|
|
7
|
+
|
|
8
|
+
console.log('Testing rdflib fetch of WebID:', webId);
|
|
9
|
+
|
|
10
|
+
fetcher.load(webId, { force: true })
|
|
11
|
+
.then(response => {
|
|
12
|
+
console.log('\n=== Response ===');
|
|
13
|
+
console.log('Status:', response.status);
|
|
14
|
+
|
|
15
|
+
const providerTerm = rdf.namedNode('http://www.w3.org/ns/solid/terms#oidcIssuer');
|
|
16
|
+
const webIdNode = rdf.namedNode(webId);
|
|
17
|
+
|
|
18
|
+
console.log('\n=== Query ===');
|
|
19
|
+
console.log('Subject:', webIdNode.value);
|
|
20
|
+
console.log('Predicate:', providerTerm.value);
|
|
21
|
+
|
|
22
|
+
const providerUri = store.anyValue(webIdNode, providerTerm);
|
|
23
|
+
console.log('\n=== Result ===');
|
|
24
|
+
console.log('Provider URI:', providerUri);
|
|
25
|
+
console.log('Provider URI type:', typeof providerUri);
|
|
26
|
+
|
|
27
|
+
// Also check what's in the store
|
|
28
|
+
console.log('\n=== All oidcIssuer statements ===');
|
|
29
|
+
const statements = store.match(null, providerTerm, null);
|
|
30
|
+
statements.forEach(st => {
|
|
31
|
+
console.log(' Subject:', st.subject.value);
|
|
32
|
+
console.log(' Object:', st.object.value);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Compare with token issuer
|
|
36
|
+
const tokenIssuer = 'https://melvincarvalho.com/';
|
|
37
|
+
console.log('\n=== Comparison ===');
|
|
38
|
+
console.log('Token issuer:', tokenIssuer);
|
|
39
|
+
console.log('Provider from profile:', providerUri);
|
|
40
|
+
console.log('Match:', providerUri === tokenIssuer);
|
|
41
|
+
})
|
|
42
|
+
.catch(err => {
|
|
43
|
+
console.error('Error:', err.message);
|
|
44
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test on solidcommunity.net to confirm DPoP auth works
|
|
3
|
+
*/
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const jose = require('jose');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
const ISSUER = 'https://melvincarvalho.com/';
|
|
9
|
+
|
|
10
|
+
async function test() {
|
|
11
|
+
console.log('=== Confirming DPoP works on solidcommunity.net ===\n');
|
|
12
|
+
|
|
13
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('ES256');
|
|
14
|
+
const publicJwk = await jose.exportJWK(publicKey);
|
|
15
|
+
|
|
16
|
+
const credDpopProof = await new jose.SignJWT({
|
|
17
|
+
htm: 'POST',
|
|
18
|
+
htu: ISSUER + 'idp/credentials',
|
|
19
|
+
iat: Math.floor(Date.now() / 1000),
|
|
20
|
+
jti: crypto.randomUUID(),
|
|
21
|
+
})
|
|
22
|
+
.setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
|
|
23
|
+
.sign(privateKey);
|
|
24
|
+
|
|
25
|
+
const tokenResp = await fetch(ISSUER + 'idp/credentials', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json', 'DPoP': credDpopProof },
|
|
28
|
+
body: JSON.stringify({ email: 'melvin', password: 'melvintest123' }),
|
|
29
|
+
});
|
|
30
|
+
const tokenData = await tokenResp.json();
|
|
31
|
+
const access_token = tokenData.access_token;
|
|
32
|
+
console.log('Got token, webid:', tokenData.webid);
|
|
33
|
+
|
|
34
|
+
// Parse token
|
|
35
|
+
const payload = JSON.parse(Buffer.from(access_token.split('.')[1], 'base64url').toString());
|
|
36
|
+
console.log('Token issuer:', payload.iss);
|
|
37
|
+
console.log('Token webid:', payload.webid);
|
|
38
|
+
console.log('Token cnf.jkt:', payload.cnf.jkt.substring(0, 20) + '...');
|
|
39
|
+
|
|
40
|
+
// Test different endpoints on solidcommunity.net
|
|
41
|
+
const tests = [
|
|
42
|
+
{ url: 'https://solidcommunity.net/', name: 'root (public)' },
|
|
43
|
+
{ url: 'https://melvin.solidcommunity.net/', name: 'user pod root' },
|
|
44
|
+
{ url: 'https://melvin.solidcommunity.net/profile/card', name: 'user profile' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
for (const t of tests) {
|
|
48
|
+
console.log('\n--- ' + t.name + ' ---');
|
|
49
|
+
console.log('URL:', t.url);
|
|
50
|
+
|
|
51
|
+
// Without auth
|
|
52
|
+
const noAuthResp = await fetch(t.url, {
|
|
53
|
+
headers: { 'Accept': 'text/turtle' },
|
|
54
|
+
});
|
|
55
|
+
console.log('Without auth:', noAuthResp.status);
|
|
56
|
+
|
|
57
|
+
// With DPoP
|
|
58
|
+
const dpopProof = await new jose.SignJWT({
|
|
59
|
+
htm: 'GET',
|
|
60
|
+
htu: t.url,
|
|
61
|
+
iat: Math.floor(Date.now() / 1000),
|
|
62
|
+
jti: crypto.randomUUID(),
|
|
63
|
+
})
|
|
64
|
+
.setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
|
|
65
|
+
.sign(privateKey);
|
|
66
|
+
|
|
67
|
+
const authResp = await fetch(t.url, {
|
|
68
|
+
headers: {
|
|
69
|
+
'Authorization': 'DPoP ' + access_token,
|
|
70
|
+
'DPoP': dpopProof,
|
|
71
|
+
'Accept': 'text/turtle',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
console.log('With DPoP:', authResp.status);
|
|
75
|
+
|
|
76
|
+
const wwwAuth = authResp.headers.get('www-authenticate');
|
|
77
|
+
if (wwwAuth) {
|
|
78
|
+
console.log('WWW-Auth:', wwwAuth);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Now compare the same tests on solidweb.org
|
|
83
|
+
console.log('\n\n=== Comparison: Same tests on solidweb.org ===\n');
|
|
84
|
+
|
|
85
|
+
const swTests = [
|
|
86
|
+
{ url: 'https://solidweb.org/', name: 'root (public)' },
|
|
87
|
+
{ url: 'https://solid-chat.solidweb.org/', name: 'solid-chat pod root' },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const t of swTests) {
|
|
91
|
+
console.log('\n--- ' + t.name + ' ---');
|
|
92
|
+
console.log('URL:', t.url);
|
|
93
|
+
|
|
94
|
+
// Without auth
|
|
95
|
+
const noAuthResp = await fetch(t.url, {
|
|
96
|
+
headers: { 'Accept': 'text/turtle' },
|
|
97
|
+
});
|
|
98
|
+
console.log('Without auth:', noAuthResp.status);
|
|
99
|
+
|
|
100
|
+
// With DPoP
|
|
101
|
+
const dpopProof = await new jose.SignJWT({
|
|
102
|
+
htm: 'GET',
|
|
103
|
+
htu: t.url,
|
|
104
|
+
iat: Math.floor(Date.now() / 1000),
|
|
105
|
+
jti: crypto.randomUUID(),
|
|
106
|
+
})
|
|
107
|
+
.setProtectedHeader({ alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk })
|
|
108
|
+
.sign(privateKey);
|
|
109
|
+
|
|
110
|
+
const authResp = await fetch(t.url, {
|
|
111
|
+
headers: {
|
|
112
|
+
'Authorization': 'DPoP ' + access_token,
|
|
113
|
+
'DPoP': dpopProof,
|
|
114
|
+
'Accept': 'text/turtle',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
console.log('With DPoP:', authResp.status);
|
|
118
|
+
|
|
119
|
+
const wwwAuth = authResp.headers.get('www-authenticate');
|
|
120
|
+
if (wwwAuth) {
|
|
121
|
+
console.log('WWW-Auth:', wwwAuth);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
test().catch(console.error);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simulate what NSS does: fetch WebID profile and discover oidcIssuer
|
|
3
|
+
*/
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
|
|
6
|
+
const WEBID = 'https://melvincarvalho.com/#me';
|
|
7
|
+
|
|
8
|
+
async function test() {
|
|
9
|
+
console.log('=== WebID Profile Discovery Test ===\n');
|
|
10
|
+
|
|
11
|
+
// Fetch WebID profile (what NSS does)
|
|
12
|
+
console.log('1. Fetching WebID profile:', WEBID);
|
|
13
|
+
|
|
14
|
+
const response = await fetch(WEBID.split('#')[0], {
|
|
15
|
+
headers: {
|
|
16
|
+
'Accept': 'text/turtle, application/ld+json, */*',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.log(' Status:', response.status);
|
|
21
|
+
console.log(' Content-Type:', response.headers.get('content-type'));
|
|
22
|
+
|
|
23
|
+
const body = await response.text();
|
|
24
|
+
console.log(' Content length:', body.length);
|
|
25
|
+
|
|
26
|
+
// Check for solid:oidcIssuer
|
|
27
|
+
console.log('\n2. Looking for solid:oidcIssuer...');
|
|
28
|
+
|
|
29
|
+
// Look for the triple
|
|
30
|
+
const oidcIssuerMatch = body.match(/solid:oidcIssuer\s+<([^>]+)>/);
|
|
31
|
+
if (oidcIssuerMatch) {
|
|
32
|
+
console.log(' Found: solid:oidcIssuer <' + oidcIssuerMatch[1] + '>');
|
|
33
|
+
} else {
|
|
34
|
+
console.log(' Pattern 1 not found, trying other patterns...');
|
|
35
|
+
|
|
36
|
+
// Try full URI pattern
|
|
37
|
+
const fullUriMatch = body.match(/http:\/\/www\.w3\.org\/ns\/solid\/terms#oidcIssuer[^<]+<([^>]+)>/);
|
|
38
|
+
if (fullUriMatch) {
|
|
39
|
+
console.log(' Found via full URI: <' + fullUriMatch[1] + '>');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Show relevant lines
|
|
44
|
+
console.log('\n3. Relevant lines from profile:');
|
|
45
|
+
const lines = body.split('\n');
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
if (line.includes('oidcIssuer') || line.includes('solid:') || line.includes('@prefix solid')) {
|
|
48
|
+
console.log(' ' + line.trim());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Also check CORS headers
|
|
53
|
+
console.log('\n4. CORS headers:');
|
|
54
|
+
console.log(' Access-Control-Allow-Origin:', response.headers.get('access-control-allow-origin') || 'NOT SET');
|
|
55
|
+
console.log(' Access-Control-Allow-Credentials:', response.headers.get('access-control-allow-credentials') || 'NOT SET');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test().catch(console.error);
|