diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a72279722 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*.nix] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index e3ca2170f..d5f372378 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ www/.DS_Store www/node_modules npm-debug.log .data + +result +.nixos-test-history diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..0ac272541 --- /dev/null +++ b/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "blueprint": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1755332143, + "narHash": "sha256-jaiZPA5ND7HPJ4U/bzp+BKGOYR14+rIe9tC6XA4jBHU=", + "owner": "numtide", + "repo": "blueprint", + "rev": "3c8bf84e28df2be19cc6623cb3ceeb6fc0839b91", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "blueprint", + "type": "github" + } + }, + "nix-filter": { + "locked": { + "lastModified": 1757882181, + "narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=", + "owner": "numtide", + "repo": "nix-filter", + "rev": "59c44d1909c72441144b93cf0f054be7fe764de5", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "nix-filter", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1757808699, + "narHash": "sha256-rmyZ6B4DtU9MwkBSEf63NU4czFoM0budKcAtlhjaGEc=", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", + "type": "tarball", + "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre861038.c23193b943c6/nixexprs.tar.xz?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1" + }, + "original": { + "type": "tarball", + "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" + } + }, + "root": { + "inputs": { + "blueprint": "blueprint", + "nix-filter": "nix-filter", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..f21c41272 --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + inputs = { + nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"; + blueprint.url = "github:numtide/blueprint"; + blueprint.inputs.nixpkgs.follows = "nixpkgs"; + nix-filter.url = "github:numtide/nix-filter"; + }; + + outputs = + inputs: + inputs.blueprint { + inherit inputs; + prefix = "nix/"; + }; +} diff --git a/nix/checks/nixos.nix b/nix/checks/nixos.nix new file mode 100644 index 000000000..22839a0b5 --- /dev/null +++ b/nix/checks/nixos.nix @@ -0,0 +1,63 @@ +{ + pkgs, + flake, + perSystem, + ... +}: +flake.inputs.nixpkgs.lib.nixos.runTest { + name = "auth"; + hostPkgs = pkgs; + node.specialArgs = { inherit flake perSystem; }; + nodes.server = + { config, ... }: + { + imports = [ + (import flake.nixosModules.auth) + ]; + + virtualisation = { + forwardPorts = [ + { + from = "host"; + host.port = 13022; + guest.port = 22; + } + ]; + }; + services.openssh = { + enable = true; + }; + + services.auth = { + enable = true; + package = perSystem.self.default; + }; + + services.postgresql = { + enable = true; + enableTCPIP = true; + initialScript = pkgs.writeText "init-postgres-with-password" '' + CREATE USER supabase_admin LOGIN CREATEROLE CREATEDB REPLICATION BYPASSRLS; + + -- Supabase super admin + CREATE USER supabase_auth_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION PASSWORD 'secret'; + CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_auth_admin; + GRANT CREATE ON DATABASE postgres TO supabase_auth_admin; + ALTER USER supabase_auth_admin SET search_path = 'auth'; + ''; + authentication = '' + host supabase_auth_admin postgres samenet scram-sha-256 + ''; + }; + }; + testScript = + { nodes, ... }: + '' + start_all() + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("postgresql.service") + + server.wait_for_unit("gotrue.service") + ''; +} diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 000000000..92e50ad1b --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: +with pkgs; +mkShell { + packages = [ + go + ]; +} diff --git a/nix/modules/nixos/auth.nix b/nix/modules/nixos/auth.nix new file mode 100644 index 000000000..6cdcc1f8b --- /dev/null +++ b/nix/modules/nixos/auth.nix @@ -0,0 +1,146 @@ +{ + pkgs, + lib, + config, + ... +}: +let + cfg = config.services.auth; + default_settings = rec { + API_EXTERNAL_URL = "http://localhost:9999"; + DB_HOST = "localhost"; + DB_NAME = "postgres"; + DB_PASSWORD = "secret"; + DB_PORT = "5432"; + DB_USER = "supabase_auth_admin"; + DISABLE_SIGNUP = "false"; + DATABASE_URL = "postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"; + GOTRUE_API_EXTERNAL_URL = "http://localhost:9999"; + GOTRUE_DB_DRIVER = "postgres"; + GOTRUE_DB_HOST = DB_HOST; + GOTRUE_DB_NAME = DB_NAME; + GOTRUE_DB_PASSWORD = DB_PASSWORD; + GOTRUE_DB_PORT = DB_PORT; + GOTRUE_DB_USER = DB_USER; + GOTRUE_DISABLE_SIGNUP = "false"; + GOTRUE_JWT_DEFAULT_GROUP_NAME = "authenticated"; + GOTRUE_JWT_EXP = "3600"; + GOTRUE_JWT_SECRET = "your-super-secret-jwt-token-with-at-least-32-characters-long"; + GOTRUE_MAILER_AUTOCONFIRM = "true"; + + # Both v2 & v3 support reloading via signals, on linux this is SIGUSR1. + GOTRUE_RELOADING_SIGNAL_ENABLED = "true"; + GOTRUE_RELOADING_SIGNAL_NUMBER = "10"; + + # Both v2 & v3 disable the poller. While gotrue sets it to off by default we + # defensively set it to false here. + GOTRUE_RELOADING_POLLER_ENABLED = "false"; + + # Determines how much idle time must pass before triggering a reload. This + # ensures only 1 reload operation occurs during a burst of config updates. + GOTRUE_RELOADING_GRACE_PERIOD_INTERVAL = "2s"; + + # v3 does not use filesystem notifications for config reloads. + GOTRUE_RELOADING_NOTIFY_ENABLED = "false"; + + # TODO: remove duplicates? + GOTRUE_SITE_URL = "http://localhost:3000"; + GOTRUE_SMTP_ADMIN_EMAIL = "admin@example.com"; + GOTRUE_SMTP_HOST = "localhost"; + GOTRUE_SMTP_PASS = ""; + GOTRUE_SMTP_PORT = "2500"; + GOTRUE_SMTP_SENDER_NAME = "Supabase"; + GOTRUE_SMTP_USER = ""; + JWT_DEFAULT_GROUP_NAME = "authenticated"; + JWT_EXP = "3600"; + JWT_SECRET = "your-super-secret-jwt-token-with-at-least-32-characters-long"; + MAILER_AUTOCONFIRM = "true"; + SITE_URL = "http://localhost:3000"; + SMTP_ADMIN_EMAIL = "admin@example.com"; + SMTP_HOST = "localhost"; + SMTP_PASS = ""; + SMTP_PORT = "2500"; + SMTP_SENDER_NAME = "Supabase"; + SMTP_USER = ""; + }; + auth_env = pkgs.writeText "auth.env" ( + lib.concatStringsSep "\n" ( + (lib.mapAttrsToList (name: value: "${name}=${value}") (default_settings // cfg.settings)) + ) + ); +in +{ + options.services.auth = { + enable = lib.mkEnableOption "Supabase Auth Service"; + + package = lib.mkOption { + type = lib.types.package; + description = "The Supabase Auth package to use."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 9999; + description = "Port to run the auth service on."; + }; + + settings = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Configuration settings for the auth service."; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 9122 ]; + + users.users.gotrue = { + isSystemUser = true; + description = "gotrue service user"; + group = "gotrue"; + }; + users.groups.gotrue = { }; + + systemd.services.gotrue = { + description = "gotrue (auth)"; + wantedBy = [ "multi-user.target" ]; + after = [ "postgresql.service" ]; + serviceConfig = { + Type = "exec"; + WorkingDirectory = "/opt/gotrue"; + ExecStart = "${cfg.package}/bin/gotrue --config-dir /etc/auth.d"; + ExecReload = "${pkgs.coreutils}/bin/kill -10 $MAINPID"; + User = "gotrue"; + Restart = "always"; + RestartSec = 3; + MemoryAccounting = true; + MemoryMax = "50%"; + Slice = "services.slice"; + EnvironmentFile = [ + "/etc/gotrue/auth.env" + "-/etc/gotrue.generated.env" + "-/etc/gotrue.overrides.env" + ]; + # preStart = '' + # pg_isready -h ${config.auth.settings.DB_HOST} -p ${config.auth.settings.DB_PORT} -U ${config.auth.settings.DB_USER}; do sleep 1; done + # ''; + }; + unitConfig = { + StartLimitIntervalSec = 10; + StartLimitBurst = 5; + }; + }; + + systemd.tmpfiles.rules = [ + "d /etc/auth.d 0755 gotrue gotrue -" + "d /opt/gotrue 0755 gotrue gotrue -" + "C /etc/gotrue/auth.env 0440 gotrue gotrue - ${auth_env}" + ]; + }; +} + +# TODO: initialization steps as activation script? +# - Wait for database to be ready: +# until pg_isready -h ${config.auth.settings.DB_HOST} -p ${config.auth.settings.DB_PORT} -U ${config.auth.settings.DB_USER}; do sleep 1; done +# - Run migrations if they exist: +# if [ -d migrations ]; then go run main.go migrate up; fi diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 000000000..d68987066 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,42 @@ +{ + flake, + inputs, + pkgs, + ... +}: +let + filter = inputs.nix-filter.lib; +in +pkgs.buildGoModule { + pname = "supabase-auth"; + version = "2.180.0"; + src = filter { + root = flake; + include = [ + (filter.matchExt "go") + "go.mod" + "go.sum" + "client" + "cmd" + "internal" + "migrations" + "tools" + ]; + }; + + vendorHash = "sha256-knYvNkEVffWisvb4Dhm5qqtqQ4co9MGoNt6yH6dUll8="; + + buildFlags = [ + "-tags" + "netgo" + ]; + + # we cannot run test in the sandbox as tests rely on postgresql tcp connection + doCheck = false; + + subPackages = [ "." ]; + + postInstall = '' + mv $out/bin/auth $out/bin/gotrue + ''; +}