From 09656316644bc2079c8495f0d9f9c68d2335ac4d Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sat, 21 Jun 2025 23:02:02 +0300 Subject: [PATCH] Finalize user signin and session backend --- .rubocop.yml | 6 +++ main.rb | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ players.rb | 21 +++++--- schema.sql | 3 +- session.rb | 49 +++++++++++++++++++ 5 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 session.rb diff --git a/.rubocop.yml b/.rubocop.yml index f251d3a..ece38e3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -36,3 +36,9 @@ Metrics/PerceivedComplexity: Metrics/CyclomaticComplexity: Enabled: false + +Layout/CaseIndentation: + Enabled: false + +Layout/EndAlignment: + Enabled: false diff --git a/main.rb b/main.rb index 5f38633..b15bfee 100644 --- a/main.rb +++ b/main.rb @@ -1,7 +1,142 @@ require "sinatra" +require "json" + +require_relative "players" +require_relative "session" + +json_path = File.expand_path("db.json") +signed_in_users = JSON.parse(File.read(json_path))["signed_in_users"] set :public_folder, "public" get "/" do send_file "index.html" end + +get "/debug" do + return get_session_all(request, response).inspect +end + +post "/new_player" do + data = JSON.parse(request.body.read) + if data["email"].nil? || data["pass"].nil? || data["username"].nil? + status 400 + return '{"message": "Bad request made!"}' + end + player = Players.mk_player(data["username"], data["email"], data["pass"]) + case player + when "Successfully registered!" + hash = XXhash.xxh64(data["email"] + Time.now.to_s, 1234) + code = "" + while hash.positive? + code << ALPHANUM[hash % 64] + hash /= 64 + end + code = code.reverse.rjust(12, "0") + set_session(request, response, "user", code) + signed_in_users[code] = data["email"] + json = JSON.parse(File.read(json_path)) + json["signed_in_users"] = signed_in_users.clone + File.write(json_path, JSON.pretty_generate(json)) + status 200 + else + status 400 + end + return "{\"message\": \"#{player}\"}" +end + +get "/verify/:code" do + if Players.verify(params[:code]) + status 200 + return "{\"message\": \"Verified successfully!\"}" + else + status 400 + return "{\"message\": \"Used or Invalid code!\"}" + end +end + +post "/login" do + data = JSON.parse(request.body.read) + player = Players.authorized?(data["email"], data["pass"]) + if player + hash = XXhash.xxh64(data["email"] + Time.now.to_s, 1234) + code = "" + while hash.positive? + code << ALPHANUM[hash % 64] + hash /= 64 + end + code = code.reverse.rjust(12, "0") + set_session(request, response, "user", code) + signed_in_users[code] = data["email"] + json = JSON.parse(File.read(json_path)) + json["signed_in_users"] = signed_in_users.clone + File.write(json_path, JSON.pretty_generate(json)) + status 200 + return "{\"message\": \"Remember to verify your email!\"}" unless Players.verified?(data["email"]) + return "{\"message\": \"Signed in successfully!\"}" + else + status 400 + return "{\"message\": \"Email or password incorrect!\"}" + end +end + +post "/logout" do + uid = get_session(request, response, "user") + if signed_in_users[uid].nil? + status 400 + return "{\"message\":\"Not signed in!\"}" + end + signed_in_users.delete(uid) + rm_session(request, response, "user") + json = JSON.parse(File.read(json_path)) + json["signed_in_users"] = signed_in_users.clone + File.write(json_path, JSON.pretty_generate(json)) + status 200 + return "{\"message\":\"Signed out!\"}" +end + +post "/forgot_password" do + data = JSON.parse(request.body.read) + if data["email"].nil? + status 400 + return "{\"message\":\"Bad request made!\"}" + end + Players.pass_req(data["email"]) + status 200 + return "{\"message\":\"Password reset email sent!\"}" +end + +post "/reset_password/:code" do + data = JSON.parse(request.body.read) + if data["pass"].nil? || params[:code].nil? + status 400 + return "{\"message\":\"Bad request made!\"}" + end + if Players.pass_reset(data["pass"], params[:code]) + status 200 + return "{\"message\":\"Password reset successfully!\"}" + else + status 400 + return "{\"message\":\"Couldn\'t reset password!\"}" + end +end + +delete "/rm_player" do + uid = get_session(request, response, "user") + if uid.nil? || signed_in_users[uid].nil? + status 400 + return '{"message": "Not signed in!"}' + end + if Players.rm_player(signed_in_users[uid]) + status 200 + signed_in_users.delete(uid) + rm_session(request, response, "user") + json = JSON.parse(File.read(json_path)) + json["signed_in_users"] = signed_in_users.clone + File.write(json_path, JSON.pretty_generate(json)) + return '{"message": "Sorry to see you go.."}' + else + status 500 + return '{"message": "Couldn\'t delete!"}' + end +end diff --git a/players.rb b/players.rb index 395ee9b..5cd23e0 100644 --- a/players.rb +++ b/players.rb @@ -14,7 +14,7 @@ module Players end def self.rm_player(email) - DB["delete from Players where email = ?", email].delete + DB["delete from Players where email = ?", email].delete != 0 end def self.mk_player(username, email, pass) @@ -46,10 +46,6 @@ module Players ].insert send_email(:new, email, username, code) - Thread.new do - sleep 24 * 60 * 60 - rm_player(email) unless verified?(email) - end "Successfully registered!" rescue ArgumentError => e @@ -62,7 +58,12 @@ module Players DB["update Players set activation_code = ? where code = ?", "!", code].update != 0 end + def self.unverified + DB["select * from Players where activation_code <> ?", "!"].all + end + def self.pass_req(email) + return unless self[email] path = File.expand_path("db.json") json = File.exist?(path) ? JSON.parse(File.read(path)) : {} json["pass_num"] ||= 0 @@ -83,7 +84,8 @@ module Players end def self.pass_reset(new_pass, code) - DB["update Players set pass = ? where new_pass_code = ?", new_pass, code].update != 0 + digest = XXhash.xxh32(new_pass, 1234) + DB["update Players set digest = ? where new_pass_code = ?", digest, code].update != 0 end def self.[](email) @@ -104,4 +106,11 @@ module Players player = self[email] player && player[:code] == "!" end + + Thread.new do + sleep 60 * 60 + unverified.each do |player| + rm_player(player[:email]) if player[:created_at] + 24 * 60 * 60 < Time.now + end + end end diff --git a/schema.sql b/schema.sql index 0d60a11..3decd50 100644 --- a/schema.sql +++ b/schema.sql @@ -4,5 +4,6 @@ CREATE TABLE Players ( digest BLOB, data BLOB, activation_code TEXT, - new_pass_code TEXT + new_pass_code TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); diff --git a/session.rb b/session.rb new file mode 100644 index 0000000..0eb8455 --- /dev/null +++ b/session.rb @@ -0,0 +1,49 @@ +require "base64" +require "json" + +def set_session(request, response, key, val) + session = request.cookies["session"] + session = "e30=\n" if session.nil? + session = JSON.parse(Base64.decode64(session)) + session[key] = val + session = Base64.encode64(JSON.generate(session)) + response.set_cookie("session", + value: session, + path: "/", + expires: Time.now + 360 * 24 * 60 * 60) +rescue JSON::ParserError + response.delete_cookie("session") +end + +def get_session(request, response, key) + session = request.cookies["session"] + session = "{}" if session.nil? + session = JSON.parse(Base64.decode64(session)) + session[key] +rescue JSON::ParserError + response.delete_cookie("session") + "" +end + +def get_session_all(request, response) + session = request.cookies["session"] + session = "{}" if session.nil? + JSON.parse(Base64.decode64(session)) +rescue JSON::ParserError + response.delete_cookie("session") + "" +end + +def rm_session(request, response, key) + session = request.cookies["session"] + session = "{}" if session.nil? + session = JSON.parse(Base64.decode64(session)) + session.delete(key) + session = Base64.encode64(JSON.generate(session)) + response.set_cookie("session", + value: session, + path: "/", + expires: Time.now + 360 * 24 * 60 * 60) +rescue JSON::ParserError + response.delete_cookie("session") +end