From fbadbb18a40c10b77918ff766978e55d5466dcec Mon Sep 17 00:00:00 2001 From: thiloho <123883702+thiloho@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:13:39 +0200 Subject: [PATCH] Manage collaborators via RLS --- .../20240724191017_row_level_security.sql | 261 ++++++------------ .../src/lib/components/WebsiteEditor.svelte | 5 +- .../[websiteId]/publish/+page.server.ts | 29 +- .../website/[websiteId]/publish/+page.svelte | 3 +- 4 files changed, 105 insertions(+), 193 deletions(-) diff --git a/rest-api/db/migrations/20240724191017_row_level_security.sql b/rest-api/db/migrations/20240724191017_row_level_security.sql index 4599f47..e4e5f48 100644 --- a/rest-api/db/migrations/20240724191017_row_level_security.sql +++ b/rest-api/db/migrations/20240724191017_row_level_security.sql @@ -9,244 +9,157 @@ ALTER TABLE internal.article ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.footer 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 FOR SELECT USING (true); -CREATE POLICY view_own_websites ON internal.website +CREATE POLICY view_websites ON internal.website 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 -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 -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 -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 -WITH CHECK ( - 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 - ) -); +WITH CHECK (internal.user_has_website_access(website_id, 20)); -CREATE POLICY view_own_settings ON internal.settings +CREATE POLICY view_settings ON internal.settings FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); -CREATE POLICY update_own_settings ON internal.settings +CREATE POLICY update_settings ON internal.settings FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 20)); -CREATE POLICY view_own_header ON internal.header +CREATE POLICY view_header ON internal.header FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); -CREATE POLICY update_own_header ON internal.header +CREATE POLICY update_header ON internal.header FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 20)); -CREATE POLICY view_own_home ON internal.home +CREATE POLICY view_home ON internal.home FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); -CREATE POLICY update_own_home ON internal.home +CREATE POLICY update_home ON internal.home FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 20)); -CREATE POLICY view_own_articles ON internal.article +CREATE POLICY view_articles ON internal.article FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); -CREATE POLICY update_own_article ON internal.article +CREATE POLICY update_article ON internal.article FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 20)); -CREATE POLICY delete_own_article ON internal.article +CREATE POLICY delete_article ON internal.article FOR DELETE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 30)); -CREATE POLICY insert_own_article ON internal.article +CREATE POLICY insert_article ON internal.article FOR INSERT -WITH CHECK ( - 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 - ) -); +WITH CHECK (internal.user_has_website_access(website_id, 20)); -CREATE POLICY view_own_footer ON internal.footer +CREATE POLICY view_footer ON internal.footer FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); -CREATE POLICY update_own_footer ON internal.footer +CREATE POLICY update_footer ON internal.footer FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 20)); CREATE POLICY view_collaborations ON internal.collab FOR SELECT -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 10)); CREATE POLICY insert_collaborations ON internal.collab FOR INSERT -WITH CHECK ( - 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 - ) -); +WITH CHECK (internal.user_has_website_access(website_id, 30)); CREATE POLICY update_collaborations ON internal.collab FOR UPDATE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 30)); CREATE POLICY delete_collaborations ON internal.collab FOR DELETE -USING ( - 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 - ) -); +USING (internal.user_has_website_access(website_id, 30)); -- migrate:down DROP POLICY view_user ON internal.user; -DROP POLICY view_own_websites ON internal.website; -DROP POLICY delete_own_website ON internal.website; -DROP POLICY update_own_website ON internal.website; -DROP POLICY view_own_media ON internal.media; -DROP POLICY insert_own_media ON internal.media; -DROP POLICY view_own_settings ON internal.settings; -DROP POLICY update_own_settings ON internal.settings; -DROP POLICY view_own_header ON internal.header; -DROP POLICY update_own_header ON internal.header; -DROP POLICY view_own_home ON internal.home; -DROP POLICY update_own_home ON internal.home; -DROP POLICY view_own_articles ON internal.article; -DROP POLICY update_own_article ON internal.article; -DROP POLICY delete_own_article ON internal.article; -DROP POLICY insert_own_article ON internal.article; -DROP POLICY view_own_footer ON internal.footer; -DROP POLICY update_own_footer ON internal.footer; +DROP POLICY view_websites ON internal.website; +DROP POLICY delete_website ON internal.website; +DROP POLICY update_website ON internal.website; +DROP POLICY view_media ON internal.media; +DROP POLICY insert_media ON internal.media; +DROP POLICY view_settings ON internal.settings; +DROP POLICY update_settings ON internal.settings; +DROP POLICY view_header ON internal.header; +DROP POLICY update_header ON internal.header; +DROP POLICY view_home ON internal.home; +DROP POLICY update_home ON internal.home; +DROP POLICY view_articles ON internal.article; +DROP POLICY update_article ON internal.article; +DROP POLICY delete_article ON internal.article; +DROP POLICY insert_article ON internal.article; +DROP POLICY view_footer ON internal.footer; +DROP POLICY update_footer ON internal.footer; DROP POLICY view_collaborations ON internal.collab; DROP POLICY insert_collaborations ON internal.collab; DROP POLICY update_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.website DISABLE ROW LEVEL SECURITY; diff --git a/web-app/src/lib/components/WebsiteEditor.svelte b/web-app/src/lib/components/WebsiteEditor.svelte index 4570981..4c8b051 100644 --- a/web-app/src/lib/components/WebsiteEditor.svelte +++ b/web-app/src/lib/components/WebsiteEditor.svelte @@ -32,10 +32,7 @@
{#if fullPreview} - + {:else} {@html md.render(previewContent)} {/if} diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts index e84cf68..c430fb3 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts @@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ params, fetch, cookies, locals }) = const websiteOverview = await websiteOverviewData.json(); - generateStaticFiles(websiteOverview, locals.user.id, params.websiteId); + generateStaticFiles(websiteOverview); return { websiteOverview @@ -30,16 +30,11 @@ export const actions: Actions = { const data = await request.formData(); const websiteOverview = JSON.parse(data.get("website-overview") as string); - generateStaticFiles(websiteOverview, locals.user.id, params.websiteId, false); + generateStaticFiles(websiteOverview, false); } }; -const generateStaticFiles = async ( - websiteData: any, - userId: string, - websiteId: string, - isPreview: boolean = true -) => { +const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => { const templatePath = join( process.cwd(), "..", @@ -60,7 +55,7 @@ const generateStaticFiles = async ( : `` ) .replace("{{title}}", `

${websiteData.title}

`) - .replace("{{main_content}}", md.render(websiteData.main_content)) + .replace("{{main_content}}", md.render(websiteData.main_content || "")) .replace( "{{articles}}", websiteData.articles @@ -81,14 +76,20 @@ const generateStaticFiles = async ( }) .join("") ) - .replace("{{additional_text}}", md.render(websiteData.additional_text)); + .replace("{{additional_text}}", md.render(websiteData.additional_text || "")); let uploadDir = ""; if (isPreview) { - uploadDir = join(process.cwd(), "static", "user-websites", userId, websiteId); + uploadDir = join( + process.cwd(), + "static", + "user-websites", + websiteData.owner_id, + websiteData.id + ); } 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 }); @@ -110,8 +111,8 @@ const generateStaticFiles = async ( .replace("{{cover_image}}", ``) .replace("{{title}}", `

${article.title}

`) .replace("{{publication_date}}", `

${article.publication_date}

`) - .replace("{{main_content}}", md.render(article.main_content)) - .replace("{{additional_text}}", md.render(websiteData.additional_text)); + .replace("{{main_content}}", md.render(article.main_content || "")) + .replace("{{additional_text}}", md.render(websiteData.additional_text || "")); await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents); } diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte index 05638d5..64b12c6 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.svelte @@ -12,7 +12,8 @@