-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauthentication.html
More file actions
95 lines (93 loc) · 64.2 KB
/
Copy pathauthentication.html
File metadata and controls
95 lines (93 loc) · 64.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Authentication | Piwi Dashboard</title>
<meta name="description" content="A modern dashboard for storing and visualising Playwright test results">
<meta name="generator" content="VitePress v1.6.4">
<link rel="preload stylesheet" href="/assets/style.B-vXWchi.css" as="style">
<link rel="preload stylesheet" href="/vp-icons.css" as="style">
<script type="module" src="/assets/app.CPG0jj1k.js"></script>
<link rel="preload" href="/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
<link rel="modulepreload" href="/assets/chunks/theme.QnjLL7m_.js">
<link rel="modulepreload" href="/assets/chunks/framework.CAXxHpAX.js">
<link rel="modulepreload" href="/assets/authentication.md.CnU14reC.lean.js">
<meta property="og:type" content="website">
<meta property="og:title" content="Piwi Dashboard — A permanent home for your Playwright test results">
<meta property="og:description" content="Live dashboards, failure clustering, and flaky-test tracking for your whole team — self-hosted, no SaaS.">
<meta property="og:image" content="https://piwitests.github.io/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://piwitests.github.io">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Piwi Dashboard — A permanent home for your Playwright test results">
<meta name="twitter:description" content="Live dashboards, failure clustering, and flaky-test tracking for your whole team — self-hosted, no SaaS.">
<meta name="twitter:image" content="https://piwitests.github.io/og-image.png">
<script id="check-dark-mode">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"auto",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
<script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
</head>
<body>
<div id="app"><div class="Layout" data-v-d8b57b2d><!--[--><!--]--><!--[--><span tabindex="-1" data-v-fcbfc0e0></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-fcbfc0e0>Skip to content</a><!--]--><!----><header class="VPNav" data-v-d8b57b2d data-v-7ad780c2><div class="VPNavBar" data-v-7ad780c2 data-v-9fd4d1dd><div class="wrapper" data-v-9fd4d1dd><div class="container" data-v-9fd4d1dd><div class="title" data-v-9fd4d1dd><div class="VPNavBarTitle has-sidebar" data-v-9fd4d1dd data-v-9f43907a><a class="title" href="/" data-v-9f43907a><!--[--><!--]--><!----><span data-v-9f43907a>Piwi Dashboard</span><!--[--><!--]--></a></div></div><div class="content" data-v-9fd4d1dd><div class="content-body" data-v-9fd4d1dd><!--[--><!--]--><div class="VPNavBarSearch search" data-v-9fd4d1dd><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-9fd4d1dd data-v-afb2845e><span id="main-nav-aria-label" class="visually-hidden" data-v-afb2845e> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Home</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/getting-started.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Getting started</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/reporter.html" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Reporter</span><!--]--></a><!--]--><!--[--><a class="VPLink link vp-external-link-icon VPNavBarMenuLink" href="https://piwitests.github.io/demo/docs" target="_blank" rel="noreferrer" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>API docs</span><!--]--></a><!--]--><!--[--><a class="VPLink link vp-external-link-icon VPNavBarMenuLink" href="https://piwitests.github.io/demo/" target="_blank" rel="noreferrer" tabindex="0" data-v-afb2845e data-v-815115f5><!--[--><span data-v-815115f5>Demo</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-9fd4d1dd data-v-3f90c1a5><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-3f90c1a5 data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-9fd4d1dd data-v-ef6192dc data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/PiwiTests/platform" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-9fd4d1dd data-v-f953d92f data-v-bfe7971f><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-bfe7971f><span class="vpi-more-horizontal icon" data-v-bfe7971f></span></button><div class="menu" data-v-bfe7971f><div class="VPMenu" data-v-bfe7971f data-v-20ed86d6><!----><!--[--><!--[--><!----><div class="group" data-v-f953d92f><div class="item appearance" data-v-f953d92f><p class="label" data-v-f953d92f>Appearance</p><div class="appearance-action" data-v-f953d92f><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-f953d92f data-v-be9742d9 data-v-b4ccac88><span class="check" data-v-b4ccac88><span class="icon" data-v-b4ccac88><!--[--><span class="vpi-sun sun" data-v-be9742d9></span><span class="vpi-moon moon" data-v-be9742d9></span><!--]--></span></span></button></div></div></div><div class="group" data-v-f953d92f><div class="item social-links" data-v-f953d92f><div class="VPSocialLinks social-links-list" data-v-f953d92f data-v-e71e869c><!--[--><a class="VPSocialLink no-icon" href="https://github.com/PiwiTests/platform" aria-label="github" target="_blank" rel="noopener" data-v-e71e869c data-v-60a9a2d3><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-9fd4d1dd data-v-6bee1efd><span class="container" data-v-6bee1efd><span class="top" data-v-6bee1efd></span><span class="middle" data-v-6bee1efd></span><span class="bottom" data-v-6bee1efd></span></span></button></div></div></div></div><div class="divider" data-v-9fd4d1dd><div class="divider-line" data-v-9fd4d1dd></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-d8b57b2d data-v-2488c25a><div class="container" data-v-2488c25a><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2488c25a><span class="vpi-align-left menu-icon" data-v-2488c25a></span><span class="menu-text" data-v-2488c25a>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-2488c25a data-v-6b867909><button data-v-6b867909>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-d8b57b2d data-v-42c4c606><div class="curtain" data-v-42c4c606></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-42c4c606><span class="visually-hidden" id="sidebar-aria-label" data-v-42c4c606> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0" data-v-51288d80 data-v-0009425e><!----><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/getting-started.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Getting started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/comparison.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Why Piwi? (comparison & FAQ)</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ui-overview.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>UI overview</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/reporter.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Reporter</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/capture-fixtures.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Capture fixtures</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Features</h2><!----></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ai-diagnosis.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>AI diagnosis & clustering</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/flaky-tests.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Flaky tests & analytics</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/notifications.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Notifications & alerts</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0 has-active" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Configuration</h2><!----></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/configuration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Configuration reference</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/authentication.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Authentication</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/storage.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Storage configuration</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/deployment.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Deployment</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-51288d80><section class="VPSidebarItem level-0" data-v-51288d80 data-v-0009425e><div class="item" role="button" tabindex="0" data-v-0009425e><div class="indicator" data-v-0009425e></div><h2 class="text" data-v-0009425e>Integrate</h2><!----></div><div class="items" data-v-0009425e><!--[--><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link vp-external-link-icon link" href="https://piwitests.github.io/demo/docs" target="_blank" rel="noreferrer" data-v-0009425e><!--[--><p class="text" data-v-0009425e>API docs (interactive)</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/mcp.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>MCP server</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/ide-integration.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Open in IDE</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-0009425e data-v-0009425e><div class="item" data-v-0009425e><div class="indicator" data-v-0009425e></div><a class="VPLink link link" href="/backend-logs.html" data-v-0009425e><!--[--><p class="text" data-v-0009425e>Backend logs</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-d8b57b2d data-v-9a6c75ad><div class="VPDoc has-sidebar has-aside" data-v-9a6c75ad data-v-e6f2a212><!--[--><!--]--><div class="container" data-v-e6f2a212><div class="aside" data-v-e6f2a212><div class="aside-curtain" data-v-e6f2a212></div><div class="aside-container" data-v-e6f2a212><div class="aside-content" data-v-e6f2a212><div class="VPDocAside" data-v-e6f2a212 data-v-cb998dce><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-cb998dce data-v-f610f197><div class="content" data-v-f610f197><div class="outline-marker" data-v-f610f197></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-f610f197>On this page</div><ul class="VPDocOutlineItem root" data-v-f610f197 data-v-53c99d69><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-cb998dce></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e6f2a212><div class="content-container" data-v-e6f2a212><!--[--><!--]--><main class="main" data-v-e6f2a212><div style="position:relative;" class="vp-doc _authentication external-link-icon-enabled" data-v-e6f2a212><div><h1 id="authentication" tabindex="-1">Authentication <a class="header-anchor" href="#authentication" aria-label="Permalink to "Authentication""></a></h1><p>The dashboard supports optional user authentication with role-based access control. Authentication is <strong>disabled by default</strong>.</p><h2 id="roles" tabindex="-1">Roles <a class="header-anchor" href="#roles" aria-label="Permalink to "Roles""></a></h2><table tabindex="0"><thead><tr><th>Role</th><th>Description</th></tr></thead><tbody><tr><td><strong>Administrator</strong></td><td>Full access to every project and feature — editing projects, managing users, and deleting runs. Never restricted by project access.</td></tr><tr><td><strong>Reporter</strong></td><td>Submits results (<code>/api/test-runs/submit</code>, <code>/api/test-runs/upload</code>) and can triage, but only for the <strong>projects it's assigned to</strong>.</td></tr><tr><td><strong>User</strong></td><td>Read-only access to the <strong>projects it's assigned to</strong>.</td></tr></tbody></table><p>Administrators always see everything. <strong>Reporter</strong> and <strong>User</strong> accounts are additionally scoped by <a href="#project-access">project access</a> — they only see and act on the projects assigned to them.</p><h2 id="enabling-authentication" tabindex="-1">Enabling authentication <a class="header-anchor" href="#enabling-authentication" aria-label="Permalink to "Enabling authentication""></a></h2><ol><li><p>Copy the example environment file:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> application</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">cp</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .env.example</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .env</span></span></code></pre></div></li><li><p>Edit <code>.env</code> and set:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_SECRET_KEY</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-secret-key-here</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # encrypts DB secrets (AI keys, SCM tokens)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_AUTH_ENABLED</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">true</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_AUTH_SECRET</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-auth-secret-here</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # encrypts session cookies</span></span></code></pre></div><p>Generate strong random values for both (run twice — once for <code>PIWI_SECRET_KEY</code>, once for <code>PIWI_AUTH_SECRET</code>):</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Cross-platform — works anywhere Node is installed (Node is already required)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">node</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -e</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "console.log(require('node:crypto').randomBytes(32).toString('hex'))"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># …or with openssl on Linux / macOS / Git Bash</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">openssl</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> rand</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -hex</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 32</span></span></code></pre></div></li><li><p>Restart the application.</p></li></ol><h2 id="initial-setup" tabindex="-1">Initial setup <a class="header-anchor" href="#initial-setup" aria-label="Permalink to "Initial setup""></a></h2><p>When authentication is first enabled and no users exist, create the first administrator account:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-mO7zs" id="tab-EyInuQO" checked><label data-title="Linux / macOS" for="tab-EyInuQO">Linux / macOS</label><input type="radio" name="group-mO7zs" id="tab-13pGTeo"><label data-title="Windows (PowerShell)" for="tab-13pGTeo">Windows (PowerShell)</label></div><div class="blocks"><div class="language-bash vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> POST</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> http://localhost:3000/api/auth/setup</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Content-Type: application/json"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '{</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "username": "admin",</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "password": "your-secure-password",</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "name": "Administrator"</span></span>
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> }'</span></span></code></pre></div><div class="language-powershell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">powershell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">$body </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> @</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ username </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'admin'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">; password </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'your-secure-password'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">; name </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'Administrator'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ConvertTo-Json</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Invoke-RestMethod</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Method Post </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Uri http:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">//</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">localhost:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3000</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">api</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">auth</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">setup </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ContentType </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'application/json'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Body $body</span></span></code></pre></div></div></div><p>This endpoint is only available when the users table is empty.</p><h2 id="logging-in" tabindex="-1">Logging in <a class="header-anchor" href="#logging-in" aria-label="Permalink to "Logging in""></a></h2><p>Navigate to <code>/login</code> in your browser, or use the API:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-KVCfO" id="tab-RWz0zN6" checked><label data-title="Linux / macOS" for="tab-RWz0zN6">Linux / macOS</label><input type="radio" name="group-KVCfO" id="tab-K13Hx2J"><label data-title="Windows (PowerShell)" for="tab-K13Hx2J">Windows (PowerShell)</label></div><div class="blocks"><div class="language-bash vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> POST</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> http://localhost:3000/api/auth/login</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Content-Type: application/json"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '{"username": "admin", "password": "your-secure-password"}'</span></span></code></pre></div><div class="language-powershell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">powershell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">$body </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> @</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ username </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'admin'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">; password </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'your-secure-password'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ConvertTo-Json</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Invoke-RestMethod</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Method Post </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Uri http:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">//</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">localhost:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3000</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">api</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">auth</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">login </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ContentType </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'application/json'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Body $body</span></span></code></pre></div></div></div><p>Sessions are stored in encrypted cookies and last for 7 days.</p><h2 id="oauth-google-github" tabindex="-1">OAuth (Google, GitHub) <a class="header-anchor" href="#oauth-google-github" aria-label="Permalink to "OAuth (Google, GitHub)""></a></h2><p>The dashboard supports signing in with Google or GitHub as an alternative to username/password authentication.</p><h3 id="configuring-oauth" tabindex="-1">Configuring OAuth <a class="header-anchor" href="#configuring-oauth" aria-label="Permalink to "Configuring OAuth""></a></h3><ol><li><p><strong>Register an OAuth application</strong> with each provider you want to use:</p><ul><li><strong>Google</strong>: Go to the <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noreferrer">Google Cloud Console</a>, create an OAuth 2.0 Client ID, and add <code>https://your-domain.com/api/auth/oauth/google/callback</code> to the authorized redirect URIs.</li><li><strong>GitHub</strong>: Go to <strong>Settings → Developer settings → OAuth Apps</strong> on GitHub, create a new OAuth app, and set the callback URL to <code>https://your-domain.com/api/auth/oauth/github/callback</code>.</li></ul></li><li><p><strong>Add the credentials to your <code>.env</code> file:</strong></p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_OAUTH_GOOGLE_CLIENT_ID</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-google-client-id</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_OAUTH_GOOGLE_CLIENT_SECRET</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-google-client-secret</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_OAUTH_GITHUB_CLIENT_ID</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-github-client-id</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">PIWI_OAUTH_GITHUB_CLIENT_SECRET</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">your-github-client-secret</span></span></code></pre></div><p>Only configure the providers you actually want to use. OAuth buttons appear automatically on the login page when both a provider's <code>CLIENT_ID</code> and <code>CLIENT_SECRET</code> are set.</p></li><li><p><strong>Restart the application.</strong> The login page now shows <strong>Sign in with Google</strong> and/or <strong>Sign in with GitHub</strong> buttons above the password form.</p></li></ol><blockquote><p><strong>Behind a reverse proxy:</strong> set <code>PIWI_SITE_URL</code> to your public URL (e.g. <code>https://piwi.example.com</code>). The OAuth <code>redirect_uri</code> is built from it, so it stays consistent with the value you registered even when the proxy rewrites the request host. Without it the redirect URI is inferred from the incoming request.</p></blockquote><h3 id="restricting-who-can-sign-in-allowlists" tabindex="-1">Restricting who can sign in (allowlists) <a class="header-anchor" href="#restricting-who-can-sign-in-allowlists" aria-label="Permalink to "Restricting who can sign in (allowlists)""></a></h3><p>By default any account at a configured provider can sign in (and a new <strong>user</strong>-role account is created). To restrict access:</p><ul><li><strong><code>PIWI_OAUTH_ALLOWED_DOMAINS</code></strong> — comma-separated email domains (e.g. <code>example.com,acme.org</code>). Only <strong>verified</strong> provider emails in these domains may sign in. Applies to all providers — ideal for limiting Google Workspace sign-in to your company domain.</li><li><strong><code>PIWI_OAUTH_GITHUB_ALLOWED_ORGS</code></strong> — comma-separated GitHub org logins. The user must be a member of at least one. Enabling this requests the <code>read:org</code> scope so membership (including private) can be checked.</li></ul><p>Rejected sign-ins are returned to the login page with an explanatory message.</p><h3 id="how-it-works" tabindex="-1">How it works <a class="header-anchor" href="#how-it-works" aria-label="Permalink to "How it works""></a></h3><ol><li>User clicks an OAuth button on the login page.</li><li>The server redirects to the provider's authorization page with a cryptographically random <code>state</code> parameter stored in an httpOnly cookie. For providers that support it (Google), a PKCE <code>code_challenge</code> (S256) is also sent and its verifier stored in an httpOnly cookie, so a stolen authorization code can't be redeemed without the verifier.</li><li>After authorization, the provider redirects back to the callback URL.</li><li>The server validates the <code>state</code> cookie (CSRF protection), exchanges the code for an access token (sending the PKCE verifier when used), and fetches the user's profile (name, email, avatar).</li><li>A local user is created or linked: <ul><li>If a user with the same OAuth provider + ID exists, their name/avatar/email are refreshed from the provider.</li><li>If a user with the same <strong>verified</strong> email exists (and isn't already linked to a <em>different</em> provider), the existing account is linked to the OAuth provider. Linking only happens when the provider asserts the email is verified, which prevents account takeover via an attacker-controlled public email.</li><li>Otherwise, a new user is created with the <strong>user</strong> role and an empty password (password login disabled for OAuth-only users). The provider email is stored on the account.</li></ul></li><li>A session is established (same encrypted cookie as password login), and the browser is redirected to the dashboard homepage.</li></ol><h3 id="notes" tabindex="-1">Notes <a class="header-anchor" href="#notes" aria-label="Permalink to "Notes""></a></h3><ul><li>OAuth users have an empty password and <strong>cannot sign in with username/password</strong>. They must always use their OAuth provider.</li><li>The provider's email address is stored on the OAuth account, so OAuth users can receive email notifications and appear with a verified email in the admin user list.</li><li>If a sign-in's verified email matches an account that is <strong>already linked to a different provider</strong>, the login is rejected with an "already linked to a different sign-in method" message (the schema links one provider per account) — sign in with the original method instead.</li><li>GitHub accounts with no verified primary email are still allowed to sign in, but a fresh account is created rather than linked.</li><li>The reporter (CI/CD) authentication is unaffected — it continues to use API keys or username/password.</li><li>OAuth is <strong>not available in demo mode</strong>; the buttons are not shown.</li><li>Avatar URLs from the provider are displayed in the user menu when available.</li><li>The dashboard does not store provider refresh/access tokens — the access token is used once at sign-in to read the profile, then discarded; only the dashboard's own session cookie persists.</li></ul><h3 id="connecting-disconnecting-a-provider" tabindex="-1">Connecting / disconnecting a provider <a class="header-anchor" href="#connecting-disconnecting-a-provider" aria-label="Permalink to "Connecting / disconnecting a provider""></a></h3><p>Signed-in users can link a provider explicitly from <strong>Settings → Account → Connected accounts</strong>:</p><ul><li><strong>Connect</strong> starts the OAuth flow in "link" mode and attaches the provider identity to the current account (rather than creating a new one). A provider identity already linked to another account is refused.</li><li><strong>Disconnect</strong> removes the link. It's only allowed when the account also has a password set, so a provider-only user can't lock themselves out — set a password first.</li></ul><p>One provider can be connected per account.</p><h2 id="user-management" tabindex="-1">User management <a class="header-anchor" href="#user-management" aria-label="Permalink to "User management""></a></h2><p>User accounts are managed through the admin interface at <code>/settings/users</code>.<br> This page is accessible to administrators (or to everyone when authentication is disabled, with an informational message).</p><p>To create additional users:</p><ol><li>Navigate to <code>/settings/users</code></li><li>Click <strong>Add user</strong></li><li>Set username, password, role, and optional display name</li></ol><h2 id="project-access" tabindex="-1">Project access <a class="header-anchor" href="#project-access" aria-label="Permalink to "Project access""></a></h2><p>Administrators see every project. <strong>Reporter</strong> and <strong>User</strong> accounts see only the projects they are <strong>assigned</strong> to — so you can give each team its own slice of the dashboard.</p><p>An assignment is one of two kinds:</p><ul><li><strong>Global</strong> — access to <em>all</em> projects, including ones created later. Good for a shared CI reporter or a team lead.</li><li><strong>Per-project</strong> — access to a specific set of projects only.</li></ul><p>What scoping affects for a non-admin user:</p><ul><li>The project list, sidebar menu, home dashboard, recent runs, and search only include assigned projects.</li><li>Runs, test cases, and clusters that belong to an unassigned project return <strong>403</strong>.</li><li>A reporter can submit results to an assigned project. Creating a <strong>new</strong> project on first submission requires <strong>global</strong> access — a per-project reporter can't invent projects.</li></ul><blockquote><p><strong>Default is no access.</strong> A freshly created Reporter/User has no assignments and sees an empty dashboard until you grant some. (When authentication is first enabled, existing accounts are automatically backfilled with global access so nothing breaks on upgrade.)</p></blockquote><h3 id="managing-assignments" tabindex="-1">Managing assignments <a class="header-anchor" href="#managing-assignments" aria-label="Permalink to "Managing assignments""></a></h3><p>Assignments are administrator-only and can be edited from either direction:</p><ul><li><strong>Per user</strong> — <strong>Settings → Users → Project access</strong> (the action next to a user). Choose global access or tick specific projects.</li><li><strong>Per project</strong> — a project's <strong>Members</strong> tab (<code>/projects/:id</code>, admins only). Add or remove users for that one project.</li></ul><p>Both edit the same underlying assignments, so use whichever is more convenient.</p><blockquote><p><strong>Try it in the demo:</strong> the <a href="https://piwitests.github.io/demo/" target="_blank" rel="noreferrer">live demo</a> ships with several pre-seeded identities — an admin, a global CI reporter, and users scoped to one or two projects (plus one with none). Use the <strong>Acting as</strong> picker in the demo banner to switch between them and watch the project list, sidebar, and search change to match each user's access. Acting as the admin, change the assignments live and switch back to see the effect. See <a href="./ui-overview.html#live-demo">UI overview → Live demo</a>.</p></blockquote><h2 id="api-authentication" tabindex="-1">API authentication <a class="header-anchor" href="#api-authentication" aria-label="Permalink to "API authentication""></a></h2><p>When authentication is enabled:</p><ul><li><code>POST</code> / <code>PUT</code> / <code>DELETE</code> endpoints require an active session with appropriate role permissions.</li><li><code>GET</code> endpoints remain publicly accessible (read-only), <strong>except</strong> <code>GET /api/users</code>, which requires an authenticated session so the user list (usernames and roles) is not exposed to anonymous callers.</li><li>The reporter's submission endpoints (<code>/api/test-runs/submit</code> and <code>/api/test-runs/upload</code>) accept both session cookies and API keys.</li></ul><h2 id="api-keys" tabindex="-1">API keys <a class="header-anchor" href="#api-keys" aria-label="Permalink to "API keys""></a></h2><p>API keys are the recommended way to authenticate CI pipelines and the Playwright reporter. They are long-lived tokens tied to a specific user account.</p><h3 id="security-properties" tabindex="-1">Security properties <a class="header-anchor" href="#security-properties" aria-label="Permalink to "Security properties""></a></h3><ul><li>Keys are generated with 256 bits of cryptographic entropy (<code>pd_</code> prefix + 64-character hex string).</li><li>Only a SHA-256 hash of the key is stored in the database — the plaintext is shown <strong>once</strong> at creation time and never retrievable again.</li><li>Each key displays a short prefix (<code>pd_xxxxxxxx…</code>) in the UI for identification without revealing the secret.</li><li>Keys are sent as <code>Authorization: Bearer <key></code> or <code>X-API-Key: <key></code> headers.</li><li>Keys can be given an optional expiry date.</li></ul><h3 id="creating-an-api-key" tabindex="-1">Creating an API key <a class="header-anchor" href="#creating-an-api-key" aria-label="Permalink to "Creating an API key""></a></h3><ol><li>Navigate to <strong>Settings → Users</strong> in the dashboard.</li><li>Click the key icon next to the user you want to generate a key for.</li><li>Click <strong>Create API key</strong>, enter a descriptive name (e.g. "GitHub Actions"), and set an optional expiry.</li><li>Copy the key <strong>immediately</strong> — it will never be shown again.</li><li>Store it as a CI secret (e.g. <code>PIWI_API_KEY</code>).</li></ol><h3 id="revoking-an-api-key" tabindex="-1">Revoking an API key <a class="header-anchor" href="#revoking-an-api-key" aria-label="Permalink to "Revoking an API key""></a></h3><ol><li>Navigate to <strong>Settings → Users</strong> and click the key icon.</li><li>Click the trash icon next to the key you want to revoke.</li><li>The key stops working immediately.</li></ol><h3 id="using-the-api-key-in-the-reporter" tabindex="-1">Using the API key in the reporter <a class="header-anchor" href="#using-the-api-key-in-the-reporter" aria-label="Permalink to "Using the API key in the reporter""></a></h3><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// playwright.config.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> reporter: [</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'@piwitests/reporter'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> serverUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'https://your-dashboard.example.com'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> projectName: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'my-project'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> apiKey: process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">PIWI_API_KEY</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><h3 id="using-the-api-key-in-raw-http-calls" tabindex="-1">Using the API key in raw HTTP calls <a class="header-anchor" href="#using-the-api-key-in-raw-http-calls" aria-label="Permalink to "Using the API key in raw HTTP calls""></a></h3><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-8NG9e" id="tab-jEG0VzL" checked><label data-title="Linux / macOS" for="tab-jEG0VzL">Linux / macOS</label><input type="radio" name="group-8NG9e" id="tab-jAVOMWY"><label data-title="Windows (PowerShell)" for="tab-jAVOMWY">Windows (PowerShell)</label></div><div class="blocks"><div class="language-bash vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Authorization: Bearer header (recommended)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> POST</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> https://your-dashboard.example.com/api/test-runs/submit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Authorization: Bearer pd_<your-key>"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Content-Type: application/json"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '{ ... }'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># X-API-Key header (alternative)</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -X</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> POST</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> https://your-dashboard.example.com/api/test-runs/submit</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "X-API-Key: pd_<your-key>"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -H</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Content-Type: application/json"</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -d</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '{ ... }'</span></span></code></pre></div><div class="language-powershell vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">powershell</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># Authorization: Bearer header (recommended)</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Invoke-RestMethod</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Method Post </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Uri https:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">//</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">your-dashboard.example.com</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">api</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">test-runs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Headers </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ Authorization </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'Bearer pd_<your-key>'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ContentType </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'application/json'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Body </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'{ ... }'</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># X-API-Key header (alternative)</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Invoke-RestMethod</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Method Post </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">-</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Uri https:</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">//</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">your-dashboard.example.com</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">api</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">test-runs</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">submit </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Headers </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'X-API-Key'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'pd_<your-key>'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">`</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">ContentType </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'application/json'</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> -</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">Body </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'{ ... }'</span></span></code></pre></div></div></div><h2 id="using-the-reporter-with-session-authentication-username-password" tabindex="-1">Using the reporter with session authentication (username/password) <a class="header-anchor" href="#using-the-reporter-with-session-authentication-username-password" aria-label="Permalink to "Using the reporter with session authentication (username/password)""></a></h2><p>As an alternative to API keys, create a dedicated user with the <strong>reporter</strong> role for your CI pipelines:</p><ol><li><p>Log in as an administrator in <code>/settings/users</code> and add a new user with the <strong>Reporter</strong> role.</p></li><li><p>Configure the reporter with the credentials:</p><div class="language-typescript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// playwright.config.ts</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineConfig</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> reporter: [</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'@piwitests/reporter'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> serverUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'https://your-dashboard.example.com'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> projectName: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'my-project'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> username: process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">PIWI_USERNAME</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> password: process.env.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">PIWI_PASSWORD</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div></li><li><p>Add <code>PIWI_USERNAME</code> and <code>PIWI_PASSWORD</code> as secrets in your CI provider.</p></li></ol><p>The reporter automatically calls <code>/api/auth/login</code> before each upload and uses the resulting session for all subsequent requests.</p><blockquote><p><strong>Tip:</strong> API keys are preferred over username/password for CI because they don't require a login round-trip and can be individually revoked.</p></blockquote><h2 id="security-considerations" tabindex="-1">Security considerations <a class="header-anchor" href="#security-considerations" aria-label="Permalink to "Security considerations""></a></h2><ul><li>Always use HTTPS in production.</li><li>Use strong, unique passwords.</li><li>Set <code>PIWI_SECRET_KEY</code> (generate with <code>node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"</code>, or <code>openssl rand -hex 32</code>) to encrypt AI API keys and SCM tokens at rest in the database. This is recommended even when authentication is disabled.</li><li>Set <code>PIWI_AUTH_SECRET</code> (same generator) for session cookie encryption — required when <code>PIWI_AUTH_ENABLED=true</code>.</li><li>Passwords are hashed using scrypt with per-password salts.</li><li>Never use the default secrets in production.</li></ul><h2 id="disabling-authentication" tabindex="-1">Disabling authentication <a class="header-anchor" href="#disabling-authentication" aria-label="Permalink to "Disabling authentication""></a></h2><p>To disable authentication:</p><ol><li>Set <code>PIWI_AUTH_ENABLED=false</code> in <code>.env</code>, or remove the variable entirely.</li><li>Restart the application.</li></ol><p>When disabled, all endpoints are accessible without authentication.</p></div></div></main><footer class="VPDocFooter" data-v-e6f2a212 data-v-1bcd8184><!--[--><!--]--><div class="edit-info" data-v-1bcd8184><div class="edit-link" data-v-1bcd8184><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://github.com/PiwiTests/platform/edit/main/docs/authentication.md" target="_blank" rel="noreferrer" data-v-1bcd8184><!--[--><span class="vpi-square-pen edit-link-icon" data-v-1bcd8184></span> Edit this page on GitHub<!--]--></a></div><div class="last-updated" data-v-1bcd8184><p class="VPLastUpdated" data-v-1bcd8184 data-v-1bb0c8a8>Updated at: <time datetime="2026-07-17T14:01:58.000Z" data-v-1bb0c8a8></time></p></div></div><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-1bcd8184><span class="visually-hidden" id="doc-footer-aria-label" data-v-1bcd8184>Pager</span><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link prev" href="/configuration.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Previous page</span><span class="title" data-v-1bcd8184>Configuration reference</span><!--]--></a></div><div class="pager" data-v-1bcd8184><a class="VPLink link pager-link next" href="/storage.html" data-v-1bcd8184><!--[--><span class="desc" data-v-1bcd8184>Next page</span><span class="title" data-v-1bcd8184>Storage configuration</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><footer class="VPFooter has-sidebar" data-v-d8b57b2d data-v-566314d4><div class="container" data-v-566314d4><p class="message" data-v-566314d4>Released under the MIT License. Zero telemetry — Piwi never phones home.<br>Piwi Dashboard is not affiliated with, endorsed by, or connected to Microsoft Corporation. Playwright is a trademark of Microsoft.</p><p class="copyright" data-v-566314d4>Copyright © 2025-present Fabien Ménager</p></div></footer><!--[--><!--]--></div></div>
<script>window.__VP_HASH_MAP__=JSON.parse("{\"ai-diagnosis.md\":\"eyQmRjjL\",\"authentication.md\":\"CnU14reC\",\"backend-logs.md\":\"D2VXsM1N\",\"capture-fixtures.md\":\"BN09UzH0\",\"comparison.md\":\"D7z0foxZ\",\"configuration.md\":\"C9OTF3Zj\",\"deployment.md\":\"D_EoyJwD\",\"flaky-tests.md\":\"DpvNJpxg\",\"getting-started.md\":\"DMA48wXC\",\"ide-integration.md\":\"DZDxcyvc\",\"index.md\":\"IWrRgzJ-\",\"mcp.md\":\"CIZpXh2R\",\"notifications.md\":\"B3FvucN7\",\"reporter.md\":\"Dtbkf-rT\",\"storage.md\":\"CxQ2QVUK\",\"ui-overview.md\":\"B-i13tIL\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Piwi Dashboard\",\"description\":\"A modern dashboard for storing and visualising Playwright test results\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"outline\":\"deep\",\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting started\",\"link\":\"/getting-started\"},{\"text\":\"Reporter\",\"link\":\"/reporter\"},{\"text\":\"API docs\",\"link\":\"https://piwitests.github.io/demo/docs\"},{\"text\":\"Demo\",\"link\":\"https://piwitests.github.io/demo/\"}],\"sidebar\":[{\"text\":\"Getting started\",\"link\":\"/getting-started\"},{\"text\":\"Why Piwi? (comparison & FAQ)\",\"link\":\"/comparison\"},{\"text\":\"UI overview\",\"link\":\"/ui-overview\"},{\"text\":\"Reporter\",\"link\":\"/reporter\"},{\"text\":\"Capture fixtures\",\"link\":\"/capture-fixtures\"},{\"text\":\"Features\",\"items\":[{\"text\":\"AI diagnosis & clustering\",\"link\":\"/ai-diagnosis\"},{\"text\":\"Flaky tests & analytics\",\"link\":\"/flaky-tests\"},{\"text\":\"Notifications & alerts\",\"link\":\"/notifications\"}]},{\"text\":\"Configuration\",\"items\":[{\"text\":\"Configuration reference\",\"link\":\"/configuration\"},{\"text\":\"Authentication\",\"link\":\"/authentication\"},{\"text\":\"Storage configuration\",\"link\":\"/storage\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Integrate\",\"items\":[{\"text\":\"API docs (interactive)\",\"link\":\"https://piwitests.github.io/demo/docs\"},{\"text\":\"MCP server\",\"link\":\"/mcp\"},{\"text\":\"Open in IDE\",\"link\":\"/ide-integration\"},{\"text\":\"Backend logs\",\"link\":\"/backend-logs\"}]}],\"editLink\":{\"pattern\":\"https://github.com/PiwiTests/platform/edit/main/docs/:path\",\"text\":\"Edit this page on GitHub\"},\"lastUpdated\":{\"text\":\"Updated at\",\"formatOptions\":{\"dateStyle\":\"full\",\"timeStyle\":\"medium\"}},\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/PiwiTests/platform\"}],\"externalLinkIcon\":true,\"footer\":{\"message\":\"Released under the MIT License. Zero telemetry — Piwi never phones home.<br>Piwi Dashboard is not affiliated with, endorsed by, or connected to Microsoft Corporation. Playwright is a trademark of Microsoft.\",\"copyright\":\"Copyright © 2025-present Fabien Ménager\"}},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
</body>
</html>