diff --git a/.env.example b/.env.example index 33b5c07..9ed6103 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,5 @@ AUDIOBOOKS_URL=https://audiobooks.necloud.us HUB_SETUP_CODE= VERSE_URL=https://versebyversewithnate.us/admin PODCAST_URL=https://podcast.necloud.us/ +# Days you stay signed in without using the app (it renews every time you do) +SESSION_DAYS=180 diff --git a/README.md b/README.md index b4657de..c1aab3b 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,13 @@ e.g. hub.necloud.us. Settings: `SEAFILE_URL`, `ONLYOFFICE_URL` env vars in docke - **Mail**: deep links to Gmail, Outlook.com, Microsoft 365 and iCloud webmail (inbox + compose). Providers forbid framing, and a real unified inbox would need Google/Azure app registrations. +## Install on iPhone / iPad (PWA) + +Open the site in **Safari** (it must be the `https://` address), tap **Share -> Add to Home Screen**. The home-screen app has proper +icons (iPhone, iPad), a splash screen for each iPhone and iPad size (including iPad Pro 12.9"), and opens full screen without Safari's bars. +iOS keeps a separate sign-in for the home-screen app, so sign in once inside it with your passkey; it then stays signed in +(`SESSION_DAYS`, default 180, renewed whenever you use it). Regenerate the artwork with `python3 tools/make-icons.py` (needs Pillow). + ## Build and deploy with Gitea `.gitea/workflows/build.yml` builds the container on every push to `main` (and `v*` tags) and pushes it to diff --git a/auth.js b/auth.js index ae50c4c..a60eb3c 100644 --- a/auth.js +++ b/auth.js @@ -29,7 +29,9 @@ const save = () => writeJson('passkeys.json', store); const SETUP_CODE = process.env.HUB_SETUP_CODE || crypto.randomBytes(4).toString('hex'); if (!store.creds.length) console.log(`[auth] No passkeys yet. Setup code: ${SETUP_CODE}`); -const SESSION_DAYS = 30; +// Sessions last SESSION_DAYS and slide forward: using the app renews them, so you only sign in again after that long idle. +const SESSION_DAYS = Math.max(1, parseInt(process.env.SESSION_DAYS, 10) || 180); +const LIFETIME = SESSION_DAYS * 864e5; const mac = v => crypto.createHmac('sha256', secret).update(v).digest('base64url'); const cookies = req => Object.fromEntries((req.headers.cookie || '').split(/; */).filter(Boolean).map(c => { const i = c.indexOf('='); return [c.slice(0, i), decodeURIComponent(c.slice(i + 1))]; })); const isHttps = req => (req.headers['x-forwarded-proto'] || '') === 'https' || (process.env.PUBLIC_URL || '').startsWith('https://'); @@ -40,9 +42,15 @@ function isAuthed(req) { const want = mac('s' + exp); return sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want)) && Number(exp) > Date.now(); } -function startSession(req, res) { - const exp = Date.now() + SESSION_DAYS * 864e5; - res.setHeader('Set-Cookie', `hub_session=${exp}.${mac('s' + exp)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_DAYS * 86400}${isHttps(req) ? '; Secure' : ''}`); +const sessionCookie = req => { const exp = Date.now() + LIFETIME; + return `hub_session=${exp}.${mac('s' + exp)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_DAYS * 86400}${isHttps(req) ? '; Secure' : ''}`; }; +// Add a Set-Cookie without clobbering ones other code already set on this response. +const addCookie = (res, c) => { const cur = res.getHeader('Set-Cookie'); res.setHeader('Set-Cookie', cur ? [].concat(cur, c) : c); }; +function startSession(req, res) { addCookie(res, sessionCookie(req)); } +/** Renew the session cookie when it is more than a day old, so an active session never runs out. */ +function refresh(req, res) { + const [exp] = String(cookies(req).hub_session || '').split('.'); + if (Number(exp) - Date.now() < LIFETIME - 864e5) addCookie(res, sessionCookie(req)); } const challenges = new Map(); // cid -> { challenge, kind, exp } @@ -100,7 +108,7 @@ async function handle(req, res) { try { if (p === '/api/auth/state') return send(res, 200, { registered: store.creds.length > 0, authed: isAuthed(req) }); if (p === '/api/auth/logout' && req.method === 'POST') { - res.setHeader('Set-Cookie', 'hub_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0'); + addCookie(res, 'hub_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0'); return send(res, 200, { ok: true }); } const body = req.method === 'POST' || req.method === 'DELETE' ? JSON.parse((await readBody(req)) || '{}') : {}; @@ -177,4 +185,4 @@ async function handle(req, res) { } catch (e) { send(res, 400, { error: e.message }); } } -module.exports = { handle, isAuthed, secret }; +module.exports = { handle, isAuthed, refresh, addCookie, secret, SESSION_DAYS }; diff --git a/docker-compose.yml b/docker-compose.yml index 7f67957..1fc0b62 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,7 @@ services: ONLYOFFICE_JWT_SECRET: ${ONLYOFFICE_JWT_SECRET:-} # same value as the OnlyOffice server's JWT secret PUBLIC_URL: ${PUBLIC_URL:-} # e.g. https://hub.necloud.us (OnlyOffice posts saved edits here) HUB_SETUP_CODE: ${HUB_SETUP_CODE:-} # first passkey; if empty, a code is printed in the log + SESSION_DAYS: ${SESSION_DAYS:-180} # how long you stay signed in when idle; using the app renews it COOKIE_SECRET: ${COOKIE_SECRET:-} # optional; auto-generated and kept in /data if empty volumes: - hub-data:/data # passkeys, calendar links and the session secret live here diff --git a/public/icons/apple-touch-icon-120.png b/public/icons/apple-touch-icon-120.png new file mode 100644 index 0000000..b95a9c8 Binary files /dev/null and b/public/icons/apple-touch-icon-120.png differ diff --git a/public/icons/apple-touch-icon-152.png b/public/icons/apple-touch-icon-152.png new file mode 100644 index 0000000..7ea7c81 Binary files /dev/null and b/public/icons/apple-touch-icon-152.png differ diff --git a/public/icons/apple-touch-icon-167.png b/public/icons/apple-touch-icon-167.png new file mode 100644 index 0000000..a92df61 Binary files /dev/null and b/public/icons/apple-touch-icon-167.png differ diff --git a/public/icons/apple-touch-icon-180.png b/public/icons/apple-touch-icon-180.png new file mode 100644 index 0000000..b9d1294 Binary files /dev/null and b/public/icons/apple-touch-icon-180.png differ diff --git a/public/icons/apple-touch-icon.png b/public/icons/apple-touch-icon.png new file mode 100644 index 0000000..b9d1294 Binary files /dev/null and b/public/icons/apple-touch-icon.png differ diff --git a/public/icons/favicon-32.png b/public/icons/favicon-32.png new file mode 100644 index 0000000..ddecbbf Binary files /dev/null and b/public/icons/favicon-32.png differ diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000..3ad7410 Binary files /dev/null and b/public/icons/icon-192.png differ diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png new file mode 100644 index 0000000..88b25e5 Binary files /dev/null and b/public/icons/icon-512.png differ diff --git a/public/icons/icon-maskable-192.png b/public/icons/icon-maskable-192.png new file mode 100644 index 0000000..b579ab7 Binary files /dev/null and b/public/icons/icon-maskable-192.png differ diff --git a/public/icons/icon-maskable-512.png b/public/icons/icon-maskable-512.png new file mode 100644 index 0000000..ee825d1 Binary files /dev/null and b/public/icons/icon-maskable-512.png differ diff --git a/public/icons/splash-1125x2436.png b/public/icons/splash-1125x2436.png new file mode 100644 index 0000000..0b09f60 Binary files /dev/null and b/public/icons/splash-1125x2436.png differ diff --git a/public/icons/splash-1170x2532.png b/public/icons/splash-1170x2532.png new file mode 100644 index 0000000..b58778c Binary files /dev/null and b/public/icons/splash-1170x2532.png differ diff --git a/public/icons/splash-1179x2556.png b/public/icons/splash-1179x2556.png new file mode 100644 index 0000000..294413b Binary files /dev/null and b/public/icons/splash-1179x2556.png differ diff --git a/public/icons/splash-1206x2622.png b/public/icons/splash-1206x2622.png new file mode 100644 index 0000000..ba03b09 Binary files /dev/null and b/public/icons/splash-1206x2622.png differ diff --git a/public/icons/splash-1242x2208.png b/public/icons/splash-1242x2208.png new file mode 100644 index 0000000..5f34337 Binary files /dev/null and b/public/icons/splash-1242x2208.png differ diff --git a/public/icons/splash-1242x2688.png b/public/icons/splash-1242x2688.png new file mode 100644 index 0000000..66267aa Binary files /dev/null and b/public/icons/splash-1242x2688.png differ diff --git a/public/icons/splash-1284x2778.png b/public/icons/splash-1284x2778.png new file mode 100644 index 0000000..67cc8e7 Binary files /dev/null and b/public/icons/splash-1284x2778.png differ diff --git a/public/icons/splash-1290x2796.png b/public/icons/splash-1290x2796.png new file mode 100644 index 0000000..82d8544 Binary files /dev/null and b/public/icons/splash-1290x2796.png differ diff --git a/public/icons/splash-1320x2868.png b/public/icons/splash-1320x2868.png new file mode 100644 index 0000000..dbe2201 Binary files /dev/null and b/public/icons/splash-1320x2868.png differ diff --git a/public/icons/splash-1488x2266.png b/public/icons/splash-1488x2266.png new file mode 100644 index 0000000..108da84 Binary files /dev/null and b/public/icons/splash-1488x2266.png differ diff --git a/public/icons/splash-1536x2048.png b/public/icons/splash-1536x2048.png new file mode 100644 index 0000000..04c30fc Binary files /dev/null and b/public/icons/splash-1536x2048.png differ diff --git a/public/icons/splash-1620x2160.png b/public/icons/splash-1620x2160.png new file mode 100644 index 0000000..3d744d8 Binary files /dev/null and b/public/icons/splash-1620x2160.png differ diff --git a/public/icons/splash-1640x2360.png b/public/icons/splash-1640x2360.png new file mode 100644 index 0000000..ddd10ea Binary files /dev/null and b/public/icons/splash-1640x2360.png differ diff --git a/public/icons/splash-1668x2224.png b/public/icons/splash-1668x2224.png new file mode 100644 index 0000000..acd65e3 Binary files /dev/null and b/public/icons/splash-1668x2224.png differ diff --git a/public/icons/splash-1668x2388.png b/public/icons/splash-1668x2388.png new file mode 100644 index 0000000..b5015d8 Binary files /dev/null and b/public/icons/splash-1668x2388.png differ diff --git a/public/icons/splash-2048x1536.png b/public/icons/splash-2048x1536.png new file mode 100644 index 0000000..101dfd6 Binary files /dev/null and b/public/icons/splash-2048x1536.png differ diff --git a/public/icons/splash-2048x2732.png b/public/icons/splash-2048x2732.png new file mode 100644 index 0000000..d8bb524 Binary files /dev/null and b/public/icons/splash-2048x2732.png differ diff --git a/public/icons/splash-2160x1620.png b/public/icons/splash-2160x1620.png new file mode 100644 index 0000000..2c2dbf0 Binary files /dev/null and b/public/icons/splash-2160x1620.png differ diff --git a/public/icons/splash-2224x1668.png b/public/icons/splash-2224x1668.png new file mode 100644 index 0000000..92b60be Binary files /dev/null and b/public/icons/splash-2224x1668.png differ diff --git a/public/icons/splash-2266x1488.png b/public/icons/splash-2266x1488.png new file mode 100644 index 0000000..730fb32 Binary files /dev/null and b/public/icons/splash-2266x1488.png differ diff --git a/public/icons/splash-2360x1640.png b/public/icons/splash-2360x1640.png new file mode 100644 index 0000000..f03fb5f Binary files /dev/null and b/public/icons/splash-2360x1640.png differ diff --git a/public/icons/splash-2388x1668.png b/public/icons/splash-2388x1668.png new file mode 100644 index 0000000..507d88b Binary files /dev/null and b/public/icons/splash-2388x1668.png differ diff --git a/public/icons/splash-2732x2048.png b/public/icons/splash-2732x2048.png new file mode 100644 index 0000000..96ba260 Binary files /dev/null and b/public/icons/splash-2732x2048.png differ diff --git a/public/icons/splash-750x1334.png b/public/icons/splash-750x1334.png new file mode 100644 index 0000000..b9fcbf0 Binary files /dev/null and b/public/icons/splash-750x1334.png differ diff --git a/public/icons/splash-828x1792.png b/public/icons/splash-828x1792.png new file mode 100644 index 0000000..5c4d840 Binary files /dev/null and b/public/icons/splash-828x1792.png differ diff --git a/public/icons/splash-links.html b/public/icons/splash-links.html new file mode 100644 index 0000000..b1f3623 --- /dev/null +++ b/public/icons/splash-links.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html index f2cb97e..5d42441 100644 --- a/public/index.html +++ b/public/index.html @@ -8,9 +8,41 @@ + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NE Hub diff --git a/public/login.html b/public/login.html index 3b8cd68..c1109bc 100644 --- a/public/login.html +++ b/public/login.html @@ -4,8 +4,43 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sign in ยท NE Hub