Update policies for collaborators for stricter rules

This commit is contained in:
thiloho
2024-08-08 16:30:01 +02:00
parent bcc26322d3
commit 837729c83c
8 changed files with 85 additions and 26 deletions

View File

@@ -22,9 +22,9 @@ CREATE TABLE internal.user (
CREATE TABLE internal.website ( CREATE TABLE internal.website (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_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 CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL, content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL,
title VARCHAR(50) NOT NULL CHECK (trim(title) <> ''), title VARCHAR(50) NOT NULL CHECK (trim(title) != ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
@@ -56,14 +56,14 @@ CREATE TABLE internal.header (
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL, last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL,
CONSTRAINT logo_content_check CHECK ( CONSTRAINT logo_content_check CHECK (
(logo_type = 'text' AND logo_text IS NOT NULL AND trim(logo_text) <> '') OR (logo_type = 'text' AND logo_text IS NOT NULL AND trim(logo_text) != '') OR
(logo_type = 'image' AND logo_image IS NOT NULL) (logo_type = 'image' AND logo_image IS NOT NULL)
) )
); );
CREATE TABLE internal.home ( CREATE TABLE internal.home (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
main_content TEXT NOT NULL CHECK (trim(main_content) <> ''), main_content TEXT NOT NULL CHECK (trim(main_content) != ''),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
@@ -72,12 +72,12 @@ CREATE TABLE internal.article (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL, website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES internal.user(id) ON DELETE SET NULL, user_id UUID REFERENCES internal.user(id) ON DELETE SET NULL,
title VARCHAR(100) NOT NULL CHECK (trim(title) <> ''), title VARCHAR(100) NOT NULL CHECK (trim(title) != ''),
meta_description VARCHAR(250) CHECK (trim(meta_description) <> ''), meta_description VARCHAR(250) CHECK (trim(meta_description) != ''),
meta_author VARCHAR(100) CHECK (trim(meta_author) <> ''), meta_author VARCHAR(100) CHECK (trim(meta_author) != ''),
cover_image UUID REFERENCES internal.media(id) ON DELETE SET NULL, cover_image UUID REFERENCES internal.media(id) ON DELETE SET NULL,
publication_date DATE NOT NULL DEFAULT CURRENT_DATE, publication_date DATE NOT NULL DEFAULT CURRENT_DATE,
main_content TEXT CHECK (trim(main_content) <> ''), main_content TEXT CHECK (trim(main_content) != ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
@@ -85,7 +85,7 @@ CREATE TABLE internal.article (
CREATE TABLE internal.footer ( CREATE TABLE internal.footer (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) <> ''), additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) != ''),
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );

View File

@@ -23,7 +23,7 @@ EXECUTE FUNCTION internal.check_role_exists();
CREATE FUNCTION CREATE FUNCTION
internal.encrypt_pass() RETURNS TRIGGER AS $$ internal.encrypt_pass() RETURNS TRIGGER AS $$
BEGIN BEGIN
IF TG_OP = 'INSERT' OR NEW.password_hash <> OLD.password_hash THEN IF TG_OP = 'INSERT' OR NEW.password_hash != OLD.password_hash THEN
NEW.password_hash = crypt(NEW.password_hash, gen_salt('bf')); NEW.password_hash = crypt(NEW.password_hash, gen_salt('bf'));
END IF; END IF;
RETURN NEW; RETURN NEW;

View File

@@ -17,7 +17,7 @@ WITH (security_invoker = on)
AS AS
SELECT SELECT
id, id,
owner_id, user_id,
content_type, content_type,
title, title,
created_at, created_at,

View File

@@ -20,7 +20,7 @@ BEGIN
SELECT EXISTS ( SELECT EXISTS (
SELECT 1 SELECT 1
FROM internal.website FROM internal.website
WHERE id = website_id AND owner_id = _user_id WHERE id = website_id AND user_id = _user_id
) INTO _has_access; ) INTO _has_access;
IF _has_access THEN IF _has_access THEN
@@ -103,7 +103,15 @@ USING (internal.user_has_website_access(website_id, 20));
CREATE POLICY delete_article ON internal.article CREATE POLICY delete_article ON internal.article
FOR DELETE FOR DELETE
USING (internal.user_has_website_access(website_id, 30)); USING (
internal.user_has_website_access(website_id, 30)
OR
(
internal.user_has_website_access(website_id, 20)
AND
user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY insert_article ON internal.article CREATE POLICY insert_article ON internal.article
FOR INSERT FOR INSERT
@@ -125,15 +133,48 @@ USING (internal.user_has_website_access(website_id, 10));
CREATE POLICY insert_collaborations ON internal.collab CREATE POLICY insert_collaborations ON internal.collab
FOR INSERT FOR INSERT
WITH CHECK (internal.user_has_website_access(website_id, 30)); WITH CHECK (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
true
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
false
END
);
CREATE POLICY update_collaborations ON internal.collab CREATE POLICY update_collaborations ON internal.collab
FOR UPDATE FOR UPDATE
USING (internal.user_has_website_access(website_id, 30)); USING (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
true
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
false
END
);
CREATE POLICY delete_collaborations ON internal.collab CREATE POLICY delete_collaborations ON internal.collab
FOR DELETE FOR DELETE
USING (internal.user_has_website_access(website_id, 30)); USING (
CASE
WHEN internal.user_has_website_access(website_id, 40) THEN
TRUE
WHEN internal.user_has_website_access(website_id, 30) THEN
(user_id != (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID)
AND
(permission_level < 30)
ELSE
FALSE
END
);
-- migrate:down -- migrate:down

View File

@@ -4,7 +4,7 @@ WITH (security_invoker = on)
AS AS
SELECT SELECT
w.id, w.id,
w.owner_id, w.user_id,
w.content_type, w.content_type,
w.title, w.title,
s.accent_color_light_theme, s.accent_color_light_theme,

View File

@@ -0,0 +1,24 @@
-- migrate:up
CREATE FUNCTION internal.check_user_not_website_owner()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM internal.website
WHERE id = NEW.website_id AND 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;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE CONSTRAINT TRIGGER check_user_not_website_owner
AFTER INSERT ON internal.collab
FOR EACH ROW
EXECUTE FUNCTION internal.check_user_not_website_owner();
-- migrate:down
DROP TRIGGER check_user_not_website_owner ON internal.collab;
DROP FUNCTION internal.check_user_not_website_owner();

View File

@@ -81,15 +81,9 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
let uploadDir = ""; let uploadDir = "";
if (isPreview) { if (isPreview) {
uploadDir = join( uploadDir = join(process.cwd(), "static", "user-websites", websiteData.user_id, websiteData.id);
process.cwd(),
"static",
"user-websites",
websiteData.owner_id,
websiteData.id
);
} else { } else {
uploadDir = join("/", "var", "www", "archtika-websites", websiteData.owner_id, websiteData.id); uploadDir = join("/", "var", "www", "archtika-websites", websiteData.user_id, websiteData.id);
} }
await mkdir(uploadDir, { recursive: true }); await mkdir(uploadDir, { recursive: true });

View File

@@ -12,7 +12,7 @@
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
title={data.website.title} title={data.website.title}
previewContent="http://localhost:5173/user-websites/{data.websiteOverview.owner_id}/{data previewContent="http://localhost:5173/user-websites/{data.websiteOverview.user_id}/{data
.websiteOverview.id}/index.html" .websiteOverview.id}/index.html"
fullPreview={true} fullPreview={true}
> >