1 { system ? builtins.currentSystem
3 , pkgs ? import ../.. { inherit system config; }
7 # * Set up a vaultwarden server
8 # * Have Firefox use the web vault to create an account, log in, and save a password to the valut
9 # * Have the bw cli log in and read that password from the vault
11 # Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured)
13 # The same tests should work without modification on the official bitwarden server, if we ever package that.
15 with import ../lib/testing-python.nix { inherit system pkgs; };
18 backends = [ "sqlite" "mysql" "postgresql" ];
20 dbPassword = "please_dont_hack";
22 userEmail = "meow@example.com";
23 userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page
25 storedPassword = "seeeecret";
27 makeVaultwardenTest = backend: makeTest {
28 name = "vaultwarden-${backend}";
30 maintainers = with pkgs.lib.maintainers; [ jjjollyjim ];
34 server = { pkgs, ... }:
39 initialScript = pkgs.writeText "mysql-init.sql" ''
40 CREATE DATABASE bitwarden;
41 CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}';
42 GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost';
45 package = pkgs.mariadb;
48 services.vaultwarden.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden";
50 systemd.services.vaultwarden.after = [ "mysql.service" ];
54 services.postgresql = {
56 initialScript = pkgs.writeText "postgresql-init.sql" ''
57 CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}';
58 CREATE DATABASE bitwarden WITH OWNER bitwardenuser;
62 services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden";
64 systemd.services.vaultwarden.after = [ "postgresql.service" ];
71 backendConfig.${backend}
73 services.vaultwarden = {
77 rocketAddress = "0.0.0.0";
82 networking.firewall.allowedTCPPorts = [ 80 ];
84 environment.systemPackages =
86 testRunner = pkgs.writers.writePython3Bin "test-runner"
88 libraries = [ pkgs.python3Packages.selenium ];
94 from selenium.webdriver.common.by import By
95 from selenium.webdriver import Firefox
96 from selenium.webdriver.firefox.options import Options
97 from selenium.webdriver.support.ui import WebDriverWait
98 from selenium.webdriver.support import expected_conditions as EC
101 options.add_argument('--headless')
102 driver = Firefox(options=options)
104 driver.implicitly_wait(20)
105 driver.get('http://localhost/#/register')
107 wait = WebDriverWait(driver, 10)
109 wait.until(EC.title_contains("Create account"))
111 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys(
114 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys(
117 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys(
120 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys(
123 if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected():
124 driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click()
126 driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click()
128 wait.until_not(EC.title_contains("Create account"))
130 driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click()
132 driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys(
135 driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click()
137 wait.until(EC.title_contains("Vaults"))
139 driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click()
141 driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
144 driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys(
148 driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
151 [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
156 client = { pkgs, ... }:
158 environment.systemPackages = [ pkgs.bitwarden-cli ];
164 server.wait_for_unit("vaultwarden.service")
165 server.wait_for_open_port(80)
167 with subtest("configure the cli"):
168 client.succeed("bw --nointeraction config server http://server")
170 with subtest("can't login to nonexistent account"):
172 "bw --nointeraction --raw login ${userEmail} ${userPassword}"
175 with subtest("use the web interface to sign up, log in, and save a password"):
176 server.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner")
178 with subtest("log in with the cli"):
179 key = client.succeed(
180 "bw --nointeraction --raw login ${userEmail} ${userPassword}"
183 with subtest("sync with the cli"):
184 client.succeed(f"bw --nointeraction --raw --session {key} sync -f")
186 with subtest("get the password with the cli"):
187 password = client.succeed(
188 f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password"
190 assert password.strip() == "${storedPassword}"
194 builtins.listToAttrs (
196 (backend: { name = backend; value = makeVaultwardenTest backend; })