From 8823fa808afbd9a384655c9bfd93d5b61b60a11b Mon Sep 17 00:00:00 2001 From: Daanish Syed Date: Thu, 24 Sep 2026 18:49:34 +0100 Subject: [PATCH] updates and cleanup. --- flake.nix | 6 +- index.erb | 1 + mailer.rb | 5 +- main.rb | 17 +++--- players.rb | 47 +++++++++++++++- public/src/js/game.js | 4 ++ public/src/js/game_controller.js | 1 + public/src/js/saver.js | 10 ++++ session.rb | 95 +++++++++++++++----------------- 9 files changed, 115 insertions(+), 71 deletions(-) diff --git a/flake.nix b/flake.nix index bde477a..bc5428d 100644 --- a/flake.nix +++ b/flake.nix @@ -37,9 +37,7 @@ runtimeInputs = [ ruby ]; text = '' set -euo pipefail - data_dir="''${INFINSWEEPER_DATA_DIR:-/var/lib/infinsweeper}" - DB="$data_dir/infinsweeper.db" - mkdir -p "$data_dir" + export DB="''${INFINSWEEPER_DB:-/var/lib/infinsweeper.db}" exec ruby ${app}/main.rb ''; }; @@ -57,6 +55,8 @@ ruby pkgs.sqlite ]; + + env.INFINSWEEPER_DB = "./infinsweeper.db"; }; }; } diff --git a/index.erb b/index.erb index 33febfe..b46d96e 100644 --- a/index.erb +++ b/index.erb @@ -29,6 +29,7 @@ />
+ diff --git a/mailer.rb b/mailer.rb index a425e3d..68e99c3 100644 --- a/mailer.rb +++ b/mailer.rb @@ -1,11 +1,7 @@ # Mailer module -# Not to actually be used. proof of concept. - module Mail def send(to, subject, body) - raise "Can't send email." - from_email = "infinsweeper@domain.com" from_name = "InfinSweeper" to = Array(to).map { |addr| { email_address: { address: addr, name: "" } } } @@ -32,5 +28,6 @@ module Mail return if response.is_a?(Net::HTTPSuccess) Logman.imp "[ZeptoMail ERROR] #{response.body}" + raise "Email failed." end end diff --git a/main.rb b/main.rb index ab7142a..d5275ec 100644 --- a/main.rb +++ b/main.rb @@ -9,17 +9,11 @@ require "zlib" ALPHANUM = [*"0".."9", *"A".."Z", *"a".."z", "-", "_"].freeze -env_data = File.exist?(".env") ? File.read(".env") : "" - -env_data.each_line do |line| - next unless (match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/)) - ENV[match[1]] = match[2] -end - load "logman.rb" # Logman.log ENV.inspect -db_file = ENV.fetch("INFINSWEEPER_DB", File.expand_path("infinsweeper.db", __dir__)) +db_file = File.expand_path(ENV.fetch("INFINSWEEPER_DB", "infinsweeper.db")) +puts db_file DB = Sequel.connect("sqlite:///#{db_file}", single_threaded: false) DB.run("PRAGMA foreign_keys = ON;") DB.create_table? :Players do @@ -45,6 +39,8 @@ load "players.rb" load "session.rb" set :port, ENV.fetch("PORT", 5161) +set :bind, '0.0.0.0' +set :host_authorization, { permitted_hosts: ["infinsweeper.syedm.dev"] } set :public_folder, "public" get "/" do @@ -72,6 +68,8 @@ post "/signup" do return { "message" => "Unauthorized (invalid CSRF token)!" }.to_json end session["csrf_token"] = Array.new(32) { ALPHANUM.sample }.join + status 401 + return { "message" => "New signups are disabled right now.", "csrf_token" => session["csrf_token"] }.to_json if data["email"].nil? || data["pass"].nil? || data["username"].nil? status 400 return { "message" => "Bad request made!", "csrf_token" => session["csrf_token"] }.to_json @@ -119,11 +117,10 @@ post "/login" do return { "message" => "Bad request made!", "csrf_token" => session["csrf_token"] }.to_json end login_status = session.login(data["username"], data["pass"]) + status login_status[0] if login_status[0] == 200 - status 200 return { "message" => login_status[1], "success" => "true", "csrf_token" => session["csrf_token"] }.to_json else - status login_status[0] return { "message" => login_status[1], "csrf_token" => session["csrf_token"] }.to_json end end diff --git a/players.rb b/players.rb index 0697aac..c65723a 100644 --- a/players.rb +++ b/players.rb @@ -51,9 +51,9 @@ module Players Logman.log "Pass req: #{email} & #{code}" - true - send_email(:pass_req, email, code) + + true end def self.pass_reset(pass, code) @@ -90,6 +90,49 @@ module Players player && player[:activation_code] == "!" end + def self.send_email(type, email, *args) + case type + when :new + username, code = args + + Mail.send( + email, + "Verify your InfinSweeper account", + <<~HTML +

Welcome to InfinSweeper, #{ERB::Util.html_escape(username)}!

+

Thanks for signing up.

+

+ Please verify your email by clicking + + this link + . +

