197 lines
5.7 KiB
Crystal
197 lines
5.7 KiB
Crystal
|
require "./authd.cr"
|
||
|
|
||
|
extend AuthD
|
||
|
|
||
|
class Baguette::Configuration
|
||
|
class Auth < IPC
|
||
|
property recreate_indexes : Bool = false
|
||
|
property storage : String = "storage"
|
||
|
property registrations : Bool = false
|
||
|
property require_email : Bool = false
|
||
|
property activation_template : String = "email-activation"
|
||
|
property recovery_template : String = "email-recovery"
|
||
|
property mailer_exe : String = "mailer"
|
||
|
property read_only_profile_keys : Array(String) = Array(String).new
|
||
|
|
||
|
property print_password_recovery_parameters : Bool = false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Provides a JWT-based authentication scheme for service-specific users.
|
||
|
class AuthD::Service < IPC
|
||
|
property configuration : Baguette::Configuration::Auth
|
||
|
|
||
|
# DB and its indexes.
|
||
|
property users : DODB::DataBase(User)
|
||
|
property users_per_uid : DODB::Index(User)
|
||
|
property users_per_login : DODB::Index(User)
|
||
|
|
||
|
property logged_users : Hash(Int32, AuthD::User::Public)
|
||
|
|
||
|
# #{@configuration.storage}/last_used_uid
|
||
|
property last_uid_file : String
|
||
|
|
||
|
def initialize(@configuration)
|
||
|
super()
|
||
|
|
||
|
@users = DODB::DataBase(User).new @configuration.storage
|
||
|
@users_per_uid = @users.new_index "uid", &.uid.to_s
|
||
|
@users_per_login = @users.new_index "login", &.login
|
||
|
|
||
|
@last_uid_file = "#{@configuration.storage}/last_used_uid"
|
||
|
|
||
|
@logged_users = Hash(Int32, AuthD::User::Public).new
|
||
|
|
||
|
if @configuration.recreate_indexes
|
||
|
@users.reindex_everything!
|
||
|
end
|
||
|
|
||
|
self.timer @configuration.ipc_timer
|
||
|
self.service_init "auth"
|
||
|
end
|
||
|
|
||
|
def hash_password(password : String) : String
|
||
|
digest = OpenSSL::Digest.new "sha256"
|
||
|
digest << password
|
||
|
digest.hexfinal
|
||
|
end
|
||
|
|
||
|
# new_uid reads the last given UID and returns it incremented.
|
||
|
# Splitting the retrieval and record of new user ids allows to
|
||
|
# only increment when an user fully registers, thus avoiding a
|
||
|
# Denial of Service attack.
|
||
|
#
|
||
|
# WARNING: to record this new UID, new_uid_commit must be called.
|
||
|
# WARNING: new_uid isn't thread safe.
|
||
|
def new_uid
|
||
|
begin
|
||
|
uid = File.read(@last_uid_file).to_i
|
||
|
rescue
|
||
|
uid = 999
|
||
|
end
|
||
|
|
||
|
uid += 1
|
||
|
end
|
||
|
|
||
|
# new_uid_commit records the new UID.
|
||
|
# WARNING: new_uid_commit isn't thread safe.
|
||
|
def new_uid_commit(uid : Int)
|
||
|
File.write @last_uid_file, uid.to_s
|
||
|
end
|
||
|
|
||
|
def get_logged_user?(fd : Int32)
|
||
|
@logged_users[fd]?
|
||
|
end
|
||
|
|
||
|
# Instead of just getting the public view of a logged user,
|
||
|
# get the actual User instance.
|
||
|
def get_logged_user_full?(fd : Int32)
|
||
|
if u = @logged_users[fd]?
|
||
|
user? u.uid
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def user?(uid_or_login : UserID)
|
||
|
if uid_or_login.is_a? Int32
|
||
|
@users_per_uid.get? uid_or_login.to_s
|
||
|
else
|
||
|
@users_per_login.get? uid_or_login
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def handle_request(event : IPC::Event)
|
||
|
request_start = Time.utc
|
||
|
|
||
|
array = event.message.not_nil!
|
||
|
slice = Slice.new array.to_unsafe, array.size
|
||
|
message = IPCMessage::TypedMessage.deserialize slice
|
||
|
request = AuthD.requests.parse_ipc_json message.not_nil!
|
||
|
|
||
|
if request.nil?
|
||
|
raise "unknown request type"
|
||
|
end
|
||
|
|
||
|
request_name = request.class.name.sub /^AuthD::Request::/, ""
|
||
|
Baguette::Log.debug "<< #{request_name}"
|
||
|
|
||
|
response = begin
|
||
|
request.handle self, event.fd
|
||
|
rescue e : UserNotFound
|
||
|
Baguette::Log.error "#{request_name} user not found"
|
||
|
AuthD::Response::Error.new "authorization error"
|
||
|
rescue e : AuthenticationInfoLacking
|
||
|
Baguette::Log.error "#{request_name} lacking authentication info"
|
||
|
AuthD::Response::Error.new "authorization error"
|
||
|
rescue e : AdminAuthorizationException
|
||
|
Baguette::Log.error "#{request_name} admin authentication failed"
|
||
|
AuthD::Response::Error.new "authorization error"
|
||
|
rescue e
|
||
|
Baguette::Log.error "#{request_name} generic error #{e}"
|
||
|
AuthD::Response::Error.new "unknown error"
|
||
|
end
|
||
|
|
||
|
# If clients sent requests with an “id” field, it is copied
|
||
|
# in the responses. Allows identifying responses easily.
|
||
|
response.id = request.id
|
||
|
|
||
|
schedule event.fd, response
|
||
|
|
||
|
duration = Time.utc - request_start
|
||
|
|
||
|
response_name = response.class.name.sub /^AuthD::Response::/, ""
|
||
|
|
||
|
if response.is_a? AuthD::Response::Error
|
||
|
Baguette::Log.warning ">> #{response_name} (#{response.reason})"
|
||
|
else
|
||
|
Baguette::Log.debug ">> #{response_name} (Total duration: #{duration})"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def get_user_from_token(token : String)
|
||
|
token_payload = Token.from_s(@configuration.secret_key, token)
|
||
|
|
||
|
@users_per_uid.get? token_payload.uid.to_s
|
||
|
end
|
||
|
|
||
|
def run
|
||
|
Baguette::Log.title "Starting authd"
|
||
|
|
||
|
Baguette::Log.info "(mailer) Email activation template: #{@configuration.activation_template}"
|
||
|
Baguette::Log.info "(mailer) Email recovery template: #{@configuration.recovery_template}"
|
||
|
|
||
|
self.loop do |event|
|
||
|
case event.type
|
||
|
when LibIPC::EventType::Timer
|
||
|
Baguette::Log.debug "Timer" if @configuration.print_ipc_timer
|
||
|
|
||
|
when LibIPC::EventType::MessageRx
|
||
|
Baguette::Log.debug "Received message from #{event.fd}" if @configuration.print_ipc_message_received
|
||
|
begin
|
||
|
handle_request event
|
||
|
rescue e
|
||
|
Baguette::Log.error "#{e.message}"
|
||
|
# send event.fd, Response::Error.new e.message
|
||
|
end
|
||
|
|
||
|
when LibIPC::EventType::MessageTx
|
||
|
Baguette::Log.debug "Message sent to #{event.fd}" if @configuration.print_ipc_message_sent
|
||
|
|
||
|
when LibIPC::EventType::Connection
|
||
|
Baguette::Log.debug "Connection from #{event.fd}" if @configuration.print_ipc_connection
|
||
|
when LibIPC::EventType::Disconnection
|
||
|
Baguette::Log.debug "Disconnection from #{event.fd}" if @configuration.print_ipc_disconnection
|
||
|
@logged_users.delete event.fd
|
||
|
else
|
||
|
Baguette::Log.error "Not implemented behavior for event: #{event}"
|
||
|
if event.responds_to?(:fd)
|
||
|
fd = event.fd
|
||
|
Baguette::Log.warning "closing #{fd}"
|
||
|
close fd
|
||
|
@logged_users.delete fd
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|