auth.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Database } from "bun:sqlite";
  2. const authDB = new Database("./data/auth.sqlite");
  3. authDB.run(`create table if not exists verifications (
  4. email text not null primary key,
  5. token text not null,
  6. code integer not null,
  7. timestamp datetime default current_timestamp
  8. );`)
  9. authDB.run(`create table if not exists users (
  10. email text not null primary key,
  11. name text,
  12. timestamp datetime default current_timestamp
  13. );`)
  14. authDB.run(`create table if not exists sessions (
  15. id text not null primary key,
  16. secretHash text not null,
  17. email text not null,
  18. timestamp datetime default current_timestamp
  19. );`)
  20. import nodemailer from "nodemailer"
  21. import generateSecureRandomString from "./generateSecureRandomString";
  22. import generateSecureCode from "./generateSecureCode";
  23. import hashSecret from "./hashSecret";
  24. import Meteostanice from "./meteostanice";
  25. const transporter = nodemailer.createTransport({
  26. host: process.env.AUTH_EMAIL_SMTP_HOSTNAME,
  27. port: process.env.AUTH_EMAIL_SMTP_PORT,
  28. secure: true, // Use true for port 465, false for port 587
  29. auth: {
  30. user: process.env.AUTH_EMAIL_SMTP_USERNAME,
  31. pass: process.env.AUTH_EMAIL_SMTP_PASSWORD,
  32. },
  33. });
  34. export default class Auth {
  35. static addVerification(email) {
  36. const token = generateSecureRandomString()
  37. const code = generateSecureCode()
  38. authDB.prepare(`
  39. INSERT INTO verifications (email, token, code)
  40. VALUES (?, ?, ?)
  41. ON CONFLICT(email) DO UPDATE SET
  42. token = excluded.token,
  43. code = excluded.code;
  44. `).run(email, token, code)
  45. return { email, token, code }
  46. }
  47. static async sendVerification(email, subject, text, html) {
  48. return await transporter.sendMail({
  49. from: '"auth — meteostanica" <auth@meteostanica.com>',
  50. to: email,
  51. subject,
  52. text, // Plain-text version of the message
  53. html, // HTML version of the message
  54. });
  55. }
  56. static getVerification(token) {
  57. const statement = authDB.prepare(`
  58. SELECT *,
  59. ( CASE
  60. WHEN (strftime('%s', 'now') - strftime('%s', timestamp)) > $seconds THEN 0
  61. ELSE 1
  62. END
  63. ) AS valid
  64. FROM verifications
  65. WHERE token = $token;
  66. `)
  67. const result = statement.get({
  68. $seconds: process.env.EMAIL_VERIFICATION_TIMEOUT,
  69. $token: token
  70. });
  71. return result;
  72. }
  73. static removeVerification(token) {
  74. authDB.prepare(`
  75. DELETE
  76. FROM verifications
  77. WHERE token = ?;
  78. `).run(token)
  79. }
  80. static removeUserVerifications(email) {
  81. authDB.prepare(`
  82. DELETE
  83. FROM verifications
  84. WHERE email = ?;
  85. `).run(email)
  86. }
  87. static addUser(email) {
  88. const statement = authDB.prepare("INSERT INTO users (email) VALUES (?);")
  89. statement.run(
  90. email
  91. );
  92. return { email };
  93. }
  94. static getUser(email) {
  95. const statement = authDB.prepare("select * from users where email = ?;")
  96. const result = statement.get(
  97. email
  98. );
  99. return result;
  100. }
  101. static editUser(email, newName, newEmail) {
  102. const statement = authDB.prepare("select * from users where email = ?;")
  103. const user = statement.get(
  104. email
  105. );
  106. if (!user) return null
  107. const result = authDB.prepare(`
  108. update users
  109. set name = ?,
  110. email = ?
  111. where email = ?;
  112. `).run(newName, newEmail, email)
  113. return result
  114. }
  115. static removeUser(email) {
  116. Meteostanice.removeOwned(email)
  117. this.removeUserSessions(email)
  118. this.removeUserVerifications(email)
  119. authDB.prepare(`
  120. DELETE
  121. FROM users
  122. WHERE email = ?;
  123. `).run(email)
  124. }
  125. static async createSession(email) {
  126. const id = generateSecureRandomString();
  127. const secret = generateSecureRandomString();
  128. const secretHash = await hashSecret(secret);
  129. const token = id + "." + secret;
  130. const statement = authDB.prepare("INSERT INTO sessions (id, secretHash, email) VALUES (?, ?, ?);")
  131. statement.run(
  132. id,
  133. secretHash,
  134. email
  135. );
  136. return { id, secretHash, token, email };
  137. }
  138. static async getSession(token) {
  139. if (!token) return null
  140. const [id, secret] = token.split(".")
  141. const statement = authDB.prepare(`
  142. SELECT *,
  143. ( CASE
  144. WHEN (strftime('%s', 'now') - strftime('%s', timestamp)) > $seconds THEN 0
  145. ELSE 1
  146. END
  147. ) AS valid
  148. FROM sessions
  149. WHERE id = $id AND secretHash = $secretHash;
  150. `)
  151. const result = statement.get({
  152. $seconds: process.env.SESSION_TIMEOUT,
  153. $id: id,
  154. $secretHash: await hashSecret(secret)
  155. });
  156. return result
  157. }
  158. static removeSession(id) {
  159. authDB.prepare(`
  160. DELETE
  161. FROM sessions
  162. WHERE id = ?;
  163. `).run(id)
  164. }
  165. static removeUserSessions(email) {
  166. authDB.prepare(`
  167. DELETE
  168. FROM sessions
  169. WHERE email = ?;
  170. `).run(email)
  171. }
  172. }