feat(h5): allow guest access without login and retain history only for WeChat
This commit is contained in:
+26
-15
@@ -27,6 +27,7 @@ module.exports = function createAccounts(root, env = process.env) {
|
||||
const prefix = base ? base.pathname.replace(/\/?$/, '/') : '/';
|
||||
if (base) { base.pathname = prefix; base.search = ''; base.hash = ''; }
|
||||
const secure = !!(base && base.protocol === 'https:');
|
||||
const guestSessions = new Map(); // No guest identities/sessions are written to disk.
|
||||
const appId = env.WECHAT_APP_ID || '';
|
||||
const secret = env.WECHAT_APP_SECRET || '';
|
||||
const available = !!(appId && secret && secure && !/^(localhost|127\.|192\.168\.|10\.|172\.(1[6-9]|2\d|3[01])\.)/.test(base.hostname));
|
||||
@@ -34,14 +35,16 @@ module.exports = function createAccounts(root, env = process.env) {
|
||||
return Object.fromEntries((req.headers.cookie || '').split(';').map(s => s.trim().split('=')));
|
||||
}
|
||||
function cookie(res, name, value, age) {
|
||||
const line = `${name}=${value}; Path=${prefix}; HttpOnly; SameSite=Lax; Max-Age=${age}${secure ? '; Secure' : ''}`;
|
||||
const line = `${name}=${value}; Path=${prefix}; HttpOnly; SameSite=Lax${age == null ? '' : '; Max-Age=' + age}${secure ? '; Secure' : ''}`;
|
||||
const old = res.getHeader('Set-Cookie') || [];
|
||||
res.setHeader('Set-Cookie', [].concat(old, line));
|
||||
}
|
||||
function session(req) {
|
||||
const token = cookies(req).juzi_session;
|
||||
const guest = token && guestSessions.get(hash(token));
|
||||
if (guest && guest.expires > Date.now()) return guest.user;
|
||||
const row = token && db.sessions[hash(token)];
|
||||
return row && row.expires > Date.now() ? db.users.find(u => u.id === row.user) : null;
|
||||
return row && row.expires > Date.now() ? db.users.find(u => u.id === row.user && u.kind === 'wechat') : null;
|
||||
}
|
||||
function login(res, user) {
|
||||
for (const key of Object.keys(db.sessions)) if (db.sessions[key].expires < Date.now()) delete db.sessions[key];
|
||||
@@ -94,19 +97,22 @@ module.exports = function createAccounts(root, env = process.env) {
|
||||
if (!rate(req)) { json(res, 429, { ok: false, message: '操作频繁,请稍后再试' }); return true; }
|
||||
const data = await body(req);
|
||||
if (data.consent !== true) { json(res, 400, { ok: false, message: '请先阅读并同意协议' }); return true; }
|
||||
let token = data.token, user;
|
||||
if (token) {
|
||||
if (typeof token !== 'string' || !/^[a-f0-9]{64}$/.test(token)) token = null;
|
||||
user = token && db.users.find(u => u.kind === 'browser' && u.credential === hash(token));
|
||||
if (!user) { json(res, 401, { ok: false, message: '游客身份已失效,请重新登录' }); return true; }
|
||||
} else {
|
||||
token = random(); user = { id: crypto.randomUUID(), kind: 'browser', credential: hash(token), consent: new Date().toISOString(), reports: [] };
|
||||
db.users.push(user);
|
||||
}
|
||||
login(res, user); json(res, 200, { ok: true, user: publicUser(user), token });
|
||||
// Old persistent guest tokens cannot restore identity or history.
|
||||
if (data.token) { json(res, 401, { ok: false, message: '游客身份已失效,请重新登录' }); return true; }
|
||||
for (const [key, value] of guestSessions) if (value.expires < Date.now()) guestSessions.delete(key);
|
||||
const previous = cookies(req).juzi_session;
|
||||
if (previous) guestSessions.delete(hash(previous));
|
||||
const token = random(), user = { id: crypto.randomUUID(), kind: 'browser' };
|
||||
guestSessions.set(hash(token), { user, expires: Date.now() + 8 * 3600000 });
|
||||
cookie(res, 'juzi_session', token, null);
|
||||
json(res, 200, { ok: true, user: publicUser(user) });
|
||||
} else if (pathname === '/api/auth/logout' && req.method === 'POST') {
|
||||
const token = cookies(req).juzi_session; if (token) delete db.sessions[hash(token)];
|
||||
cookie(res, 'juzi_session', '', 0); save(); json(res, 200, { ok: true });
|
||||
const token = cookies(req).juzi_session;
|
||||
if (token) {
|
||||
guestSessions.delete(hash(token));
|
||||
if (db.sessions[hash(token)]) { delete db.sessions[hash(token)]; save(); }
|
||||
}
|
||||
cookie(res, 'juzi_session', '', 0); json(res, 200, { ok: true });
|
||||
} else if (pathname === '/api/auth/wechat' && req.method === 'POST') {
|
||||
const data = await body(req);
|
||||
if (data.consent !== true) { json(res, 400, { ok: false, message: '请先阅读并同意协议' }); return true; }
|
||||
@@ -147,6 +153,11 @@ module.exports = function createAccounts(root, env = process.env) {
|
||||
const user = session(req);
|
||||
if (!user) { json(res, 401, { ok: false, message: '请先登录' }); return true; }
|
||||
const id = pathname.slice('/api/reports/'.length);
|
||||
if (user.kind !== 'wechat') {
|
||||
json(res, pathname === '/api/reports' ? 200 : 404,
|
||||
pathname === '/api/reports' ? { ok: true, total: 0, items: [] } : { ok: false, message: '游客模式不保存历史报告' });
|
||||
return true;
|
||||
}
|
||||
if (pathname === '/api/reports') json(res, 200, { ok: true, total: user.reports.length, items: user.reports });
|
||||
else if (/^R[\w-]+$/.test(id) && user.reports.some(r => r.id === id)) {
|
||||
const file = path.join(snapshots, user.id, id + '.json');
|
||||
@@ -159,7 +170,7 @@ module.exports = function createAccounts(root, env = process.env) {
|
||||
}
|
||||
function storeReport(ownerId, snapshot) {
|
||||
const user = db.users.find(u => u.id === ownerId);
|
||||
if (!user || !/^R[\w-]+$/.test(snapshot.id)) throw new Error('Invalid report owner');
|
||||
if (!user || user.kind !== 'wechat' || !/^R[\w-]+$/.test(snapshot.id)) throw new Error('Invalid report owner');
|
||||
const dir = path.join(snapshots, user.id); fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
atomic(path.join(dir, snapshot.id + '.json'), snapshot);
|
||||
user.reports = [{ id: snapshot.id, created_at: snapshot.created_at, total: snapshot.vm.total, skinAge: snapshot.vm.skinAgeDisplay }]
|
||||
|
||||
Reference in New Issue
Block a user