authd/src/authd.cr

336 lines
6.2 KiB
Crystal
Raw Normal View History

2019-11-22 18:14:52 +01:00
require "json"
require "jwt"
require "ipc"
require "./user.cr"
2019-11-22 22:55:12 +01:00
class AuthD::Response
include JSON::Serializable
annotation MessageType
end
class_getter type = -1
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 token : String
initialize :token
end
class User < Response
property user : Passwd::User
initialize :user
end
class UserAdded < Response
property user : Passwd::User
initialize :user
end
class UserEdited < Response
property uid : Int32
initialize :uid
end
# This creates a Request::Type enumeration. One entry for each request type.
{% begin %}
2019-11-22 17:31:56 +01:00
enum Type
2019-11-22 22:55:12 +01:00
{% 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 %}
2019-11-22 17:31:56 +01:00
end
2019-11-22 22:55:12 +01:00
{% 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)
payload = String.new message.payload
type = Type.new message.type.to_i
begin
request = requests.find(&.type.==(type)).try &.from_json(payload)
rescue e : JSON::ParseException
raise Exception.new "malformed request"
end
request
end
end
class IPC::Connection
def send(response : AuthD::Response)
send response.type.to_u8, response.to_json
end
2019-11-22 18:14:52 +01:00
end
2019-11-22 18:14:52 +01:00
class AuthD::Request
include JSON::Serializable
2019-11-22 18:14:52 +01:00
annotation MessageType
end
2019-11-22 18:14:52 +01:00
class_getter type = -1
macro inherited
def self.type
::AuthD::Request::Type::{{ @type.name.split("::").last.id }}
2019-11-22 17:31:56 +01:00
end
2019-11-22 18:14:52 +01:00
end
2018-12-19 13:54:19 +01:00
2019-11-22 18:14:52 +01:00
macro initialize(*properties)
def initialize(
{% for value in properties %}
@{{value.id}}{% if value != properties.last %},{% end %}
{% end %}
)
2019-11-22 17:31:56 +01:00
end
2019-01-07 17:04:20 +01:00
2019-11-22 18:14:52 +01:00
def type
Type::{{ @type.name.split("::").last.id }}
2019-11-22 17:31:56 +01:00
end
2019-11-22 18:14:52 +01:00
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 uid : Int32?
property gid : Int32?
property home : String?
property shell : String?
initialize :shared_key, :login, :password
end
class GetUser < Request
property uid : Int32
initialize :uid
end
2019-11-22 18:14:52 +01:00
class GetUserByCredentials < Request
property login : String
property password : String
2019-11-22 18:14:52 +01:00
initialize :login, :password
end
class ModUser < Request
property shared_key : String
property uid : Int32
property password : String?
property avatar : String?
initialize :shared_key, :uid
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 %}
2019-11-22 17:31:56 +01:00
end
2019-11-22 18:14:52 +01:00
{% 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)
payload = String.new message.payload
type = Type.new message.type.to_i
begin
request = requests.find(&.type.==(type)).try &.from_json(payload)
rescue e : JSON::ParseException
raise Exception.new "misformed request"
end
request
2019-05-29 16:06:11 +02:00
end
2019-11-22 18:14:52 +01:00
end
class IPC::Connection
def send(request : AuthD::Request)
send request.type.to_u8, request.to_json
end
end
2019-05-29 16:06:11 +02:00
2019-11-22 18:14:52 +01:00
module AuthD
2019-06-05 22:30:29 +02:00
class Client < IPC::Connection
property key : String
def initialize
@key = ""
initialize "auth"
end
2019-06-06 01:16:52 +02:00
def get_token?(login : String, password : String) : String?
2019-11-22 18:14:52 +01:00
send Request::GetToken.new login, password
2019-11-22 22:55:12 +01:00
response = Response.from_ipc read
2019-11-22 22:55:12 +01:00
if response.is_a?(Response::Token)
response.token
else
nil
end
end
2019-11-17 15:50:26 +01:00
def get_user?(login : String, password : String) : Passwd::User?
2019-11-22 18:14:52 +01:00
send Request::GetUserByCredentials.new login, password
2019-11-22 22:55:12 +01:00
response = Response.from_ipc read
2019-11-22 22:55:12 +01:00
if response.is_a? Response::User
response.user
else
nil
end
end
2019-01-07 17:04:20 +01:00
def get_user?(uid : Int32)
2019-11-22 18:14:52 +01:00
send Request::GetUser.new uid
2019-01-07 17:04:20 +01:00
response = read
2019-11-22 17:31:56 +01:00
if response.type == Response::Type::Ok.value.to_u8
User.from_json String.new response.payload
2019-01-07 17:04:20 +01:00
else
nil
end
end
2019-11-22 17:31:56 +01:00
def send(type : Request::Type, payload)
2018-12-19 13:54:19 +01:00
send type.value.to_u8, payload
end
def decode_token(token)
2019-06-28 18:20:34 +02:00
user, meta = JWT.decode token, @key, JWT::Algorithm::HS256
2019-11-17 15:50:26 +01:00
user = Passwd::User.from_json user.to_json
{user, meta}
end
2018-12-19 13:54:19 +01:00
# FIXME: Extra options may be useful to implement here.
2019-11-17 15:50:26 +01:00
def add_user(login : String, password : String) : Passwd::User | Exception
2019-11-22 18:14:52 +01:00
send Request::AddUser.new @key, login, password
2018-12-19 13:54:19 +01:00
2019-11-22 22:55:12 +01:00
response = Response.from_ipc read
2018-12-19 13:54:19 +01:00
2019-11-22 22:55:12 +01:00
case response
when Response::UserAdded
response.user
when Response::Error
Exception.new response.reason
2018-12-19 13:54:19 +01:00
else
2019-11-22 22:55:12 +01:00
# Should not happen in serialized connections, but…
# itll happen if you run several requests at once.
Exception.new
2018-12-19 13:54:19 +01:00
end
end
2019-05-29 16:06:11 +02:00
2019-05-29 19:45:03 +02:00
def mod_user(uid : Int32, password : String? = nil, avatar : String? = nil) : Bool | Exception
2019-11-22 18:14:52 +01:00
request = Request::ModUser.new @key, uid
2019-05-29 16:06:11 +02:00
2019-11-22 18:14:52 +01:00
request.password = password if password
request.avatar = avatar if avatar
2019-05-29 19:45:03 +02:00
2019-11-22 18:14:52 +01:00
send request
2019-05-29 16:06:11 +02:00
2019-11-22 22:55:12 +01:00
response = Response.from_ipc read
2019-05-29 16:06:11 +02:00
2019-11-22 22:55:12 +01:00
case response
when Response::UserEdited
2019-05-29 16:06:11 +02:00
true
2019-11-22 22:55:12 +01:00
when Response::Error
Exception.new response.reason
2019-05-29 16:06:11 +02:00
else
2019-11-22 22:55:12 +01:00
Exception.new "???"
2019-05-29 16:06:11 +02:00
end
end
end
end