diff --git a/nix/deploy/qs/default.nix b/nix/deploy/qs/default.nix index 22e14dc..fad8b18 100644 --- a/nix/deploy/qs/default.nix +++ b/nix/deploy/qs/default.nix @@ -15,6 +15,10 @@ acmeEmail = "thilo.hohlt@tutanota.com"; dnsProvider = "porkbun"; dnsEnvironmentFile = /var/lib/porkbun.env; - disableRegistration = true; + settings = { + disableRegistration = true; + maxWebsiteStorageSize = 250; + maxUserWebsites = 3; + }; }; } diff --git a/nix/dev-vm.nix b/nix/dev-vm.nix index 4e8d313..42aef6c 100644 --- a/nix/dev-vm.nix +++ b/nix/dev-vm.nix @@ -52,6 +52,15 @@ postgresql = { enable = true; package = pkgs.postgresql_16; + /* + PL/Perl: + overrideAttrs ( + finalAttrs: previousAttrs: { + buildInputs = previousAttrs.buildInputs ++ [ pkgs.perl ]; + configureFlags = previousAttrs.configureFlags ++ [ "--with-perl" ]; + } + ); + */ ensureDatabases = [ "archtika" ]; authentication = lib.mkForce '' local all all trust diff --git a/nix/module.nix b/nix/module.nix index e14181d..84eeb8e 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -76,10 +76,26 @@ in description = "API secrets for the DNS-01 challenge (required for wildcard domains)."; }; - disableRegistration = mkOption { - type = types.bool; - default = false; - description = "By default any user can create an account. That behavior can be disabled by using this option."; + settings = mkOption { + type = types.submodule { + options = { + disableRegistration = mkOption { + type = types.bool; + default = false; + description = "By default any user can create an account. That behavior can be disabled by using this option."; + }; + maxUserWebsites = mkOption { + type = types.int; + default = 2; + description = "Maximum number of websites allowed per user by default."; + }; + maxWebsiteStorageSize = mkOption { + type = types.int; + default = 500; + description = "Maximum amount of disk space in MB allowed per user website by default."; + }; + }; + }; }; }; @@ -91,7 +107,7 @@ in users.groups.${cfg.group} = { }; - systemd.tmpfiles.rules = [ "d /var/www/archtika-websites 0755 ${cfg.user} ${cfg.group} -" ]; + systemd.tmpfiles.rules = [ "d /var/www/archtika-websites 0777 ${cfg.user} ${cfg.group} -" ]; systemd.services.archtika-api = { description = "archtika API service"; @@ -112,6 +128,8 @@ in JWT_SECRET=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c64) ${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.jwt_secret\" TO '$JWT_SECRET'" + ${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_storage_size\" TO ${toString cfg.settings.maxWebsiteStorageSize}" + ${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_number_user\" TO ${toString cfg.settings.maxUserWebsites}" ${pkgs.dbmate}/bin/dbmate --url postgres://postgres@localhost:5432/archtika?sslmode=disable --migrations-dir ${cfg.package}/rest-api/db/migrations up @@ -132,7 +150,7 @@ in }; script = '' - REGISTRATION_IS_DISABLED=${toString cfg.disableRegistration} BODY_SIZE_LIMIT=10M ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app + REGISTRATION_IS_DISABLED=${toString cfg.settings.disableRegistration} BODY_SIZE_LIMIT=10M ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app ''; }; @@ -189,7 +207,7 @@ in default_type application/json; ''; }; - "/api/rpc/register" = mkIf cfg.disableRegistration { + "/api/rpc/register" = mkIf cfg.settings.disableRegistration { extraConfig = '' deny all; ''; diff --git a/rest-api/db/migrations/20240719071602_main_tables.sql b/rest-api/db/migrations/20240719071602_main_tables.sql index a374607..fd0ef7c 100644 --- a/rest-api/db/migrations/20240719071602_main_tables.sql +++ b/rest-api/db/migrations/20240719071602_main_tables.sql @@ -29,7 +29,7 @@ ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; CREATE TABLE internal.user ( id UUID PRIMARY KEY DEFAULT gen_random_uuid (), - username VARCHAR(16) UNIQUE NOT NULL CHECK (LENGTH(username) >= 3), + username VARCHAR(16) UNIQUE NOT NULL CHECK (LENGTH(username) >= 3 AND username ~ '^[a-zA-Z0-9_-]+$'), password_hash CHAR(60) NOT NULL, user_role NAME NOT NULL DEFAULT 'authenticated_user', max_number_websites INT NOT NULL DEFAULT CURRENT_SETTING('app.website_max_number_user') ::INT, diff --git a/rest-api/db/migrations/20240720132802_exposed_views_functions.sql b/rest-api/db/migrations/20240720132802_exposed_views_functions.sql index 2835147..fa3a980 100644 --- a/rest-api/db/migrations/20240720132802_exposed_views_functions.sql +++ b/rest-api/db/migrations/20240720132802_exposed_views_functions.sql @@ -26,19 +26,7 @@ CREATE VIEW api.website WITH ( security_invoker = ON SELECT * FROM - internal.website AS w -WHERE - w.user_id = ( - CURRENT_SETTING( - 'request.jwt.claims', TRUE -)::JSON ->> 'user_id')::UUID - OR w.id IN ( - SELECT - c.website_id - FROM - internal.collab AS c - WHERE - c.user_id = (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID); + internal.website; CREATE VIEW api.settings WITH ( security_invoker = ON ) AS diff --git a/rest-api/db/migrations/20241006165029_administrator.sql b/rest-api/db/migrations/20241006165029_administrator.sql index 4eb0a9d..4e28b0b 100644 --- a/rest-api/db/migrations/20241006165029_administrator.sql +++ b/rest-api/db/migrations/20241006165029_administrator.sql @@ -146,22 +146,6 @@ CREATE TRIGGER _prevent_storage_excess_settings FOR EACH ROW EXECUTE FUNCTION internal.prevent_website_storage_size_excess (); -CREATE VIEW api.all_user_websites AS -SELECT - u.id AS user_id, - u.username, - u.created_at AS user_created_at, - u.max_number_websites, - COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT('id', w.id, 'title', w.title, 'max_storage_size', w.max_storage_size) - ORDER BY w.created_at DESC) FILTER (WHERE w.id IS NOT NULL), '[]'::JSONB) AS websites -FROM - internal.user AS u - LEFT JOIN internal.website AS w ON u.id = w.user_id -GROUP BY - u.id; - -GRANT SELECT ON api.all_user_websites TO administrator; - GRANT UPDATE (max_storage_size) ON internal.website TO administrator; GRANT UPDATE, DELETE ON internal.user TO administrator; @@ -193,8 +177,6 @@ DROP TRIGGER _prevent_storage_excess_settings ON internal.settings; DROP FUNCTION internal.prevent_website_storage_size_excess; -DROP VIEW api.all_user_websites; - REVOKE UPDATE (max_storage_size) ON internal.website FROM administrator; REVOKE UPDATE, DELETE ON internal.user FROM administrator; diff --git a/rest-api/db/migrations/20241011092744_filesystem_triggers.sql b/rest-api/db/migrations/20241011092744_filesystem_triggers.sql new file mode 100644 index 0000000..0369006 --- /dev/null +++ b/rest-api/db/migrations/20241011092744_filesystem_triggers.sql @@ -0,0 +1,52 @@ +-- migrate:up +CREATE FUNCTION internal.cleanup_filesystem () + RETURNS TRIGGER + AS $$ +DECLARE + _website_id UUID; + _domain_prefix VARCHAR(16); +BEGIN + IF TG_TABLE_NAME = 'website' THEN + _website_id := OLD.id; + SELECT + d.prefix INTO _domain_prefix + FROM + internal.domain_prefix AS d + WHERE + d.website_id = _website_id; + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s''', _website_id); + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s''', COALESCE(_domain_prefix, _website_id::VARCHAR)); + ELSE + _website_id := OLD.website_id; + SELECT + d.prefix INTO _domain_prefix + FROM + internal.domain_prefix AS d + WHERE + d.website_id = _website_id; + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s/legal-information.html''', _website_id); + EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s/legal-information.html''', COALESCE(_domain_prefix, _website_id::VARCHAR)); + END IF; + RETURN OLD; +END; +$$ +LANGUAGE plpgsql +SECURITY DEFINER; + +CREATE TRIGGER _cleanup_filesystem_website + BEFORE DELETE ON internal.website + FOR EACH ROW + EXECUTE FUNCTION internal.cleanup_filesystem (); + +CREATE TRIGGER _cleanup_filesystem_legal_information + BEFORE DELETE ON internal.legal_information + FOR EACH ROW + EXECUTE FUNCTION internal.cleanup_filesystem (); + +-- migrate:down +DROP TRIGGER _cleanup_filesystem_website ON internal.website; + +DROP TRIGGER _cleanup_filesystem_legal_information ON internal.legal_information; + +DROP FUNCTION internal.cleanup_filesystem; + diff --git a/web-app/src/lib/templates/blog/BlogIndex.svelte b/web-app/src/lib/templates/blog/BlogIndex.svelte index 2c66f11..9424d8f 100644 --- a/web-app/src/lib/templates/blog/BlogIndex.svelte +++ b/web-app/src/lib/templates/blog/BlogIndex.svelte @@ -32,7 +32,7 @@ {websiteUrl} /> -