auth.js 5.1 KB

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