Skip to content

Commit c0c4050

Browse files
committed
Improved handling of users not verified and without profile (except superusers)
1 parent 554c408 commit c0c4050

File tree

9 files changed

+174
-20
lines changed

9 files changed

+174
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Merged "My Profile" and "My settings" pages;
1414
* Merged user settings into user (\#862);
1515
* Adapted to OAuth2 changes (\#862);
16+
* Handled user without profile as not verified users (\#862);
1617

1718
# 1.20.0
1819

src/hooks.server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export async function handle({ event, resolve }) {
8383
if (userInfo === null) {
8484
logger.debug('Authentication required - No auth cookie found - Redirecting to login');
8585
redirect(302, '/auth/login?invalidate=true');
86-
} else if (userInfo.profile_id === null) {
87-
logger.debug('User without profile - Redirecting to home');
86+
} else if (!userInfo.is_superuser && (userInfo.profile_id === null || !userInfo.is_verified)) {
87+
logger.debug('User not verified or without profile - Redirecting to home');
8888
redirect(302, '/');
8989
}
9090
}

src/routes/+layout.svelte

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@
124124
const server = $derived(page.data.serverInfo || {});
125125
const warningBanner = $derived(page.data.warningBanner);
126126
const userEmail = $derived(userLoggedIn ? page.data.userInfo.email : undefined);
127-
const isVerified = $derived(userLoggedIn && page.data.userInfo.is_verified);
128127
</script>
129128
130129
<main>
@@ -216,19 +215,6 @@
216215
</div>
217216
</div>
218217
{/if}
219-
{#if userLoggedIn && !isVerified}
220-
<div class="container mt-3">
221-
<div class="row">
222-
<div class="col">
223-
<div class="alert alert-warning">
224-
<i class="bi bi-exclamation-triangle"></i>
225-
<strong>Warning</strong>: as a non-verified user, you have limited access; please
226-
contact an admin.
227-
</div>
228-
</div>
229-
</div>
230-
</div>
231-
{/if}
232218
{@render children?.()}
233219
<div class="d-flex flex-column min-vh-100 min-vw-100 loading" class:show={$navigating || loading}>
234220
<div class="d-flex flex-grow-1 justify-content-center align-items-center">

src/routes/+page.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
</script>
2424

2525
<div class="container mt-3">
26-
{#if userInfo && userInfo.profile_id === null}
26+
{#if userInfo && !userInfo.is_superuser && (userInfo.profile_id === null || !userInfo.is_verified)}
2727
<div class="alert alert-warning">
28+
<i class="bi bi-exclamation-triangle"></i>
2829
This user is not authorized to use this Fractal instance - please contact {env.PUBLIC_FRACTAL_ADMIN_SUPPORT_EMAIL}.
2930
</div>
3031
{/if}

src/routes/auth/login/+page.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,13 @@
9090
{#if oauth2Provider}
9191
<h2 class="accordion-header">
9292
<button
93-
class="accordion-button collapsed"
93+
class="accordion-button"
9494
type="button"
9595
data-bs-toggle="collapse"
9696
data-bs-target="#localLoginCollapse"
9797
aria-expanded="false"
9898
aria-controls="localLoginCollapse"
99+
class:collapsed={!form?.invalidMessage}
99100
>
100101
Log in with username & password
101102
</button>
@@ -104,7 +105,7 @@
104105
<div
105106
id="localLoginCollapse"
106107
class="accordion-collapse collapse"
107-
class:show={!oauth2Provider}
108+
class:show={!oauth2Provider || form?.invalidMessage}
108109
>
109110
<div class="accordion-body">
110111
<form method="POST">
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { expect, test } from '@playwright/test';
2+
import { login, logout, waitModal, waitPageLoading } from './utils.js';
3+
4+
// Reset storage state for this file to avoid being authenticated
5+
test.use({ storageState: { cookies: [], origins: [] } });
6+
7+
test('Admin without profile', async ({ page }) => {
8+
const randomValue = Math.random().toString(36).substring(7);
9+
const randomEmail = `${randomValue}@example.com`;
10+
11+
await test.step('Login as admin and create test user', async () => {
12+
await login(page, '[email protected]', '1234');
13+
await page.goto('/v2/admin/users/register');
14+
await waitPageLoading(page);
15+
await page.getByRole('textbox', { name: 'E-mail' }).fill(randomEmail);
16+
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('1234');
17+
await page.getByRole('textbox', { name: 'Confirm password' }).fill('1234');
18+
await page.getByRole('textbox', { name: 'Project dir' }).fill('/tmp');
19+
await page.getByRole('button', { name: 'Save' }).first().click();
20+
await page.waitForURL(/\/v2\/admin\/users\/\d+\/edit/);
21+
});
22+
23+
await test.step('Grant superuser privileges and unset the profile', async () => {
24+
await page.getByLabel('Superuser').check();
25+
26+
// Unset the profile
27+
await page
28+
.getByRole('combobox', { name: 'Select resource' })
29+
.selectOption('Select resource...');
30+
await expect(page.getByRole('combobox', { name: 'Select profile' })).toBeDisabled();
31+
await expect(page.getByRole('combobox', { name: 'Select profile' })).toHaveValue('');
32+
33+
await page.getByRole('button', { name: 'Save' }).click();
34+
35+
const modal = await waitModal(page);
36+
await modal.getByRole('button', { name: 'Confirm' }).click();
37+
38+
await expect(page.getByText('User successfully updated')).toBeVisible();
39+
});
40+
41+
await test.step('Login as test user', async () => {
42+
await logout(page, '[email protected]');
43+
44+
await page.goto('/auth/login');
45+
await waitPageLoading(page);
46+
await page.getByRole('button', { name: 'Log in with username & password' }).click();
47+
await page.getByLabel('Email address').fill(randomEmail);
48+
await page.getByLabel('Password').fill('1234');
49+
await page.getByRole('button', { name: 'Log in', exact: true }).click();
50+
51+
await page.waitForURL(/\/v2\/projects/);
52+
53+
await expect(page.getByText(/Forbidden access/)).toBeVisible();
54+
});
55+
56+
await test.step('Set user profile', async () => {
57+
await page.goto('/v2/admin/users');
58+
await waitPageLoading(page);
59+
60+
await page.getByRole('row', { name: randomEmail }).getByRole('link', { name: 'Edit' }).click();
61+
62+
await page.getByRole('combobox', { name: 'Select resource' }).selectOption('Local resource');
63+
await page.getByRole('combobox', { name: 'Select profile' }).selectOption('Local profile');
64+
await page.getByRole('button', { name: 'Save' }).click();
65+
await expect(page.getByText('User successfully updated')).toBeVisible();
66+
});
67+
68+
await test.step('Check projects page', async () => {
69+
await page.goto('/v2/projects');
70+
await waitPageLoading(page);
71+
72+
await expect(page.getByRole('button', { name: 'Create new project' })).toBeVisible();
73+
await logout(page, randomEmail);
74+
});
75+
});

tests/user_not_active.spec.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect, test } from '@playwright/test';
2+
import { login, logout, waitPageLoading } from './utils.js';
3+
4+
// Reset storage state for this file to avoid being authenticated
5+
test.use({ storageState: { cookies: [], origins: [] } });
6+
7+
test('User not active', async ({ page }) => {
8+
const randomValue = Math.random().toString(36).substring(7);
9+
const randomEmail = `${randomValue}@example.com`;
10+
11+
await test.step('Login as admin and create test user', async () => {
12+
await login(page, '[email protected]', '1234');
13+
await page.goto('/v2/admin/users/register');
14+
await waitPageLoading(page);
15+
await page.getByRole('textbox', { name: 'E-mail' }).fill(randomEmail);
16+
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('1234');
17+
await page.getByRole('textbox', { name: 'Confirm password' }).fill('1234');
18+
await page.getByRole('combobox', { name: 'Select resource' }).selectOption('Local resource');
19+
await page.getByRole('combobox', { name: 'Select profile' }).selectOption('Local profile');
20+
await page.getByRole('textbox', { name: 'Project dir' }).fill('/tmp');
21+
await page.getByRole('button', { name: 'Save' }).first().click();
22+
await page.waitForURL(/\/v2\/admin\/users\/\d+\/edit/);
23+
24+
// Uncheck the active checkbox
25+
await page.getByLabel('Active').uncheck();
26+
await page.getByRole('button', { name: 'Save' }).click();
27+
await expect(page.getByText('User successfully updated')).toBeVisible();
28+
});
29+
30+
await test.step('Login as test user', async () => {
31+
await logout(page, '[email protected]');
32+
33+
await page.goto('/auth/login');
34+
await waitPageLoading(page);
35+
await page.getByRole('button', { name: 'Log in with username & password' }).click();
36+
await page.getByLabel('Email address').fill(randomEmail);
37+
await page.getByLabel('Password').fill('1234');
38+
await page.getByRole('button', { name: 'Log in', exact: true }).click();
39+
40+
await expect(page.getByText('Invalid credentials')).toBeVisible();
41+
});
42+
});

tests/user_not_verified.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { expect, test } from '@playwright/test';
2+
import { login, logout, waitPageLoading } from './utils.js';
3+
4+
// Reset storage state for this file to avoid being authenticated
5+
test.use({ storageState: { cookies: [], origins: [] } });
6+
7+
test('User without profile', async ({ page }) => {
8+
const randomValue = Math.random().toString(36).substring(7);
9+
const randomEmail = `${randomValue}@example.com`;
10+
11+
await test.step('Login as admin and create test user', async () => {
12+
await login(page, '[email protected]', '1234');
13+
await page.goto('/v2/admin/users/register');
14+
await waitPageLoading(page);
15+
await page.getByRole('textbox', { name: 'E-mail' }).fill(randomEmail);
16+
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('1234');
17+
await page.getByRole('textbox', { name: 'Confirm password' }).fill('1234');
18+
await page.getByRole('combobox', { name: 'Select resource' }).selectOption('Local resource');
19+
await page.getByRole('combobox', { name: 'Select profile' }).selectOption('Local profile');
20+
await page.getByRole('textbox', { name: 'Project dir' }).fill('/tmp');
21+
await page.getByRole('button', { name: 'Save' }).click();
22+
await page.waitForURL(/\/v2\/admin\/users\/\d+\/edit/);
23+
24+
// Uncheck the verified checkbox
25+
await page.getByLabel('Verified').uncheck();
26+
await page.getByRole('button', { name: 'Save' }).click();
27+
await expect(page.getByText('User successfully updated')).toBeVisible();
28+
});
29+
30+
await test.step('Login as test user', async () => {
31+
await logout(page, '[email protected]');
32+
33+
await page.goto('/auth/login');
34+
await waitPageLoading(page);
35+
await page.getByRole('button', { name: 'Log in with username & password' }).click();
36+
await page.getByLabel('Email address').fill(randomEmail);
37+
await page.getByLabel('Password').fill('1234');
38+
await page.getByRole('button', { name: 'Log in', exact: true }).click();
39+
40+
await page.waitForURL('/');
41+
42+
await expect(
43+
page.getByText(/This user is not authorized to use this Fractal instance/)
44+
).toBeVisible();
45+
46+
await logout(page, randomEmail);
47+
});
48+
});

tests/v2/user_without_profile.spec.js renamed to tests/user_without_profile.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from '@playwright/test';
2-
import { login, logout, waitPageLoading } from '../utils.js';
2+
import { login, logout, waitPageLoading } from './utils.js';
33

44
// Reset storage state for this file to avoid being authenticated
55
test.use({ storageState: { cookies: [], origins: [] } });

0 commit comments

Comments
 (0)