+106
test/e2e.test.js
+106
test/e2e.test.js
···
1450
'Should show full access warning',
1451
);
1452
});
1453
+
1454
+
it('supports direct authorization without PAR', async () => {
1455
+
const clientId = 'http://localhost:3000';
1456
+
const redirectUri = 'http://localhost:3000/callback';
1457
+
const codeVerifier = 'test-verifier-for-direct-auth-flow-min-43-chars!!';
1458
+
const challengeBuffer = await crypto.subtle.digest(
1459
+
'SHA-256',
1460
+
new TextEncoder().encode(codeVerifier),
1461
+
);
1462
+
const codeChallenge = Buffer.from(challengeBuffer).toString('base64url');
1463
+
const state = 'test-direct-auth-state';
1464
+
1465
+
// Step 1: GET authorize with direct parameters (no PAR)
1466
+
const authorizeUrl = new URL(`${BASE}/oauth/authorize`);
1467
+
authorizeUrl.searchParams.set('client_id', clientId);
1468
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
1469
+
authorizeUrl.searchParams.set('response_type', 'code');
1470
+
authorizeUrl.searchParams.set('scope', 'atproto');
1471
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
1472
+
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
1473
+
authorizeUrl.searchParams.set('state', state);
1474
+
authorizeUrl.searchParams.set('login_hint', DID);
1475
+
1476
+
const getRes = await fetch(authorizeUrl.toString());
1477
+
assert.strictEqual(getRes.status, 200, 'Direct authorize GET should succeed');
1478
+
1479
+
const html = await getRes.text();
1480
+
assert.ok(html.includes('Authorize'), 'Should show consent page');
1481
+
assert.ok(html.includes('request_uri'), 'Should include request_uri in form');
1482
+
});
1483
+
1484
+
it('completes full direct authorization flow', async () => {
1485
+
const clientId = 'http://localhost:3000';
1486
+
const redirectUri = 'http://localhost:3000/callback';
1487
+
const codeVerifier = 'test-verifier-for-direct-auth-flow-min-43-chars!!';
1488
+
const challengeBuffer = await crypto.subtle.digest(
1489
+
'SHA-256',
1490
+
new TextEncoder().encode(codeVerifier),
1491
+
);
1492
+
const codeChallenge = Buffer.from(challengeBuffer).toString('base64url');
1493
+
const state = 'test-direct-auth-state';
1494
+
1495
+
// Step 1: GET authorize with direct parameters
1496
+
const authorizeUrl = new URL(`${BASE}/oauth/authorize`);
1497
+
authorizeUrl.searchParams.set('client_id', clientId);
1498
+
authorizeUrl.searchParams.set('redirect_uri', redirectUri);
1499
+
authorizeUrl.searchParams.set('response_type', 'code');
1500
+
authorizeUrl.searchParams.set('scope', 'atproto');
1501
+
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
1502
+
authorizeUrl.searchParams.set('code_challenge_method', 'S256');
1503
+
authorizeUrl.searchParams.set('state', state);
1504
+
authorizeUrl.searchParams.set('login_hint', DID);
1505
+
1506
+
const getRes = await fetch(authorizeUrl.toString());
1507
+
assert.strictEqual(getRes.status, 200);
1508
+
const html = await getRes.text();
1509
+
1510
+
// Extract request_uri from the form
1511
+
const requestUriMatch = html.match(/name="request_uri" value="([^"]+)"/);
1512
+
assert.ok(requestUriMatch, 'Should have request_uri in form');
1513
+
const requestUri = requestUriMatch[1];
1514
+
1515
+
// Step 2: POST to authorize (user approval)
1516
+
const authRes = await fetch(`${BASE}/oauth/authorize`, {
1517
+
method: 'POST',
1518
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1519
+
body: new URLSearchParams({
1520
+
request_uri: requestUri,
1521
+
client_id: clientId,
1522
+
password: PASSWORD,
1523
+
}).toString(),
1524
+
redirect: 'manual',
1525
+
});
1526
+
1527
+
assert.strictEqual(authRes.status, 302, 'Should redirect after approval');
1528
+
const location = authRes.headers.get('location');
1529
+
assert.ok(location, 'Should have Location header');
1530
+
const locationUrl = new URL(location);
1531
+
const code = locationUrl.searchParams.get('code');
1532
+
assert.ok(code, 'Should have authorization code');
1533
+
assert.strictEqual(locationUrl.searchParams.get('state'), state);
1534
+
1535
+
// Step 3: Exchange code for tokens
1536
+
const dpop = await DpopClient.create();
1537
+
const dpopProof = await dpop.createProof('POST', `${BASE}/oauth/token`);
1538
+
1539
+
const tokenRes = await fetch(`${BASE}/oauth/token`, {
1540
+
method: 'POST',
1541
+
headers: {
1542
+
'Content-Type': 'application/x-www-form-urlencoded',
1543
+
DPoP: dpopProof,
1544
+
},
1545
+
body: new URLSearchParams({
1546
+
grant_type: 'authorization_code',
1547
+
code,
1548
+
redirect_uri: redirectUri,
1549
+
client_id: clientId,
1550
+
code_verifier: codeVerifier,
1551
+
}).toString(),
1552
+
});
1553
+
1554
+
assert.strictEqual(tokenRes.status, 200, 'Token exchange should succeed');
1555
+
const tokenData = await tokenRes.json();
1556
+
assert.ok(tokenData.access_token, 'Should have access_token');
1557
+
assert.strictEqual(tokenData.token_type, 'DPoP');
1558
+
});
1559
});
1560
1561
describe('Foreign DID proxying', () => {