+ HTML + ) + + when :pass_req + code = args.first + + Mail.send( + email, + "Reset your InfinSweeper password", + <<~HTML +

Password reset

+

You requested a password reset for your InfinSweeper account.

+

+ + Click here to reset your password + +

+

If you didn't request this, you can safely ignore this email.

+ HTML + ) + + else + raise ArgumentError, "Unknown email type: #{type.inspect}" + end + end + Thread.new do loop do unverified.each do |player| diff --git a/public/src/js/game.js b/public/src/js/game.js index 2e2034f..9902da0 100644 --- a/public/src/js/game.js +++ b/public/src/js/game.js @@ -18,4 +18,8 @@ document.addEventListener("DOMContentLoaded", () => { */ window.game = new GameController(); window.game.init(); + + document.getElementById("reset-button").addEventListener("click", () => { + window.game.reset(); + }); }); diff --git a/public/src/js/game_controller.js b/public/src/js/game_controller.js index 5d15507..73f56a6 100644 --- a/public/src/js/game_controller.js +++ b/public/src/js/game_controller.js @@ -147,6 +147,7 @@ export default class GameController { * @param {number} tries - Number of times the game has been reset */ async reset(tries = 0) { + this.bus.emit("clear_save"); this._clearGameState(); this._generateNewSeed(); await this.start(tries); diff --git a/public/src/js/saver.js b/public/src/js/saver.js index 615d4cf..3ea12dc 100644 --- a/public/src/js/saver.js +++ b/public/src/js/saver.js @@ -39,6 +39,8 @@ export default class Saver { this.bus.on("load_save", this.load.bind(this)); /** @listens EventBus#delete_save */ this.bus.on("delete_save", this.deleteSave.bind(this)); + /** @listens EventBus#clear_save */ + this.bus.on("clear_save", this.clearSave.bind(this)); /** @listens EventBus#start_autosaver */ this.bus.on("start_autosaver", this.startAutosaver.bind(this)); /** @listens EventBus#stop_autosaver */ @@ -154,6 +156,14 @@ export default class Saver { this.delete_save = true; this.stopAutosaver(); } + /** + * @function clearSave + * @description clears the save + */ + clearSave() { + localStorage.removeItem("save"); + this.delete_save = false; + } /** * @function loadFromFile * @description Loads the game data from a file diff --git a/session.rb b/session.rb index 81c8a11..f2ac0b5 100644 --- a/session.rb +++ b/session.rb @@ -3,6 +3,36 @@ class Sessions def initialize(request, response) @request = request @response = response + @session = load_session + end + + def save + compressed = Zlib::Deflate.deflate(JSON.generate(@session)) + encoded = Base64.strict_encode64(compressed) + @response.set_cookie( + "session", + value: encoded, + path: "/", + expires: Time.now + 360 * 24 * 60 * 60, + httponly: true, + secure: ENV["ENV"] == "prod", + samesite: :strict + ) + uid = @session["user"] + if uid + DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update + end + rescue Sequel::Error => e + Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}" + end + + def load_session + cookie = @request.cookies["session"] + return {} unless cookie + JSON.parse(Zlib::Inflate.inflate(Base64.strict_decode64(cookie))) + rescue JSON::ParserError, Zlib::Error, ArgumentError + @response.delete_cookie("session") + {} end def signed_in? @@ -35,38 +65,21 @@ class Sessions return [200, "Remember to verify your email!"] unless Players.verified?(username) [200, "Signed in successfully!"] else - [200, "Couldn't sign you in (Username or password incorrect)!"] + [401, "Couldn't sign you in (Username or password incorrect)!"] end end - def []=(key, val) - session = all - session[key] = val - Logman.log "Updated: #{key} to #{val}" - compressed = Zlib::Deflate.deflate(JSON.generate(session)) - encoded = Base64.encode64(compressed) - @response.set_cookie("session", - value: encoded, - path: "/", - expires: Time.now + 360 * 24 * 60 * 60, - httponly: true, - secure: ENV_HASH["ENV"] == "prod", - samesite: :strict) - uid = session["user"] - DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid - rescue Sequel::Error => e - Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}" - end - def [](key) - session = all - begin - uid = session["user"] - DB["UPDATE SignedInUsers SET last_used_at = CURRENT_TIMESTAMP WHERE code = ?", uid].update if uid - rescue Sequel::Error => e - Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}" - end - session[key] + @session[key] + end + + def []=(key, val) + @session[key] = val + save + end + + def all + @session end def message=(val) @@ -86,31 +99,9 @@ class Sessions @request.env["HTTP_X_CSRF_TOKEN"] == self["csrf_token"] end - def all - session = @request.cookies["session"] - session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) - JSON.parse(session) - rescue JSON::ParserError, Zlib::Error - @response.delete_cookie("session") - {} - end - def delete(key) - session = all - session.delete(key) - Logman.log "Deleted: #{key}" - Logman.log session.inspect - compressed = Zlib::Deflate.deflate(JSON.generate(session)) - encoded = Base64.encode64(compressed) - @response.set_cookie("session", - value: encoded, - path: "/", - expires: Time.now + 360 * 24 * 60 * 60, - httponly: true, - secure: ENV["ENV"] == "prod", - samesite: :strict) - rescue JSON::ParserError, Zlib::Error - @response.delete_cookie("session") + @session.delete(key) + save end Thread.new do