import assert from 'node:assert/strict'; import { chromium } from 'playwright'; const [appUrl, issuerUrl, password] = process.argv.slice(2); const app = new URL(appUrl); const issuer = new URL(issuerUrl); for (const base of [app, issuer]) { if (base.protocol !== 'http:' || !['localhost', '127.0.0.1'].includes(base.hostname)) { throw new Error('Communication checks require isolated loopback servers'); } } const allowed = new Set([app.origin, issuer.origin]); const browser = await chromium.launch({headless: true}); try { const context = await browser.newContext(); const aborted = []; await context.route('**/*', route => { const origin = new URL(route.request().url()).origin; if (allowed.has(origin)) return route.continue(); aborted.push(route.request().url()); return route.abort(); }); const page = await context.newPage(); page.setDefaultTimeout(10000); let callbackUrl = ''; page.on('request', request => { const url = request.url(); if (url.includes('/auth/callback/') && !callbackUrl) callbackUrl = url; }); await page.goto(`${issuer.origin}/recover`); await page.getByRole('button', {name: 'Send reset'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); const mailboxText = await page.locator('main').innerText(); assert.match(mailboxText, /Local mailbox/); assert.doesNotMatch(mailboxText, /handle=/); assert.equal(mailboxText.includes(password), false); await page.getByRole('button', {name: 'Use reset', exact: true}).click(); await page.getByLabel('Password').fill(password); await page.getByRole('button', {name: 'Save password'}).click(); assert.match(await page.locator('main').innerText(), /Password updated/); assert.equal((await page.locator('main').innerText()).includes(password), false); await page.goto(app.origin + '/'); await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click(); await page.waitForURL(url => url.origin === issuer.origin && url.pathname === '/authorize'); await page.getByRole('button', {name: 'Continue'}).click(); await page.waitForURL('**/app/'); assert.ok(callbackUrl); await page.goto(app.origin + '/account/session/'); const sessionText = await page.locator('main').innerText(); const sessionHtml = await page.content(); const resetCode = new URL(callbackUrl).searchParams.get('code'); assert.match(sessionText, /member-recovered/); assert.match(sessionText, /member@example\.test/); assert.equal(sessionText.includes(password), false); assert.equal(sessionHtml.includes(password), false); assert.equal(sessionHtml.includes(resetCode), false); const replay = await page.goto(callbackUrl); assert.equal(replay.status(), 400); assert.match(await page.locator('body').innerText(), /Invalid OIDC callback state/); await page.goto(`${issuer.origin}/recover?expired=1`); await page.getByRole('button', {name: 'Send reset'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); await page.getByRole('button', {name: 'Use expired reset'}).click(); assert.match(await page.locator('main').innerText(), /Handle expired/); await page.goto(app.origin + '/auth/logout/'); await page.waitForURL(app.origin + '/'); await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click(); await page.getByLabel('Subject').fill('member-other'); await page.getByLabel('Name').fill('Other Member'); await page.getByLabel('Email').fill('member@example.test'); await page.getByRole('button', {name: 'Continue'}).click(); await page.waitForURL('**/app/'); await page.goto(app.origin + '/account/session/'); const other = await page.locator('main').innerText(); assert.match(other, /member-other/); assert.doesNotMatch(other, /member-recovered/); await page.goto(app.origin + '/auth/logout/'); await page.waitForURL(app.origin + '/'); await page.goto(app.origin + '/auth/register/'); await page.waitForURL(`${issuer.origin}/register`); assert.match(await page.locator('main').innerText(), /Registration sink/); await page.getByRole('button', {name: 'Send verification'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); await page.getByRole('button', {name: 'Cancel verification'}).click(); assert.match(await page.locator('main').innerText(), /Handle cancelled/); await page.goto(`${issuer.origin}/mailbox`); assert.match(await page.locator('main').innerText(), /verification cancelled/); assert.equal(await page.getByRole('button', {name: 'Use verification'}).count(), 0); await page.goto(`${issuer.origin}/register`); await page.getByRole('button', {name: 'Send verification'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); await page.getByRole('button', {name: 'Use verification'}).click(); assert.match(await page.locator('main').innerText(), /Verification consumed/); assert.match(await page.locator('main').innerText(), /does not sign in/); await page.goto(app.origin + '/'); assert.equal( await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).count(), 1, ); const refused = new URL('/authorize', issuer.origin); refused.searchParams.set('client_id', 'intruder'); refused.searchParams.set('redirect_uri', 'http://127.0.0.1:9/callback'); refused.searchParams.set('response_type', 'code'); refused.searchParams.set('state', 'x'); refused.searchParams.set('code_challenge', 'not-used'); refused.searchParams.set('code_challenge_method', 'S256'); await page.goto(refused.toString()); assert.match(await page.locator('main').innerText(), /Client refused/); await page.goto(app.origin + '/'); await page.locator('main').getByRole('link', {name: 'Sign in', exact: true}).click(); await page.getByLabel('Tenant').fill('tenant:other'); await page.getByRole('button', {name: 'Continue'}).click(); assert.match(await page.locator('main').innerText(), /Tenant refused/); await page.goto(`${issuer.origin}/invite`); await page.getByLabel('Caller').fill('intruder'); await page.getByRole('button', {name: 'Send invitation'}).click(); assert.match(await page.locator('main').innerText(), /Caller refused/); await page.goto(`${issuer.origin}/invite`); await page.getByLabel('Caller').fill('coulomb-social-test'); await page.getByLabel('Suppress').check(); await page.getByRole('button', {name: 'Send invitation'}).click(); assert.match(await page.locator('main').innerText(), /Invitation suppressed/); await page.goto(`${issuer.origin}/invite`); await page.getByLabel('Caller').fill('coulomb-social-test'); await page.getByRole('button', {name: 'Send invitation'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); await page.goto(`${issuer.origin}/invite`); await page.getByLabel('Caller').fill('coulomb-social-test'); await page.getByRole('button', {name: 'Send invitation'}).click(); await page.getByRole('link', {name: 'Open mailbox'}).click(); assert.equal(await page.getByRole('button', {name: 'Open invitation'}).count(), 2); assert.equal(aborted.length, 0); console.log('Isolated recovery, registration and invitation journey passed'); } finally { await browser.close(); }