mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Update policies for collaborators for stricter rules
This commit is contained in:
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user