mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
Three features from this session, full server suite green (535/535). TOTP 2FA (#100) — backend shipped without a UI; add it: - Login: mfa_required -> 6-digit challenge (recovery codes accepted) -> /totp/verify. - Settings > Account: enable (QR + confirm -> recovery codes once), regenerate, disable; SSO accounts see "managed by your identity provider". - /totp/setup returns a server-rendered qr_data_url (bundled qrcode dep). keyuri folds the request Host into the issuer so multi-instance accounts are distinguishable in the authenticator app. Email verification on signup — hosted HARD-block / self-host SOFT-nudge: - email_verified column; existing users asked on first login (SSO + platform admins grandfathered); single-use 24h tokens (SHA-256 hashed). - Gate engages only when email is configured (never locks out a no-mail instance). GET /verify-email + POST /resend-verification (generic, no account enumeration). - Client: "confirm your email" flow + resend, verified/error toasts, self-host banner; onAuthSuccess refuses a tokenless response (defensive). Tizen SSSP URL-Launcher install — Fusion-style one-URL native install: - Server hosts /tizen/sssp_config.xml (dynamic <size>, always matches the served .wgt) + /tizen/ScreenTinker.wgt + a human landing. lib/wgt-cache.js resolves the signed .wgt (/data mount wins, mirroring the APK). - build-wgt.sh also emits a static sssp_config.xml for CDN hosting. - Retail panels require a Samsung Partner cert; dev-mode is SDB self-signed only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
1.7 KiB
JavaScript
37 lines
1.7 KiB
JavaScript
'use strict';
|
|
|
|
// Signup email-verification tokens. The emailed token is random and stored ONLY as a
|
|
// SHA-256 hash (single-use, same discipline as recovery codes / api tokens) — the
|
|
// plaintext lives just in the email link. One pending token per user, kept on the users
|
|
// row (email_verify_hash + email_verify_expires), so a resend simply overwrites the old.
|
|
|
|
const crypto = require('crypto');
|
|
const { db } = require('../db/database');
|
|
const { hashToken } = require('../middleware/apiToken');
|
|
|
|
const TTL_SEC = 24 * 3600; // link valid for 24h
|
|
|
|
// Mint a fresh token for the user, store its hash + expiry, return the PLAINTEXT (emailed once).
|
|
function issue(userId) {
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
const expires = Math.floor(Date.now() / 1000) + TTL_SEC;
|
|
db.prepare('UPDATE users SET email_verify_hash = ?, email_verify_expires = ? WHERE id = ?')
|
|
.run(hashToken(token), expires, userId);
|
|
return token;
|
|
}
|
|
|
|
// Consume a token: mark the matching user verified + clear the token. Returns the user id on
|
|
// success, else null (unknown / expired / already-used). Single-use — the hash is cleared.
|
|
function consume(token) {
|
|
if (!token || typeof token !== 'string') return null;
|
|
const row = db.prepare('SELECT id, email_verify_expires FROM users WHERE email_verify_hash = ?')
|
|
.get(hashToken(token));
|
|
if (!row) return null;
|
|
if (!row.email_verify_expires || row.email_verify_expires < Math.floor(Date.now() / 1000)) return null;
|
|
db.prepare("UPDATE users SET email_verified = 1, email_verify_hash = NULL, email_verify_expires = NULL, updated_at = strftime('%s','now') WHERE id = ?")
|
|
.run(row.id);
|
|
return row.id;
|
|
}
|
|
|
|
module.exports = { issue, consume, TTL_SEC };
|