mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-21 01:21:51 +00:00
The IAM callback at /auth/callback reads IAM's userinfo answer and refuses an address IAM has not verified. An account is found by its linked IAM identity alone (provider hanzo, the IAM subject); an existing account that only shares the address is refused, never merged. A first sign-in makes an account only for an admin of the org the application serves or for someone an organisation invited at that verified address. Every refusal returns to /signin with a sentence saying what to do. Accepting an organisation invitation joins the signed-in person, and only when their verified address is the invited one; opening the link no longer adds whoever holds the address. The local email/password sign-up, sign-in, reset, verify-email and update routes, pages, jobs and mail are gone, as are passkey sign-in, the Google, Microsoft and generic OIDC providers, the per-organisation SSO portal and its account-link flow. Passkeys and two-factor stay for confirming a signature. The e2e suite starts sessions the way a finished IAM sign-in leaves them. Co-authored-by: Hanzo Dev <[email protected]>
79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
import { type Page, expect, test } from '@playwright/test';
|
|
|
|
import { seedUser } from '@hanzo/esign-prisma/seed/users';
|
|
|
|
import { apiSignin } from '../fixtures/authentication';
|
|
import { expectTextToBeVisible } from '../fixtures/generic';
|
|
|
|
test('[USER] revoke sessions', async ({ page }: { page: Page }) => {
|
|
const { user, team } = await seedUser();
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: '/settings/security/sessions',
|
|
});
|
|
|
|
// Expect 2 rows length (header + 1)
|
|
await expect(page.getByRole('row')).toHaveCount(2);
|
|
|
|
// Clear cookies
|
|
await page.context().clearCookies();
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: '/settings/security/sessions',
|
|
});
|
|
|
|
await page.context().clearCookies();
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: '/settings/security/sessions',
|
|
});
|
|
|
|
// Expect 4 (3 sessions + 1 header) rows length
|
|
await expect(page.getByRole('row')).toHaveCount(4);
|
|
|
|
// Revoke all sessions
|
|
await page.getByRole('button', { name: 'Revoke all sessions' }).click();
|
|
await page.getByRole('button', { name: 'Revoke all sessions' }).click();
|
|
|
|
await expectTextToBeVisible(page, 'Sessions have been revoked');
|
|
|
|
// Expect (1 sessions + 1 header) rows length
|
|
await expect(page.getByRole('row')).toHaveCount(2);
|
|
|
|
await page.context().clearCookies();
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: '/settings/security/sessions',
|
|
});
|
|
|
|
// Find table row which does not have text 'Current' and click the button called Revoke within the row.
|
|
await page
|
|
.getByRole('row')
|
|
.filter({ hasNotText: 'Current' })
|
|
.nth(1)
|
|
.getByRole('button', { name: 'Revoke' })
|
|
.click();
|
|
await expectTextToBeVisible(page, 'Session revoked');
|
|
|
|
// Expect (1 sessions + 1 header) rows length
|
|
await expect(page.getByRole('row')).toHaveCount(2);
|
|
|
|
// Revoke own session.
|
|
await page
|
|
.getByRole('row')
|
|
.filter({ hasText: 'Current' })
|
|
.first()
|
|
.getByRole('button', { name: 'Revoke' })
|
|
.click();
|
|
|
|
await expect(page).toHaveURL('/signin');
|
|
});
|