auth.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import crypto from 'node:crypto';
  2. const COOKIE_NAME = 'ppai_session';
  3. const DEFAULT_MAX_AGE = 12;
  4. const getConfig = () => ({
  5. username: process.env.PPAI_AUTH_USER || 'admin',
  6. password: process.env.PPAI_AUTH_PASSWORD || 'admin123',
  7. secret: process.env.PPAI_AUTH_SECRET || 'ppai-dev-secret',
  8. maxAge: Number(process.env.PPAI_AUTH_MAX_AGE || DEFAULT_MAX_AGE) * 60 * 60 * 1000,
  9. });
  10. const base64UrlEncode = (value) => Buffer.from(value).toString('base64url');
  11. const base64UrlDecode = (value) => Buffer.from(value, 'base64url').toString();
  12. const sign = (payload, secret) => {
  13. return crypto.createHmac('sha256', secret).update(payload).digest('base64url');
  14. };
  15. const safeEqual = (a = '', b = '') => {
  16. const aBuffer = Buffer.from(a);
  17. const bBuffer = Buffer.from(b);
  18. if (aBuffer.length !== bBuffer.length) {
  19. return false;
  20. }
  21. return crypto.timingSafeEqual(aBuffer, bBuffer);
  22. };
  23. export const cookieOptions = () => {
  24. const { maxAge } = getConfig();
  25. return {
  26. httpOnly: true,
  27. sameSite: 'lax',
  28. secure: process.env.NODE_ENV === 'production',
  29. maxAge,
  30. path: '/',
  31. };
  32. };
  33. export const clearCookieOptions = () => ({
  34. ...cookieOptions(),
  35. maxAge: 0,
  36. });
  37. export const createSession = (username) => {
  38. const { secret, maxAge } = getConfig();
  39. const payload = base64UrlEncode(JSON.stringify({
  40. username,
  41. exp: Date.now() + maxAge,
  42. }));
  43. const signature = sign(payload, secret);
  44. return `${payload}.${signature}`;
  45. };
  46. export const verifySession = (token) => {
  47. if (!token || typeof token !== 'string') {
  48. return null;
  49. }
  50. const [payload, signature] = token.split('.');
  51. if (!payload || !signature) {
  52. return null;
  53. }
  54. const { secret } = getConfig();
  55. const expectedSignature = sign(payload, secret);
  56. if (!safeEqual(signature, expectedSignature)) {
  57. return null;
  58. }
  59. try {
  60. const session = JSON.parse(base64UrlDecode(payload));
  61. if (!session?.username || !session?.exp || Date.now() > session.exp) {
  62. return null;
  63. }
  64. return { username: session.username };
  65. }
  66. catch {
  67. return null;
  68. }
  69. };
  70. export const validateCredentials = (username, password) => {
  71. const config = getConfig();
  72. return safeEqual(username, config.username) && safeEqual(password, config.password);
  73. };
  74. export const authCookieName = COOKIE_NAME;