auth.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { Elysia } from 'elysia'
  2. import { Eta } from "eta"
  3. const eta = new Eta({ views: "./templates" })
  4. import ratelimits from '../../utils/ratelimits'
  5. import normalizeEmail from '../../utils/normalizeEmail';
  6. import Auth from '../../utils/auth';
  7. import validateTurnstile from '../../utils/validateTurnstile';
  8. import getFormatBasedOnCount from '../../utils/getFormatBasedOnCount';
  9. import formatTimeToString from '../../utils/formatTimeToString';
  10. export default (langName, lang) => new Elysia({ prefix: "/auth" })
  11. .get("/", async ({ cookie, redirect, query, set }) => {
  12. const token = cookie.session.value
  13. const session = await Auth.getSession(token)
  14. if (session) {
  15. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel`)
  16. }
  17. const error = query?.error
  18. set.headers['content-type'] = 'text/html; charset=utf8'
  19. return eta.render(`${langName}/auth/index`, { siteKey: process.env.TURNSTILE_SITE_KEY, lang, error })
  20. })
  21. .post("/", async ({ request, server, body, redirect }) => {
  22. const clientIP = request.headers.get('x-forwarded-for') ?? server.requestIP(request).address
  23. const email = body?.email
  24. if (!normalizeEmail(email)) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=invalidEmail`)
  25. const hourlyEmailRatelimit = ratelimits("authEmailHourly", email, process.env.AUTH_EMAIL_HOURLY_RATELIMIT, 3600)
  26. if (hourlyEmailRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.email`)
  27. const hourlyIPRatelimit = ratelimits("authIPHourly", clientIP, process.env.AUTH_IP_HOURLY_RATELIMIT, 3600)
  28. if (hourlyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip`)
  29. const dailyIPRatelimit = ratelimits("authIPDaily", clientIP, process.env.AUTH_IP_DAILY_RATELIMIT, 86400)
  30. if (dailyIPRatelimit.status) return Response.redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=ratelimits.ip`)
  31. const turnstileResponse = body?.["cf-turnstile-response"]
  32. // const turnstileResponse = "1x00000000000000000000AA"
  33. if (!turnstileResponse) return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=turnstile.noToken`)
  34. const turnstileValid = await validateTurnstile(turnstileResponse, clientIP)
  35. if (!turnstileValid.success) {
  36. let errorMessage = `turnstile.unavailable`;
  37. if (turnstileValid["error-codes"]?.includes("invalid-input-response"))
  38. errorMessage = `turnstile.invalidResponse`
  39. if (turnstileValid["error-codes"]?.includes("timeout-or-duplicate"))
  40. errorMessage = `turnstile.keyUsedOrExpired`
  41. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=${errorMessage}`)
  42. }
  43. const verification = Auth.addVerification(email)
  44. const emailLink = `https://meteostanica.com/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}&code=${verification.code}`
  45. await Auth.sendVerification(email, lang.emails.auth.subject, lang.emails.auth.text(verification.code, emailLink), eta.render(`${langName}/email/auth`, { code: verification.code, link: emailLink }))
  46. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth/verify?token=${verification.token}`)
  47. })
  48. .get("/verify", async ({ query, redirect, set, cookie }) => {
  49. const token = query?.token
  50. const code = Number.parseInt(query?.code)
  51. if (!token) {
  52. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=noVerificationToken`)
  53. }
  54. const verification = Auth.getVerification(token)
  55. if (!verification?.valid) {
  56. return redirect(`/${langName === "sk" ? `` : `${langName}/`}auth?error=verificationTokenUsedOrExpired`)
  57. }
  58. if (!code) {
  59. set.headers['content-type'] = 'text/html; charset=utf8'
  60. return eta.render(`${langName}/auth/verify`, { token })
  61. }
  62. if (code !== verification.code) {
  63. const codeRatelimit = ratelimits("authCodeHourly", token, process.env.AUTH_CODE_HOURLY_RATELIMIT, 3600)
  64. if (codeRatelimit.status) return eta.render(`${langName}/auth/verify`, { token, lang, error: `ratelimits.code` })
  65. set.headers['content-type'] = 'text/html; charset=utf8'
  66. return eta.render(`${langName}/auth/verify`, { token, lang, error: `invalidVerificationCode` })
  67. }
  68. Auth.removeVerification(token)
  69. let user = Auth.getUser(verification.email)
  70. if (!user) {
  71. user = Auth.addUser(verification.email)
  72. }
  73. const session = await Auth.createSession(verification.email)
  74. cookie.session.value = session.token
  75. return redirect(`/${langName === "sk" ? `` : `${langName}/`}panel`)
  76. })
  77. .get("/logout", async ({ cookie, redirect }) => {
  78. const token = cookie.session.value
  79. const session = await Auth.getSession(token)
  80. if (session) {
  81. Auth.removeSession(session.id)
  82. delete cookie.session
  83. }
  84. return redirect(`/${langName === "sk" ? `` : langName}`)
  85. })