mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Integrate reviewed safe recovery and incident guidance
Preserve both exact Product intelligence commits. Replace the obsolete recovery-token recipe with the existing deployment-specific reset path and client-bound recovery facts, and guide users to bounded sanitized evidence with privacy review rather than dangerous retries. Change-source: pulse-maintainer
This commit is contained in:
+40
-15
@@ -309,25 +309,50 @@ check, report the Pulse version and displayed check time/status separately; API
|
||||
success alone does not confirm the Pulse display has recovered.
|
||||
|
||||
### Recovery Mode
|
||||
If you are completely locked out, you can trigger a recovery token from localhost:
|
||||
```bash
|
||||
curl -X POST http://localhost:7655/api/security/recovery \
|
||||
-d '{"action":"generate_token","duration":30}'
|
||||
```
|
||||
Use the returned token in `X-Recovery-Token` when calling `/api/security/recovery` to enable or disable local-only auth bypass (`disable_auth` / `enable_auth`). Token generation is localhost-only.
|
||||
|
||||
Example (enable recovery mode):
|
||||
```bash
|
||||
curl -X POST http://localhost:7655/api/security/recovery \
|
||||
-H "X-Recovery-Token: <token>" \
|
||||
-d '{"action":"disable_auth"}'
|
||||
```
|
||||
For a forgotten local password, follow [I forgot my password](#i-forgot-my-password)
|
||||
above, using the steps for your deployment. Enter the host-only bootstrap token
|
||||
in that instance's setup screen; do not paste it into a command or a report.
|
||||
For OIDC, SAML or proxy login, use the identity-provider or administrator path
|
||||
described there instead.
|
||||
|
||||
The advanced `/api/security/recovery` API creates a **browser-bound recovery
|
||||
session**, not a server-wide authentication bypass. Despite its legacy name,
|
||||
`disable_auth` does not disable authentication for other clients. Recovery
|
||||
sessions work only over direct loopback requests; remote and reverse-proxy
|
||||
requests cannot use them. A successful curl response does not unlock a separate
|
||||
browser: the session cookie belongs to the client that made the request.
|
||||
`enable_auth` clears that recovery session; it does not reset a password.
|
||||
|
||||
Do not transfer recovery cookies between clients or paste recovery tokens into
|
||||
command arguments, URLs, screenshots or GitHub threads. The password-reset steps
|
||||
above avoid that credential-handling detour.
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
If you're still stuck:
|
||||
1. **Check Logs**: `journalctl -u pulse -n 100` or `docker logs --tail 100 pulse`.
|
||||
2. **Check Version**: `curl http://localhost:7655/api/version`.
|
||||
3. **Open Issue**: Report on [GitHub Issues](https://github.com/rcourtman/Pulse/issues) with your logs and version info.
|
||||
|
||||
1. **Keep the original evidence**: note what you did, when it happened and the
|
||||
exact error. Do not repeat an update, outage or notification storm merely to
|
||||
reproduce it. A failed update banner does not prove the action left the
|
||||
target unchanged; check its current state before another attempt.
|
||||
2. **Identify the affected version**: give the running Pulse and relevant agent
|
||||
versions, not just the version before an upgrade. For Docker, include the
|
||||
running image tag or digest. If installation never started Pulse, give the
|
||||
attempted release and public installer/helper source, or say "unknown".
|
||||
3. **Choose relevant, safe evidence**: if Pulse is running and collection is
|
||||
safe, use **Settings → Diagnostics → Export for GitHub (sanitized)** for
|
||||
connection or data failures. For a visual problem, a screenshot or the exact
|
||||
error may be enough. If logs are needed, inspect a bounded local excerpt
|
||||
(`journalctl -u pulse -n 100 --no-pager` or `docker logs --tail 100 pulse`),
|
||||
not a full configuration or data-directory upload.
|
||||
4. **Review before posting**: even a sanitized export or screenshot can contain
|
||||
identifying details. Remove credentials, session cookies, webhook URLs and
|
||||
private host, network or personal information. Never post bootstrap/recovery
|
||||
tokens, `.env` files, private keys or an unsanitized export.
|
||||
5. **Use the appropriate thread**: [GitHub Issues](https://github.com/rcourtman/Pulse/issues)
|
||||
for a bug, or [Discussions](https://github.com/rcourtman/Pulse/discussions) for
|
||||
a setup question. Add new evidence to an existing matching report rather
|
||||
than opening a duplicate. Do not refile information you have already supplied.
|
||||
|
||||
@@ -20,6 +20,7 @@ const targets: Record<string, string> = {
|
||||
reload: '/docs/API#resource-maintenance-and-operator-state',
|
||||
malformed: '/docs/API#%invalid',
|
||||
missing: '/docs/API#does-not-exist',
|
||||
troubleshooting: '/docs/TROUBLESHOOTING#recovery-mode',
|
||||
};
|
||||
|
||||
window.history.replaceState({}, '', targets[scenario] ?? targets.plain);
|
||||
|
||||
@@ -309,25 +309,50 @@ check, report the Pulse version and displayed check time/status separately; API
|
||||
success alone does not confirm the Pulse display has recovered.
|
||||
|
||||
### Recovery Mode
|
||||
If you are completely locked out, you can trigger a recovery token from localhost:
|
||||
```bash
|
||||
curl -X POST http://localhost:7655/api/security/recovery \
|
||||
-d '{"action":"generate_token","duration":30}'
|
||||
```
|
||||
Use the returned token in `X-Recovery-Token` when calling `/api/security/recovery` to enable or disable local-only auth bypass (`disable_auth` / `enable_auth`). Token generation is localhost-only.
|
||||
|
||||
Example (enable recovery mode):
|
||||
```bash
|
||||
curl -X POST http://localhost:7655/api/security/recovery \
|
||||
-H "X-Recovery-Token: <token>" \
|
||||
-d '{"action":"disable_auth"}'
|
||||
```
|
||||
For a forgotten local password, follow [I forgot my password](#i-forgot-my-password)
|
||||
above, using the steps for your deployment. Enter the host-only bootstrap token
|
||||
in that instance's setup screen; do not paste it into a command or a report.
|
||||
For OIDC, SAML or proxy login, use the identity-provider or administrator path
|
||||
described there instead.
|
||||
|
||||
The advanced `/api/security/recovery` API creates a **browser-bound recovery
|
||||
session**, not a server-wide authentication bypass. Despite its legacy name,
|
||||
`disable_auth` does not disable authentication for other clients. Recovery
|
||||
sessions work only over direct loopback requests; remote and reverse-proxy
|
||||
requests cannot use them. A successful curl response does not unlock a separate
|
||||
browser: the session cookie belongs to the client that made the request.
|
||||
`enable_auth` clears that recovery session; it does not reset a password.
|
||||
|
||||
Do not transfer recovery cookies between clients or paste recovery tokens into
|
||||
command arguments, URLs, screenshots or GitHub threads. The password-reset steps
|
||||
above avoid that credential-handling detour.
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
If you're still stuck:
|
||||
1. **Check Logs**: `journalctl -u pulse -n 100` or `docker logs --tail 100 pulse`.
|
||||
2. **Check Version**: `curl http://localhost:7655/api/version`.
|
||||
3. **Open Issue**: Report on [GitHub Issues](https://github.com/rcourtman/Pulse/issues) with your logs and version info.
|
||||
|
||||
1. **Keep the original evidence**: note what you did, when it happened and the
|
||||
exact error. Do not repeat an update, outage or notification storm merely to
|
||||
reproduce it. A failed update banner does not prove the action left the
|
||||
target unchanged; check its current state before another attempt.
|
||||
2. **Identify the affected version**: give the running Pulse and relevant agent
|
||||
versions, not just the version before an upgrade. For Docker, include the
|
||||
running image tag or digest. If installation never started Pulse, give the
|
||||
attempted release and public installer/helper source, or say "unknown".
|
||||
3. **Choose relevant, safe evidence**: if Pulse is running and collection is
|
||||
safe, use **Settings → Diagnostics → Export for GitHub (sanitized)** for
|
||||
connection or data failures. For a visual problem, a screenshot or the exact
|
||||
error may be enough. If logs are needed, inspect a bounded local excerpt
|
||||
(`journalctl -u pulse -n 100 --no-pager` or `docker logs --tail 100 pulse`),
|
||||
not a full configuration or data-directory upload.
|
||||
4. **Review before posting**: even a sanitized export or screenshot can contain
|
||||
identifying details. Remove credentials, session cookies, webhook URLs and
|
||||
private host, network or personal information. Never post bootstrap/recovery
|
||||
tokens, `.env` files, private keys or an unsanitized export.
|
||||
5. **Use the appropriate thread**: [GitHub Issues](https://github.com/rcourtman/Pulse/issues)
|
||||
for a bug, or [Discussions](https://github.com/rcourtman/Pulse/discussions) for
|
||||
a setup question. Add new evidence to an existing matching report rather
|
||||
than opening a duplicate. Do not refile information you have already supplied.
|
||||
|
||||
@@ -131,6 +131,27 @@ describe('docsLinks', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps lockout and incident reporting guidance safe for the affected client', () => {
|
||||
const troubleshooting = readFileSync(
|
||||
path.join(repoRoot, 'docs', 'TROUBLESHOOTING.md'),
|
||||
'utf8',
|
||||
);
|
||||
const recovery = troubleshooting.split('### Recovery Mode')[1].split('\n---')[0];
|
||||
expect(recovery).toContain('[I forgot my password](#i-forgot-my-password)');
|
||||
expect(recovery).toContain('browser-bound recovery');
|
||||
expect(recovery).toContain('A successful curl response does not unlock a separate');
|
||||
expect(recovery).toContain('remote and reverse-proxy');
|
||||
expect(recovery).not.toContain('generate_token');
|
||||
expect(recovery).not.toMatch(/```(?:bash|sh)[\s\S]*?X-Recovery-Token/);
|
||||
|
||||
const help = troubleshooting.split('## 🆘 Getting Help')[1];
|
||||
expect(help).toContain('Do not repeat an update, outage or notification storm');
|
||||
expect(help).toContain('Export for GitHub (sanitized)');
|
||||
expect(help).toContain('Review before posting');
|
||||
expect(help).toContain('session cookies, webhook URLs');
|
||||
expect(help).toContain('If installation never started Pulse');
|
||||
});
|
||||
|
||||
it('ships a bounded maintenance example separately from incident snoozing', () => {
|
||||
const apiReference = readFileSync(path.join(repoRoot, 'docs', 'API.md'), 'utf8');
|
||||
const maintenance = apiReference
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Render the real shipped troubleshooting guide with the production Docs page.
|
||||
// Run from the assigned workspace root:
|
||||
// pulse-worker-browser scripts/check-community-troubleshooting.cjs
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
const root = process.cwd();
|
||||
const frontend = path.join(root, 'frontend-modern');
|
||||
const output = path.join(root, 'tmp', 'community-troubleshooting');
|
||||
const port = 5198;
|
||||
const observations = [];
|
||||
const errors = [];
|
||||
|
||||
function check(condition, message) {
|
||||
observations.push({ passed: condition, message });
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
// Tailwind resolves its config/content globs from the frontend working dir.
|
||||
process.chdir(frontend);
|
||||
const { createServer } = await import(
|
||||
path.join(frontend, 'node_modules', 'vite', 'dist', 'node', 'index.js')
|
||||
);
|
||||
const server = await createServer({
|
||||
root: frontend,
|
||||
configFile: path.join(frontend, 'vite.config.ts'),
|
||||
server: { host: '127.0.0.1', port, strictPort: true },
|
||||
});
|
||||
let browser;
|
||||
let result;
|
||||
try {
|
||||
await server.listen();
|
||||
browser = await chromium.launch({
|
||||
headless: true, channel: 'chromium', args: ['--no-sandbox'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) errors.push(`${response.status()} ${response.url()}`);
|
||||
});
|
||||
// Only app-runtime auth probes are mocked; the Markdown asset is real.
|
||||
await page.route(new RegExp(`^http://127\\.0\\.0\\.1:${port}/api/`), (route) =>
|
||||
route.fulfill({
|
||||
status: 200, contentType: 'application/json',
|
||||
body: JSON.stringify({ hasAuthentication: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
for (const [size, width, height] of [['desktop', 1440, 900], ['phone', 390, 844]]) {
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(`http://127.0.0.1:${port}/browser-tests/docs-fragment-navigation.html?scenario=troubleshooting`);
|
||||
await page.waitForFunction(() => document.activeElement?.id === 'recovery-mode');
|
||||
const article = page.locator('article');
|
||||
const text = await article.innerText();
|
||||
check(text.includes('browser-bound recovery session'), `${size}: browser-bound recovery is explained`);
|
||||
check(text.includes('A successful curl response does not unlock a separate browser'), `${size}: curl is not presented as a browser unlock`);
|
||||
check(!(await article.locator('pre').allTextContents()).some((code) => code.includes('X-Recovery-Token') || code.includes('generate_token')), `${size}: no recovery credential command is offered`);
|
||||
check(text.includes('Export for GitHub (sanitized)') && text.includes('Review before posting'), `${size}: safe reporting evidence is visible`);
|
||||
check(text.includes('Do not repeat an update, outage or notification storm'), `${size}: unsafe reproduction is not required`);
|
||||
check(!(await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth)), `${size}: no horizontal page overflow`);
|
||||
await page.screenshot({ path: path.join(output, `${size}-recovery.png`) });
|
||||
|
||||
// The supported reset link must work with the real router and heading ID.
|
||||
await article.getByRole('link', { name: 'I forgot my password', exact: true }).click();
|
||||
await page.waitForFunction(() => document.activeElement?.id === 'i-forgot-my-password');
|
||||
check((await page.locator('#i-forgot-my-password').innerText()) === 'I forgot my password', `${size}: reset link reaches the deployment-specific steps`);
|
||||
await article.getByText(size === 'phone' ? 'Review before posting' : 'Keep the original evidence', { exact: true }).scrollIntoViewIfNeeded();
|
||||
await page.screenshot({ path: path.join(output, `${size}-help.png`) });
|
||||
}
|
||||
check(errors.length === 0, 'no page errors or failed HTTP responses');
|
||||
result = {
|
||||
result: 'passed', observations, errors, chromium: browser.version(),
|
||||
playwright: require('playwright/package.json').version,
|
||||
sourceAsset: 'frontend-modern/public/docs/TROUBLESHOOTING.md',
|
||||
};
|
||||
} catch (error) {
|
||||
result = { result: 'failed', observations, errors, error: error.message };
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify(result, null, 2));
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exitCode = result.result === 'passed' ? 0 : 1;
|
||||
})();
|
||||
Reference in New Issue
Block a user