From 226df14ad06c7f4edf32636d3f425454785fa636 Mon Sep 17 00:00:00 2001 From: Stavros Date: Thu, 16 Jul 2026 17:07:59 +0300 Subject: [PATCH] feat: add simple e2e tests --- .github/workflows/e2e.yml | 44 +++++ e2e/.gitignore | 7 + e2e/config.e2e.yaml | 22 +++ e2e/docker-compose.e2e.yml | 34 ++++ e2e/fixtures/auth.fixtures.ts | 47 +++++ e2e/package.json | 30 +++ e2e/playwright.config.ts | 46 +++++ e2e/pnpm-lock.yaml | 338 ++++++++++++++++++++++++++++++++++ e2e/specs/apps.spec.ts | 28 +++ e2e/specs/auth.spec.ts | 48 +++++ 10 files changed, 644 insertions(+) create mode 100644 .github/workflows/e2e.yml create mode 100644 e2e/.gitignore create mode 100644 e2e/config.e2e.yaml create mode 100644 e2e/docker-compose.e2e.yml create mode 100644 e2e/fixtures/auth.fixtures.ts create mode 100644 e2e/package.json create mode 100644 e2e/playwright.config.ts create mode 100644 e2e/pnpm-lock.yaml create mode 100644 e2e/specs/apps.spec.ts create mode 100644 e2e/specs/auth.spec.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..3aa4105e --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,44 @@ +name: Run e2e tests +on: + workflow_dispatch: + pull_request: + branches: + - main + +jobs: + test: + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7 + + - name: Setup pnpm + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6 + with: + package_json_file: ./e2e/package.json + + - name: Set up Docker + uses: docker/setup-docker-action@6d7cfa65f60a9dda7b46e5513fa982536f3c9877 # ratchet:docker/setup-docker-action@v5 + + - name: Install dependencies + run: npm install -g pnpm && pnpm install + with: + working-directory: e2e + + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + with: + working-directory: e2e + + - name: Run Playwright tests + run: pnpm exec playwright test + with: + working-directory: e2e + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: e2e/playwright-report/ + retention-days: 5 diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 00000000..276a277c --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,7 @@ +# playwright files +/node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/e2e/config.e2e.yaml b/e2e/config.e2e.yaml new file mode 100644 index 00000000..9ea3d482 --- /dev/null +++ b/e2e/config.e2e.yaml @@ -0,0 +1,22 @@ +appUrl: http://tinyauth.127.0.0.1.sslip.io + +log: + level: debug + +auth: + users: + # user1:password,user2:password,user3:password:token + - user1:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e + - user2:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e + - user3:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e:MVR4JQWNXYKNM6HHJEYEFP2O74QIIEJE + # disable rate limits for multiple workers to work + loginMaxRetries: 0 + +apps: + whoami: + config: + domain: whoami.127.0.0.1.sslip.io + path: + allow: /foo + users: + allow: user1 \ No newline at end of file diff --git a/e2e/docker-compose.e2e.yml b/e2e/docker-compose.e2e.yml new file mode 100644 index 00000000..280c2c90 --- /dev/null +++ b/e2e/docker-compose.e2e.yml @@ -0,0 +1,34 @@ +services: + traefik: + image: traefik:v3.6 + command: | + --api.insecure=true + --providers.docker + --entrypoints.web.address=:80 + ports: + - 80:80 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + whoami: + image: traefik/whoami:latest + labels: + traefik.enable: true + traefik.http.routers.whoami.rule: Host(`whoami.127.0.0.1.sslip.io`) + traefik.http.routers.whoami.middlewares: tinyauth + + tinyauth: + build: + context: ../ + dockerfile: Dockerfile + args: + - VERSION=e2e + - BUILD_TAGS=nomsgpack + - LD_FLAGS=-s -w + command: ["--configfile", "/app/config.yaml"] + volumes: + - ./config.e2e.yaml:/app/config.yaml:ro + labels: + traefik.enable: true + traefik.http.routers.tinyauth.rule: Host(`tinyauth.127.0.0.1.sslip.io`) + traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik \ No newline at end of file diff --git a/e2e/fixtures/auth.fixtures.ts b/e2e/fixtures/auth.fixtures.ts new file mode 100644 index 00000000..73b612fc --- /dev/null +++ b/e2e/fixtures/auth.fixtures.ts @@ -0,0 +1,47 @@ +import {expect, Page} from '@playwright/test'; +import { OTP } from 'otplib'; + +export class LoginFixture { + constructor(public readonly page: Page) {} + + async run(username: string, password: string) { + await expect(this.page.getByText('Welcome back, please login')).toBeVisible(); + await this.page.getByLabel('Username').fill(username); + await this.page.getByLabel('Password').fill(password); + await this.page.getByRole('button', { name: 'Login' }).click(); + } + + async expectSuccess(username: string) { + await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible() + } +} + +export class LogoutFixture { + constructor(public readonly page: Page) {} + + async run() { + await expect(this.page.getByText('Click the button below to logout.')).toBeVisible(); + await this.page.getByRole('button', { name: 'Logout' }).click(); + } + + async expectSuccess() { + await expect(this.page.getByText('Welcome back, please login')).toBeVisible(); + } +} + +export class TOTPFixture { + constructor(public readonly page: Page) {} + + async run(secret: string) { + await expect(this.page.getByText('Enter your TOTP code')).toBeVisible(); + const otp = new OTP(); + const token = await otp.generate({ secret }); + await this.page.getByPlaceholder('XXXXXX').fill(token); + // we shouldn't need to click continue, it will auto submit + // await this.page.getByRole('button', { name: 'Continue' }).click(); + } + + async expectSuccess(username: string) { + await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible() + } +} \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 00000000..b42e3b59 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,30 @@ +{ + "name": "e2e", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "down": "docker compose -f docker-compose.e2e.yml down", + "up": "docker compose -f docker-compose.e2e.yml up --build --pull=always --force-recreate --remove-orphans", + "test": "playwright test", + "report": "playwright show-report" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "^11.1.2", + "onFail": "download" + } + }, + "type": "module", + "devDependencies": { + "@playwright/test": "^1.61.1", + "@types/node": "^26.1.1" + }, + "dependencies": { + "otplib": "^13.4.1" + } +} diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts new file mode 100644 index 00000000..46c128a9 --- /dev/null +++ b/e2e/playwright.config.ts @@ -0,0 +1,46 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './specs', + fullyParallel: true, + forbidOnly: false, + retries: 0, + workers: 4, + reporter: 'html', + use: { + trace: 'on-first-retry', + video: 'on', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], + webServer: { + command: 'docker compose -f docker-compose.e2e.yml up --build --pull=always --force-recreate --remove-orphans', + url: 'http://tinyauth.127.0.0.1.sslip.io/api/healthz', + reuseExistingServer: true, + gracefulShutdown: { + signal: 'SIGINT', + timeout: 1000, + }, + }, +}); diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml new file mode 100644 index 00000000..93b50bea --- /dev/null +++ b/e2e/pnpm-lock.yaml @@ -0,0 +1,338 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + '@pnpm/exe': + specifier: ^11.1.2 + version: 11.13.1 + pnpm: + specifier: ^11.1.2 + version: 11.13.1 + +packages: + + '@pnpm/exe@11.13.1': + resolution: {integrity: sha512-P4euEK6lOFnd5oTHEc5M/HhvyF4XUhTnVsklEcM6rmY0QJxPD6xbT+u1+gskEIBp4nSRorz20IJQtAU1Nerggg==} + hasBin: true + + '@pnpm/linux-arm64@11.13.1': + resolution: {integrity: sha512-wB8zloqrYrudPyuA5qbuTCnJGe4eETPwqOjoPjoyyyvA4zFI5XfLpxgqOOcaY5UJBoqzckcGpRVDwhSRfsQ/6A==} + cpu: [arm64] + os: [linux] + + '@pnpm/linux-x64@11.13.1': + resolution: {integrity: sha512-A+wnEvzfWEvanXiwww3tnOPmtjPSrrf5tOP6vk8+K0BRFEe/Df0oPytm2nWgGcn5iwPnqtr1Btkof913McnSPA==} + cpu: [x64] + os: [linux] + + '@pnpm/linuxstatic-arm64@11.13.1': + resolution: {integrity: sha512-k4t65VeqRX4COMFe45TF58CVmCpmAsKZShaR1HobmUeleo98mWTctggKolrA2MHcVUeSS+12yB5Urb3uDazhmw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/linuxstatic-x64@11.13.1': + resolution: {integrity: sha512-A65GqPzwCl0bAMk3kRWfbjSRBm5RRaqR2oMxV/9AYZrwO0X9yEfngbLBISCPHjt6/Qe4nH7DFemyhy6yODYwEw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/macos-arm64@11.13.1': + resolution: {integrity: sha512-MJvOtyGOWSfBoqdVEfAH8ljmHs13mt82k/UxN4f+q7koDxJRR2n4Nie6Og6RwbnbaubCz0Fh2bTeL1+MxDSFpA==} + cpu: [arm64] + os: [darwin] + + '@pnpm/win-arm64@11.13.1': + resolution: {integrity: sha512-kl/g1cCKOJPe4HntspyrAJW0LRco0UHnVfxHSspezo4Zj4AanJAZ8WzLqfa6/w3lBSKHTEN4x0pb3m4J7B7Vpw==} + cpu: [arm64] + os: [win32] + + '@pnpm/win-x64@11.13.1': + resolution: {integrity: sha512-Bcb14NeBlbHS2Gq1qr8VnCiAz5eC1lYzXOls7zH0bnV0Taaj4/xyfm0HVO4dn9R2TQtVtz1qnBZHHL9PDFumqQ==} + cpu: [x64] + os: [win32] + + '@reflink/reflink-darwin-arm64@0.1.19': + resolution: {integrity: sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@reflink/reflink-darwin-x64@0.1.19': + resolution: {integrity: sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + resolution: {integrity: sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-arm64-musl@0.1.19': + resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@reflink/reflink-linux-x64-gnu@0.1.19': + resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@reflink/reflink-linux-x64-musl@0.1.19': + resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@reflink/reflink-win32-x64-msvc@0.1.19': + resolution: {integrity: sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@reflink/reflink@0.1.19': + resolution: {integrity: sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==} + engines: {node: '>= 10'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + pnpm@11.13.1: + resolution: {integrity: sha512-svx2g7imUlQU59E+G6KMqt3elr9m7FQL+ut+cCuB8+C+TR8pXt9/n+A5Z0Co3ORQnFgt33mJH0VD/qMtN2RfJQ==} + engines: {node: '>=22.13'} + hasBin: true + +snapshots: + + '@pnpm/exe@11.13.1': + dependencies: + '@reflink/reflink': 0.1.19 + detect-libc: 2.1.2 + optionalDependencies: + '@pnpm/linux-arm64': 11.13.1 + '@pnpm/linux-x64': 11.13.1 + '@pnpm/linuxstatic-arm64': 11.13.1 + '@pnpm/linuxstatic-x64': 11.13.1 + '@pnpm/macos-arm64': 11.13.1 + '@pnpm/win-arm64': 11.13.1 + '@pnpm/win-x64': 11.13.1 + + '@pnpm/linux-arm64@11.13.1': + optional: true + + '@pnpm/linux-x64@11.13.1': + optional: true + + '@pnpm/linuxstatic-arm64@11.13.1': + optional: true + + '@pnpm/linuxstatic-x64@11.13.1': + optional: true + + '@pnpm/macos-arm64@11.13.1': + optional: true + + '@pnpm/win-arm64@11.13.1': + optional: true + + '@pnpm/win-x64@11.13.1': + optional: true + + '@reflink/reflink-darwin-arm64@0.1.19': + optional: true + + '@reflink/reflink-darwin-x64@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-arm64-musl@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-gnu@0.1.19': + optional: true + + '@reflink/reflink-linux-x64-musl@0.1.19': + optional: true + + '@reflink/reflink-win32-arm64-msvc@0.1.19': + optional: true + + '@reflink/reflink-win32-x64-msvc@0.1.19': + optional: true + + '@reflink/reflink@0.1.19': + optionalDependencies: + '@reflink/reflink-darwin-arm64': 0.1.19 + '@reflink/reflink-darwin-x64': 0.1.19 + '@reflink/reflink-linux-arm64-gnu': 0.1.19 + '@reflink/reflink-linux-arm64-musl': 0.1.19 + '@reflink/reflink-linux-x64-gnu': 0.1.19 + '@reflink/reflink-linux-x64-musl': 0.1.19 + '@reflink/reflink-win32-arm64-msvc': 0.1.19 + '@reflink/reflink-win32-x64-msvc': 0.1.19 + + detect-libc@2.1.2: {} + + pnpm@11.13.1: {} + +--- +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + otplib: + specifier: ^13.4.1 + version: 13.4.1 + devDependencies: + '@playwright/test': + specifier: ^1.61.1 + version: 1.61.1 + '@types/node': + specifier: ^26.1.1 + version: 26.1.1 + +packages: + + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + + '@otplib/core@13.4.1': + resolution: {integrity: sha512-KIXgK1hNtWJEBMTastbe1bpmuais+3f+ATeO8TkMs2rNkfGO1FbQy8+/UWVEu3TR/iTJerU0idkPudaPmLP2BA==} + + '@otplib/hotp@13.4.1': + resolution: {integrity: sha512-g9q04SwpG5ZtMnVkUcgcoAlwCH4YLROZN1qhyBwgkBzqYYVSYhpP6gSGaxGHwePLt1c+e6NqDlgIZN+e1/XPuA==} + + '@otplib/plugin-base32-scure@13.4.1': + resolution: {integrity: sha512-Fs/r5qisC05SRhT6xWXaypB6PVC0vgWf6zztmi0J5RnQ09OJiPDWCJFH6cDm6ANsrdvB9di7X+Jb7L13BoEbUA==} + + '@otplib/plugin-crypto-noble@13.4.1': + resolution: {integrity: sha512-PJfVW8/1hdS6CfxLheKPZSLTwDq4TijZbN4yRjxlv0ODdzmxpM+wGwWr1JXMdy0xJPxLziydQD5gdVqrR4/gAg==} + + '@otplib/totp@13.4.1': + resolution: {integrity: sha512-QOkBVPrf6AM4qZaReZPSk9/I8ATVdZpIISJz115MqeVtcrbcr5llPZ0J7804tpnjnp1vCRkI5Qjd47HhgVteBQ==} + + '@otplib/uri@13.4.1': + resolution: {integrity: sha512-xaIm7bvICMhoB2rZIR5luiaMdssWR5nY5nXnR1fdezUgZuEO58D6zrGzLp7pQuBmlpmL0HagnscDQFoskp9yiA==} + + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} + engines: {node: '>=18'} + hasBin: true + + '@scure/base@2.2.0': + resolution: {integrity: sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==} + + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + otplib@13.4.1: + resolution: {integrity: sha512-o5CxfDw6bh7hoDv0NUUIcc0RqzJ9ipfUrzeKheKJ+vs4rXZnDlA9n4a/7R1cDjpmLjKLix4BgNVRmoDkm5rLSQ==} + + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} + engines: {node: '>=18'} + hasBin: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + +snapshots: + + '@noble/hashes@2.2.0': {} + + '@otplib/core@13.4.1': {} + + '@otplib/hotp@13.4.1': + dependencies: + '@otplib/core': 13.4.1 + '@otplib/uri': 13.4.1 + + '@otplib/plugin-base32-scure@13.4.1': + dependencies: + '@otplib/core': 13.4.1 + '@scure/base': 2.2.0 + + '@otplib/plugin-crypto-noble@13.4.1': + dependencies: + '@noble/hashes': 2.2.0 + '@otplib/core': 13.4.1 + + '@otplib/totp@13.4.1': + dependencies: + '@otplib/core': 13.4.1 + '@otplib/hotp': 13.4.1 + '@otplib/uri': 13.4.1 + + '@otplib/uri@13.4.1': + dependencies: + '@otplib/core': 13.4.1 + + '@playwright/test@1.61.1': + dependencies: + playwright: 1.61.1 + + '@scure/base@2.2.0': {} + + '@types/node@26.1.1': + dependencies: + undici-types: 8.3.0 + + fsevents@2.3.2: + optional: true + + otplib@13.4.1: + dependencies: + '@otplib/core': 13.4.1 + '@otplib/hotp': 13.4.1 + '@otplib/plugin-base32-scure': 13.4.1 + '@otplib/plugin-crypto-noble': 13.4.1 + '@otplib/totp': 13.4.1 + '@otplib/uri': 13.4.1 + + playwright-core@1.61.1: {} + + playwright@1.61.1: + dependencies: + playwright-core: 1.61.1 + optionalDependencies: + fsevents: 2.3.2 + + undici-types@8.3.0: {} diff --git a/e2e/specs/apps.spec.ts b/e2e/specs/apps.spec.ts new file mode 100644 index 00000000..f79d977b --- /dev/null +++ b/e2e/specs/apps.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from '@playwright/test'; +import {LoginFixture, LogoutFixture} from "../fixtures/auth.fixtures"; + +test('should be able to login to app with forward-auth', async ({ page }) => { + const loginFixture = new LoginFixture(page); + await page.goto('http://whoami.127.0.0.1.sslip.io'); + // redirect to tinyauth + await expect(page.getByText('Welcome back, please login')).toBeVisible() + await loginFixture.run('user1', 'password') + // redirect to app + await expect(page.getByText('whoami.127.0.0.1.sslip.io')).toBeVisible() +}); + +test('non authorized user should not be able to access app', async ({ page }) => { + const loginFixture = new LoginFixture(page); + await page.goto('http://whoami.127.0.0.1.sslip.io'); + // redirect to tinyauth + await expect(page.getByText('Welcome back, please login')).toBeVisible() + // user2 is not authorized to access app + await loginFixture.run('user2', 'password') + // redirect to app + await expect(page.getByText('The user with username user2 is not authorized to access the resource whoami.')).toBeVisible() +}) + +test('allowed path should skip authentication', async ({ page }) => { + await page.goto('http://whoami.127.0.0.1.sslip.io/foo'); + await expect(page.getByText('whoami.127.0.0.1.sslip.io')).toBeVisible() +}) \ No newline at end of file diff --git a/e2e/specs/auth.spec.ts b/e2e/specs/auth.spec.ts new file mode 100644 index 00000000..83eb1e96 --- /dev/null +++ b/e2e/specs/auth.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; +import {LoginFixture, LogoutFixture, TOTPFixture} from "../fixtures/auth.fixtures"; + +test('should be able to login', async ({ page }) => { + const loginFixture = new LoginFixture(page); + await page.goto('http://tinyauth.127.0.0.1.sslip.io'); + await loginFixture.run('user1', 'password') + await loginFixture.expectSuccess('user1') +}); + +test('should fail to login with wrong credentials', async ({ page }) => { + const loginFixture = new LoginFixture(page); + await page.goto('http://tinyauth.127.0.0.1.sslip.io'); + await loginFixture.run('user27267', 'password') + const toast = page.locator('[data-sonner-toast]').first(); + await expect(toast).toBeVisible(); + await expect(toast).toContainText('Failed to log in'); +}); + + +test('should be able to logout', async ({ page }) => { + const loginFixture = new LoginFixture(page); + await page.goto('http://tinyauth.127.0.0.1.sslip.io'); + await loginFixture.run('user1', 'password') + await loginFixture.expectSuccess('user1') + const logoutFixture = new LogoutFixture(page); + await logoutFixture.run() +}) + +test('should be able to login with totp', async ({ page }) => { + const loginFixture = new LoginFixture(page); + const totpFixture = new TOTPFixture(page); + await page.goto('http://tinyauth.127.0.0.1.sslip.io'); + await loginFixture.run('user3', 'password') + await totpFixture.run('MVR4JQWNXYKNM6HHJEYEFP2O74QIIEJE') + await loginFixture.expectSuccess('user3'); +}); + +test('should fail to login with wrong totp', async ({ page }) => { + const loginFixture = new LoginFixture(page); + const totpFixture = new TOTPFixture(page); + await page.goto('http://tinyauth.127.0.0.1.sslip.io'); + await loginFixture.run('user3', 'password') + await totpFixture.run('VZVMOMQCBN24DJ5VRFAL5TJAZGBHXMN3') + const toast = page.locator('[data-sonner-toast]').first(); + await expect(toast).toBeVisible(); + await expect(toast).toContainText('Failed to verify code'); +});