javascript-solid-server 0.0.100 → 0.0.101
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/LICENSE +1 -0
- package/README.md +54 -2
- package/bin/jss.js +10 -0
- package/package.json +2 -1
- package/src/config.js +12 -1
- package/src/handlers/pay.js +248 -0
- package/src/mrc20.js +146 -0
- package/src/server.js +23 -0
- package/src/webledger.js +162 -0
- package/test/helpers.js +1 -2
- package/test/idp.test.js +57 -41
- package/test/{live-reload.test.js → live-reload.standalone.js} +1 -0
- package/test/mrc20.test.js +237 -0
- package/test/pay.test.js +231 -0
- package/test/webledger.test.js +174 -0
package/src/webledger.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Ledger — spec-compliant balance tracking
|
|
3
|
+
*
|
|
4
|
+
* Implements the Web Ledgers specification (https://webledgers.org/)
|
|
5
|
+
* for mapping URIs to numerical balances. Default unit: satoshi.
|
|
6
|
+
*
|
|
7
|
+
* JSON-LD context: https://w3id.org/webledgers
|
|
8
|
+
*
|
|
9
|
+
* @module webledger
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as storage from './storage/filesystem.js';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PATH = '/.well-known/webledgers/webledgers.json';
|
|
15
|
+
const CONTEXT = 'https://w3id.org/webledgers';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create an empty spec-compliant ledger
|
|
19
|
+
* @param {object} options
|
|
20
|
+
* @param {string} options.name - Ledger name
|
|
21
|
+
* @param {string} options.description - Ledger description
|
|
22
|
+
* @param {string} options.id - Ledger URI identifier
|
|
23
|
+
* @param {string} options.defaultCurrency - Default currency (default 'satoshi')
|
|
24
|
+
* @returns {object} Empty WebLedger object
|
|
25
|
+
*/
|
|
26
|
+
export function createLedger(options = {}) {
|
|
27
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28
|
+
return {
|
|
29
|
+
'@context': CONTEXT,
|
|
30
|
+
type: 'WebLedger',
|
|
31
|
+
...(options.id && { id: options.id }),
|
|
32
|
+
name: options.name ?? 'Pod Credits',
|
|
33
|
+
description: options.description ?? 'Paid API balance ledger',
|
|
34
|
+
defaultCurrency: options.defaultCurrency ?? 'satoshi',
|
|
35
|
+
created: now,
|
|
36
|
+
updated: now,
|
|
37
|
+
entries: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read a webledger from storage
|
|
43
|
+
* @param {string} ledgerPath - URL path to ledger file
|
|
44
|
+
* @returns {Promise<object>} WebLedger object
|
|
45
|
+
*/
|
|
46
|
+
export async function readLedger(ledgerPath = DEFAULT_PATH) {
|
|
47
|
+
const buf = await storage.read(ledgerPath);
|
|
48
|
+
if (!buf) {
|
|
49
|
+
return createLedger();
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const ledger = JSON.parse(buf.toString('utf8'));
|
|
53
|
+
// Migrate legacy format: add missing spec fields
|
|
54
|
+
if (!ledger['@context']) ledger['@context'] = CONTEXT;
|
|
55
|
+
if (!ledger.type) ledger.type = 'WebLedger';
|
|
56
|
+
if (!ledger.defaultCurrency) ledger.defaultCurrency = 'satoshi';
|
|
57
|
+
if (!ledger.created) ledger.created = Math.floor(Date.now() / 1000);
|
|
58
|
+
if (!ledger.entries) ledger.entries = [];
|
|
59
|
+
return ledger;
|
|
60
|
+
} catch {
|
|
61
|
+
return createLedger();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Write a webledger to storage (updates the `updated` timestamp)
|
|
67
|
+
* @param {object} ledger - WebLedger object
|
|
68
|
+
* @param {string} ledgerPath - URL path to ledger file
|
|
69
|
+
* @returns {Promise<boolean>}
|
|
70
|
+
*/
|
|
71
|
+
export async function writeLedger(ledger, ledgerPath = DEFAULT_PATH) {
|
|
72
|
+
ledger.updated = Math.floor(Date.now() / 1000);
|
|
73
|
+
return storage.write(ledgerPath, JSON.stringify(ledger, null, 2));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get balance for a URI
|
|
78
|
+
* @param {object} ledger - WebLedger object
|
|
79
|
+
* @param {string} uri - Agent URI (e.g. did:nostr:...)
|
|
80
|
+
* @returns {number} Balance as integer
|
|
81
|
+
*/
|
|
82
|
+
export function getBalance(ledger, uri) {
|
|
83
|
+
const entry = ledger.entries.find(e => e.url === uri);
|
|
84
|
+
if (!entry) return 0;
|
|
85
|
+
// Handle both simple string and array amount formats
|
|
86
|
+
if (Array.isArray(entry.amount)) {
|
|
87
|
+
const sat = entry.amount.find(a => a.currency === 'satoshi' || a.currency === 'sat');
|
|
88
|
+
return sat ? parseInt(sat.value, 10) || 0 : 0;
|
|
89
|
+
}
|
|
90
|
+
return parseInt(entry.amount, 10) || 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Set balance for a URI
|
|
95
|
+
* @param {object} ledger - WebLedger object
|
|
96
|
+
* @param {string} uri - Agent URI
|
|
97
|
+
* @param {number} amount - New balance
|
|
98
|
+
*/
|
|
99
|
+
export function setBalance(ledger, uri, amount) {
|
|
100
|
+
const entry = ledger.entries.find(e => e.url === uri);
|
|
101
|
+
if (entry) {
|
|
102
|
+
entry.amount = String(amount);
|
|
103
|
+
} else {
|
|
104
|
+
ledger.entries.push({ type: 'Entry', url: uri, amount: String(amount) });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Credit (add to) a balance
|
|
110
|
+
* @param {object} ledger - WebLedger object
|
|
111
|
+
* @param {string} uri - Agent URI
|
|
112
|
+
* @param {number} amount - Amount to add
|
|
113
|
+
* @returns {number} New balance
|
|
114
|
+
*/
|
|
115
|
+
export function credit(ledger, uri, amount) {
|
|
116
|
+
const current = getBalance(ledger, uri);
|
|
117
|
+
const newBalance = current + amount;
|
|
118
|
+
setBalance(ledger, uri, newBalance);
|
|
119
|
+
return newBalance;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Debit (subtract from) a balance
|
|
124
|
+
* @param {object} ledger - WebLedger object
|
|
125
|
+
* @param {string} uri - Agent URI
|
|
126
|
+
* @param {number} amount - Amount to subtract
|
|
127
|
+
* @returns {{success: boolean, balance: number}} Result
|
|
128
|
+
*/
|
|
129
|
+
export function debit(ledger, uri, amount) {
|
|
130
|
+
const current = getBalance(ledger, uri);
|
|
131
|
+
if (current < amount) {
|
|
132
|
+
return { success: false, balance: current };
|
|
133
|
+
}
|
|
134
|
+
const newBalance = current - amount;
|
|
135
|
+
setBalance(ledger, uri, newBalance);
|
|
136
|
+
return { success: true, balance: newBalance };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* List all entries with non-zero balances
|
|
141
|
+
* @param {object} ledger - WebLedger object
|
|
142
|
+
* @returns {Array<{url: string, amount: number}>}
|
|
143
|
+
*/
|
|
144
|
+
export function listBalances(ledger) {
|
|
145
|
+
return ledger.entries
|
|
146
|
+
.map(e => ({ url: e.url, amount: getBalance(ledger, e.url) }))
|
|
147
|
+
.filter(e => e.amount > 0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Remove entries with zero balance
|
|
152
|
+
* @param {object} ledger - WebLedger object
|
|
153
|
+
*/
|
|
154
|
+
export function compact(ledger) {
|
|
155
|
+
ledger.entries = ledger.entries.filter(e => {
|
|
156
|
+
const bal = getBalance(ledger, e.url);
|
|
157
|
+
return bal > 0;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Re-export the default path for consumers
|
|
162
|
+
export { DEFAULT_PATH as LEDGER_PATH };
|
package/test/helpers.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function startTestServer(options = {}) {
|
|
|
24
24
|
// Clean up any existing test data
|
|
25
25
|
await fs.emptyDir(TEST_DATA_DIR);
|
|
26
26
|
|
|
27
|
-
server = createServer({ logger: false, ...options });
|
|
27
|
+
server = createServer({ logger: false, forceCloseConnections: true, ...options });
|
|
28
28
|
// Use port 0 to let OS assign available port
|
|
29
29
|
await server.listen({ port: 0, host: '127.0.0.1' });
|
|
30
30
|
|
|
@@ -39,7 +39,6 @@ export async function startTestServer(options = {}) {
|
|
|
39
39
|
*/
|
|
40
40
|
export async function stopTestServer() {
|
|
41
41
|
if (server) {
|
|
42
|
-
// Force close all connections to avoid hanging
|
|
43
42
|
await server.close();
|
|
44
43
|
server = null;
|
|
45
44
|
}
|
package/test/idp.test.js
CHANGED
|
@@ -8,28 +8,42 @@ import { createServer } from '../src/server.js';
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
|
|
11
|
-
const TEST_PORT = 3099;
|
|
12
11
|
const TEST_HOST = 'localhost';
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
import { createServer as createNetServer } from 'net';
|
|
13
|
+
|
|
14
|
+
/** Get an available port by briefly binding to port 0 */
|
|
15
|
+
async function getAvailablePort() {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const srv = createNetServer();
|
|
18
|
+
srv.on('error', (err) => reject(err));
|
|
19
|
+
srv.listen(0, TEST_HOST, () => {
|
|
20
|
+
const port = srv.address().port;
|
|
21
|
+
srv.close(() => resolve(port));
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
describe('Identity Provider', () => {
|
|
17
27
|
let server;
|
|
28
|
+
let baseUrl;
|
|
29
|
+
const DATA_DIR = './test-data-idp';
|
|
18
30
|
|
|
19
31
|
before(async () => {
|
|
20
|
-
// Clean up any existing test data
|
|
21
32
|
await fs.remove(DATA_DIR);
|
|
22
33
|
await fs.ensureDir(DATA_DIR);
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
const port = await getAvailablePort();
|
|
36
|
+
baseUrl = `http://${TEST_HOST}:${port}`;
|
|
37
|
+
|
|
25
38
|
server = createServer({
|
|
26
39
|
logger: false,
|
|
27
40
|
root: DATA_DIR,
|
|
28
41
|
idp: true,
|
|
29
|
-
idpIssuer:
|
|
42
|
+
idpIssuer: baseUrl,
|
|
43
|
+
forceCloseConnections: true,
|
|
30
44
|
});
|
|
31
45
|
|
|
32
|
-
await server.listen({ port
|
|
46
|
+
await server.listen({ port, host: TEST_HOST });
|
|
33
47
|
});
|
|
34
48
|
|
|
35
49
|
after(async () => {
|
|
@@ -39,19 +53,19 @@ describe('Identity Provider', () => {
|
|
|
39
53
|
|
|
40
54
|
describe('OIDC Discovery', () => {
|
|
41
55
|
it('should serve /.well-known/openid-configuration', async () => {
|
|
42
|
-
const res = await fetch(`${
|
|
56
|
+
const res = await fetch(`${baseUrl}/.well-known/openid-configuration`);
|
|
43
57
|
assert.strictEqual(res.status, 200);
|
|
44
58
|
|
|
45
59
|
const config = await res.json();
|
|
46
60
|
// Issuer has trailing slash for CTH compatibility
|
|
47
|
-
assert.strictEqual(config.issuer,
|
|
61
|
+
assert.strictEqual(config.issuer, baseUrl + '/');
|
|
48
62
|
assert.ok(config.authorization_endpoint);
|
|
49
63
|
assert.ok(config.token_endpoint);
|
|
50
64
|
assert.ok(config.jwks_uri);
|
|
51
65
|
});
|
|
52
66
|
|
|
53
67
|
it('should include required Solid-OIDC endpoints', async () => {
|
|
54
|
-
const res = await fetch(`${
|
|
68
|
+
const res = await fetch(`${baseUrl}/.well-known/openid-configuration`);
|
|
55
69
|
const config = await res.json();
|
|
56
70
|
|
|
57
71
|
assert.ok(config.registration_endpoint, 'should have registration endpoint');
|
|
@@ -60,7 +74,7 @@ describe('Identity Provider', () => {
|
|
|
60
74
|
});
|
|
61
75
|
|
|
62
76
|
it('should serve /.well-known/jwks.json', async () => {
|
|
63
|
-
const res = await fetch(`${
|
|
77
|
+
const res = await fetch(`${baseUrl}/.well-known/jwks.json`);
|
|
64
78
|
assert.strictEqual(res.status, 200);
|
|
65
79
|
|
|
66
80
|
const jwks = await res.json();
|
|
@@ -73,7 +87,7 @@ describe('Identity Provider', () => {
|
|
|
73
87
|
|
|
74
88
|
describe('Pod Creation with IdP', () => {
|
|
75
89
|
it('should require email when IdP is enabled', async () => {
|
|
76
|
-
const res = await fetch(`${
|
|
90
|
+
const res = await fetch(`${baseUrl}/.pods`, {
|
|
77
91
|
method: 'POST',
|
|
78
92
|
headers: { 'Content-Type': 'application/json' },
|
|
79
93
|
body: JSON.stringify({ name: 'noemail' }),
|
|
@@ -85,7 +99,7 @@ describe('Identity Provider', () => {
|
|
|
85
99
|
});
|
|
86
100
|
|
|
87
101
|
it('should require password when IdP is enabled', async () => {
|
|
88
|
-
const res = await fetch(`${
|
|
102
|
+
const res = await fetch(`${baseUrl}/.pods`, {
|
|
89
103
|
method: 'POST',
|
|
90
104
|
headers: { 'Content-Type': 'application/json' },
|
|
91
105
|
body: JSON.stringify({ name: 'nopass', email: 'test@example.com' }),
|
|
@@ -98,7 +112,7 @@ describe('Identity Provider', () => {
|
|
|
98
112
|
|
|
99
113
|
it('should create pod with account', async () => {
|
|
100
114
|
const uniqueId = Date.now();
|
|
101
|
-
const res = await fetch(`${
|
|
115
|
+
const res = await fetch(`${baseUrl}/.pods`, {
|
|
102
116
|
method: 'POST',
|
|
103
117
|
headers: { 'Content-Type': 'application/json' },
|
|
104
118
|
body: JSON.stringify({
|
|
@@ -125,7 +139,7 @@ describe('Identity Provider', () => {
|
|
|
125
139
|
const duplicateEmail = `duplicate${uniqueId}@example.com`;
|
|
126
140
|
|
|
127
141
|
// First user
|
|
128
|
-
await fetch(`${
|
|
142
|
+
await fetch(`${baseUrl}/.pods`, {
|
|
129
143
|
method: 'POST',
|
|
130
144
|
headers: { 'Content-Type': 'application/json' },
|
|
131
145
|
body: JSON.stringify({
|
|
@@ -136,7 +150,7 @@ describe('Identity Provider', () => {
|
|
|
136
150
|
});
|
|
137
151
|
|
|
138
152
|
// Second user with same email
|
|
139
|
-
const res = await fetch(`${
|
|
153
|
+
const res = await fetch(`${baseUrl}/.pods`, {
|
|
140
154
|
method: 'POST',
|
|
141
155
|
headers: { 'Content-Type': 'application/json' },
|
|
142
156
|
body: JSON.stringify({
|
|
@@ -154,15 +168,10 @@ describe('Identity Provider', () => {
|
|
|
154
168
|
|
|
155
169
|
describe('Login Interaction', () => {
|
|
156
170
|
it('should respond to authorization endpoint', async () => {
|
|
157
|
-
|
|
158
|
-
// Various responses are acceptable - 302/303 (redirect), 400 (bad request), 404 (no route)
|
|
159
|
-
// This just verifies the server handles the request
|
|
160
|
-
const res = await fetch(`${BASE_URL}/idp/auth?client_id=test&redirect_uri=http://localhost&response_type=code&scope=openid`, {
|
|
171
|
+
const res = await fetch(`${baseUrl}/idp/auth?client_id=test&redirect_uri=http://localhost&response_type=code&scope=openid`, {
|
|
161
172
|
redirect: 'manual',
|
|
162
173
|
});
|
|
163
174
|
|
|
164
|
-
// oidc-provider mounted via middie may return different status codes
|
|
165
|
-
// The important thing is it doesn't crash and returns a valid HTTP response
|
|
166
175
|
assert.ok(res.status >= 200 && res.status < 600, `got valid HTTP status ${res.status}`);
|
|
167
176
|
});
|
|
168
177
|
});
|
|
@@ -170,20 +179,25 @@ describe('Identity Provider', () => {
|
|
|
170
179
|
|
|
171
180
|
describe('Identity Provider - Accounts', () => {
|
|
172
181
|
let server;
|
|
182
|
+
let accountsUrl;
|
|
173
183
|
const ACCOUNTS_DATA_DIR = './test-data-idp-accounts';
|
|
174
184
|
|
|
175
185
|
before(async () => {
|
|
176
186
|
await fs.remove(ACCOUNTS_DATA_DIR);
|
|
177
187
|
await fs.ensureDir(ACCOUNTS_DATA_DIR);
|
|
178
188
|
|
|
189
|
+
const port = await getAvailablePort();
|
|
190
|
+
accountsUrl = `http://${TEST_HOST}:${port}`;
|
|
191
|
+
|
|
179
192
|
server = createServer({
|
|
180
193
|
logger: false,
|
|
181
194
|
root: ACCOUNTS_DATA_DIR,
|
|
182
195
|
idp: true,
|
|
183
|
-
idpIssuer:
|
|
196
|
+
idpIssuer: accountsUrl,
|
|
197
|
+
forceCloseConnections: true,
|
|
184
198
|
});
|
|
185
199
|
|
|
186
|
-
await server.listen({ port
|
|
200
|
+
await server.listen({ port, host: TEST_HOST });
|
|
187
201
|
});
|
|
188
202
|
|
|
189
203
|
after(async () => {
|
|
@@ -195,7 +209,7 @@ describe('Identity Provider - Accounts', () => {
|
|
|
195
209
|
const uniqueName = `stored${Date.now()}`;
|
|
196
210
|
const uniqueEmail = `stored${Date.now()}@example.com`;
|
|
197
211
|
|
|
198
|
-
const res = await fetch(
|
|
212
|
+
const res = await fetch(`${accountsUrl}/.pods`, {
|
|
199
213
|
method: 'POST',
|
|
200
214
|
headers: { 'Content-Type': 'application/json' },
|
|
201
215
|
body: JSON.stringify({
|
|
@@ -221,7 +235,7 @@ describe('Identity Provider - Accounts', () => {
|
|
|
221
235
|
const uniqueName = `hashed${Date.now()}`;
|
|
222
236
|
const uniqueEmail = `hashed${Date.now()}@example.com`;
|
|
223
237
|
|
|
224
|
-
const res = await fetch(
|
|
238
|
+
const res = await fetch(`${accountsUrl}/.pods`, {
|
|
225
239
|
method: 'POST',
|
|
226
240
|
headers: { 'Content-Type': 'application/json' },
|
|
227
241
|
body: JSON.stringify({
|
|
@@ -248,24 +262,26 @@ describe('Identity Provider - Accounts', () => {
|
|
|
248
262
|
|
|
249
263
|
describe('Identity Provider - Credentials Endpoint', () => {
|
|
250
264
|
let server;
|
|
251
|
-
|
|
265
|
+
let credsUrl;
|
|
252
266
|
const CREDS_DATA_DIR = './data';
|
|
253
|
-
const CREDS_PORT = 3101;
|
|
254
|
-
const CREDS_URL = `http://${TEST_HOST}:${CREDS_PORT}`;
|
|
255
267
|
|
|
256
268
|
before(async () => {
|
|
257
269
|
await fs.emptyDir(CREDS_DATA_DIR);
|
|
258
270
|
|
|
271
|
+
const port = await getAvailablePort();
|
|
272
|
+
credsUrl = `http://${TEST_HOST}:${port}`;
|
|
273
|
+
|
|
259
274
|
server = createServer({
|
|
260
275
|
logger: false,
|
|
261
276
|
idp: true,
|
|
262
|
-
idpIssuer:
|
|
277
|
+
idpIssuer: credsUrl,
|
|
278
|
+
forceCloseConnections: true,
|
|
263
279
|
});
|
|
264
280
|
|
|
265
|
-
await server.listen({ port
|
|
281
|
+
await server.listen({ port, host: TEST_HOST });
|
|
266
282
|
|
|
267
283
|
// Create a test user
|
|
268
|
-
const res = await fetch(`${
|
|
284
|
+
const res = await fetch(`${credsUrl}/.pods`, {
|
|
269
285
|
method: 'POST',
|
|
270
286
|
headers: { 'Content-Type': 'application/json' },
|
|
271
287
|
body: JSON.stringify({
|
|
@@ -286,7 +302,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
286
302
|
|
|
287
303
|
describe('GET /idp/credentials', () => {
|
|
288
304
|
it('should return endpoint info', async () => {
|
|
289
|
-
const res = await fetch(`${
|
|
305
|
+
const res = await fetch(`${credsUrl}/idp/credentials`);
|
|
290
306
|
assert.strictEqual(res.status, 200);
|
|
291
307
|
|
|
292
308
|
const info = await res.json();
|
|
@@ -299,7 +315,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
299
315
|
|
|
300
316
|
describe('POST /idp/credentials', () => {
|
|
301
317
|
it('should return 400 for missing credentials', async () => {
|
|
302
|
-
const res = await fetch(`${
|
|
318
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
303
319
|
method: 'POST',
|
|
304
320
|
headers: { 'Content-Type': 'application/json' },
|
|
305
321
|
body: JSON.stringify({}),
|
|
@@ -311,7 +327,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
311
327
|
});
|
|
312
328
|
|
|
313
329
|
it('should return 401 for wrong password', async () => {
|
|
314
|
-
const res = await fetch(`${
|
|
330
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
315
331
|
method: 'POST',
|
|
316
332
|
headers: { 'Content-Type': 'application/json' },
|
|
317
333
|
body: JSON.stringify({
|
|
@@ -326,7 +342,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
326
342
|
});
|
|
327
343
|
|
|
328
344
|
it('should return 401 for unknown email', async () => {
|
|
329
|
-
const res = await fetch(`${
|
|
345
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
330
346
|
method: 'POST',
|
|
331
347
|
headers: { 'Content-Type': 'application/json' },
|
|
332
348
|
body: JSON.stringify({
|
|
@@ -339,7 +355,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
339
355
|
});
|
|
340
356
|
|
|
341
357
|
it('should return access token for valid credentials', async () => {
|
|
342
|
-
const res = await fetch(`${
|
|
358
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
343
359
|
method: 'POST',
|
|
344
360
|
headers: { 'Content-Type': 'application/json' },
|
|
345
361
|
body: JSON.stringify({
|
|
@@ -358,7 +374,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
358
374
|
});
|
|
359
375
|
|
|
360
376
|
it('should return JWT token with webid claim', async () => {
|
|
361
|
-
const res = await fetch(`${
|
|
377
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
362
378
|
method: 'POST',
|
|
363
379
|
headers: { 'Content-Type': 'application/json' },
|
|
364
380
|
body: JSON.stringify({
|
|
@@ -382,7 +398,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
382
398
|
});
|
|
383
399
|
|
|
384
400
|
it('should work with form-encoded body', async () => {
|
|
385
|
-
const res = await fetch(`${
|
|
401
|
+
const res = await fetch(`${credsUrl}/idp/credentials`, {
|
|
386
402
|
method: 'POST',
|
|
387
403
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
388
404
|
body: 'email=credtest%40example.com&password=testpassword123',
|
|
@@ -395,7 +411,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
395
411
|
|
|
396
412
|
it('should allow using token to access protected resource', async () => {
|
|
397
413
|
// Get access token
|
|
398
|
-
const tokenRes = await fetch(`${
|
|
414
|
+
const tokenRes = await fetch(`${credsUrl}/idp/credentials`, {
|
|
399
415
|
method: 'POST',
|
|
400
416
|
headers: { 'Content-Type': 'application/json' },
|
|
401
417
|
body: JSON.stringify({
|
|
@@ -407,7 +423,7 @@ describe('Identity Provider - Credentials Endpoint', () => {
|
|
|
407
423
|
const { access_token } = await tokenRes.json();
|
|
408
424
|
|
|
409
425
|
// Try to access private resource
|
|
410
|
-
const res = await fetch(`${
|
|
426
|
+
const res = await fetch(`${credsUrl}/credtest/private/`, {
|
|
411
427
|
headers: { 'Authorization': `Bearer ${access_token}` },
|
|
412
428
|
});
|
|
413
429
|
|