# class Sessions class Sessions def initialize(request, response) @request = request @response = response end def signed_in? $active_users[self["user"]] end def logout uid = self["user"] $active_users.delete(uid) delete("user") DB["delete from SignedInUsers where code = ?", uid].delete true rescue Sequel::Error => e Logman.log "DB Error: #{e.message}" false end def login(username, pass) Logman.log "Logging in: #{username} & #{pass} #{ENV_HASH["SALT"]}" player = Players.authorized?(username, pass) if player code = Array.new(24) { ALPHANUM.sample }.join self["user"] = code $active_users[code] = username begin DB["insert into SignedInUsers (code, player) values (?, ?)", code, username].insert rescue Sequel::Error => e Logman.log "DB Error: #{e.message}" return [500, "Internal server error when signing you in!"] end 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)!"] end end # TODO: Use .all here def []=(key, val) session = @request.cookies["session"] session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) session = JSON.parse(session) 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 JSON::ParserError, Zlib::Error @response.delete_cookie("session") rescue Sequel::Error => e Logman.log "DB Error: #{e.message} when updating last_used_at for #{uid}" end def [](key) session = @request.cookies["session"] session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) session = JSON.parse(session) 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] rescue JSON::ParserError, Zlib::Error @response.delete_cookie("session") "" end def message=(val) @response.set_cookie("message", value: val, path: "/", expires: Time.now + 360 * 24 * 60 * 60, secure: ENV_HASH["ENV"] == "prod", samesite: :strict) end def message @request.cookies["message"] end def csrf_auth? @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 = @request.cookies["session"] session = session.nil? ? "{}" : Zlib::Inflate.inflate(Base64.decode64(session)) session = JSON.parse(session) 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_HASH["ENV"] == "prod", samesite: :strict) rescue JSON::ParserError, Zlib::Error @response.delete_cookie("session") end Thread.new do loop do now = Time.now fifteen_days_ago = now - (60 * 60 * 24 * 15) six_days_ago = now - (60 * 60 * 24 * 6) old_sessions = (DB[:SignedInUsers].where { created_at < fifteen_days_ago }.all + DB[:SignedInUsers].where { last_used_at < six_days_ago }.all).uniq { |s| s[:code] } old_sessions.each do |session| begin DB[:SignedInUsers].where(code: session[:code]).delete rescue StandardError => e Logman.log "Thread DB error: #{e.message} on #{session[:code]} for #{session[:player]}" end $active_users.delete(session[:code]) puts "Auto-logged out: #{session[:player]} (expired session)" end rescue StandardError => e Logman.log "Thread error: #{e.message}" ensure sleep 60 * 60 * 24 Logman.log "Thread sleeping" end end end