javascript-solid-server 0.0.12 → 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.
- package/.claude/settings.local.json +2 -1
- package/README.md +24 -2
- package/data/alice/.acl +50 -0
- package/data/alice/inbox/.acl +50 -0
- package/data/alice/index.html +80 -0
- package/data/alice/private/.acl +32 -0
- package/data/alice/public/test.json +1 -0
- package/data/alice/settings/.acl +32 -0
- package/data/alice/settings/prefs +17 -0
- package/data/alice/settings/privateTypeIndex +7 -0
- package/data/alice/settings/publicTypeIndex +7 -0
- package/data/bob/.acl +50 -0
- package/data/bob/inbox/.acl +50 -0
- package/data/bob/index.html +80 -0
- package/data/bob/private/.acl +32 -0
- package/data/bob/settings/.acl +32 -0
- package/data/bob/settings/prefs +17 -0
- package/data/bob/settings/privateTypeIndex +7 -0
- package/data/bob/settings/publicTypeIndex +7 -0
- package/package.json +2 -1
- package/scripts/test-cth-compat.js +369 -0
- package/src/idp/credentials.js +225 -0
- package/src/idp/index.js +19 -2
- package/test/idp.test.js +169 -0
package/test/idp.test.js
CHANGED
|
@@ -256,3 +256,172 @@ describe('Identity Provider - Accounts', () => {
|
|
|
256
256
|
assert.ok(!account.password, 'should not store plain password');
|
|
257
257
|
});
|
|
258
258
|
});
|
|
259
|
+
|
|
260
|
+
describe('Identity Provider - Credentials Endpoint', () => {
|
|
261
|
+
let server;
|
|
262
|
+
const CREDS_DATA_DIR = './test-data-idp-creds';
|
|
263
|
+
const CREDS_PORT = 3101;
|
|
264
|
+
const CREDS_URL = `http://${TEST_HOST}:${CREDS_PORT}`;
|
|
265
|
+
|
|
266
|
+
before(async () => {
|
|
267
|
+
await fs.remove(CREDS_DATA_DIR);
|
|
268
|
+
await fs.ensureDir(CREDS_DATA_DIR);
|
|
269
|
+
|
|
270
|
+
server = createServer({
|
|
271
|
+
logger: false,
|
|
272
|
+
root: CREDS_DATA_DIR,
|
|
273
|
+
idp: true,
|
|
274
|
+
idpIssuer: CREDS_URL,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await server.listen({ port: CREDS_PORT, host: TEST_HOST });
|
|
278
|
+
|
|
279
|
+
// Create a test user
|
|
280
|
+
await fetch(`${CREDS_URL}/.pods`, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: { 'Content-Type': 'application/json' },
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
name: 'credtest',
|
|
285
|
+
email: 'credtest@example.com',
|
|
286
|
+
password: 'testpassword123',
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
after(async () => {
|
|
292
|
+
await server.close();
|
|
293
|
+
await fs.remove(CREDS_DATA_DIR);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('GET /idp/credentials', () => {
|
|
297
|
+
it('should return endpoint info', async () => {
|
|
298
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`);
|
|
299
|
+
assert.strictEqual(res.status, 200);
|
|
300
|
+
|
|
301
|
+
const info = await res.json();
|
|
302
|
+
assert.ok(info.endpoint);
|
|
303
|
+
assert.strictEqual(info.method, 'POST');
|
|
304
|
+
assert.ok(info.parameters.email);
|
|
305
|
+
assert.ok(info.parameters.password);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('POST /idp/credentials', () => {
|
|
310
|
+
it('should return 400 for missing credentials', async () => {
|
|
311
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: { 'Content-Type': 'application/json' },
|
|
314
|
+
body: JSON.stringify({}),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
assert.strictEqual(res.status, 400);
|
|
318
|
+
const body = await res.json();
|
|
319
|
+
assert.strictEqual(body.error, 'invalid_request');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should return 401 for wrong password', async () => {
|
|
323
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
email: 'credtest@example.com',
|
|
328
|
+
password: 'wrongpassword',
|
|
329
|
+
}),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
assert.strictEqual(res.status, 401);
|
|
333
|
+
const body = await res.json();
|
|
334
|
+
assert.strictEqual(body.error, 'invalid_grant');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should return 401 for unknown email', async () => {
|
|
338
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: { 'Content-Type': 'application/json' },
|
|
341
|
+
body: JSON.stringify({
|
|
342
|
+
email: 'unknown@example.com',
|
|
343
|
+
password: 'anypassword',
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
assert.strictEqual(res.status, 401);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should return access token for valid credentials', async () => {
|
|
351
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
354
|
+
body: JSON.stringify({
|
|
355
|
+
email: 'credtest@example.com',
|
|
356
|
+
password: 'testpassword123',
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
assert.strictEqual(res.status, 200);
|
|
361
|
+
const body = await res.json();
|
|
362
|
+
|
|
363
|
+
assert.ok(body.access_token, 'should have access_token');
|
|
364
|
+
assert.strictEqual(body.token_type, 'Bearer');
|
|
365
|
+
assert.ok(body.expires_in > 0, 'should have expires_in');
|
|
366
|
+
assert.ok(body.webid.includes('credtest'), 'should have webid');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should return simple token with webid for Bearer auth', async () => {
|
|
370
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
371
|
+
method: 'POST',
|
|
372
|
+
headers: { 'Content-Type': 'application/json' },
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
email: 'credtest@example.com',
|
|
375
|
+
password: 'testpassword123',
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const body = await res.json();
|
|
380
|
+
|
|
381
|
+
// Simple tokens have format: base64payload.signature
|
|
382
|
+
const parts = body.access_token.split('.');
|
|
383
|
+
assert.strictEqual(parts.length, 2, 'simple token has 2 parts');
|
|
384
|
+
|
|
385
|
+
// Decode the payload
|
|
386
|
+
const payload = JSON.parse(Buffer.from(parts[0], 'base64url').toString());
|
|
387
|
+
|
|
388
|
+
assert.ok(payload.webId, 'token should have webId');
|
|
389
|
+
assert.ok(payload.webId.includes('credtest'), 'webId should reference user');
|
|
390
|
+
assert.ok(payload.exp > payload.iat, 'should have valid expiry');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should work with form-encoded body', async () => {
|
|
394
|
+
const res = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
397
|
+
body: 'email=credtest%40example.com&password=testpassword123',
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
assert.strictEqual(res.status, 200);
|
|
401
|
+
const body = await res.json();
|
|
402
|
+
assert.ok(body.access_token);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should allow using token to access protected resource', async () => {
|
|
406
|
+
// Get access token
|
|
407
|
+
const tokenRes = await fetch(`${CREDS_URL}/idp/credentials`, {
|
|
408
|
+
method: 'POST',
|
|
409
|
+
headers: { 'Content-Type': 'application/json' },
|
|
410
|
+
body: JSON.stringify({
|
|
411
|
+
email: 'credtest@example.com',
|
|
412
|
+
password: 'testpassword123',
|
|
413
|
+
}),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const { access_token } = await tokenRes.json();
|
|
417
|
+
|
|
418
|
+
// Try to access private resource
|
|
419
|
+
const res = await fetch(`${CREDS_URL}/credtest/private/`, {
|
|
420
|
+
headers: { 'Authorization': `Bearer ${access_token}` },
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Should succeed (not 401/403)
|
|
424
|
+
assert.ok([200, 404].includes(res.status), `expected 200 or 404, got ${res.status}`);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|