6A: Enterprise IdP CRUD, SAML callback, OIDC callback, email domain lookup 6B: Magic link send/verify (15min TTL, anti-enumeration), HIBP breach check 6D: 3 new E2E specs (account-linking, step-up, enterprise) — total 8 SmartAuth specs - All 53 auth tests passing
131 lines
4.0 KiB
TypeScript
131 lines
4.0 KiB
TypeScript
/**
|
|
* SmartAuth E2E — Enterprise SAML/OIDC
|
|
* Tests IdP configuration, email domain lookup, and SSO login flows.
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('SmartAuth: Enterprise SSO', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route('**/api/auth/me', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({
|
|
id: 'usr_admin',
|
|
email: 'admin@bytelyst.com',
|
|
role: 'super_admin',
|
|
displayName: 'Super Admin',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
test('should lookup IdP by email domain', async ({ page }) => {
|
|
await page.route('**/api/auth/enterprise/lookup?email=*', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({
|
|
found: true,
|
|
idp: {
|
|
id: 'idp_acme_saml_abc123',
|
|
orgId: 'org_acme',
|
|
protocol: 'saml',
|
|
name: 'Acme Corp SAML',
|
|
emailDomains: ['acme.com'],
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
await page.goto('/settings/security');
|
|
const response = await page.evaluate(async () => {
|
|
const res = await fetch('/api/auth/enterprise/lookup?email=user@acme.com');
|
|
return res.json();
|
|
});
|
|
expect(response.found).toBe(true);
|
|
expect(response.idp.protocol).toBe('saml');
|
|
});
|
|
|
|
test('should return not found for unknown domain', async ({ page }) => {
|
|
await page.route('**/api/auth/enterprise/lookup?email=*', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({ found: false, idp: null }),
|
|
})
|
|
);
|
|
await page.goto('/settings/security');
|
|
const response = await page.evaluate(async () => {
|
|
const res = await fetch('/api/auth/enterprise/lookup?email=user@unknown.com');
|
|
return res.json();
|
|
});
|
|
expect(response.found).toBe(false);
|
|
});
|
|
|
|
test('should create IdP config (admin)', async ({ page }) => {
|
|
await page.route('**/api/auth/enterprise/idps', route => {
|
|
if (route.request().method() === 'POST') {
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({
|
|
id: 'idp_acme_oidc_def456',
|
|
orgId: 'org_acme',
|
|
protocol: 'oidc',
|
|
name: 'Acme OIDC',
|
|
emailDomains: ['acme.com'],
|
|
enabled: true,
|
|
}),
|
|
});
|
|
} else {
|
|
route.fulfill({ status: 200, body: '[]' });
|
|
}
|
|
});
|
|
await page.goto('/settings/security');
|
|
const response = await page.evaluate(async () => {
|
|
const res = await fetch('/api/auth/enterprise/idps', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
orgId: 'org_acme',
|
|
protocol: 'oidc',
|
|
name: 'Acme OIDC',
|
|
emailDomains: ['acme.com'],
|
|
oidc: {
|
|
issuer: 'https://login.acme.com',
|
|
clientId: 'client123',
|
|
clientSecret: 'secret456',
|
|
authorizationUrl: 'https://login.acme.com/authorize',
|
|
tokenUrl: 'https://login.acme.com/token',
|
|
},
|
|
}),
|
|
});
|
|
return res.json();
|
|
});
|
|
expect(response.id).toMatch(/^idp_/);
|
|
expect(response.protocol).toBe('oidc');
|
|
});
|
|
|
|
test('should handle SAML callback', async ({ page }) => {
|
|
await page.route('**/api/auth/saml/callback', route =>
|
|
route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({
|
|
accessToken: 'at_test',
|
|
refreshToken: 'rt_test',
|
|
user: { id: 'usr_saml', email: 'user@acme.com', displayName: 'SAML User' },
|
|
}),
|
|
})
|
|
);
|
|
await page.goto('/');
|
|
const response = await page.evaluate(async () => {
|
|
const samlResponse = btoa('<saml:NameID>user@acme.com</saml:NameID>');
|
|
const res = await fetch('/api/auth/saml/callback', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ SAMLResponse: samlResponse }),
|
|
});
|
|
return res.json();
|
|
});
|
|
expect(response).toHaveProperty('accessToken');
|
|
expect(response.user.email).toBe('user@acme.com');
|
|
});
|
|
});
|