Show logs and usernames for deleted users and remove svg mimetype for client side

This commit is contained in:
thiloho
2024-09-14 15:12:08 +02:00
parent 79d1c9f5c7
commit 5f38500b9c
11 changed files with 119 additions and 95 deletions

View File

@@ -43,7 +43,7 @@ CREATE TABLE internal.website (
CREATE TABLE internal.media (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
website_id UUID REFERENCES internal.website (id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES internal.user (id) ON DELETE CASCADE NOT NULL DEFAULT (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id') ::UUID,
user_id UUID REFERENCES internal.user (id) ON DELETE SET NULL DEFAULT (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id') ::UUID,
blob BYTEA NOT NULL,
mimetype TEXT NOT NULL,
original_name TEXT NOT NULL,

View File

@@ -7,18 +7,17 @@ CREATE FUNCTION internal.check_role_exists ()
RETURNS TRIGGER
AS $$
BEGIN
IF NOT EXISTS (
IF (NOT EXISTS (
SELECT
1
FROM
pg_roles AS r
WHERE
r.rolname = NEW.role) THEN
r.rolname = NEW.role)) THEN
RAISE foreign_key_violation
USING message = 'Unknown database role: ' || NEW.role;
RETURN NULL;
END IF;
RETURN NEW;
RETURN NULL;
END
$$
LANGUAGE plpgsql;
@@ -67,42 +66,41 @@ DECLARE
_password_length_min CONSTANT INT := 12;
_password_length_max CONSTANT INT := 128;
BEGIN
CASE WHEN LENGTH(register.username)
NOT BETWEEN _username_length_min AND _username_length_max THEN
IF (LENGTH(register.username)
NOT BETWEEN _username_length_min AND _username_length_max) THEN
RAISE string_data_length_mismatch
USING message = FORMAT('Username must be between %s and %s characters long', _username_length_min, _username_length_max);
WHEN EXISTS (
ELSIF (EXISTS (
SELECT
1
FROM
internal.user AS u
WHERE
u.username = register.username) THEN
u.username = register.username)) THEN
RAISE unique_violation
USING message = 'Username is already taken';
WHEN LENGTH(register.pass) NOT BETWEEN _password_length_min AND _password_length_max THEN
ELSIF (LENGTH(register.pass)
NOT BETWEEN _password_length_min AND _password_length_max) THEN
RAISE string_data_length_mismatch
USING message = FORMAT('Password must be between %s and %s characters long', _password_length_min, _password_length_max);
WHEN register.pass !~ '[a-z]' THEN
ELSIF register.pass !~ '[a-z]' THEN
RAISE invalid_parameter_value
USING message = 'Password must contain at least one lowercase letter';
WHEN register.pass !~ '[A-Z]' THEN
ELSIF register.pass !~ '[A-Z]' THEN
RAISE invalid_parameter_value
USING message = 'Password must contain at least one uppercase letter';
WHEN register.pass !~ '[0-9]' THEN
ELSIF register.pass !~ '[0-9]' THEN
RAISE invalid_parameter_value
USING message = 'Password must contain at least one number';
WHEN register.pass !~ '[!@#$%^&*(),.?":{}|<>]' THEN
ELSIF register.pass !~ '[!@#$%^&*(),.?":{}|<>]' THEN
RAISE invalid_parameter_value
USING message = 'Password must contain at least one special character';
ELSE
INSERT
INTO internal.user (username, password_hash)
INSERT INTO internal.user (username, password_hash)
VALUES (register.username, register.pass)
RETURNING
id INTO user_id;
END
CASE;
END IF;
END;
$$
LANGUAGE plpgsql
@@ -120,7 +118,7 @@ BEGIN
IF _role IS NULL THEN
RAISE invalid_password
USING message = 'Invalid username or password';
END IF;
ELSE
SELECT
id INTO _user_id
FROM
@@ -130,6 +128,7 @@ BEGIN
_exp := EXTRACT(EPOCH FROM CLOCK_TIMESTAMP())::INTEGER + 86400;
SELECT
SIGN(JSON_BUILD_OBJECT('role', _role, 'user_id', _user_id, 'username', login.username, 'exp', _exp), CURRENT_SETTING('app.jwt_secret')) INTO token;
END IF;
END;
$$
LANGUAGE plpgsql
@@ -146,10 +145,11 @@ BEGIN
IF _role IS NULL THEN
RAISE invalid_password
USING message = 'Invalid password';
END IF;
ELSE
DELETE FROM internal.user AS u
WHERE u.username = _username;
was_deleted := TRUE;
END IF;
END;
$$
LANGUAGE plpgsql

View File

@@ -2,19 +2,32 @@
CREATE FUNCTION internal.update_last_modified ()
RETURNS TRIGGER
AS $$
DECLARE
_user_id UUID := (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID;
BEGIN
IF (NOT EXISTS (
SELECT
id
FROM
internal.user
WHERE
id = _user_id)) THEN
RETURN COALESCE(NEW, OLD);
END IF;
IF TG_OP != 'DELETE' THEN
NEW.last_modified_at = CLOCK_TIMESTAMP();
NEW.last_modified_by = (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID;
NEW.last_modified_by = _user_id;
END IF;
IF TG_TABLE_NAME != 'website' THEN
UPDATE
internal.website
SET
last_modified_at = CLOCK_TIMESTAMP(),
last_modified_by = (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID
last_modified_by = _user_id
WHERE
id = COALESCE(NEW.website_id, OLD.website_id);
END IF;
RETURN NEW;
RETURN COALESCE(NEW, OLD);
END;
$$
LANGUAGE plpgsql;

View File

@@ -3,18 +3,17 @@ CREATE FUNCTION internal.check_user_not_website_owner ()
RETURNS TRIGGER
AS $$
BEGIN
IF EXISTS (
IF (EXISTS (
SELECT
1
FROM
internal.website AS w
WHERE
w.id = NEW.website_id
AND w.user_id = NEW.user_id) THEN
w.id = NEW.website_id AND w.user_id = NEW.user_id)) THEN
RAISE foreign_key_violation
USING message = 'User cannot be added as a collaborator to their own website';
END IF;
RETURN NEW;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;

View File

@@ -16,21 +16,21 @@ BEGIN
IF OCTET_LENGTH($1) = 0 THEN
RAISE invalid_parameter_value
USING message = 'No file data was provided';
END IF;
IF _mimetype IS NULL OR _mimetype NOT IN (
ELSIF (_mimetype IS NULL
OR _mimetype NOT IN (
SELECT
UNNEST(_allowed_mimetypes)) THEN
UNNEST(_allowed_mimetypes))) THEN
RAISE invalid_parameter_value
USING message = 'Invalid MIME type. Allowed types are: png, jpg, webp';
END IF;
IF OCTET_LENGTH($1) > _max_file_size THEN
ELSIF OCTET_LENGTH($1) > _max_file_size THEN
RAISE program_limit_exceeded
USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
END IF;
ELSE
INSERT INTO internal.media (website_id, blob, mimetype, original_name)
VALUES (_website_id, $1, _mimetype, _original_filename)
RETURNING
id INTO file_id;
END IF;
END;
$$
LANGUAGE plpgsql

View File

@@ -4,7 +4,8 @@ CREATE EXTENSION hstore;
CREATE TABLE internal.change_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
website_id UUID REFERENCES internal.website (id) ON DELETE CASCADE,
user_id UUID REFERENCES internal.user (id) ON DELETE CASCADE DEFAULT (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id') ::UUID,
user_id UUID REFERENCES internal.user (id) ON DELETE SET NULL DEFAULT (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id') ::UUID,
username VARCHAR(16) NOT NULL DEFAULT (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'username'),
tstamp TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
table_name TEXT NOT NULL,
operation TEXT NOT NULL,
@@ -17,9 +18,16 @@ CREATE FUNCTION internal.track_changes ()
AS $$
DECLARE
_website_id UUID;
_user_id UUID := (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID;
BEGIN
IF (to_jsonb (OLD.*) - 'last_modified_at') = (to_jsonb (NEW.*) - 'last_modified_at') THEN
RETURN NEW;
IF (NOT EXISTS (
SELECT
id
FROM
internal.user
WHERE
id = _user_id) OR (to_jsonb (OLD.*) - 'last_modified_at' - 'last_modified_by') = (to_jsonb (NEW.*) - 'last_modified_at' - 'last_modified_by')) THEN
RETURN NULL;
END IF;
IF TG_TABLE_NAME = 'website' THEN
_website_id := NEW.id;
@@ -29,31 +37,28 @@ BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO internal.change_log (website_id, table_name, operation, new_value)
VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (NEW));
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
ELSIF (TG_OP = 'UPDATE'
AND EXISTS (
SELECT
id
FROM
internal.website
WHERE
id = _website_id) THEN
id = _website_id)) THEN
INSERT INTO internal.change_log (website_id, table_name, operation, old_value, new_value)
VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (OLD) - HSTORE (NEW), HSTORE (NEW) - HSTORE (OLD));
RETURN NEW;
ELSIF TG_OP = 'DELETE'
ELSIF (TG_OP = 'DELETE'
AND EXISTS (
SELECT
id
FROM
internal.website
WHERE
id = _website_id) THEN
id = _website_id)) THEN
INSERT INTO internal.change_log (website_id, table_name, operation, old_value)
VALUES (_website_id, TG_TABLE_NAME, TG_OP, HSTORE (OLD));
RETURN NEW;
END IF;
RETURN NEW;
RETURN NULL;
END;
$$
LANGUAGE plpgsql

View File

@@ -83,6 +83,7 @@ export interface ChangeLog {
id: string;
website_id: string | null;
user_id: string | null;
username: string;
tstamp: Date;
table_name: string;
operation: string;
@@ -93,6 +94,7 @@ export interface ChangeLogInput {
id?: string;
website_id?: string | null;
user_id?: string | null;
username?: string;
tstamp?: Date;
table_name: string;
operation: string;
@@ -105,6 +107,7 @@ const change_log = {
"id",
"website_id",
"user_id",
"username",
"tstamp",
"table_name",
"operation",
@@ -320,7 +323,7 @@ const legal_information = {
export interface Media {
id: string;
website_id: string;
user_id: string;
user_id: string | null;
blob: string;
mimetype: string;
original_name: string;
@@ -329,7 +332,7 @@ export interface Media {
export interface MediaInput {
id?: string;
website_id: string;
user_id?: string;
user_id?: string | null;
blob: string;
mimetype: string;
original_name: string;

View File

@@ -15,7 +15,7 @@ import type {
LegalInformation
} from "$lib/db-schema";
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "image/webp"];
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];
export const slugify = (string: string) => {
return string

View File

@@ -69,10 +69,10 @@
</h2>
<ul class="unpadded">
{#each data.collaborators as { website_id, user_id, permission_level, user: { username } } (`${website_id}-${user_id}`)}
{#each data.collaborators as { website_id, user_id, permission_level, user } (`${website_id}-${user_id}`)}
<li class="collaborator-card">
<p>
<strong>{username} ({permission_level})</strong>
<strong>{user?.username} ({permission_level})</strong>
</p>
<div class="collaborator-card__actions">

View File

@@ -9,10 +9,10 @@ export const load: PageServerLoad = async ({ parent, fetch, params, cookies, url
const searchParams = new URLSearchParams();
const baseFetchUrl = `${API_BASE_PREFIX}/change_log?website_id=eq.${params.websiteId}&select=id,table_name,operation,tstamp,old_value,new_value,user!inner(username)&order=tstamp.desc`;
const baseFetchUrl = `${API_BASE_PREFIX}/change_log?website_id=eq.${params.websiteId}&select=id,table_name,operation,tstamp,old_value,new_value,user_id,username&order=tstamp.desc`;
if (userFilter && userFilter !== "all") {
searchParams.append("user.username", `eq.${userFilter}`);
searchParams.append("username", `eq.${userFilter}`);
}
if (resourceFilter && resourceFilter !== "all") {

View File

@@ -126,9 +126,13 @@
</tr>
</thead>
<tbody>
{#each data.changeLog as { id, table_name, operation, tstamp, old_value, new_value, user }}
{#each data.changeLog as { id, table_name, operation, tstamp, old_value, new_value, user_id, username }}
<tr>
<td>{user.username}</td>
<td>
<span style:text-decoration={user_id ? "" : "line-through"}>
{username}
</span>
</td>
<td>{table_name}</td>
<td>{operation}</td>
<td>