main-socket-handler.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import { SocketHandler } from "../socket-handler.js";
  2. import { DockgeServer } from "../dockge-server";
  3. import { log } from "../log";
  4. import { R } from "redbean-node";
  5. import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
  6. import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
  7. import { User } from "../models/user";
  8. import { checkLogin, DockgeSocket, doubleCheckPassword } from "../util-server";
  9. import { passwordStrength } from "check-password-strength";
  10. import jwt from "jsonwebtoken";
  11. import { Settings } from "../settings";
  12. export class MainSocketHandler extends SocketHandler {
  13. create(socket : DockgeSocket, server : DockgeServer) {
  14. // ***************************
  15. // Public Socket API
  16. // ***************************
  17. // Setup
  18. socket.on("setup", async (username, password, callback) => {
  19. try {
  20. if (passwordStrength(password).value === "Too weak") {
  21. throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
  22. }
  23. if ((await R.knex("user").count("id as count").first()).count !== 0) {
  24. throw new Error("Dockge has been initialized. If you want to run setup again, please delete the database.");
  25. }
  26. const user = R.dispense("user");
  27. user.username = username;
  28. user.password = generatePasswordHash(password);
  29. await R.store(user);
  30. server.needSetup = false;
  31. callback({
  32. ok: true,
  33. msg: "successAdded",
  34. msgi18n: true,
  35. });
  36. } catch (e) {
  37. callback({
  38. ok: false,
  39. msg: e.message,
  40. });
  41. }
  42. });
  43. // Login by token
  44. socket.on("loginByToken", async (token, callback) => {
  45. const clientIP = await server.getClientIP(socket);
  46. log.info("auth", `Login by token. IP=${clientIP}`);
  47. try {
  48. const decoded = jwt.verify(token, server.jwtSecret);
  49. log.info("auth", "Username from JWT: " + decoded.username);
  50. const user = await R.findOne("user", " username = ? AND active = 1 ", [
  51. decoded.username,
  52. ]) as User;
  53. if (user) {
  54. // Check if the password changed
  55. if (decoded.h !== shake256(user.password, SHAKE256_LENGTH)) {
  56. throw new Error("The token is invalid due to password change or old token");
  57. }
  58. log.debug("auth", "afterLogin");
  59. await server.afterLogin(socket, user);
  60. log.debug("auth", "afterLogin ok");
  61. log.info("auth", `Successfully logged in user ${decoded.username}. IP=${clientIP}`);
  62. callback({
  63. ok: true,
  64. });
  65. } else {
  66. log.info("auth", `Inactive or deleted user ${decoded.username}. IP=${clientIP}`);
  67. callback({
  68. ok: false,
  69. msg: "authUserInactiveOrDeleted",
  70. msgi18n: true,
  71. });
  72. }
  73. } catch (error) {
  74. log.error("auth", `Invalid token. IP=${clientIP}`);
  75. if (error.message) {
  76. log.error("auth", error.message, `IP=${clientIP}`);
  77. }
  78. callback({
  79. ok: false,
  80. msg: "authInvalidToken",
  81. msgi18n: true,
  82. });
  83. }
  84. });
  85. // Login
  86. socket.on("login", async (data, callback) => {
  87. const clientIP = await server.getClientIP(socket);
  88. log.info("auth", `Login by username + password. IP=${clientIP}`);
  89. // Checking
  90. if (typeof callback !== "function") {
  91. return;
  92. }
  93. if (!data) {
  94. return;
  95. }
  96. // Login Rate Limit
  97. if (!await loginRateLimiter.pass(callback)) {
  98. log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`);
  99. return;
  100. }
  101. const user = await this.login(data.username, data.password);
  102. if (user) {
  103. if (user.twofa_status === 0) {
  104. server.afterLogin(socket, user);
  105. log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
  106. callback({
  107. ok: true,
  108. token: User.createJWT(user, server.jwtSecret),
  109. });
  110. }
  111. if (user.twofa_status === 1 && !data.token) {
  112. log.info("auth", `2FA token required for user ${data.username}. IP=${clientIP}`);
  113. callback({
  114. tokenRequired: true,
  115. });
  116. }
  117. if (data.token) {
  118. const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
  119. if (user.twofa_last_token !== data.token && verify) {
  120. server.afterLogin(socket, user);
  121. await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [
  122. data.token,
  123. socket.userID,
  124. ]);
  125. log.info("auth", `Successfully logged in user ${data.username}. IP=${clientIP}`);
  126. callback({
  127. ok: true,
  128. token: User.createJWT(user, server.jwtSecret),
  129. });
  130. } else {
  131. log.warn("auth", `Invalid token provided for user ${data.username}. IP=${clientIP}`);
  132. callback({
  133. ok: false,
  134. msg: "authInvalidToken",
  135. msgi18n: true,
  136. });
  137. }
  138. }
  139. } else {
  140. log.warn("auth", `Incorrect username or password for user ${data.username}. IP=${clientIP}`);
  141. callback({
  142. ok: false,
  143. msg: "authIncorrectCreds",
  144. msgi18n: true,
  145. });
  146. }
  147. });
  148. // Change Password
  149. socket.on("changePassword", async (password, callback) => {
  150. try {
  151. checkLogin(socket);
  152. if (! password.newPassword) {
  153. throw new Error("Invalid new password");
  154. }
  155. if (passwordStrength(password.newPassword).value === "Too weak") {
  156. throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
  157. }
  158. let user = await doubleCheckPassword(socket, password.currentPassword);
  159. await user.resetPassword(password.newPassword);
  160. callback({
  161. ok: true,
  162. msg: "Password has been updated successfully.",
  163. });
  164. } catch (e) {
  165. callback({
  166. ok: false,
  167. msg: e.message,
  168. });
  169. }
  170. });
  171. socket.on("getSettings", async (callback) => {
  172. try {
  173. checkLogin(socket);
  174. const data = await Settings.getSettings("general");
  175. callback({
  176. ok: true,
  177. data: data,
  178. });
  179. } catch (e) {
  180. callback({
  181. ok: false,
  182. msg: e.message,
  183. });
  184. }
  185. });
  186. socket.on("setSettings", async (data, currentPassword, callback) => {
  187. try {
  188. checkLogin(socket);
  189. // If currently is disabled auth, don't need to check
  190. // Disabled Auth + Want to Disable Auth => No Check
  191. // Disabled Auth + Want to Enable Auth => No Check
  192. // Enabled Auth + Want to Disable Auth => Check!!
  193. // Enabled Auth + Want to Enable Auth => No Check
  194. const currentDisabledAuth = await Settings.get("disableAuth");
  195. if (!currentDisabledAuth && data.disableAuth) {
  196. await doubleCheckPassword(socket, currentPassword);
  197. }
  198. console.log(data);
  199. await Settings.setSettings("general", data);
  200. callback({
  201. ok: true,
  202. msg: "Saved"
  203. });
  204. server.sendInfo(socket);
  205. } catch (e) {
  206. callback({
  207. ok: false,
  208. msg: e.message,
  209. });
  210. }
  211. });
  212. }
  213. async login(username : string, password : string) {
  214. if (typeof username !== "string" || typeof password !== "string") {
  215. return null;
  216. }
  217. const user = await R.findOne("user", " username = ? AND active = 1 ", [
  218. username,
  219. ]);
  220. if (user && verifyPassword(password, user.password)) {
  221. // Upgrade the hash to bcrypt
  222. if (needRehashPassword(user.password)) {
  223. await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
  224. generatePasswordHash(password),
  225. user.id,
  226. ]);
  227. }
  228. return user;
  229. }
  230. return null;
  231. }
  232. }