Productionize WP Suite: auth, security hardening, sync, dashboard, PWA, email
Brings the Work Package Suite from a browser-local prototype to a multi-tenant, SQL-backed deployment hardened for customer IP. Auth & access control - Local username/password login (bcrypt + JWT in an HttpOnly cookie), admin-managed users, per-project membership, and project-scoped API access. - Admin console: change user roles, view the audit trail, manage settings. Security hardening - CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping. - Login lockout, token_version session revocation, stronger password policy, fail-closed secret loading, encrypted (AES-256) database backups. Persistence & schema - SOPs and Work Packages are now DB-backed and shared across users, written through a durable client sync outbox that queues offline edits. - Alembic migrations applied automatically on container start. New capabilities - Phase 2 dashboard (progress, gating, pagination, archive). - Phase 3 PWA "Field View" with offline caching and auth fallback. - WP owner assignment with OPTIONAL email notifications, OFF by default and toggled from the admin console. SMTP password is read only from the SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link, never customer IP. Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
63
html/sw.js
Normal file
63
html/sw.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/* Service worker for the Work Package Suite PWA.
|
||||
|
||||
Goal: let the app (and especially the field view) load and run offline. Data
|
||||
durability is already handled by the sync outbox in project-data.js — this
|
||||
worker only caches the static app shell so the pages open without a network.
|
||||
|
||||
Strategy:
|
||||
• /api/* and non-GET → never touched (pass straight to the network; offline
|
||||
reads fall back to the app's localStorage cache, writes queue in the outbox).
|
||||
• same-origin GET → stale-while-revalidate (instant from cache, refreshed
|
||||
in the background when online).
|
||||
*/
|
||||
'use strict';
|
||||
const CACHE = 'wp-suite-shell-v1';
|
||||
const SHELL = [
|
||||
'/', '/index.html', '/work-package-suite.html', '/wp-creation-index.html',
|
||||
'/field.html', '/login.html', '/admin.html',
|
||||
'/theme-light.css', '/work-package-suite-styles.css', '/wp-creation-styles.css',
|
||||
'/auth-guard.js', '/project-data.js', '/feedback-config.js', '/help.js',
|
||||
'/work-package-suite-app.js', '/wp-creation-app.js', '/field.js',
|
||||
'/prime-controls-logo.jpg', '/favicon.ico',
|
||||
'/manifest.webmanifest', '/icon-192.png', '/icon-512.png',
|
||||
];
|
||||
|
||||
self.addEventListener('install', (e) => {
|
||||
// Cache each shell asset individually so one missing file doesn't abort install.
|
||||
e.waitUntil(
|
||||
caches.open(CACHE)
|
||||
.then((c) => Promise.all(SHELL.map((u) => c.add(u).catch(() => {}))))
|
||||
.then(() => self.skipWaiting())
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (e) => {
|
||||
e.waitUntil(
|
||||
caches.keys()
|
||||
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
||||
.then(() => self.clients.claim())
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (e) => {
|
||||
const req = e.request;
|
||||
if (req.method !== 'GET') return; // outbox owns writes
|
||||
const url = new URL(req.url);
|
||||
if (url.origin !== self.location.origin) return; // third-party: default
|
||||
if (url.pathname.startsWith('/api/')) return; // never cache the API
|
||||
|
||||
e.respondWith(
|
||||
caches.match(req).then((cached) => {
|
||||
const network = fetch(req)
|
||||
.then((res) => {
|
||||
if (res && res.ok) {
|
||||
const copy = res.clone();
|
||||
caches.open(CACHE).then((c) => c.put(req, copy));
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.catch(() => cached); // offline → cached copy
|
||||
return cached || network; // cache-first, then refresh
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user