require "kemal" require "../*" require "io" require "file" require "exception" require "crypto/bcrypt/password" require "uuid" require "uuid/json" require "socket/udp_socket" require "../../config" def fread(file) : String slc = Bytes.new file.size count = file.read slc String.new slc[0, count] end def authenticate(user : String, token : UUID) : (User | Nil) user_file : User | Nil = nil user_file = KVStore.access.fetch!("user/"+user) if user_file.nil? return nil end if nil == user_file.not_nil!.tokens.not_nil!.find{ |tok| token == tok} nil else user_file.not_nil!.password_hash = "" user_file end end def authenticate!(user : String, token : UUID) : User authenticate(user, token).not_nil! end def authenticate_admin!(user : String, token : UUID) : User user = authenticate(user, token).not_nil! if(user.type==UserType::Administrator) return user end raise "Administrator only" end ws "/socket/user/authenticate" do |socket| socket.on_message do |message| json = JSON.parse(message) if(authenticate(json["user"].to_s, UUID.new(json["api_token"].to_s)).nil?) json.as_h.["valid"]=JSON::Any.new false else json.as_h.["valid"]=JSON::Any.new true end socket.send json.to_json end end udp_listener = UDPSocket.new udp_listener.bind "0.0.0.0", 3000 udp_mutex = Mutex.new (1..Statics.nb_udp_listeners).each do spawn do while true message : String | Nil = nil address : Socket::IPAddress | Nil = nil udp_mutex.synchronize do message, address = udp_listener.receive end json = JSON.parse(message.not_nil!) if(authenticate(json["user"].to_s, UUID.new(json["api_token"].to_s)).nil?) json.as_h.["valid"]=JSON::Any.new false else json.as_h.["valid"]=JSON::Any.new true end udp_mutex.synchronize do udp_listener.send json.to_json, address.not_nil! end end end end post "/login" do |context| user = User.from_json context.request.body.not_nil! user_file : User token : UUID | Nil = nil KVStore.access.transaction do |store| begin user_file = store.fetch! "user/"+user.email rescue ex halt context, status_code: 403, response: ex.to_s end if Crypto::Bcrypt::Password.new(user_file.password_hash.not_nil!) == user.password_hash.not_nil! else halt context, status_code: 403, response: "Invalid password" end if user_file.tokens.nil? user_file.tokens = Array(UUID).new end token = UUID.random() user_file.tokens.not_nil!<5 user_file.tokens = user_file.tokens.not_nil!.last(5) end store.push "user/"+user.email, user_file.to_json end context.response.content_type = "application/json" token.not_nil!.to_json end get "/logout" do |context| user = context.request.headers["user"] KVStore.access.transaction do |store| user_file = store.fetch!("user/"+user) user_file.tokens.not_nil!.delete(UUID.new(context.request.headers["api_token"])) store.push "user/"+user, user_file.to_json end context.response.content_type = "application/json" "OK".to_json end post "/logout-all" do |context| user : User begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex halt context, status_code: 403, response: ex.to_s end KVStore.access.transaction do |store| user_file = store.fetch!("user/"+user.email) user_file.tokens=Array(UUID).new store.push "user/"+user_file.email, user_file.to_json end context.response.content_type = "application/json" "OK".to_json end post "/user" do |context| user = User.from_json context.request.body.not_nil! ph = user.password_hash user.tokens = Array(UUID).new user.invoices = Array(Invoice).new if ph.nil? halt context, status_code: 401, response: "No password provided" else user.password_hash=Crypto::Bcrypt::Password.create(ph,cost: 12).to_s end if Statics.email_regex.match(user.email)==nil halt context, status_code: 401, response: "Bad email address provided" end begin KVStore.access.transaction do |store| if store.fetch("user/"+user.email)!=nil raise IndexError.new end store.push "user/"+user.email, user.to_json end rescue ex halt context, status_code: 401, response: "Email address already in use" end context.response.content_type = "application/json" "OK".to_json end get "/user/tokens" do |context| user : User begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex halt context, status_code: 403, response: ex.to_s end context.response.content_type = "application/json" user.tokens.to_json end get "/user/authenticate" do |context| user : User begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex halt context, status_code: 403, response: ex.to_s end context.response.content_type = "application/json" "OK".to_json end get "/user/address" do |context| user : User begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex halt context, status_code: 403, response: ex.to_s end context.response.content_type = "application/json" user.addresses.to_json end post "/user/address" do |context| user : User begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex halt context, status_code: 403, response: ex.to_s end addresses = Array(Address).from_json(context.request.body.not_nil!).not_nil! KVStore.access.transaction do |store| user_file = store.fetch!("user/"+user.email) old_list=user_file.addresses if old_list.nil? else addresses=old_list+addresses end user_file.addresses=addresses store.push "user/"+user_file.email, user_file.to_json end context.response.content_type = "application/json" "OK".to_json end delete "/user/address" do |context| user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) addresses = Array(Address).from_json(context.request.body.not_nil!).not_nil! KVStore.access.transaction do |store| user_file = store.fetch!("user/"+user.email) old_list=user_file.addresses if old_list.nil? addresses=Array(Address).new else addresses=old_list.select do |v| isin=false addresses.each do |va| isin |= v==va end !isin end end user_file.addresses=addresses store.push "user/"+user_file.email, user_file.to_json end context.response.content_type = "application/json" "OK".to_json end get "/user" do |context| context.response.content_type = "application/json" user : User | Nil begin user = authenticate!(context.request.headers["user"],UUID.new(context.request.headers["api_token"])) rescue ex resp = String::Builder.build do |builder| if ENV["KEMAL_ENV"] == "test" ex.inspect_with_backtrace builder else ex.to_s builder end end halt context, status_code: 403, response: resp end user.not_nil! end