test(auth): add admin auth split unit+e2e coverage
This commit is contained in:
parent
95af9c2788
commit
9980cd4fe5
10 changed files with 348 additions and 20 deletions
66
package-lock.json
generated
66
package-lock.json
generated
|
|
@ -12,6 +12,9 @@
|
||||||
"solid-js": "^1.9.5",
|
"solid-js": "^1.9.5",
|
||||||
"vinxi": "^0.5.7"
|
"vinxi": "^0.5.7"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.58.2"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
|
|
@ -1362,6 +1365,22 @@
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.58.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||||
|
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.58.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@poppinss/colors": {
|
"node_modules/@poppinss/colors": {
|
||||||
"version": "4.1.6",
|
"version": "4.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
|
||||||
|
|
@ -6307,6 +6326,53 @@
|
||||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.58.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||||
|
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.58.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.58.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||||
|
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.8",
|
"version": "8.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vinxi dev",
|
"dev": "vinxi dev",
|
||||||
"build": "vinxi build",
|
"build": "vinxi build",
|
||||||
"start": "vinxi start"
|
"start": "vinxi start",
|
||||||
|
"test": "node --test --experimental-strip-types src/lib/**/*.test.ts",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:headed": "playwright test --headed"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
|
|
@ -15,5 +18,8 @@
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.58.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
playwright.config.ts
Normal file
25
playwright.config.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests/e2e',
|
||||||
|
fullyParallel: true,
|
||||||
|
reporter: [['list']],
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run build && PORT=3102 npm run start',
|
||||||
|
url: 'http://127.0.0.1:3102/',
|
||||||
|
reuseExistingServer: true,
|
||||||
|
timeout: 300_000,
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://127.0.0.1:3102',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
viewport: { width: 1440, height: 900 },
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { A, useLocation, useNavigate } from '@solidjs/router';
|
import { A, useLocation, useNavigate } from '@solidjs/router';
|
||||||
import { createMemo, createSignal, onMount, type JSX } from 'solid-js';
|
import { createMemo, createSignal, onMount, type JSX } from 'solid-js';
|
||||||
import AdminSidebar from './AdminSidebar';
|
import AdminSidebar from './AdminSidebar';
|
||||||
|
import { isExternalIdentity } from '~/lib/admin-auth';
|
||||||
import { clearAdminSession, hasAdminSession } from '~/lib/admin-session';
|
import { clearAdminSession, hasAdminSession } from '~/lib/admin-session';
|
||||||
import { sidebarCollapsed } from '~/lib/sidebar-state';
|
import { sidebarCollapsed } from '~/lib/sidebar-state';
|
||||||
|
|
||||||
|
|
@ -115,15 +116,47 @@ export default function AdminShell(props: { children: JSX.Element }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
const verify = async () => {
|
||||||
if (!hasAdminSession()) {
|
if (!hasAdminSession()) {
|
||||||
const from = encodeURIComponent(location.pathname + location.search);
|
const from = encodeURIComponent(location.pathname + location.search);
|
||||||
navigate(`/login?from=${from}`, { replace: true });
|
navigate(`/login?from=${from}`, { replace: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/gateway/users/auth/me', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'x-portal-target': 'admin',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok || isExternalIdentity(payload)) {
|
||||||
|
throw new Error('Unauthorized');
|
||||||
|
}
|
||||||
setCheckedSession(true);
|
setCheckedSession(true);
|
||||||
|
} catch {
|
||||||
|
clearAdminSession();
|
||||||
|
const from = encodeURIComponent(location.pathname + location.search);
|
||||||
|
navigate(`/login?from=${from}`, { replace: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void verify();
|
||||||
});
|
});
|
||||||
|
|
||||||
const onLogout = () => {
|
const onLogout = async () => {
|
||||||
|
await fetch('/api/gateway/users/auth/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'x-portal-target': 'admin',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
clearAdminSession();
|
clearAdminSession();
|
||||||
navigate('/login', { replace: true });
|
navigate('/login', { replace: true });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
37
src/lib/admin-auth.test.ts
Normal file
37
src/lib/admin-auth.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { isExternalIdentity, pickManagementLoginError } from './admin-auth.ts';
|
||||||
|
|
||||||
|
test('pickManagementLoginError prefers wrong-portal message', () => {
|
||||||
|
const message = pickManagementLoginError({ error_code: 'WRONG_PORTAL', message: 'Ignored' });
|
||||||
|
assert.equal(
|
||||||
|
message,
|
||||||
|
'This login is only for internal management users. External users must use the public login page.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pickManagementLoginError uses payload message when available', () => {
|
||||||
|
const message = pickManagementLoginError({ message: 'Invalid credentials' });
|
||||||
|
assert.equal(message, 'Invalid credentials');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pickManagementLoginError falls back when payload has no message', () => {
|
||||||
|
const message = pickManagementLoginError({});
|
||||||
|
assert.equal(message, 'Sign in failed.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isExternalIdentity returns true for public audience', () => {
|
||||||
|
assert.equal(isExternalIdentity({ audience: 'public' }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isExternalIdentity returns true for external user type', () => {
|
||||||
|
assert.equal(isExternalIdentity({ user: { user_type: 'external_user' } }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isExternalIdentity returns true for external account type', () => {
|
||||||
|
assert.equal(isExternalIdentity({ user: { accountType: 'external' } }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isExternalIdentity returns false for internal/admin identity', () => {
|
||||||
|
assert.equal(isExternalIdentity({ audience: 'admin', user: { userType: 'employee' } }), false);
|
||||||
|
});
|
||||||
31
src/lib/admin-auth.ts
Normal file
31
src/lib/admin-auth.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
export function pickManagementLoginError(payload: any): string {
|
||||||
|
const message = String(payload?.message || payload?.error || '').trim();
|
||||||
|
if (payload?.error_code === 'WRONG_PORTAL') {
|
||||||
|
return 'This login is only for internal management users. External users must use the public login page.';
|
||||||
|
}
|
||||||
|
if (message) return message;
|
||||||
|
return 'Sign in failed.';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExternalIdentity(payload: any): boolean {
|
||||||
|
const audience = String(payload?.audience || payload?.user?.audience || '').trim().toLowerCase();
|
||||||
|
if (audience === 'public' || audience === 'external') return true;
|
||||||
|
|
||||||
|
const userType = String(
|
||||||
|
payload?.userType ||
|
||||||
|
payload?.user_type ||
|
||||||
|
payload?.user?.userType ||
|
||||||
|
payload?.user?.user_type ||
|
||||||
|
'',
|
||||||
|
).trim().toLowerCase();
|
||||||
|
if (userType === 'external_user' || userType === 'external') return true;
|
||||||
|
|
||||||
|
const accountType = String(
|
||||||
|
payload?.accountType ||
|
||||||
|
payload?.account_type ||
|
||||||
|
payload?.user?.accountType ||
|
||||||
|
payload?.user?.account_type ||
|
||||||
|
'',
|
||||||
|
).trim().toLowerCase();
|
||||||
|
return accountType === 'external' || accountType === 'public';
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
const SESSION_COOKIE = 'nxtgauge_admin_session';
|
const SESSION_COOKIE = 'nxtgauge_admin_session';
|
||||||
|
const SESSION_VALUE = 'internal_management';
|
||||||
const SESSION_TTL_SECONDS = 60 * 60 * 12;
|
const SESSION_TTL_SECONDS = 60 * 60 * 12;
|
||||||
|
|
||||||
export function hasAdminSession(): boolean {
|
export function hasAdminSession(): boolean {
|
||||||
if (typeof document === 'undefined') return false;
|
if (typeof document === 'undefined') return false;
|
||||||
return document.cookie.split(';').some((entry) => entry.trim().startsWith(`${SESSION_COOKIE}=`));
|
return document.cookie.split(';').some((entry) => entry.trim() === `${SESSION_COOKIE}=${SESSION_VALUE}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAdminSession(): void {
|
export function setAdminSession(): void {
|
||||||
if (typeof document === 'undefined') return;
|
if (typeof document === 'undefined') return;
|
||||||
document.cookie = `${SESSION_COOKIE}=1; Path=/; Max-Age=${SESSION_TTL_SECONDS}; SameSite=Lax`;
|
document.cookie = `${SESSION_COOKIE}=${SESSION_VALUE}; Path=/; Max-Age=${SESSION_TTL_SECONDS}; SameSite=Lax`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearAdminSession(): void {
|
export function clearAdminSession(): void {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
import { A, useNavigate } from '@solidjs/router';
|
import { A } from '@solidjs/router';
|
||||||
import { setAdminSession } from '~/lib/admin-session';
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const skipToAdmin = () => {
|
|
||||||
setAdminSession();
|
|
||||||
navigate('/admin', { replace: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class="auth-page">
|
<main class="auth-page">
|
||||||
<div class="auth-bg" />
|
<div class="auth-bg" />
|
||||||
|
|
@ -18,7 +10,6 @@ export default function Home() {
|
||||||
<p class="auth-copy">Secure sign-in and runtime control center for NXTGAUGE operations.</p>
|
<p class="auth-copy">Secure sign-in and runtime control center for NXTGAUGE operations.</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<A class="btn primary" href="/login">Sign In</A>
|
<A class="btn primary" href="/login">Sign In</A>
|
||||||
<button type="button" class="btn" onClick={skipToAdmin}>Skip to Admin</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
import { createMemo, createSignal, onMount } from 'solid-js';
|
import { createMemo, createSignal, onMount } from 'solid-js';
|
||||||
|
import { isExternalIdentity, pickManagementLoginError } from '~/lib/admin-auth';
|
||||||
import { hasAdminSession, setAdminSession } from '~/lib/admin-session';
|
import { hasAdminSession, setAdminSession } from '~/lib/admin-session';
|
||||||
|
|
||||||
type AuthMode = 'login' | 'reset';
|
type AuthMode = 'login' | 'reset';
|
||||||
|
|
@ -97,6 +98,50 @@ export default function LoginPage() {
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
|
const loginPayload = {
|
||||||
|
email: email().trim().toLowerCase(),
|
||||||
|
password: password(),
|
||||||
|
loginTarget: 'admin',
|
||||||
|
};
|
||||||
|
|
||||||
|
const attempts: string[] = [
|
||||||
|
'/api/gateway/users/auth/internal/login',
|
||||||
|
'/api/gateway/auth/internal/login',
|
||||||
|
];
|
||||||
|
|
||||||
|
let payload: any = {};
|
||||||
|
let status = 500;
|
||||||
|
let success = false;
|
||||||
|
for (const url of attempts) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
'x-portal-target': 'admin',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(loginPayload),
|
||||||
|
});
|
||||||
|
status = response.status;
|
||||||
|
payload = await response.json().catch(() => ({}));
|
||||||
|
if (response.ok) {
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
const fallback = status === 502
|
||||||
|
? 'Auth service is temporarily unavailable (502). Please retry in 1-2 minutes.'
|
||||||
|
: 'Sign in failed.';
|
||||||
|
throw new Error(pickManagementLoginError(payload) || fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExternalIdentity(payload)) {
|
||||||
|
throw new Error('External users are not allowed on management login. Please use the external user login.');
|
||||||
|
}
|
||||||
|
|
||||||
completeAdminLogin();
|
completeAdminLogin();
|
||||||
} catch (nextError: any) {
|
} catch (nextError: any) {
|
||||||
setError(String(nextError?.message || 'Sign in failed.'));
|
setError(String(nextError?.message || 'Sign in failed.'));
|
||||||
|
|
|
||||||
93
tests/e2e/admin-auth.spec.ts
Normal file
93
tests/e2e/admin-auth.spec.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Admin Auth Split', () => {
|
||||||
|
test('blocks external identities on internal management login', async ({ page }) => {
|
||||||
|
await page.route('**/api/gateway/users/auth/internal/login', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
audience: 'public',
|
||||||
|
user: {
|
||||||
|
audience: 'public',
|
||||||
|
user_type: 'external_user',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/login');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Employee Login' })).toBeVisible();
|
||||||
|
await page.getByPlaceholder('Enter your email').fill('external.user@example.com');
|
||||||
|
await page.getByPlaceholder('Enter your password').fill('StrongPass123!');
|
||||||
|
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
|
||||||
|
await expect(page.getByText('External users are not allowed on management login. Please use the external user login.')).toBeVisible();
|
||||||
|
await expect(page).toHaveURL(/\/login/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allows internal identities and lands on admin shell', async ({ page }) => {
|
||||||
|
await page.route('**/api/gateway/users/auth/internal/login', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
audience: 'admin',
|
||||||
|
user: {
|
||||||
|
audience: 'admin',
|
||||||
|
user_type: 'employee',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route('**/api/gateway/users/auth/me', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: 'admin-1',
|
||||||
|
audience: 'admin',
|
||||||
|
userType: 'employee',
|
||||||
|
role: { name: 'Super Admin' },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/login');
|
||||||
|
await expect(page.getByRole('heading', { name: 'Employee Login' })).toBeVisible();
|
||||||
|
await page.getByPlaceholder('Enter your email').fill('admin@nxtgauge.com');
|
||||||
|
await page.getByPlaceholder('Enter your password').fill('StrongPass123!');
|
||||||
|
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/admin/);
|
||||||
|
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('redirects back to login if admin session resolves as external identity', async ({ page }) => {
|
||||||
|
await page.context().addCookies([
|
||||||
|
{
|
||||||
|
name: 'nxtgauge_admin_session',
|
||||||
|
value: 'internal_management',
|
||||||
|
domain: '127.0.0.1',
|
||||||
|
path: '/',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.route('**/api/gateway/users/auth/me', async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
audience: 'public',
|
||||||
|
user_type: 'external_user',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto('/admin');
|
||||||
|
await expect(page).toHaveURL(/\/login\?from=%2Fadmin/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue