Compare commits

...

3 Commits

Author SHA1 Message Date
Thomas Violent
843c063649 Make database connection pool dynamic (#6166)
* Add min_idle and idle_timeout to database pool

* Update src/config.rs

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>

---------

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
2025-08-25 18:32:05 +02:00
Daniel
550b670dba Switch to GHA's concurrency control (#6164)
- removes the need to use a 3rd party action
2025-08-25 18:00:10 +02:00
Timshel
de808c5ad9 Fix Playwright docker (#6206) 2025-08-25 17:59:55 +02:00
11 changed files with 49 additions and 66 deletions

View File

@@ -10,32 +10,14 @@ on:
# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
- '[1-2].[0-9]+.[0-9]+' - '[1-2].[0-9]+.[0-9]+'
concurrency:
# Apply concurrency control only on the upstream repo
group: ${{ github.repository == 'dani-garcia/vaultwarden' && format('{0}-{1}', github.workflow, github.ref) || github.run_id }}
# Don't cancel other runs when creating a tag
cancel-in-progress: ${{ github.ref_type == 'branch' }}
jobs: jobs:
# https://github.com/marketplace/actions/skip-duplicate-actions
# Some checks to determine if we need to continue with building a new docker.
# We will skip this check if we are creating a tag, because that has the same hash as a previous run already.
skip_check:
# Only run this in the upstream repo and not on forks
if: ${{ github.repository == 'dani-garcia/vaultwarden' }}
name: Cancel older jobs when running
permissions:
actions: write
runs-on: ubuntu-24.04
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- name: Skip Duplicates Actions
id: skip_check
uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5.3.1
with:
cancel_others: 'true'
# Only run this when not creating a tag
if: ${{ github.ref_type == 'branch' }}
docker-build: docker-build:
needs: skip_check
if: ${{ needs.skip_check.outputs.should_skip != 'true' && github.repository == 'dani-garcia/vaultwarden' }}
name: Build Vaultwarden containers name: Build Vaultwarden containers
permissions: permissions:
packages: write packages: write

View File

@@ -91,17 +91,25 @@ When creating new scenario use the recorder to more easily identify elements
This does not start the server, you will need to start it manually. This does not start the server, you will need to start it manually.
```bash ```bash
npx playwright codegen "http://127.0.0.1:8000" DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden
npx playwright codegen "http://127.0.0.1:8003"
``` ```
## Override web-vault ## Override web-vault
It is possible to change the `web-vault` used by referencing a different `bw_web_builds` commit. It is possible to change the `web-vault` used by referencing a different `bw_web_builds` commit.
Simplest is to set and uncomment `PW_WV_REPO_URL` and `PW_WV_COMMIT_HASH` in the `test.env`.
Ensure that the image is built with:
```bash ```bash
export PW_WV_REPO_URL=https://github.com/Timshel/oidc_web_builds.git DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env build Vaultwarden
export PW_WV_COMMIT_HASH=8707dc76df3f0cceef2be5bfae37bb29bd17fae6 ```
DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env build Playwright
You can check the result running:
```bash
DOCKER_BUILDKIT=1 docker compose --profile playwright --env-file test.env up Vaultwarden
``` ```
# OpenID Connect test setup # OpenID Connect test setup

View File

@@ -1,6 +1,6 @@
FROM playwright_oidc_vaultwarden_prebuilt AS prebuilt FROM playwright_oidc_vaultwarden_prebuilt AS prebuilt
FROM node:22-bookworm AS build FROM node:22-trixie AS build
ARG REPO_URL ARG REPO_URL
ARG COMMIT_HASH ARG COMMIT_HASH
@@ -14,7 +14,7 @@ COPY build.sh /build.sh
RUN /build.sh RUN /build.sh
######################## RUNTIME IMAGE ######################## ######################## RUNTIME IMAGE ########################
FROM docker.io/library/debian:bookworm-slim FROM docker.io/library/debian:trixie-slim
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
@@ -24,7 +24,7 @@ RUN mkdir /data && \
--no-install-recommends \ --no-install-recommends \
ca-certificates \ ca-certificates \
curl \ curl \
libmariadb-dev-compat \ libmariadb-dev \
libpq5 \ libpq5 \
openssl && \ openssl && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*

View File

@@ -16,7 +16,6 @@ if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then
export VAULT_VERSION=$(cat Dockerfile | grep "ARG VAULT_VERSION" | cut -d "=" -f2) export VAULT_VERSION=$(cat Dockerfile | grep "ARG VAULT_VERSION" | cut -d "=" -f2)
./scripts/checkout_web_vault.sh ./scripts/checkout_web_vault.sh
./scripts/patch_web_vault.sh
./scripts/build_web_vault.sh ./scripts/build_web_vault.sh
printf '{"version":"%s"}' "$COMMIT_HASH" > ./web-vault/apps/web/build/vw-version.json printf '{"version":"%s"}' "$COMMIT_HASH" > ./web-vault/apps/web/build/vw-version.json

View File

@@ -26,9 +26,9 @@ export default defineConfig({
* But short action/nav/expect timeouts to fail on specific step (raise locally if not enough). * But short action/nav/expect timeouts to fail on specific step (raise locally if not enough).
*/ */
timeout: 120 * 1000, timeout: 120 * 1000,
actionTimeout: 10 * 1000, actionTimeout: 20 * 1000,
navigationTimeout: 10 * 1000, navigationTimeout: 20 * 1000,
expect: { timeout: 10 * 1000 }, expect: { timeout: 20 * 1000 },
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {

View File

@@ -66,6 +66,10 @@ SSO_CLIENT_SECRET=warden
SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM} SSO_AUTHORITY=http://${KC_HTTP_HOST}:${KC_HTTP_PORT}/realms/${TEST_REALM}
SSO_DEBUG_TOKENS=true SSO_DEBUG_TOKENS=true
# Custom web-vault build
# PW_WV_REPO_URL=https://github.com/dani-garcia/bw_web_builds.git
# PW_WV_COMMIT_HASH=a5f5390895516bce2f48b7baadb6dc399e5fe75a
########################### ###########################
# Docker MariaDb container# # Docker MariaDb container#
########################### ###########################

View File

@@ -12,7 +12,7 @@ export async function logNewUser(
test: Test, test: Test,
page: Page, page: Page,
user: { email: string, name: string, password: string }, user: { email: string, name: string, password: string },
options: { mailBuffer?: MailBuffer, override?: boolean } = {} options: { mailBuffer?: MailBuffer } = {}
) { ) {
await test.step(`Create user ${user.name}`, async () => { await test.step(`Create user ${user.name}`, async () => {
await page.context().clearCookies(); await page.context().clearCookies();
@@ -20,12 +20,8 @@ export async function logNewUser(
await test.step('Landing page', async () => { await test.step('Landing page', async () => {
await utils.cleanLanding(page); await utils.cleanLanding(page);
if( options.override ) { await page.locator("input[type=email].vw-email-sso").fill(user.email);
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: /Use single sign-on/ }).click();
} else {
await page.getByLabel(/Email address/).fill(user.email);
await page.getByRole('button', { name: /Use single sign-on/ }).click();
}
}); });
await test.step('Keycloak login', async () => { await test.step('Keycloak login', async () => {
@@ -69,7 +65,6 @@ export async function logUser(
user: { email: string, password: string }, user: { email: string, password: string },
options: { options: {
mailBuffer ?: MailBuffer, mailBuffer ?: MailBuffer,
override?: boolean,
totp?: OTPAuth.TOTP, totp?: OTPAuth.TOTP,
mail2fa?: boolean, mail2fa?: boolean,
} = {} } = {}
@@ -82,12 +77,8 @@ export async function logUser(
await test.step('Landing page', async () => { await test.step('Landing page', async () => {
await utils.cleanLanding(page); await utils.cleanLanding(page);
if( options.override ) { await page.locator("input[type=email].vw-email-sso").fill(user.email);
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: /Use single sign-on/ }).click();
} else {
await page.getByLabel(/Email address/).fill(user.email);
await page.getByRole('button', { name: /Use single sign-on/ }).click();
}
}); });
await test.step('Keycloak login', async () => { await test.step('Keycloak login', async () => {

View File

@@ -29,8 +29,8 @@ test('SSO login', async ({ page }) => {
test('Non SSO login', async ({ page }) => { test('Non SSO login', async ({ page }) => {
// Landing page // Landing page
await page.goto('/'); await page.goto('/');
await page.getByLabel(/Email address/).fill(users.user1.email); await page.locator("input[type=email].vw-email-sso").fill(users.user1.email);
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Other' }).click();
// Unlock page // Unlock page
await page.getByLabel('Master password').fill(users.user1.password); await page.getByLabel('Master password').fill(users.user1.password);
@@ -58,20 +58,12 @@ test('Non SSO login impossible', async ({ page, browser }, testInfo: TestInfo) =
// Landing page // Landing page
await page.goto('/'); await page.goto('/');
await page.getByLabel(/Email address/).fill(users.user1.email);
// Check that SSO login is available // Check that SSO login is available
await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(1); await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(1);
await page.getByLabel(/Email address/).fill(users.user1.email); // No Continue/Other
await page.getByRole('button', { name: 'Continue' }).click(); await expect(page.getByRole('button', { name: 'Other' })).toHaveCount(0);
// Unlock page
await page.getByLabel('Master password').fill(users.user1.password);
await page.getByRole('button', { name: 'Log in with master password' }).click();
// An error should appear
await page.getByLabel('SSO sign-in is required')
}); });
@@ -82,13 +74,12 @@ test('No SSO login', async ({ page }, testInfo: TestInfo) => {
// Landing page // Landing page
await page.goto('/'); await page.goto('/');
await page.getByLabel(/Email address/).fill(users.user1.email);
// No SSO button (rely on a correct selector checked in previous test) // No SSO button (rely on a correct selector checked in previous test)
await page.getByLabel('Master password');
await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(0); await expect(page.getByRole('button', { name: /Use single sign-on/ })).toHaveCount(0);
// Can continue to Master password // Can continue to Master password
await page.getByLabel(/Email address/).fill(users.user1.email);
await page.getByRole('button', { name: 'Continue' }).click(); await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('button', { name: /Log in with master password/ })).toHaveCount(1); await expect(page.getByRole('button', { name: 'Log in with master password' })).toHaveCount(1);
}); });

View File

@@ -65,7 +65,7 @@ test('Enforce password policy', async ({ page }) => {
await utils.logout(test, page, users.user1); await utils.logout(test, page, users.user1);
await test.step(`Unlock trigger policy`, async () => { await test.step(`Unlock trigger policy`, async () => {
await page.getByRole('textbox', { name: 'Email address (required)' }).fill(users.user1.email); await page.locator("input[type=email].vw-email-sso").fill(users.user1.email);
await page.getByRole('button', { name: 'Use single sign-on' }).click(); await page.getByRole('button', { name: 'Use single sign-on' }).click();
await page.getByRole('textbox', { name: 'Master password (required)' }).fill(users.user1.password); await page.getByRole('textbox', { name: 'Master password (required)' }).fill(users.user1.password);

View File

@@ -639,9 +639,15 @@ make_config! {
/// Timeout when acquiring database connection /// Timeout when acquiring database connection
database_timeout: u64, false, def, 30; database_timeout: u64, false, def, 30;
/// Database connection pool size /// Timeout in seconds before idle connections to the database are closed
database_idle_timeout: u64, false, def, 600;
/// Database connection max pool size
database_max_conns: u32, false, def, 10; database_max_conns: u32, false, def, 10;
/// Database connection min pool size
database_min_conns: u32, false, def, 2;
/// Database connection init |> SQL statements to run when creating a new database connection, mainly useful for connection-scoped pragmas. If empty, a database-specific default is used. /// Database connection init |> SQL statements to run when creating a new database connection, mainly useful for connection-scoped pragmas. If empty, a database-specific default is used.
database_conn_init: String, false, def, String::new(); database_conn_init: String, false, def, String::new();

View File

@@ -134,6 +134,8 @@ macro_rules! generate_connections {
let manager = ConnectionManager::new(&url); let manager = ConnectionManager::new(&url);
let pool = Pool::builder() let pool = Pool::builder()
.max_size(CONFIG.database_max_conns()) .max_size(CONFIG.database_max_conns())
.min_idle(Some(CONFIG.database_min_conns()))
.idle_timeout(Some(Duration::from_secs(CONFIG.database_idle_timeout())))
.connection_timeout(Duration::from_secs(CONFIG.database_timeout())) .connection_timeout(Duration::from_secs(CONFIG.database_timeout()))
.connection_customizer(Box::new(DbConnOptions{ .connection_customizer(Box::new(DbConnOptions{
init_stmts: conn_type.get_init_stmts() init_stmts: conn_type.get_init_stmts()