Skip to content

Commit 2dfbaf0

Browse files
authored
Merge pull request #336 from fractal-analytics-platform/users-crud
Admin area - users CRUD
2 parents 0c9e987 + 357dd76 commit 2dfbaf0

25 files changed

+1061
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ node_modules
1212
/tests/.auth
1313
coverage*/
1414
.nyc_output
15+
playwright-report/

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
This release requires fractal-server 1.4.0.
66

7+
* Added user profile page (\#336).
8+
* Added admin area with users management (\#336).
79
* Added Jobs button in home page (\#346).
810
* Improved jobs table layout for small screens (\#346).
911
* Fixed jobs status badge color bug (\#346).

src/hooks.server.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FRACTAL_SERVER_HOST } from '$env/static/private';
2+
import { error } from '@sveltejs/kit';
23

34
export async function handle({ event, resolve }) {
45
console.log(`[${event.request.method}] - ${event.url.pathname}`);
@@ -17,16 +18,29 @@ export async function handle({ event, resolve }) {
1718
const fastApiUsersAuth = event.cookies.get('fastapiusersauth');
1819
if (!fastApiUsersAuth) {
1920
console.log('Authentication required - No auth cookie found - Redirecting to login');
20-
return new Response(null, { status: 302, headers: { location: '/auth/login?invalidate=true' } });
21+
return new Response(null, {
22+
status: 302,
23+
headers: { location: '/auth/login?invalidate=true' }
24+
});
2125
}
2226

2327
const whoami = await event.fetch(`${FRACTAL_SERVER_HOST}/auth/whoami/`);
24-
if (whoami.ok) {
25-
return await resolve(event);
26-
} else {
28+
if (!whoami.ok) {
2729
console.log('Validation of authentication - Error loading user info');
28-
return new Response(null, { status: 302, headers: { location: '/auth/login?invalidate=true' } });
30+
return new Response(null, {
31+
status: 302,
32+
headers: { location: '/auth/login?invalidate=true' }
33+
});
2934
}
35+
36+
if (event.url.pathname.startsWith('/admin')) {
37+
const user = await whoami.json();
38+
if (!user.is_superuser) {
39+
throw error(403, `Only superusers can access the admin area`);
40+
}
41+
}
42+
43+
return await resolve(event);
3044
}
3145

3246
/** @type {import('@sveltejs/kit').HandleFetch} */

src/lib/common/component_utilities.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ export function nullifyEmptyStrings(inputValues) {
121121
return clearedValues;
122122
}
123123

124+
/**
125+
* Removes null values from an object
126+
* @param {object} inputValues
127+
* @returns {object}
128+
*/
129+
export function removeNullValues(inputValues) {
130+
const clearedValues = {};
131+
for (let key in inputValues) {
132+
if (inputValues[key] !== null) {
133+
clearedValues[key] = inputValues[key];
134+
}
135+
}
136+
return clearedValues;
137+
}
138+
124139
/**
125140
* Replacer function to ignore empty strings when using JSON.stringify().
126141
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description}

src/lib/common/errors.js

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,11 @@ export class AlertError extends Error {
5555
* @returns {null | { loc: string[], msg: string }}
5656
*/
5757
function getSimpleValidationMessage(reason, statusCode) {
58-
if (
59-
statusCode !== 422 ||
60-
!('detail' in reason) ||
61-
!Array.isArray(reason.detail) ||
62-
reason.detail.length !== 1
63-
) {
58+
if (!isValidationError(reason, statusCode) || reason.detail.length !== 1) {
6459
return null;
6560
}
6661
const err = reason.detail[0];
67-
if (!Array.isArray(err.loc) || !err.msg || err.type !== 'value_error') {
62+
if (!isValueError(err)) {
6863
return null;
6964
}
7065
return {
@@ -73,6 +68,46 @@ function getSimpleValidationMessage(reason, statusCode) {
7368
};
7469
}
7570

71+
/**
72+
* @param {any} reason
73+
* @param {number | null} statusCode
74+
* @returns {null | { [key: string]: string }}
75+
*/
76+
export function getValidationMessagesMap(reason, statusCode) {
77+
if (!isValidationError(reason, statusCode) || reason.detail.length === 0) {
78+
return null;
79+
}
80+
/** @type {{[key: string]: string}} */
81+
const map = {};
82+
for (const error of reason.detail) {
83+
if (!isValueError(error)) {
84+
return null;
85+
}
86+
if (error.loc.length !== 2 || error.loc[0] !== 'body') {
87+
return null;
88+
}
89+
map[error.loc[1]] = error.msg;
90+
}
91+
return map;
92+
}
93+
94+
/**
95+
* @param {any} reason
96+
* @param {number | null} statusCode
97+
* @returns {boolean}
98+
*/
99+
function isValidationError(reason, statusCode) {
100+
return statusCode === 422 && 'detail' in reason && Array.isArray(reason.detail);
101+
}
102+
103+
/**
104+
* @param {any} err
105+
* @returns {boolean}
106+
*/
107+
function isValueError(err) {
108+
return Array.isArray(err.loc) && !!err.msg && err.type === 'value_error';
109+
}
110+
76111
/**
77112
* Display a standard error alert on the desired HTML element.
78113
* @param {any} error

0 commit comments

Comments
 (0)