An authentication server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

264 lines
7.1 KiB

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!<<token
if user_file.tokens.not_nil!.size>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