init: 整合项目(dashboard + www + docs)

This commit is contained in:
eafonyang
2026-07-30 11:23:50 +08:00
commit 99ab6cc4f4
435 changed files with 61037 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'dev-only-change-me-for-production';
const JWT_ALGORITHM = 'HS256';
const TOKEN_TTL_HOURS = 12;
function createAccessToken(user) {
const now = Math.floor(Date.now() / 1000);
const payload = {
sub: user.id,
username: user.username,
is_super_admin: !!user.is_super_admin,
iat: now,
exp: now + TOKEN_TTL_HOURS * 3600,
};
return jwt.sign(payload, JWT_SECRET, { algorithm: JWT_ALGORITHM });
}
function decodeToken(token) {
return jwt.verify(token, JWT_SECRET, { algorithms: [JWT_ALGORITHM] });
}
function getTokenTtlSeconds() {
return TOKEN_TTL_HOURS * 3600;
}
module.exports = { createAccessToken, decodeToken, getTokenTtlSeconds, TOKEN_TTL_HOURS };
+36
View File
@@ -0,0 +1,36 @@
const crypto = require('crypto');
const { promisify } = require('util');
const pbkdf2Async = promisify(crypto.pbkdf2);
const ITERATIONS = 310000;
const KEYLEN = 32;
const DIGEST = 'sha256';
async function hashPassword(password) {
const salt = crypto.randomBytes(16).toString('hex');
const hash = await pbkdf2Async(password, salt, ITERATIONS, KEYLEN, DIGEST);
return `pbkdf2:${DIGEST}:${ITERATIONS}:${salt}:${hash.toString('hex')}`;
}
async function verifyPassword(password, stored) {
if (!password || !stored || typeof stored !== 'string') return false;
const parts = stored.split(':');
if (parts.length !== 5 || parts[0] !== 'pbkdf2') return false;
const digest = parts[1];
const iterations = parseInt(parts[2], 10);
const salt = parts[3];
const expectedHex = parts[4];
if (!digest || !Number.isFinite(iterations) || !salt || !expectedHex) return false;
const expected = Buffer.from(expectedHex, 'hex');
const actual = await pbkdf2Async(password, salt, iterations, expected.length, digest);
if (expected.length !== actual.length) return false;
return crypto.timingSafeEqual(expected, actual);
}
module.exports = { hashPassword, verifyPassword };
+24
View File
@@ -0,0 +1,24 @@
const ApiResponse = {
success(data = null, msg = 'success') {
return { code: 1, msg, data };
},
error(msg = 'error', code = 0) {
return { code, msg, data: null };
},
noData(msg = 'no data') {
return { code: 2, msg, data: null };
},
};
class PageData {
constructor(items, total, skip, limit) {
this.items = items;
this.total = total;
this.skip = skip;
this.limit = limit;
}
}
module.exports = { ApiResponse, PageData };