New authd code structure.
This commit is contained in:
parent
b717be649f
commit
dbcfa4880b
668
src/authd.cr
668
src/authd.cr
@ -21,667 +21,11 @@ class Baguette::Configuration
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class AuthD::Exception < Exception
|
# Requests and responses.
|
||||||
end
|
require "./exceptions"
|
||||||
|
|
||||||
class AuthD::MalformedRequest < Exception
|
# Requests and responses.
|
||||||
getter ipc_type : Int32
|
require "./network"
|
||||||
getter payload : String
|
|
||||||
|
|
||||||
def initialize(@ipc_type, @payload)
|
|
||||||
@message = "malformed payload"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AuthD::Response
|
|
||||||
include JSON::Serializable
|
|
||||||
|
|
||||||
property id : JSON::Any?
|
|
||||||
|
|
||||||
annotation MessageType
|
|
||||||
end
|
|
||||||
|
|
||||||
class_getter type = -1
|
|
||||||
def type
|
|
||||||
@@type
|
|
||||||
end
|
|
||||||
|
|
||||||
macro inherited
|
|
||||||
def self.type
|
|
||||||
::AuthD::Response::Type::{{ @type.name.split("::").last.id }}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
macro initialize(*properties)
|
|
||||||
def initialize(
|
|
||||||
{% for value in properties %}
|
|
||||||
@{{value.id}}{% if value != properties.last %},{% end %}
|
|
||||||
{% end %}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
Type::{{ @type.name.split("::").last.id }}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Error < Response
|
|
||||||
property reason : String?
|
|
||||||
|
|
||||||
initialize :reason
|
|
||||||
end
|
|
||||||
|
|
||||||
class Token < Response
|
|
||||||
property uid : Int32
|
|
||||||
property token : String
|
|
||||||
|
|
||||||
initialize :token, :uid
|
|
||||||
end
|
|
||||||
|
|
||||||
class User < Response
|
|
||||||
property user : ::AuthD::User::Public
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserAdded < Response
|
|
||||||
property user : ::AuthD::User::Public
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserEdited < Response
|
|
||||||
property uid : Int32
|
|
||||||
|
|
||||||
initialize :uid
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserValidated < Response
|
|
||||||
property user : ::AuthD::User::Public
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class UsersList < Response
|
|
||||||
property users : Array(::AuthD::User::Public)
|
|
||||||
|
|
||||||
initialize :users
|
|
||||||
end
|
|
||||||
|
|
||||||
class PermissionCheck < Response
|
|
||||||
property user : Int32
|
|
||||||
property service : String
|
|
||||||
property resource : String
|
|
||||||
property permission : ::AuthD::User::PermissionLevel
|
|
||||||
|
|
||||||
initialize :service, :resource, :user, :permission
|
|
||||||
end
|
|
||||||
|
|
||||||
class PermissionSet < Response
|
|
||||||
property user : Int32
|
|
||||||
property service : String
|
|
||||||
property resource : String
|
|
||||||
property permission : ::AuthD::User::PermissionLevel
|
|
||||||
|
|
||||||
initialize :user, :service, :resource, :permission
|
|
||||||
end
|
|
||||||
|
|
||||||
class PasswordRecoverySent < Response
|
|
||||||
property user : ::AuthD::User::Public
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class PasswordRecovered < Response
|
|
||||||
property user : ::AuthD::User::Public
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class MatchingUsers < Response
|
|
||||||
property users : Array(::AuthD::User::Public)
|
|
||||||
|
|
||||||
initialize :users
|
|
||||||
end
|
|
||||||
|
|
||||||
class Contacts < Response
|
|
||||||
property user : Int32
|
|
||||||
property email : String?
|
|
||||||
property phone : String?
|
|
||||||
|
|
||||||
initialize user, email, phone
|
|
||||||
end
|
|
||||||
|
|
||||||
# This creates a Request::Type enumeration. One entry for each request type.
|
|
||||||
{% begin %}
|
|
||||||
enum Type
|
|
||||||
{% for ivar in @type.subclasses %}
|
|
||||||
{% klass = ivar.name %}
|
|
||||||
{% name = ivar.name.split("::").last.id %}
|
|
||||||
|
|
||||||
{% a = ivar.annotation(MessageType) %}
|
|
||||||
|
|
||||||
{% if a %}
|
|
||||||
{% value = a[0] %}
|
|
||||||
{{ name }} = {{ value }}
|
|
||||||
{% else %}
|
|
||||||
{{ name }}
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
# This is an array of all requests types.
|
|
||||||
{% begin %}
|
|
||||||
class_getter requests = [
|
|
||||||
{% for ivar in @type.subclasses %}
|
|
||||||
{% klass = ivar.name %}
|
|
||||||
|
|
||||||
{{klass}},
|
|
||||||
{% end %}
|
|
||||||
]
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
def self.from_ipc(message : IPC::Message) : Response?
|
|
||||||
payload = String.new message.payload
|
|
||||||
type = Type.new message.utype.to_i
|
|
||||||
|
|
||||||
begin
|
|
||||||
requests.find(&.type.==(type)).try &.from_json(payload)
|
|
||||||
rescue e : JSON::ParseException
|
|
||||||
raise MalformedRequest.new message.utype.to_i, payload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AuthD::Request
|
|
||||||
include JSON::Serializable
|
|
||||||
|
|
||||||
property id : JSON::Any?
|
|
||||||
|
|
||||||
annotation MessageType
|
|
||||||
end
|
|
||||||
|
|
||||||
class_getter type = -1
|
|
||||||
|
|
||||||
macro inherited
|
|
||||||
def self.type
|
|
||||||
::AuthD::Request::Type::{{ @type.name.split("::").last.id }}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
macro initialize(*properties)
|
|
||||||
def initialize(
|
|
||||||
{% for value in properties %}
|
|
||||||
@{{value.id}}{% if value != properties.last %},{% end %}
|
|
||||||
{% end %}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def type
|
|
||||||
Type::{{ @type.name.split("::").last.id }}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetToken < Request
|
|
||||||
property login : String
|
|
||||||
property password : String
|
|
||||||
|
|
||||||
initialize :login, :password
|
|
||||||
end
|
|
||||||
|
|
||||||
class AddUser < Request
|
|
||||||
# Only clients that have the right shared key will be allowed
|
|
||||||
# to create users.
|
|
||||||
property shared_key : String
|
|
||||||
|
|
||||||
property login : String
|
|
||||||
property password : String
|
|
||||||
property email : String?
|
|
||||||
property phone : String?
|
|
||||||
property profile : Hash(String, JSON::Any)?
|
|
||||||
|
|
||||||
initialize :shared_key, :login, :password, :email, :phone, :profile
|
|
||||||
end
|
|
||||||
|
|
||||||
class ValidateUser < Request
|
|
||||||
property login : String
|
|
||||||
property activation_key : String
|
|
||||||
|
|
||||||
initialize :login, :activation_key
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetUser < Request
|
|
||||||
property user : Int32 | String
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetUserByCredentials < Request
|
|
||||||
property login : String
|
|
||||||
property password : String
|
|
||||||
|
|
||||||
initialize :login, :password
|
|
||||||
end
|
|
||||||
|
|
||||||
class ModUser < Request
|
|
||||||
property shared_key : String
|
|
||||||
|
|
||||||
property user : Int32 | String
|
|
||||||
property password : String?
|
|
||||||
property email : String?
|
|
||||||
property phone : String?
|
|
||||||
property avatar : String?
|
|
||||||
|
|
||||||
initialize :shared_key, :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class Register < Request
|
|
||||||
property login : String
|
|
||||||
property password : String
|
|
||||||
property email : String?
|
|
||||||
property phone : String?
|
|
||||||
property profile : Hash(String, JSON::Any)?
|
|
||||||
|
|
||||||
initialize :login, :password, :email, :phone, :profile
|
|
||||||
end
|
|
||||||
|
|
||||||
class UpdatePassword < Request
|
|
||||||
property login : String
|
|
||||||
property old_password : String
|
|
||||||
property new_password : String
|
|
||||||
end
|
|
||||||
|
|
||||||
class ListUsers < Request
|
|
||||||
property token : String?
|
|
||||||
property key : String?
|
|
||||||
end
|
|
||||||
|
|
||||||
class CheckPermission < Request
|
|
||||||
property shared_key : String?
|
|
||||||
property token : String?
|
|
||||||
|
|
||||||
property user : Int32 | String
|
|
||||||
property service : String
|
|
||||||
property resource : String
|
|
||||||
|
|
||||||
initialize :shared_key, :user, :service, :resource
|
|
||||||
end
|
|
||||||
|
|
||||||
class SetPermission < Request
|
|
||||||
property shared_key : String
|
|
||||||
|
|
||||||
property user : Int32 | String
|
|
||||||
property service : String
|
|
||||||
property resource : String
|
|
||||||
property permission : ::AuthD::User::PermissionLevel
|
|
||||||
|
|
||||||
initialize :shared_key, :user, :service, :resource, :permission
|
|
||||||
end
|
|
||||||
|
|
||||||
class PasswordRecovery < Request
|
|
||||||
property user : Int32 | String
|
|
||||||
property password_renew_key : String
|
|
||||||
property new_password : String
|
|
||||||
|
|
||||||
initialize :user, :password_renew_key, :new_password
|
|
||||||
end
|
|
||||||
|
|
||||||
class AskPasswordRecovery < Request
|
|
||||||
property user : Int32 | String
|
|
||||||
property email : String
|
|
||||||
|
|
||||||
initialize :user, :email
|
|
||||||
end
|
|
||||||
|
|
||||||
class SearchUser < Request
|
|
||||||
property user : String
|
|
||||||
|
|
||||||
initialize :user
|
|
||||||
end
|
|
||||||
|
|
||||||
class EditProfile < Request
|
|
||||||
property token : String
|
|
||||||
property new_profile : Hash(String, JSON::Any)
|
|
||||||
|
|
||||||
initialize :token, :new_profile
|
|
||||||
end
|
|
||||||
|
|
||||||
# Same as above, but doesn’t reset the whole profile, only resets elements
|
|
||||||
# for which keys are present in `new_profile`.
|
|
||||||
class EditProfileContent < Request
|
|
||||||
property token : String?
|
|
||||||
|
|
||||||
property shared_key : String?
|
|
||||||
property user : Int32 | String | Nil
|
|
||||||
|
|
||||||
property new_profile : Hash(String, JSON::Any)
|
|
||||||
|
|
||||||
initialize :shared_key, :user, :new_profile
|
|
||||||
initialize :token, :new_profile
|
|
||||||
end
|
|
||||||
|
|
||||||
class EditContacts < Request
|
|
||||||
property token : String
|
|
||||||
|
|
||||||
property email : String?
|
|
||||||
property phone : String?
|
|
||||||
end
|
|
||||||
|
|
||||||
class Delete < Request
|
|
||||||
# Deletion can be triggered by either an admin or the user.
|
|
||||||
property shared_key : String?
|
|
||||||
|
|
||||||
property login : String?
|
|
||||||
property password : String?
|
|
||||||
|
|
||||||
property user : String | Int32
|
|
||||||
|
|
||||||
initialize :user, :login, :password
|
|
||||||
initialize :user, :shared_key
|
|
||||||
end
|
|
||||||
|
|
||||||
class GetContacts < Request
|
|
||||||
property token : String
|
|
||||||
end
|
|
||||||
|
|
||||||
# This creates a Request::Type enumeration. One entry for each request type.
|
|
||||||
{% begin %}
|
|
||||||
enum Type
|
|
||||||
{% for ivar in @type.subclasses %}
|
|
||||||
{% klass = ivar.name %}
|
|
||||||
{% name = ivar.name.split("::").last.id %}
|
|
||||||
|
|
||||||
{% a = ivar.annotation(MessageType) %}
|
|
||||||
|
|
||||||
{% if a %}
|
|
||||||
{% value = a[0] %}
|
|
||||||
{{ name }} = {{ value }}
|
|
||||||
{% else %}
|
|
||||||
{{ name }}
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
# This is an array of all requests types.
|
|
||||||
{% begin %}
|
|
||||||
class_getter requests = [
|
|
||||||
{% for ivar in @type.subclasses %}
|
|
||||||
{% klass = ivar.name %}
|
|
||||||
|
|
||||||
{{klass}},
|
|
||||||
{% end %}
|
|
||||||
]
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
def self.from_ipc(message : IPC::Message) : Request?
|
|
||||||
payload = String.new message.payload
|
|
||||||
type = Type.new message.utype.to_i
|
|
||||||
|
|
||||||
begin
|
|
||||||
requests.find(&.type.==(type)).try &.from_json(payload)
|
|
||||||
rescue e : JSON::ParseException
|
|
||||||
raise MalformedRequest.new message.utype.to_i, payload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module AuthD
|
|
||||||
class Client < IPC::Client
|
|
||||||
property key : String
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@key = ""
|
|
||||||
|
|
||||||
initialize "auth"
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_token?(login : String, password : String) : String?
|
|
||||||
send Request::GetToken.new login, password
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
if response.is_a?(Response::Token)
|
|
||||||
response.token
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_user?(login : String, password : String) : AuthD::User::Public?
|
|
||||||
send Request::GetUserByCredentials.new login, password
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
if response.is_a? Response::User
|
|
||||||
response.user
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_user?(uid_or_login : Int32 | String) : ::AuthD::User::Public?
|
|
||||||
send Request::GetUser.new uid_or_login
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
if response.is_a? Response::User
|
|
||||||
response.user
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send(type : Request::Type, payload)
|
|
||||||
send_now @server_fd, type.value.to_u8, payload
|
|
||||||
end
|
|
||||||
|
|
||||||
def decode_token(token)
|
|
||||||
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
|
|
||||||
|
|
||||||
user = ::AuthD::User::Public.from_json user.to_json
|
|
||||||
|
|
||||||
{user, meta}
|
|
||||||
end
|
|
||||||
|
|
||||||
# FIXME: Extra options may be useful to implement here.
|
|
||||||
def add_user(login : String, password : String,
|
|
||||||
email : String?,
|
|
||||||
phone : String?,
|
|
||||||
profile : Hash(String, JSON::Any)?) : ::AuthD::User::Public | Exception
|
|
||||||
|
|
||||||
send Request::AddUser.new @key, login, password, email, phone, profile
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::UserAdded
|
|
||||||
response.user
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
# Should not happen in serialized connections, but…
|
|
||||||
# it’ll happen if you run several requests at once.
|
|
||||||
Exception.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_user(login : String, activation_key : String) : ::AuthD::User::Public | Exception
|
|
||||||
send Request::ValidateUser.new login, activation_key
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::UserValidated
|
|
||||||
response.user
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
# Should not happen in serialized connections, but…
|
|
||||||
# it’ll happen if you run several requests at once.
|
|
||||||
Exception.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ask_password_recovery(uid_or_login : String | Int32, email : String)
|
|
||||||
send Request::AskPasswordRecovery.new uid_or_login, email
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::PasswordRecoverySent
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
Exception.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def change_password(uid_or_login : String | Int32, new_pass : String, renew_key : String)
|
|
||||||
send Request::PasswordRecovery.new uid_or_login, renew_key, new_pass
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::PasswordRecovered
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
Exception.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def register(login : String,
|
|
||||||
password : String,
|
|
||||||
email : String?,
|
|
||||||
phone : String?,
|
|
||||||
profile : Hash(String, JSON::Any)?) : ::AuthD::User::Public?
|
|
||||||
|
|
||||||
send Request::Register.new login, password, email, phone, profile
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::UserAdded
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mod_user(uid_or_login : Int32 | String, password : String? = nil, email : String? = nil, phone : String? = nil, avatar : String? = nil) : Bool | Exception
|
|
||||||
request = Request::ModUser.new @key, uid_or_login
|
|
||||||
|
|
||||||
request.password = password if password
|
|
||||||
request.email = email if email
|
|
||||||
request.phone = phone if phone
|
|
||||||
request.avatar = avatar if avatar
|
|
||||||
|
|
||||||
send request
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::UserEdited
|
|
||||||
true
|
|
||||||
when Response::Error
|
|
||||||
Exception.new response.reason
|
|
||||||
else
|
|
||||||
Exception.new "???"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_permission(user : Int32, service_name : String, resource_name : String) : User::PermissionLevel
|
|
||||||
request = Request::CheckPermission.new @key, user, service_name, resource_name
|
|
||||||
|
|
||||||
send request
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::PermissionCheck
|
|
||||||
response.permission
|
|
||||||
when Response
|
|
||||||
raise Exception.new "unexpected response: #{response.type}"
|
|
||||||
else
|
|
||||||
raise Exception.new "unexpected response"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_permission(uid : Int32, service : String, resource : String, permission : User::PermissionLevel)
|
|
||||||
request = Request::SetPermission.new @key, uid, service, resource, permission
|
|
||||||
|
|
||||||
send request
|
|
||||||
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::PermissionSet
|
|
||||||
true
|
|
||||||
when Response
|
|
||||||
raise Exception.new "unexpected response: #{response.type}"
|
|
||||||
else
|
|
||||||
raise Exception.new "unexpected response"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_user(user_login : String)
|
|
||||||
send Request::SearchUser.new user_login
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::MatchingUsers
|
|
||||||
response.users
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
Exception.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit_profile_content(user : Int32 | String, new_values)
|
|
||||||
send Request::EditProfileContent.new key, user, new_values
|
|
||||||
response = Response.from_ipc read
|
|
||||||
|
|
||||||
case response
|
|
||||||
when Response::User
|
|
||||||
response.user
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
else
|
|
||||||
raise Exception.new "unexpected response"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(user : Int32 | String, key : String)
|
|
||||||
send Request::Delete.new user, key
|
|
||||||
delete_
|
|
||||||
end
|
|
||||||
def delete(user : Int32 | String, login : String, pass : String)
|
|
||||||
send Request::Delete.new user, login, pass
|
|
||||||
delete_
|
|
||||||
end
|
|
||||||
def delete_
|
|
||||||
response = Response.from_ipc read
|
|
||||||
case response
|
|
||||||
when Response::Error
|
|
||||||
raise Exception.new response.reason
|
|
||||||
end
|
|
||||||
response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class IPC::Context
|
|
||||||
def send(fd, response : AuthD::Response)
|
|
||||||
send fd, response.type.to_u8, response.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class IPC::Client
|
|
||||||
def send(request : AuthD::Request)
|
|
||||||
unless (fd = @server_fd).nil?
|
|
||||||
send_now fd, request.type.to_u8, request.to_json
|
|
||||||
else
|
|
||||||
raise "Client not connected to the server"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# Functions to request the authd server.
|
||||||
|
require "./libclient.cr"
|
||||||
|
13
src/exceptions.cr
Normal file
13
src/exceptions.cr
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module AuthD
|
||||||
|
class Exception < ::Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class UserNotFound < ::Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class AuthenticationInfoLacking < ::Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class AdminAuthorizationException < ::Exception
|
||||||
|
end
|
||||||
|
end
|
242
src/libclient.cr
Normal file
242
src/libclient.cr
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
|
||||||
|
module AuthD
|
||||||
|
class Client < IPC::Client
|
||||||
|
property key : String
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@key = ""
|
||||||
|
|
||||||
|
initialize "auth"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_token?(login : String, password : String) : String?
|
||||||
|
send Request::GetToken.new login, password
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
if response.is_a?(Response::Token)
|
||||||
|
response.token
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_user?(login : String, password : String) : AuthD::User::Public?
|
||||||
|
send Request::GetUserByCredentials.new login, password
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
if response.is_a? Response::User
|
||||||
|
response.user
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_user?(uid_or_login : Int32 | String) : ::AuthD::User::Public?
|
||||||
|
send Request::GetUser.new uid_or_login
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
if response.is_a? Response::User
|
||||||
|
response.user
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send(type : Request::Type, payload)
|
||||||
|
send_now @server_fd, type.value.to_u8, payload
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_token(token)
|
||||||
|
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
|
||||||
|
|
||||||
|
user = ::AuthD::User::Public.from_json user.to_json
|
||||||
|
|
||||||
|
{user, meta}
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: Extra options may be useful to implement here.
|
||||||
|
def add_user(login : String, password : String,
|
||||||
|
email : String?,
|
||||||
|
phone : String?,
|
||||||
|
profile : Hash(String, JSON::Any)?) : ::AuthD::User::Public | Exception
|
||||||
|
|
||||||
|
send Request::AddUser.new @key, login, password, email, phone, profile
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::UserAdded
|
||||||
|
response.user
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
# Should not happen in serialized connections, but…
|
||||||
|
# it’ll happen if you run several requests at once.
|
||||||
|
Exception.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_user(login : String, activation_key : String) : ::AuthD::User::Public | Exception
|
||||||
|
send Request::ValidateUser.new login, activation_key
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::UserValidated
|
||||||
|
response.user
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
# Should not happen in serialized connections, but…
|
||||||
|
# it’ll happen if you run several requests at once.
|
||||||
|
Exception.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ask_password_recovery(uid_or_login : String | Int32, email : String)
|
||||||
|
send Request::AskPasswordRecovery.new uid_or_login, email
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PasswordRecoverySent
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
Exception.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_password(uid_or_login : String | Int32, new_pass : String, renew_key : String)
|
||||||
|
send Request::PasswordRecovery.new uid_or_login, renew_key, new_pass
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PasswordRecovered
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
Exception.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def register(login : String,
|
||||||
|
password : String,
|
||||||
|
email : String?,
|
||||||
|
phone : String?,
|
||||||
|
profile : Hash(String, JSON::Any)?) : ::AuthD::User::Public?
|
||||||
|
|
||||||
|
send Request::Register.new login, password, email, phone, profile
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::UserAdded
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mod_user(uid_or_login : Int32 | String, password : String? = nil, email : String? = nil, phone : String? = nil, avatar : String? = nil) : Bool | Exception
|
||||||
|
request = Request::ModUser.new @key, uid_or_login
|
||||||
|
|
||||||
|
request.password = password if password
|
||||||
|
request.email = email if email
|
||||||
|
request.phone = phone if phone
|
||||||
|
request.avatar = avatar if avatar
|
||||||
|
|
||||||
|
send request
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::UserEdited
|
||||||
|
true
|
||||||
|
when Response::Error
|
||||||
|
Exception.new response.reason
|
||||||
|
else
|
||||||
|
Exception.new "???"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_permission(user : Int32, service_name : String, resource_name : String) : User::PermissionLevel
|
||||||
|
request = Request::CheckPermission.new @key, user, service_name, resource_name
|
||||||
|
|
||||||
|
send request
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PermissionCheck
|
||||||
|
response.permission
|
||||||
|
when Response
|
||||||
|
raise Exception.new "unexpected response: #{response.type}"
|
||||||
|
else
|
||||||
|
raise Exception.new "unexpected response"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_permission(uid : Int32, service : String, resource : String, permission : User::PermissionLevel)
|
||||||
|
request = Request::SetPermission.new @key, uid, service, resource, permission
|
||||||
|
|
||||||
|
send request
|
||||||
|
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::PermissionSet
|
||||||
|
true
|
||||||
|
when Response
|
||||||
|
raise Exception.new "unexpected response: #{response.type}"
|
||||||
|
else
|
||||||
|
raise Exception.new "unexpected response"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def search_user(user_login : String)
|
||||||
|
send Request::SearchUser.new user_login
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::MatchingUsers
|
||||||
|
response.users
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
Exception.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit_profile_content(user : Int32 | String, new_values)
|
||||||
|
send Request::EditProfileContent.new key, user, new_values
|
||||||
|
response = Response.from_ipc read
|
||||||
|
|
||||||
|
case response
|
||||||
|
when Response::User
|
||||||
|
response.user
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
else
|
||||||
|
raise Exception.new "unexpected response"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(user : Int32 | String, key : String)
|
||||||
|
send Request::Delete.new user, key
|
||||||
|
delete_
|
||||||
|
end
|
||||||
|
def delete(user : Int32 | String, login : String, pass : String)
|
||||||
|
send Request::Delete.new user, login, pass
|
||||||
|
delete_
|
||||||
|
end
|
||||||
|
def delete_
|
||||||
|
response = Response.from_ipc read
|
||||||
|
case response
|
||||||
|
when Response::Error
|
||||||
|
raise Exception.new response.reason
|
||||||
|
end
|
||||||
|
response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
652
src/main.cr
652
src/main.cr
@ -29,11 +29,17 @@ class Baguette::Configuration
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class AuthD::Service
|
# Provides a JWT-based authentication scheme for service-specific users.
|
||||||
|
class AuthD::Service < IPC::Server
|
||||||
property configuration : Baguette::Configuration::Auth
|
property configuration : Baguette::Configuration::Auth
|
||||||
|
|
||||||
@users_per_login : DODB::Index(User)
|
# DB and its indexes.
|
||||||
@users_per_uid : DODB::Index(User)
|
property users : DODB::DataBase(User)
|
||||||
|
property users_per_uid : DODB::Index(User)
|
||||||
|
property users_per_login : DODB::Index(User)
|
||||||
|
|
||||||
|
# #{@configuration.storage}/last_used_uid
|
||||||
|
property last_uid_file : String
|
||||||
|
|
||||||
def initialize(@configuration)
|
def initialize(@configuration)
|
||||||
@users = DODB::DataBase(User).new @configuration.storage
|
@users = DODB::DataBase(User).new @configuration.storage
|
||||||
@ -45,6 +51,8 @@ class AuthD::Service
|
|||||||
if @configuration.recreate_indexes
|
if @configuration.recreate_indexes
|
||||||
@users.reindex_everything!
|
@users.reindex_everything!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super "auth"
|
||||||
end
|
end
|
||||||
|
|
||||||
def hash_password(password : String) : String
|
def hash_password(password : String) : String
|
||||||
@ -67,573 +75,48 @@ class AuthD::Service
|
|||||||
uid
|
uid
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_request(request : AuthD::Request?)
|
def handle_request(event : IPC::Event::MessageReceived)
|
||||||
case request
|
request_start = Time.utc
|
||||||
when Request::GetToken
|
|
||||||
begin
|
request = AuthD.requests.parse_ipc_json event.message
|
||||||
user = @users_per_login.get request.login
|
|
||||||
rescue e : DODB::MissingEntry
|
if request.nil?
|
||||||
return Response::Error.new "invalid credentials"
|
raise "unknown request type"
|
||||||
end
|
end
|
||||||
|
|
||||||
if user.nil?
|
request_name = request.class.name.sub /^AuthD::Request::/, ""
|
||||||
return Response::Error.new "invalid credentials"
|
Baguette::Log.debug "<< #{request_name}"
|
||||||
end
|
|
||||||
|
|
||||||
if user.password_hash != hash_password request.password
|
response = begin
|
||||||
return Response::Error.new "invalid credentials"
|
request.handle self, event
|
||||||
end
|
rescue e : UserNotFound
|
||||||
|
Baguette::Log.error "#{request_name} user not found"
|
||||||
user.date_last_connection = Time.local
|
AuthD::Response::Error.new "authorization error"
|
||||||
token = user.to_token
|
rescue e : AuthenticationInfoLacking
|
||||||
|
Baguette::Log.error "#{request_name} lacking authentication info"
|
||||||
# change the date of the last connection
|
AuthD::Response::Error.new "authorization error"
|
||||||
@users_per_uid.update user.uid.to_s, user
|
rescue e : AdminAuthorizationException
|
||||||
|
Baguette::Log.error "#{request_name} admin authentication failed"
|
||||||
Response::Token.new (token.to_s @configuration.shared_key), user.uid
|
AuthD::Response::Error.new "authorization error"
|
||||||
when Request::AddUser
|
|
||||||
# No verification of the users' informations when an admin adds it.
|
|
||||||
# No mail address verification.
|
|
||||||
if request.shared_key != @configuration.shared_key
|
|
||||||
return Response::Error.new "invalid authentication key"
|
|
||||||
end
|
|
||||||
|
|
||||||
if @users_per_login.get? request.login
|
|
||||||
return Response::Error.new "login already used"
|
|
||||||
end
|
|
||||||
|
|
||||||
if @configuration.require_email && request.email.nil?
|
|
||||||
return Response::Error.new "email required"
|
|
||||||
end
|
|
||||||
|
|
||||||
password_hash = hash_password request.password
|
|
||||||
|
|
||||||
uid = new_uid
|
|
||||||
|
|
||||||
user = User.new uid, request.login, password_hash
|
|
||||||
user.contact.email = request.email unless request.email.nil?
|
|
||||||
user.contact.phone = request.phone unless request.phone.nil?
|
|
||||||
|
|
||||||
request.profile.try do |profile|
|
|
||||||
user.profile = profile
|
|
||||||
end
|
|
||||||
|
|
||||||
# We consider adding the user as a registration
|
|
||||||
user.date_registration = Time.local
|
|
||||||
|
|
||||||
@users << user
|
|
||||||
|
|
||||||
Response::UserAdded.new user.to_public
|
|
||||||
when Request::ValidateUser
|
|
||||||
user = @users_per_login.get? request.login
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "user not found"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.contact.activation_key.nil?
|
|
||||||
return Response::Error.new "user already validated"
|
|
||||||
end
|
|
||||||
|
|
||||||
# remove the user contact activation key: the email is validated
|
|
||||||
if user.contact.activation_key == request.activation_key
|
|
||||||
user.contact.activation_key = nil
|
|
||||||
else
|
|
||||||
return Response::Error.new "wrong activation key"
|
|
||||||
end
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::UserValidated.new user.to_public
|
|
||||||
when Request::GetUserByCredentials
|
|
||||||
user = @users_per_login.get? request.login
|
|
||||||
|
|
||||||
unless user
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
if hash_password(request.password) != user.password_hash
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
user.date_last_connection = Time.local
|
|
||||||
|
|
||||||
# change the date of the last connection
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::User.new user.to_public
|
|
||||||
when Request::GetUser
|
|
||||||
uid_or_login = request.user
|
|
||||||
user = 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
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "user not found"
|
|
||||||
end
|
|
||||||
|
|
||||||
Response::User.new user.to_public
|
|
||||||
when Request::ModUser
|
|
||||||
if request.shared_key != @configuration.shared_key
|
|
||||||
return Response::Error.new "invalid authentication key"
|
|
||||||
end
|
|
||||||
|
|
||||||
uid_or_login = request.user
|
|
||||||
user = 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
|
|
||||||
|
|
||||||
unless user
|
|
||||||
return Response::Error.new "user not found"
|
|
||||||
end
|
|
||||||
|
|
||||||
request.password.try do |s|
|
|
||||||
user.password_hash = hash_password s
|
|
||||||
end
|
|
||||||
|
|
||||||
request.email.try do |email|
|
|
||||||
user.contact.email = email
|
|
||||||
end
|
|
||||||
|
|
||||||
request.phone.try do |phone|
|
|
||||||
user.contact.phone = phone
|
|
||||||
end
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::UserEdited.new user.uid
|
|
||||||
when Request::Register
|
|
||||||
if ! @configuration.registrations
|
|
||||||
return Response::Error.new "registrations not allowed"
|
|
||||||
end
|
|
||||||
|
|
||||||
if @users_per_login.get? request.login
|
|
||||||
return Response::Error.new "login already used"
|
|
||||||
end
|
|
||||||
|
|
||||||
if @configuration.require_email && request.email.nil?
|
|
||||||
return Response::Error.new "email required"
|
|
||||||
end
|
|
||||||
|
|
||||||
activation_url = @configuration.activation_url
|
|
||||||
if activation_url.nil?
|
|
||||||
# In this case we should not accept its registration.
|
|
||||||
return Response::Error.new "No activation URL were entered. Cannot send activation mails."
|
|
||||||
end
|
|
||||||
|
|
||||||
if ! request.email.nil?
|
|
||||||
# Test on the email address format.
|
|
||||||
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
|
|
||||||
result = grok.parse request.email.not_nil!
|
|
||||||
email = result["email"]?
|
|
||||||
|
|
||||||
if email.nil?
|
|
||||||
return Response::Error.new "invalid email format"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# In this case we should not accept its registration.
|
|
||||||
if request.password.size < 4
|
|
||||||
return Response::Error.new "password too short"
|
|
||||||
end
|
|
||||||
|
|
||||||
uid = new_uid
|
|
||||||
password = hash_password request.password
|
|
||||||
|
|
||||||
user = User.new uid, request.login, password
|
|
||||||
user.contact.email = request.email unless request.email.nil?
|
|
||||||
user.contact.phone = request.phone unless request.phone.nil?
|
|
||||||
|
|
||||||
request.profile.try do |profile|
|
|
||||||
user.profile = profile
|
|
||||||
end
|
|
||||||
|
|
||||||
user.date_registration = Time.local
|
|
||||||
|
|
||||||
begin
|
|
||||||
field_subject = @configuration.field_subject.not_nil!
|
|
||||||
field_from = @configuration.field_from.not_nil!
|
|
||||||
activation_url = @configuration.activation_url.not_nil!
|
|
||||||
|
|
||||||
u_login = user.login
|
|
||||||
u_email = user.contact.email.not_nil!
|
|
||||||
u_activation_key = user.contact.activation_key.not_nil!
|
|
||||||
|
|
||||||
# Once the user is created and stored, we try to contact him
|
|
||||||
unless Process.run("activation-mailer", [
|
|
||||||
"-l", u_login,
|
|
||||||
"-e", u_email,
|
|
||||||
"-t", field_subject,
|
|
||||||
"-f", field_from,
|
|
||||||
"-u", activation_url,
|
|
||||||
"-a", u_activation_key
|
|
||||||
]).success?
|
|
||||||
raise "cannot contact user #{user.login} address #{user.contact.email}"
|
|
||||||
end
|
|
||||||
rescue e
|
rescue e
|
||||||
Baguette::Log.error "activation-mailer: #{e}"
|
Baguette::Log.error "#{request_name} generic error #{e}"
|
||||||
return Response::Error.new "cannot contact the user (not registered)"
|
AuthD::Response::Error.new "unknown error"
|
||||||
end
|
end
|
||||||
|
|
||||||
# add the user only if we were able to send the confirmation mail
|
# If clients sent requests with an “id” field, it is copied
|
||||||
@users << user
|
# in the responses. Allows identifying responses easily.
|
||||||
|
response.id = request.id
|
||||||
|
|
||||||
Response::UserAdded.new user.to_public
|
send event.fd, response
|
||||||
when Request::UpdatePassword
|
|
||||||
user = @users_per_login.get? request.login
|
|
||||||
|
|
||||||
unless user
|
duration = Time.utc - request_start
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
if hash_password(request.old_password) != user.password_hash
|
response_name = response.class.name.sub /^AuthD::Response::/, ""
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
user.password_hash = hash_password request.new_password
|
if response.is_a? AuthD::Response::Error
|
||||||
|
Baguette::Log.warning ">> #{response_name} (#{response.reason})"
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::UserEdited.new user.uid
|
|
||||||
when Request::ListUsers
|
|
||||||
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
|
|
||||||
request.token.try do |token|
|
|
||||||
user = get_user_from_token token
|
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (user not found from token)"
|
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (user not in authd group)" unless user.permissions["authd"]?.try(&.["*"].>=(User::PermissionLevel::Read))
|
|
||||||
end
|
|
||||||
|
|
||||||
request.key.try do |key|
|
|
||||||
return Response::Error.new "unauthorized (wrong shared key)" unless key == @configuration.shared_key
|
|
||||||
end
|
|
||||||
|
|
||||||
return Response::Error.new "unauthorized (no key nor token)" unless request.key || request.token
|
|
||||||
|
|
||||||
Response::UsersList.new @users.to_h.map &.[1].to_public
|
|
||||||
when Request::CheckPermission
|
|
||||||
authorized = false
|
|
||||||
|
|
||||||
if key = request.shared_key
|
|
||||||
if key == @configuration.shared_key
|
|
||||||
authorized = true
|
|
||||||
else
|
else
|
||||||
return Response::Error.new "invalid key provided"
|
Baguette::Log.debug ">> #{response_name} (Total duration: #{duration})"
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if token = request.token
|
|
||||||
user = get_user_from_token token
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "token does not match user"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.login != request.user && user.uid != request.user
|
|
||||||
return Response::Error.new "token does not match user"
|
|
||||||
end
|
|
||||||
|
|
||||||
authorized = true
|
|
||||||
end
|
|
||||||
|
|
||||||
unless authorized
|
|
||||||
return Response::Error.new "unauthorized"
|
|
||||||
end
|
|
||||||
|
|
||||||
user = case u = request.user
|
|
||||||
when .is_a? Int32
|
|
||||||
@users_per_uid.get? u.to_s
|
|
||||||
else
|
|
||||||
@users_per_login.get? u
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "no such user"
|
|
||||||
end
|
|
||||||
|
|
||||||
service = request.service
|
|
||||||
service_permissions = user.permissions[service]?
|
|
||||||
|
|
||||||
if service_permissions.nil?
|
|
||||||
return Response::PermissionCheck.new service, request.resource, user.uid, User::PermissionLevel::None
|
|
||||||
end
|
|
||||||
|
|
||||||
resource_permissions = service_permissions[request.resource]?
|
|
||||||
|
|
||||||
if resource_permissions.nil?
|
|
||||||
return Response::PermissionCheck.new service, request.resource, user.uid, User::PermissionLevel::None
|
|
||||||
end
|
|
||||||
|
|
||||||
return Response::PermissionCheck.new service, request.resource, user.uid, resource_permissions
|
|
||||||
when Request::SetPermission
|
|
||||||
unless request.shared_key == @configuration.shared_key
|
|
||||||
return Response::Error.new "unauthorized"
|
|
||||||
end
|
|
||||||
|
|
||||||
user = @users_per_uid.get? request.user.to_s
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "no such user"
|
|
||||||
end
|
|
||||||
|
|
||||||
service = request.service
|
|
||||||
service_permissions = user.permissions[service]?
|
|
||||||
|
|
||||||
if service_permissions.nil?
|
|
||||||
service_permissions = Hash(String, User::PermissionLevel).new
|
|
||||||
user.permissions[service] = service_permissions
|
|
||||||
end
|
|
||||||
|
|
||||||
if request.permission.none?
|
|
||||||
service_permissions.delete request.resource
|
|
||||||
else
|
|
||||||
service_permissions[request.resource] = request.permission
|
|
||||||
end
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::PermissionSet.new user.uid, service, request.resource, request.permission
|
|
||||||
when Request::AskPasswordRecovery
|
|
||||||
|
|
||||||
uid_or_login = request.user
|
|
||||||
user = 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
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "no such user"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.contact.email != request.email
|
|
||||||
# Same error as when users are not found.
|
|
||||||
return Response::Error.new "no such user"
|
|
||||||
end
|
|
||||||
|
|
||||||
user.password_renew_key = UUID.random.to_s
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
unless (activation_url = @configuration.activation_url).nil?
|
|
||||||
|
|
||||||
field_from = @configuration.field_from.not_nil!
|
|
||||||
activation_url = @configuration.activation_url.not_nil!
|
|
||||||
|
|
||||||
# Once the user is created and stored, we try to contact him
|
|
||||||
|
|
||||||
if @configuration.print_password_recovery_parameters
|
|
||||||
pp! user.login,
|
|
||||||
user.contact.email.not_nil!,
|
|
||||||
field_from,
|
|
||||||
activation_url,
|
|
||||||
user.password_renew_key.not_nil!
|
|
||||||
end
|
|
||||||
|
|
||||||
unless Process.run("password-recovery-mailer", [
|
|
||||||
"-l", user.login,
|
|
||||||
"-e", user.contact.email.not_nil!,
|
|
||||||
"-t", "Password recovery email",
|
|
||||||
"-f", field_from,
|
|
||||||
"-u", activation_url,
|
|
||||||
"-a", user.password_renew_key.not_nil!
|
|
||||||
]).success?
|
|
||||||
|
|
||||||
return Response::Error.new "cannot contact the user for password recovery"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Response::PasswordRecoverySent.new user.to_public
|
|
||||||
when Request::PasswordRecovery
|
|
||||||
uid_or_login = request.user
|
|
||||||
user = 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
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "user not found"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.password_renew_key == request.password_renew_key
|
|
||||||
user.password_hash = hash_password request.new_password
|
|
||||||
else
|
|
||||||
return Response::Error.new "renew key not valid"
|
|
||||||
end
|
|
||||||
|
|
||||||
user.password_renew_key = nil
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::PasswordRecovered.new user.to_public
|
|
||||||
when Request::SearchUser
|
|
||||||
pattern = Regex.new request.user, Regex::Options::IGNORE_CASE
|
|
||||||
|
|
||||||
matching_users = Array(AuthD::User::Public).new
|
|
||||||
|
|
||||||
users = @users.to_a
|
|
||||||
users.each do |u|
|
|
||||||
if pattern =~ u.login || u.profile.try do |profile|
|
|
||||||
full_name = profile["full_name"]?
|
|
||||||
if full_name.nil?
|
|
||||||
false
|
|
||||||
else
|
|
||||||
pattern =~ full_name.as_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Baguette::Log.debug "#{u.login} matches #{pattern}"
|
|
||||||
matching_users << u.to_public
|
|
||||||
else
|
|
||||||
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Response::MatchingUsers.new matching_users
|
|
||||||
when Request::EditProfile
|
|
||||||
user = get_user_from_token request.token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
new_profile = request.new_profile
|
|
||||||
|
|
||||||
profile = user.profile || Hash(String, JSON::Any).new
|
|
||||||
|
|
||||||
@configuration.read_only_profile_keys.each do |key|
|
|
||||||
if new_profile[key]? != profile[key]?
|
|
||||||
return Response::Error.new "tried to edit read only key"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
user.profile = new_profile
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::User.new user.to_public
|
|
||||||
when Request::EditProfileContent
|
|
||||||
user = if token = request.token
|
|
||||||
user = get_user_from_token token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
user
|
|
||||||
elsif shared_key = request.shared_key
|
|
||||||
return Response::Error.new "invalid shared key" if shared_key != @configuration.shared_key
|
|
||||||
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
user = if user.is_a? Int32
|
|
||||||
@users_per_uid.get? user.to_s
|
|
||||||
else
|
|
||||||
@users_per_login.get? user
|
|
||||||
end
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
user
|
|
||||||
else
|
|
||||||
return Response::Error.new "no token or shared_key/user pair"
|
|
||||||
end
|
|
||||||
|
|
||||||
new_profile = user.profile || Hash(String, JSON::Any).new
|
|
||||||
|
|
||||||
unless request.shared_key
|
|
||||||
@configuration.read_only_profile_keys.each do |key|
|
|
||||||
if request.new_profile.has_key? key
|
|
||||||
return Response::Error.new "tried to edit read only key"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
request.new_profile.each do |key, value|
|
|
||||||
new_profile[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
user.profile = new_profile
|
|
||||||
|
|
||||||
@users_per_uid.update user.uid.to_s, user
|
|
||||||
|
|
||||||
Response::User.new user.to_public
|
|
||||||
when Request::EditContacts
|
|
||||||
user = get_user_from_token request.token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
if email = request.email
|
|
||||||
# FIXME: This *should* require checking the new mail, with
|
|
||||||
# a new activation key and everything else.
|
|
||||||
user.contact.email = email
|
|
||||||
end
|
|
||||||
|
|
||||||
@users_per_uid.update user
|
|
||||||
|
|
||||||
Response::UserEdited.new user.uid
|
|
||||||
when Request::Delete
|
|
||||||
uid_or_login = request.user
|
|
||||||
user_to_delete = 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
|
|
||||||
|
|
||||||
if user_to_delete.nil?
|
|
||||||
return Response::Error.new "invalid user"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Either the request comes from an admin or the user.
|
|
||||||
# Shared key == admin, check the key.
|
|
||||||
if key = request.shared_key
|
|
||||||
return Response::Error.new "unauthorized (wrong shared key)" unless key == @configuration.shared_key
|
|
||||||
else
|
|
||||||
login = request.login
|
|
||||||
pass = request.password
|
|
||||||
if login.nil? || pass.nil?
|
|
||||||
return Response::Error.new "authentication failed (no shared key, no login)"
|
|
||||||
end
|
|
||||||
|
|
||||||
# authenticate the user
|
|
||||||
begin
|
|
||||||
user = @users_per_login.get login
|
|
||||||
rescue e : DODB::MissingEntry
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.nil?
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
if user.password_hash != hash_password pass
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Is the user to delete the requesting user?
|
|
||||||
if user.uid != user_to_delete.uid
|
|
||||||
return Response::Error.new "invalid credentials"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# User or admin is now verified: let's proceed with the user deletion.
|
|
||||||
@users_per_login.delete user_to_delete.login
|
|
||||||
|
|
||||||
# TODO: better response
|
|
||||||
Response::User.new user_to_delete.to_public
|
|
||||||
when Request::GetContacts
|
|
||||||
user = get_user_from_token request.token
|
|
||||||
|
|
||||||
return Response::Error.new "invalid user" unless user
|
|
||||||
|
|
||||||
_c = user.contact
|
|
||||||
|
|
||||||
Response::Contacts.new user.uid, _c.email, _c.phone
|
|
||||||
else
|
|
||||||
Response::Error.new "unhandled request type"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -644,51 +127,36 @@ class AuthD::Service
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
##
|
Baguette::Log.title "Starting authd"
|
||||||
# Provides a JWT-based authentication scheme for service-specific users.
|
|
||||||
server = IPC::Server.new "auth"
|
|
||||||
server.base_timer = @configuration.ipc_timer
|
|
||||||
server.timer = @configuration.ipc_timer
|
|
||||||
server.loop do |event|
|
|
||||||
if event.is_a? IPC::Exception
|
|
||||||
Baguette::Log.error "IPC::Exception"
|
|
||||||
pp! event
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
|
@base_timer = @configuration.ipc_timer
|
||||||
|
@timer = @configuration.ipc_timer
|
||||||
|
|
||||||
|
self.loop do |event|
|
||||||
case event
|
case event
|
||||||
when IPC::Event::Timer
|
when IPC::Event::Timer
|
||||||
Baguette::Log.debug "Timer" if @configuration.print_ipc_timer
|
Baguette::Log.debug "Timer" if @configuration.print_ipc_timer
|
||||||
|
|
||||||
when IPC::Event::MessageReceived
|
when IPC::Event::MessageReceived
|
||||||
|
Baguette::Log.debug "Received message from #{event.fd}" if @configuration.print_ipc_message_received
|
||||||
begin
|
begin
|
||||||
request = Request.from_ipc(event.message)
|
handle_request event
|
||||||
|
|
||||||
if request.nil?
|
|
||||||
raise "Unknown request (#{event.message.utype.to_i})"
|
|
||||||
end
|
|
||||||
|
|
||||||
Baguette::Log.info "<< #{request.class.name.sub /^Request::/, ""}"
|
|
||||||
|
|
||||||
response = handle_request request
|
|
||||||
|
|
||||||
response.id = request.id
|
|
||||||
|
|
||||||
server.send event.fd, response
|
|
||||||
rescue e : MalformedRequest
|
|
||||||
Baguette::Log.error "#{e.message}"
|
|
||||||
Baguette::Log.error " .. type was: #{e.ipc_type}"
|
|
||||||
Baguette::Log.error " .. tried class was: #{Request.requests.find(&.type.==(e.ipc_type)).to_s}"
|
|
||||||
Baguette::Log.error " .. payload was: #{e.payload}"
|
|
||||||
Baguette::Log.error " .. tried class was: #{Request.requests.find(&.type.==(e.ipc_type)).to_s}"
|
|
||||||
response = Response::Error.new e.message
|
|
||||||
rescue e
|
rescue e
|
||||||
Baguette::Log.error "#{e.message}"
|
Baguette::Log.error "#{e.message}"
|
||||||
response = Response::Error.new e.message
|
# send event.fd, Response::Error.new e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
Baguette::Log.info ">> #{response.class.name.sub /^Response::/, ""}"
|
when IPC::Event::MessageSent
|
||||||
|
Baguette::Log.debug "Message sent to #{event.fd}" if @configuration.print_ipc_message_sent
|
||||||
|
|
||||||
|
when IPC::Exception
|
||||||
|
Baguette::Log.error "IPC::Exception"
|
||||||
|
pp! event
|
||||||
|
else
|
||||||
|
Baguette::Log.error "Not implemented behavior for event: #{event}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
34
src/network.cr
Normal file
34
src/network.cr
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
require "ipc"
|
||||||
|
require "json"
|
||||||
|
require "ipc/json"
|
||||||
|
|
||||||
|
class IPC::JSON
|
||||||
|
def handle(service : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
raise "unimplemented"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module AuthD
|
||||||
|
class_getter requests = [] of IPC::JSON.class
|
||||||
|
class_getter responses = [] of IPC::JSON.class
|
||||||
|
end
|
||||||
|
|
||||||
|
class IPC::Context
|
||||||
|
def send(fd, response : AuthD::Response)
|
||||||
|
send fd, response.type.to_u8, response.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class IPC::Client
|
||||||
|
def send(request : AuthD::Request)
|
||||||
|
unless (fd = @server_fd).nil?
|
||||||
|
send_now fd, request.type.to_u8, request.to_json
|
||||||
|
else
|
||||||
|
raise "Client not connected to the server"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
require "./requests/*"
|
||||||
|
require "./responses/*"
|
100
src/requests/admin.cr
Normal file
100
src/requests/admin.cr
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message AddUser, 1 do
|
||||||
|
# Only clients that have the right shared key will be allowed
|
||||||
|
# to create users.
|
||||||
|
property shared_key : String
|
||||||
|
|
||||||
|
property login : String
|
||||||
|
property password : String
|
||||||
|
property email : String? = nil
|
||||||
|
property phone : String? = nil
|
||||||
|
property profile : Hash(String, JSON::Any)? = nil
|
||||||
|
|
||||||
|
def initialize(@shared_key, @login, @password, @email, @phone, @profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
# No verification of the users' informations when an admin adds it.
|
||||||
|
# No mail address verification.
|
||||||
|
if @shared_key != authd.configuration.shared_key
|
||||||
|
return Response::Error.new "invalid authentication key"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.users_per_login.get? @login
|
||||||
|
return Response::Error.new "login already used"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.configuration.require_email && @email.nil?
|
||||||
|
return Response::Error.new "email required"
|
||||||
|
end
|
||||||
|
|
||||||
|
password_hash = authd.hash_password @password
|
||||||
|
|
||||||
|
uid = authd.new_uid
|
||||||
|
|
||||||
|
user = User.new uid, @login, password_hash
|
||||||
|
user.contact.email = @email unless @email.nil?
|
||||||
|
user.contact.phone = @phone unless @phone.nil?
|
||||||
|
|
||||||
|
@profile.try do |profile|
|
||||||
|
user.profile = profile
|
||||||
|
end
|
||||||
|
|
||||||
|
# We consider adding the user as a registration
|
||||||
|
user.date_registration = Time.local
|
||||||
|
|
||||||
|
authd.users << user
|
||||||
|
|
||||||
|
Response::UserAdded.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << AddUser
|
||||||
|
|
||||||
|
|
||||||
|
IPC::JSON.message ModUser, 5 do
|
||||||
|
property shared_key : String
|
||||||
|
|
||||||
|
property user : Int32 | String
|
||||||
|
property password : String? = nil
|
||||||
|
property email : String? = nil
|
||||||
|
property phone : String? = nil
|
||||||
|
property avatar : String? = nil
|
||||||
|
|
||||||
|
def initialize(@shared_key, @user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
if @shared_key != authd.configuration.shared_key
|
||||||
|
return Response::Error.new "invalid authentication key"
|
||||||
|
end
|
||||||
|
|
||||||
|
uid_or_login = @user
|
||||||
|
user = if uid_or_login.is_a? Int32
|
||||||
|
authd.users_per_uid.get? uid_or_login.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? uid_or_login
|
||||||
|
end
|
||||||
|
|
||||||
|
unless user
|
||||||
|
return Response::Error.new "user not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
@password.try do |s|
|
||||||
|
user.password_hash = authd.hash_password s
|
||||||
|
end
|
||||||
|
|
||||||
|
@email.try do |email|
|
||||||
|
user.contact.email = email
|
||||||
|
end
|
||||||
|
|
||||||
|
@phone.try do |phone|
|
||||||
|
user.contact.phone = phone
|
||||||
|
end
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::UserEdited.new user.uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << ModUser
|
||||||
|
end
|
46
src/requests/contact.cr
Normal file
46
src/requests/contact.cr
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message EditContacts, 16 do
|
||||||
|
property token : String
|
||||||
|
|
||||||
|
property email : String? = nil
|
||||||
|
property phone : String? = nil
|
||||||
|
|
||||||
|
def initialize(@token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.get_user_from_token @token
|
||||||
|
|
||||||
|
return Response::Error.new "invalid user" unless user
|
||||||
|
|
||||||
|
if email = @email
|
||||||
|
# FIXME: This *should* require checking the new mail, with
|
||||||
|
# a new activation key and everything else.
|
||||||
|
user.contact.email = email
|
||||||
|
end
|
||||||
|
|
||||||
|
authd.users_per_uid.update user
|
||||||
|
|
||||||
|
Response::UserEdited.new user.uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << EditContacts
|
||||||
|
|
||||||
|
IPC::JSON.message GetContacts, 18 do
|
||||||
|
property token : String
|
||||||
|
|
||||||
|
def initialize(@token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.get_user_from_token @token
|
||||||
|
|
||||||
|
return Response::Error.new "invalid user" unless user
|
||||||
|
|
||||||
|
_c = user.contact
|
||||||
|
|
||||||
|
Response::Contacts.new user.uid, _c.email, _c.phone
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << GetContacts
|
||||||
|
end
|
69
src/requests/delete.cr
Normal file
69
src/requests/delete.cr
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message Delete, 17 do
|
||||||
|
# Deletion can be triggered by either an admin or the user.
|
||||||
|
property shared_key : String? = nil
|
||||||
|
|
||||||
|
property login : String? = nil
|
||||||
|
property password : String? = nil
|
||||||
|
|
||||||
|
property user : String | Int32
|
||||||
|
|
||||||
|
def initialize(@user, @login, @password)
|
||||||
|
end
|
||||||
|
def initialize(@user, @shared_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
uid_or_login = @user
|
||||||
|
user_to_delete = if uid_or_login.is_a? Int32
|
||||||
|
authd.users_per_uid.get? uid_or_login.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? uid_or_login
|
||||||
|
end
|
||||||
|
|
||||||
|
if user_to_delete.nil?
|
||||||
|
return Response::Error.new "invalid user"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Either the request comes from an admin or the user.
|
||||||
|
# Shared key == admin, check the key.
|
||||||
|
if key = @shared_key
|
||||||
|
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
|
||||||
|
else
|
||||||
|
login = @login
|
||||||
|
pass = @password
|
||||||
|
if login.nil? || pass.nil?
|
||||||
|
return Response::Error.new "authentication failed (no shared key, no login)"
|
||||||
|
end
|
||||||
|
|
||||||
|
# authenticate the user
|
||||||
|
begin
|
||||||
|
user = authd.users_per_login.get login
|
||||||
|
rescue e : DODB::MissingEntry
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.password_hash != authd.hash_password pass
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is the user to delete the requesting user?
|
||||||
|
if user.uid != user_to_delete.uid
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# User or admin is now verified: let's proceed with the user deletion.
|
||||||
|
authd.users_per_login.delete user_to_delete.login
|
||||||
|
|
||||||
|
# TODO: better response
|
||||||
|
Response::User.new user_to_delete.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << Delete
|
||||||
|
|
||||||
|
end
|
41
src/requests/list.cr
Normal file
41
src/requests/list.cr
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message ListUsers, 8 do
|
||||||
|
property token : String? = nil
|
||||||
|
property key : String? = nil
|
||||||
|
|
||||||
|
def initialize(@token, @key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
|
||||||
|
@token.try do |token|
|
||||||
|
user = authd.get_user_from_token token
|
||||||
|
|
||||||
|
return Response::Error.new "unauthorized (user not found from token)" unless user
|
||||||
|
|
||||||
|
# Test if the user is a moderator.
|
||||||
|
if permissions = user.permissions["authd"]?
|
||||||
|
if rights = permissions["*"]?
|
||||||
|
if rights >= User::PermissionLevel::Read
|
||||||
|
else
|
||||||
|
raise AdminAuthorizationException.new "unauthorized (insufficient rights on '*')"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise AdminAuthorizationException.new "unauthorized (no rights on '*')"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise AdminAuthorizationException.new "unauthorized (user not in authd group)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@key.try do |key|
|
||||||
|
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
|
||||||
|
end
|
||||||
|
|
||||||
|
return Response::Error.new "unauthorized (no key nor token)" unless @key || @token
|
||||||
|
|
||||||
|
Response::UsersList.new authd.users.to_h.map &.[1].to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << ListUsers
|
||||||
|
end
|
125
src/requests/password.cr
Normal file
125
src/requests/password.cr
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message UpdatePassword, 7 do
|
||||||
|
property login : String
|
||||||
|
property old_password : String
|
||||||
|
property new_password : String
|
||||||
|
|
||||||
|
def initialize(@login, @old_password, @new_password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.users_per_login.get? @login
|
||||||
|
|
||||||
|
unless user
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.hash_password(@old_password) != user.password_hash
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
user.password_hash = authd.hash_password @new_password
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::UserEdited.new user.uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << UpdatePassword
|
||||||
|
|
||||||
|
IPC::JSON.message PasswordRecovery, 11 do
|
||||||
|
property user : Int32 | String
|
||||||
|
property password_renew_key : String
|
||||||
|
property new_password : String
|
||||||
|
|
||||||
|
def initialize(@user, @password_renew_key, @new_password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
uid_or_login = @user
|
||||||
|
user = if uid_or_login.is_a? Int32
|
||||||
|
authd.users_per_uid.get? uid_or_login.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? uid_or_login
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "user not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.password_renew_key == @password_renew_key
|
||||||
|
user.password_hash = authd.hash_password @new_password
|
||||||
|
else
|
||||||
|
return Response::Error.new "renew key not valid"
|
||||||
|
end
|
||||||
|
|
||||||
|
user.password_renew_key = nil
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::PasswordRecovered.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << PasswordRecovery
|
||||||
|
|
||||||
|
IPC::JSON.message AskPasswordRecovery, 12 do
|
||||||
|
property user : Int32 | String
|
||||||
|
property email : String
|
||||||
|
|
||||||
|
def initialize(@user, @email)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
uid_or_login = @user
|
||||||
|
user = if uid_or_login.is_a? Int32
|
||||||
|
authd.users_per_uid.get? uid_or_login.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? uid_or_login
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.contact.email != @email
|
||||||
|
# Same error as when users are not found.
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
user.password_renew_key = UUID.random.to_s
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
unless (activation_url = authd.configuration.activation_url).nil?
|
||||||
|
|
||||||
|
field_from = authd.configuration.field_from.not_nil!
|
||||||
|
activation_url = authd.configuration.activation_url.not_nil!
|
||||||
|
|
||||||
|
# Once the user is created and stored, we try to contact him
|
||||||
|
|
||||||
|
if authd.configuration.print_password_recovery_parameters
|
||||||
|
pp! user.login,
|
||||||
|
user.contact.email.not_nil!,
|
||||||
|
field_from,
|
||||||
|
activation_url,
|
||||||
|
user.password_renew_key.not_nil!
|
||||||
|
end
|
||||||
|
|
||||||
|
unless Process.run("password-recovery-mailer", [
|
||||||
|
"-l", user.login,
|
||||||
|
"-e", user.contact.email.not_nil!,
|
||||||
|
"-t", "Password recovery email",
|
||||||
|
"-f", field_from,
|
||||||
|
"-u", activation_url,
|
||||||
|
"-a", user.password_renew_key.not_nil!
|
||||||
|
]).success?
|
||||||
|
|
||||||
|
return Response::Error.new "cannot contact the user for password recovery"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Response::PasswordRecoverySent.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << AskPasswordRecovery
|
||||||
|
end
|
113
src/requests/permissions.cr
Normal file
113
src/requests/permissions.cr
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message CheckPermission, 9 do
|
||||||
|
property shared_key : String? = nil
|
||||||
|
property token : String? = nil
|
||||||
|
|
||||||
|
property user : Int32 | String
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
|
||||||
|
def initialize(@shared_key, @user, @service, @resource)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
authorized = false
|
||||||
|
|
||||||
|
if key = @shared_key
|
||||||
|
if key == authd.configuration.shared_key
|
||||||
|
authorized = true
|
||||||
|
else
|
||||||
|
return Response::Error.new "invalid key provided"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if token = @token
|
||||||
|
user = authd.get_user_from_token token
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "token does not match user"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.login != @user && user.uid != @user
|
||||||
|
return Response::Error.new "token does not match user"
|
||||||
|
end
|
||||||
|
|
||||||
|
authorized = true
|
||||||
|
end
|
||||||
|
|
||||||
|
unless authorized
|
||||||
|
return Response::Error.new "unauthorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
user = case u = @user
|
||||||
|
when .is_a? Int32
|
||||||
|
authd.users_per_uid.get? u.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? u
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
service = @service
|
||||||
|
service_permissions = user.permissions[service]?
|
||||||
|
|
||||||
|
if service_permissions.nil?
|
||||||
|
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
|
||||||
|
end
|
||||||
|
|
||||||
|
resource_permissions = service_permissions[@resource]?
|
||||||
|
|
||||||
|
if resource_permissions.nil?
|
||||||
|
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
|
||||||
|
end
|
||||||
|
|
||||||
|
return Response::PermissionCheck.new service, @resource, user.uid, resource_permissions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << CheckPermission
|
||||||
|
|
||||||
|
IPC::JSON.message SetPermission, 10 do
|
||||||
|
property shared_key : String
|
||||||
|
|
||||||
|
property user : Int32 | String
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
|
||||||
|
def initialize(@shared_key, @user, @service, @resource, @permission)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
unless @shared_key == authd.configuration.shared_key
|
||||||
|
return Response::Error.new "unauthorized"
|
||||||
|
end
|
||||||
|
|
||||||
|
user = authd.users_per_uid.get? @user.to_s
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "no such user"
|
||||||
|
end
|
||||||
|
|
||||||
|
service = @service
|
||||||
|
service_permissions = user.permissions[service]?
|
||||||
|
|
||||||
|
if service_permissions.nil?
|
||||||
|
service_permissions = Hash(String, User::PermissionLevel).new
|
||||||
|
user.permissions[service] = service_permissions
|
||||||
|
end
|
||||||
|
|
||||||
|
if @permission.none?
|
||||||
|
service_permissions.delete @resource
|
||||||
|
else
|
||||||
|
service_permissions[@resource] = @permission
|
||||||
|
end
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::PermissionSet.new user.uid, service, @resource, @permission
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << SetPermission
|
||||||
|
end
|
93
src/requests/profile.cr
Normal file
93
src/requests/profile.cr
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message EditProfile, 14 do
|
||||||
|
property token : String
|
||||||
|
property new_profile : Hash(String, JSON::Any)
|
||||||
|
|
||||||
|
def initialize(@token, @new_profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.get_user_from_token @token
|
||||||
|
|
||||||
|
return Response::Error.new "invalid user" unless user
|
||||||
|
|
||||||
|
new_profile = @new_profile
|
||||||
|
|
||||||
|
profile = user.profile || Hash(String, JSON::Any).new
|
||||||
|
|
||||||
|
authd.configuration.read_only_profile_keys.each do |key|
|
||||||
|
if new_profile[key]? != profile[key]?
|
||||||
|
return Response::Error.new "tried to edit read only key"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
user.profile = new_profile
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << EditProfile
|
||||||
|
|
||||||
|
# Same as above, but doesn’t reset the whole profile, only resets elements
|
||||||
|
# for which keys are present in `new_profile`.
|
||||||
|
IPC::JSON.message EditProfileContent, 15 do
|
||||||
|
property token : String? = nil
|
||||||
|
|
||||||
|
property shared_key : String? = nil
|
||||||
|
property user : Int32 | String | Nil
|
||||||
|
|
||||||
|
property new_profile : Hash(String, JSON::Any)
|
||||||
|
|
||||||
|
def initialize(@shared_key, @user, @new_profile)
|
||||||
|
end
|
||||||
|
def initialize(@token, @new_profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = if token = @token
|
||||||
|
u = authd.get_user_from_token token
|
||||||
|
raise UserNotFound.new unless u
|
||||||
|
u
|
||||||
|
elsif shared_key = @shared_key
|
||||||
|
raise AdminAuthorizationException.new if shared_key != authd.configuration.shared_key
|
||||||
|
|
||||||
|
u = @user
|
||||||
|
raise UserNotFound.new unless u
|
||||||
|
|
||||||
|
u = if u.is_a? Int32
|
||||||
|
authd.users_per_uid.get? u.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? u
|
||||||
|
end
|
||||||
|
raise UserNotFound.new unless u
|
||||||
|
|
||||||
|
u
|
||||||
|
else
|
||||||
|
raise AuthenticationInfoLacking.new
|
||||||
|
end
|
||||||
|
|
||||||
|
new_profile = user.profile || Hash(String, JSON::Any).new
|
||||||
|
|
||||||
|
unless @shared_key
|
||||||
|
authd.configuration.read_only_profile_keys.each do |key|
|
||||||
|
if @new_profile.has_key? key
|
||||||
|
return Response::Error.new "tried to edit read only key"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@new_profile.each do |key, value|
|
||||||
|
new_profile[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
user.profile = new_profile
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << EditProfileContent
|
||||||
|
end
|
92
src/requests/register.cr
Normal file
92
src/requests/register.cr
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message Register, 6 do
|
||||||
|
property login : String
|
||||||
|
property password : String
|
||||||
|
property email : String? = nil
|
||||||
|
property phone : String? = nil
|
||||||
|
property profile : Hash(String, JSON::Any)? = nil
|
||||||
|
|
||||||
|
def initialize(@login, @password, @email, @phone, @profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
if ! authd.configuration.registrations
|
||||||
|
return Response::Error.new "registrations not allowed"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.users_per_login.get? @login
|
||||||
|
return Response::Error.new "login already used"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.configuration.require_email && @email.nil?
|
||||||
|
return Response::Error.new "email required"
|
||||||
|
end
|
||||||
|
|
||||||
|
activation_url = authd.configuration.activation_url
|
||||||
|
if activation_url.nil?
|
||||||
|
# In this case we should not accept its registration.
|
||||||
|
return Response::Error.new "No activation URL were entered. Cannot send activation mails."
|
||||||
|
end
|
||||||
|
|
||||||
|
if ! @email.nil?
|
||||||
|
# Test on the email address format.
|
||||||
|
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
|
||||||
|
result = grok.parse @email.not_nil!
|
||||||
|
email = result["email"]?
|
||||||
|
|
||||||
|
if email.nil?
|
||||||
|
return Response::Error.new "invalid email format"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# In this case we should not accept its registration.
|
||||||
|
if @password.size < 4
|
||||||
|
return Response::Error.new "password too short"
|
||||||
|
end
|
||||||
|
|
||||||
|
uid = authd.new_uid
|
||||||
|
password = authd.hash_password @password
|
||||||
|
|
||||||
|
user = User.new uid, @login, password
|
||||||
|
user.contact.email = @email unless @email.nil?
|
||||||
|
user.contact.phone = @phone unless @phone.nil?
|
||||||
|
|
||||||
|
@profile.try do |profile|
|
||||||
|
user.profile = profile
|
||||||
|
end
|
||||||
|
|
||||||
|
user.date_registration = Time.local
|
||||||
|
|
||||||
|
begin
|
||||||
|
field_subject = authd.configuration.field_subject.not_nil!
|
||||||
|
field_from = authd.configuration.field_from.not_nil!
|
||||||
|
activation_url = authd.configuration.activation_url.not_nil!
|
||||||
|
|
||||||
|
u_login = user.login
|
||||||
|
u_email = user.contact.email.not_nil!
|
||||||
|
u_activation_key = user.contact.activation_key.not_nil!
|
||||||
|
|
||||||
|
# Once the user is created and stored, we try to contact him
|
||||||
|
unless Process.run("activation-mailer", [
|
||||||
|
"-l", u_login,
|
||||||
|
"-e", u_email,
|
||||||
|
"-t", field_subject,
|
||||||
|
"-f", field_from,
|
||||||
|
"-u", activation_url,
|
||||||
|
"-a", u_activation_key
|
||||||
|
]).success?
|
||||||
|
raise "cannot contact user #{user.login} address #{user.contact.email}"
|
||||||
|
end
|
||||||
|
rescue e
|
||||||
|
Baguette::Log.error "activation-mailer: #{e}"
|
||||||
|
return Response::Error.new "cannot contact the user (not registered)"
|
||||||
|
end
|
||||||
|
|
||||||
|
# add the user only if we were able to send the confirmation mail
|
||||||
|
authd.users << user
|
||||||
|
|
||||||
|
Response::UserAdded.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << Register
|
||||||
|
end
|
34
src/requests/search.cr
Normal file
34
src/requests/search.cr
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message SearchUser, 13 do
|
||||||
|
property user : String
|
||||||
|
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
pattern = Regex.new @user, Regex::Options::IGNORE_CASE
|
||||||
|
|
||||||
|
matching_users = Array(AuthD::User::Public).new
|
||||||
|
|
||||||
|
users = authd.users.to_a
|
||||||
|
users.each do |u|
|
||||||
|
if pattern =~ u.login || u.profile.try do |profile|
|
||||||
|
full_name = profile["full_name"]?
|
||||||
|
if full_name.nil?
|
||||||
|
false
|
||||||
|
else
|
||||||
|
pattern =~ full_name.as_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Baguette::Log.debug "#{u.login} matches #{pattern}"
|
||||||
|
matching_users << u.to_public
|
||||||
|
else
|
||||||
|
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Response::MatchingUsers.new matching_users
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << SearchUser
|
||||||
|
end
|
34
src/requests/token.cr
Normal file
34
src/requests/token.cr
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message GetToken, 0 do
|
||||||
|
property login : String
|
||||||
|
property password : String
|
||||||
|
|
||||||
|
def initialize(@login, @password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
begin
|
||||||
|
user = authd.users_per_login.get @login
|
||||||
|
rescue e : DODB::MissingEntry
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.password_hash != authd.hash_password @password
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
user.date_last_connection = Time.local
|
||||||
|
token = user.to_token
|
||||||
|
|
||||||
|
# change the date of the last connection
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::Token.new (token.to_s authd.configuration.shared_key), user.uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << GetToken
|
||||||
|
end
|
84
src/requests/users.cr
Normal file
84
src/requests/users.cr
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
class AuthD::Request
|
||||||
|
IPC::JSON.message ValidateUser, 2 do
|
||||||
|
property login : String
|
||||||
|
property activation_key : String
|
||||||
|
|
||||||
|
def initialize(@login, @activation_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.users_per_login.get? @login
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "user not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.contact.activation_key.nil?
|
||||||
|
return Response::Error.new "user already validated"
|
||||||
|
end
|
||||||
|
|
||||||
|
# remove the user contact activation key: the email is validated
|
||||||
|
if user.contact.activation_key == @activation_key
|
||||||
|
user.contact.activation_key = nil
|
||||||
|
else
|
||||||
|
return Response::Error.new "wrong activation key"
|
||||||
|
end
|
||||||
|
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::UserValidated.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << ValidateUser
|
||||||
|
|
||||||
|
IPC::JSON.message GetUser, 3 do
|
||||||
|
property user : Int32 | String
|
||||||
|
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
uid_or_login = @user
|
||||||
|
user = if uid_or_login.is_a? Int32
|
||||||
|
authd.users_per_uid.get? uid_or_login.to_s
|
||||||
|
else
|
||||||
|
authd.users_per_login.get? uid_or_login
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.nil?
|
||||||
|
return Response::Error.new "user not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << GetUser
|
||||||
|
|
||||||
|
IPC::JSON.message GetUserByCredentials, 4 do
|
||||||
|
property login : String
|
||||||
|
property password : String
|
||||||
|
|
||||||
|
def initialize(@login, @password)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(authd : AuthD::Service, event : IPC::Event::Events)
|
||||||
|
user = authd.users_per_login.get? @login
|
||||||
|
|
||||||
|
unless user
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
if authd.hash_password(@password) != user.password_hash
|
||||||
|
return Response::Error.new "invalid credentials"
|
||||||
|
end
|
||||||
|
|
||||||
|
user.date_last_connection = Time.local
|
||||||
|
|
||||||
|
# change the date of the last connection
|
||||||
|
authd.users_per_uid.update user.uid.to_s, user
|
||||||
|
|
||||||
|
Response::User.new user.to_public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AuthD.requests << GetUserByCredentials
|
||||||
|
end
|
9
src/responses/contact.cr
Normal file
9
src/responses/contact.cr
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message Contacts, 12 do
|
||||||
|
property user : Int32
|
||||||
|
property email : String? = nil
|
||||||
|
property phone : String? = nil
|
||||||
|
def initialize(@user, @email, @phone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
src/responses/errors.cr
Normal file
7
src/responses/errors.cr
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message Error, 0 do
|
||||||
|
property reason : String? = nil
|
||||||
|
def initialize(@reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
src/responses/password.cr
Normal file
13
src/responses/password.cr
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message PasswordRecoverySent, 9 do
|
||||||
|
property user : ::AuthD::User::Public
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message PasswordRecovered, 10 do
|
||||||
|
property user : ::AuthD::User::Public
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
19
src/responses/permissions.cr
Normal file
19
src/responses/permissions.cr
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message PermissionCheck, 7 do
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
def initialize(@service, @resource, @user, @permission)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message PermissionSet, 8 do
|
||||||
|
property user : Int32
|
||||||
|
property service : String
|
||||||
|
property resource : String
|
||||||
|
property permission : ::AuthD::User::PermissionLevel
|
||||||
|
def initialize(@user, @service, @resource, @permission)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
8
src/responses/token.cr
Normal file
8
src/responses/token.cr
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message Token, 1 do
|
||||||
|
property uid : Int32
|
||||||
|
property token : String
|
||||||
|
def initialize(@token, @uid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
37
src/responses/users.cr
Normal file
37
src/responses/users.cr
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
class AuthD::Response
|
||||||
|
IPC::JSON.message User, 2 do
|
||||||
|
property user : ::AuthD::User::Public
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message UserAdded, 3 do
|
||||||
|
property user : ::AuthD::User::Public
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message UserEdited, 4 do
|
||||||
|
property uid : Int32
|
||||||
|
def initialize(@uid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message UserValidated, 5 do
|
||||||
|
property user : ::AuthD::User::Public
|
||||||
|
def initialize(@user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message UsersList, 6 do
|
||||||
|
property users : Array(::AuthD::User::Public)
|
||||||
|
def initialize(@users)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IPC::JSON.message MatchingUsers, 11 do
|
||||||
|
property users : Array(::AuthD::User::Public)
|
||||||
|
def initialize(@users)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user