Files
shadowdao 74bd3c2e98 security(web): remove creds from image URLs, auth-fail state, tighten CSP
Minor PR-review follow-ups for the local PWA:
- Macro images load via a header-authenticated fetch -> blob object URL in both
  token and relay modes, so no credential ever sits in an image URL; prior
  object URLs are revoked on re-render.
- Local-mode auth failure now shows a clear "Access token required" state and
  stops the reconnect/toast loop (one-shot), instead of retrying forever.
- CSP connect-src tightened from 'self' ws: wss: to 'self' (same-origin WS is
  covered); verified the live WebSocket still connects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:12:23 -07:00

100 lines
4.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; connect-src 'self'; object-src 'none'; base-uri 'none'">
<meta name="theme-color" content="#15161a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#eceef4" media="(prefers-color-scheme: light)">
<meta name="description" content="Remote macro control for your desktop">
<!-- PWA / iOS specific -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="MacroPad">
<meta name="application-name" content="MacroPad">
<meta name="msapplication-TileColor" content="#7c5cff">
<meta name="msapplication-tap-highlight" content="no">
<title>MacroPad</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons/icon-192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/static/icons/icon-512.png">
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
<link rel="apple-touch-icon" sizes="512x512" href="/static/icons/icon-512.png">
<link rel="stylesheet" href="/static/css/styles.css">
</head>
<body>
<!-- Header -->
<header class="header">
<h1>MacroPad</h1>
<div class="header-actions">
<div class="connection-status" id="connection-status" role="status" aria-live="polite">
<div class="status-dot"></div>
<span>Disconnected</span>
</div>
<button type="button" class="wake-lock-status" id="wake-lock-status" title="Screen wake lock" aria-label="Toggle screen wake lock">
<span class="wake-icon" aria-hidden="true"></span>
</button>
<button type="button" class="header-btn icon-btn" id="fullscreen-btn" title="Toggle fullscreen" aria-label="Toggle fullscreen"></button>
<button type="button" class="header-btn secondary" id="refresh-btn">Refresh</button>
</div>
</header>
<!-- Tabs -->
<nav class="tabs" id="tabs-container" role="tablist" aria-label="Macro categories">
<!-- Tabs rendered dynamically -->
</nav>
<!-- Macro Grid -->
<main class="macro-grid" id="macro-grid">
<div class="loading">
<div class="spinner"></div>
</div>
</main>
<!-- Floating action button: create new macro -->
<button type="button" class="fab" id="new-macro-btn" aria-label="New macro"></button>
<!-- Macro create/edit modal -->
<div class="modal" id="macro-modal" role="dialog" aria-modal="true" aria-labelledby="macro-modal-title" hidden>
<div class="modal-content">
<div class="modal-header">
<h2 id="macro-modal-title">New Macro</h2>
<button type="button" class="modal-close" id="macro-modal-close" aria-label="Close dialog">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="macro-name-input">Name</label>
<input type="text" id="macro-name-input" autocomplete="off" placeholder="Macro name">
</div>
<div class="form-group">
<label for="macro-category-input">Category</label>
<input type="text" id="macro-category-input" autocomplete="off" list="macro-category-list" placeholder="Category (optional)">
<datalist id="macro-category-list"></datalist>
</div>
<div class="form-group">
<label>Commands</label>
<div class="command-list" id="command-list"></div>
<div class="add-command-btns">
<button type="button" class="add-command-btn" id="add-command-btn"> Add step</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="delete-macro-btn" hidden>Delete</button>
<button type="button" class="btn btn-secondary" id="cancel-macro-btn">Cancel</button>
<button type="button" class="btn btn-primary" id="save-macro-btn">Save</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toast-container" role="status" aria-live="polite"></div>
<script src="/static/js/app.js"></script>
</body>
</html>