Manage collaborators via RLS

This commit is contained in:
thiloho
2024-08-07 19:13:39 +02:00
parent 75aac7b1bc
commit fbadbb18a4
4 changed files with 105 additions and 193 deletions

View File

@@ -9,244 +9,157 @@ ALTER TABLE internal.article ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.footer ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.footer ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.collab ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.collab ENABLE ROW LEVEL SECURITY;
CREATE FUNCTION internal.user_has_website_access(website_id UUID, required_permission INTEGER DEFAULT 10)
RETURNS BOOLEAN AS $$
DECLARE
_user_id UUID;
_has_access BOOLEAN;
BEGIN
_user_id := (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID;
SELECT EXISTS (
SELECT 1
FROM internal.website
WHERE id = website_id AND owner_id = _user_id
) INTO _has_access;
IF _has_access THEN
RETURN _has_access;
END IF;
SELECT EXISTS (
SELECT 1
FROM internal.collab c
WHERE c.website_id = user_has_website_access.website_id
AND c.user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
AND c.permission_level >= user_has_website_access.required_permission
) INTO _has_access;
RETURN _has_access;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE POLICY view_user ON internal.user CREATE POLICY view_user ON internal.user
FOR SELECT FOR SELECT
USING (true); USING (true);
CREATE POLICY view_own_websites ON internal.website CREATE POLICY view_websites ON internal.website
FOR SELECT FOR SELECT
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (internal.user_has_website_access(id, 10));
CREATE POLICY update_own_website ON internal.website CREATE POLICY update_website ON internal.website
FOR UPDATE FOR UPDATE
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (internal.user_has_website_access(id, 20));
CREATE POLICY delete_own_website ON internal.website CREATE POLICY delete_website ON internal.website
FOR DELETE FOR DELETE
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (internal.user_has_website_access(id, 40));
CREATE POLICY view_own_media ON internal.media CREATE POLICY view_media ON internal.media
FOR SELECT FOR SELECT
USING (user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (internal.user_has_website_access(website_id, 10));
CREATE POLICY insert_own_media ON internal.media CREATE POLICY insert_media ON internal.media
FOR INSERT FOR INSERT
WITH CHECK ( WITH CHECK (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.media.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_own_settings ON internal.settings CREATE POLICY view_settings ON internal.settings
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.settings.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_own_settings ON internal.settings CREATE POLICY update_settings ON internal.settings
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.settings.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_own_header ON internal.header CREATE POLICY view_header ON internal.header
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.header.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_own_header ON internal.header CREATE POLICY update_header ON internal.header
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.header.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_own_home ON internal.home CREATE POLICY view_home ON internal.home
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.home.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_own_home ON internal.home CREATE POLICY update_home ON internal.home
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.home.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_own_articles ON internal.article CREATE POLICY view_articles ON internal.article
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.article.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_own_article ON internal.article CREATE POLICY update_article ON internal.article
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.article.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY delete_own_article ON internal.article CREATE POLICY delete_article ON internal.article
FOR DELETE FOR DELETE
USING ( USING (internal.user_has_website_access(website_id, 30));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.article.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY insert_own_article ON internal.article CREATE POLICY insert_article ON internal.article
FOR INSERT FOR INSERT
WITH CHECK ( WITH CHECK (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.article.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_own_footer ON internal.footer CREATE POLICY view_footer ON internal.footer
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.footer.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_own_footer ON internal.footer CREATE POLICY update_footer ON internal.footer
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 20));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.footer.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY view_collaborations ON internal.collab CREATE POLICY view_collaborations ON internal.collab
FOR SELECT FOR SELECT
USING ( USING (internal.user_has_website_access(website_id, 10));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.collab.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY insert_collaborations ON internal.collab CREATE POLICY insert_collaborations ON internal.collab
FOR INSERT FOR INSERT
WITH CHECK ( WITH CHECK (internal.user_has_website_access(website_id, 30));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.collab.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY update_collaborations ON internal.collab CREATE POLICY update_collaborations ON internal.collab
FOR UPDATE FOR UPDATE
USING ( USING (internal.user_has_website_access(website_id, 30));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.collab.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
CREATE POLICY delete_collaborations ON internal.collab CREATE POLICY delete_collaborations ON internal.collab
FOR DELETE FOR DELETE
USING ( USING (internal.user_has_website_access(website_id, 30));
EXISTS (
SELECT 1
FROM internal.website
WHERE internal.website.id = internal.collab.website_id
AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
)
);
-- migrate:down -- migrate:down
DROP POLICY view_user ON internal.user; DROP POLICY view_user ON internal.user;
DROP POLICY view_own_websites ON internal.website; DROP POLICY view_websites ON internal.website;
DROP POLICY delete_own_website ON internal.website; DROP POLICY delete_website ON internal.website;
DROP POLICY update_own_website ON internal.website; DROP POLICY update_website ON internal.website;
DROP POLICY view_own_media ON internal.media; DROP POLICY view_media ON internal.media;
DROP POLICY insert_own_media ON internal.media; DROP POLICY insert_media ON internal.media;
DROP POLICY view_own_settings ON internal.settings; DROP POLICY view_settings ON internal.settings;
DROP POLICY update_own_settings ON internal.settings; DROP POLICY update_settings ON internal.settings;
DROP POLICY view_own_header ON internal.header; DROP POLICY view_header ON internal.header;
DROP POLICY update_own_header ON internal.header; DROP POLICY update_header ON internal.header;
DROP POLICY view_own_home ON internal.home; DROP POLICY view_home ON internal.home;
DROP POLICY update_own_home ON internal.home; DROP POLICY update_home ON internal.home;
DROP POLICY view_own_articles ON internal.article; DROP POLICY view_articles ON internal.article;
DROP POLICY update_own_article ON internal.article; DROP POLICY update_article ON internal.article;
DROP POLICY delete_own_article ON internal.article; DROP POLICY delete_article ON internal.article;
DROP POLICY insert_own_article ON internal.article; DROP POLICY insert_article ON internal.article;
DROP POLICY view_own_footer ON internal.footer; DROP POLICY view_footer ON internal.footer;
DROP POLICY update_own_footer ON internal.footer; DROP POLICY update_footer ON internal.footer;
DROP POLICY view_collaborations ON internal.collab; DROP POLICY view_collaborations ON internal.collab;
DROP POLICY insert_collaborations ON internal.collab; DROP POLICY insert_collaborations ON internal.collab;
DROP POLICY update_collaborations ON internal.collab; DROP POLICY update_collaborations ON internal.collab;
DROP POLICY delete_collaborations ON internal.collab; DROP POLICY delete_collaborations ON internal.collab;
DROP FUNCTION internal.user_has_website_access(UUID, INTEGER);
ALTER TABLE internal.user DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.user DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.website DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.website DISABLE ROW LEVEL SECURITY;

View File

@@ -32,10 +32,7 @@
<div class="preview"> <div class="preview">
{#if fullPreview} {#if fullPreview}
<iframe <iframe src={previewContent} title="Preview"></iframe>
src="http://localhost:5173/user-websites/e6710116-f2b7-4318-82de-35a25d22ed2e/0015130f-3024-402b-8421-aaee4a6f0890/index.html"
title="Preview"
></iframe>
{:else} {:else}
{@html md.render(previewContent)} {@html md.render(previewContent)}
{/if} {/if}

View File

@@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ params, fetch, cookies, locals }) =
const websiteOverview = await websiteOverviewData.json(); const websiteOverview = await websiteOverviewData.json();
generateStaticFiles(websiteOverview, locals.user.id, params.websiteId); generateStaticFiles(websiteOverview);
return { return {
websiteOverview websiteOverview
@@ -30,16 +30,11 @@ export const actions: Actions = {
const data = await request.formData(); const data = await request.formData();
const websiteOverview = JSON.parse(data.get("website-overview") as string); const websiteOverview = JSON.parse(data.get("website-overview") as string);
generateStaticFiles(websiteOverview, locals.user.id, params.websiteId, false); generateStaticFiles(websiteOverview, false);
} }
}; };
const generateStaticFiles = async ( const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
websiteData: any,
userId: string,
websiteId: string,
isPreview: boolean = true
) => {
const templatePath = join( const templatePath = join(
process.cwd(), process.cwd(),
"..", "..",
@@ -60,7 +55,7 @@ const generateStaticFiles = async (
: `<img src="https://picsum.photos/32/32" />` : `<img src="https://picsum.photos/32/32" />`
) )
.replace("{{title}}", `<h1>${websiteData.title}</h1>`) .replace("{{title}}", `<h1>${websiteData.title}</h1>`)
.replace("{{main_content}}", md.render(websiteData.main_content)) .replace("{{main_content}}", md.render(websiteData.main_content || ""))
.replace( .replace(
"{{articles}}", "{{articles}}",
websiteData.articles websiteData.articles
@@ -81,14 +76,20 @@ const generateStaticFiles = async (
}) })
.join("") .join("")
) )
.replace("{{additional_text}}", md.render(websiteData.additional_text)); .replace("{{additional_text}}", md.render(websiteData.additional_text || ""));
let uploadDir = ""; let uploadDir = "";
if (isPreview) { if (isPreview) {
uploadDir = join(process.cwd(), "static", "user-websites", userId, websiteId); uploadDir = join(
process.cwd(),
"static",
"user-websites",
websiteData.owner_id,
websiteData.id
);
} else { } else {
uploadDir = join("/", "var", "www", "archtika-websites", userId, websiteId); uploadDir = join("/", "var", "www", "archtika-websites", websiteData.owner_id, websiteData.id);
} }
await mkdir(uploadDir, { recursive: true }); await mkdir(uploadDir, { recursive: true });
@@ -110,8 +111,8 @@ const generateStaticFiles = async (
.replace("{{cover_image}}", `<img src="https://picsum.photos/600/200" />`) .replace("{{cover_image}}", `<img src="https://picsum.photos/600/200" />`)
.replace("{{title}}", `<h1>${article.title}</h1>`) .replace("{{title}}", `<h1>${article.title}</h1>`)
.replace("{{publication_date}}", `<p>${article.publication_date}</p>`) .replace("{{publication_date}}", `<p>${article.publication_date}</p>`)
.replace("{{main_content}}", md.render(article.main_content)) .replace("{{main_content}}", md.render(article.main_content || ""))
.replace("{{additional_text}}", md.render(websiteData.additional_text)); .replace("{{additional_text}}", md.render(websiteData.additional_text || ""));
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents); await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
} }

View File

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