Finished conversion to a libIPC-based microservice.

This commit is contained in:
Luka Vandervelden 2018-11-13 02:51:21 +09:00
parent 37fdc6332d
commit 621135ce5a
4 changed files with 105 additions and 198 deletions

View File

@ -1,12 +1,12 @@
name: authd
version: 0.1.0
version: 0.2.0
authors:
- Karchnu <karchnu@karchnu.fr>
- Luka Vandervelden <lukc@upyum.com>
description: |
Web authentication daemon.
JWT-based authentication daemon.
targets:
authd:
@ -14,12 +14,10 @@ targets:
authd-adduser:
main: src/adduser.cr
crystal: 0.26
crystal: 0.27
dependencies:
kemal:
github: kemalcr/kemal
branch: master
# FIXME: Missing ipc.
jwt:
github: crystal-community/jwt
branch: master

View File

@ -1,52 +1,59 @@
require "kemal"
require "jwt"
require "ipc"
require "./user.cr"
class HTTP::Server::Context
property authd_user : AuthD::User?
end
class AuthD::Middleware < Kemal::Handler
property key : String = ""
@configured = false
@configurator : Proc(Middleware, Nil)
def initialize(&block : Proc(Middleware, Nil))
@configurator = block
module AuthD
enum RequestTypes
GET_TOKEN
end
def call(context)
unless @configured
@configured = true
@configurator.call self
enum ResponseTypes
OK
MALFORMED_REQUEST
INVALID_CREDENTIALS
end
class GetTokenRequest
JSON.mapping({
username: String,
password: String
})
end
class Client < IPC::Client
property key : String
def initialize
@key = ""
initialize "auth"
end
context.request.headers["X-Token"]?.try do |x_token|
payload, header = JWT.decode x_token, @key, "HS256"
def get_token?(username : String, password : String)
send RequestTypes::GET_TOKEN.value.to_u8, {
:username => username,
:password => password
}.to_json
if payload
context.authd_user = AuthD::User.new.tap do |u|
u.username = payload["username"].as_s?
u.realname = payload["realname"].as_s?
u.avatar = payload["avatar"].as_s?
u.perms = Array(String).new
response = read
payload["perms"].as_a.tap do |perms|
perms.each do |perm|
if perm.class == String
u.perms! << perm.as_s
end
end
end
end
if response.type == ResponseTypes::OK.value.to_u8
response.payload
else
nil
end
end
call_next context
def decode_token(token)
user, meta = JWT.decode token, @key, "HS256"
user = AuthD::User.from_json user.to_json
{user, meta}
end
end
end

View File

@ -1,117 +0,0 @@
require "uuid"
require "option_parser"
require "ipc"
require "jwt"
require "pg"
require "crecto"
require "./user.cr"
authd_db_name = "authd"
authd_db_hostname = "localhost"
authd_db_user = "user"
authd_db_password = "nico-nico-nii"
authd_jwt_key = "nico-nico-nii"
OptionParser.parse! do |parser|
parser.on "-d name", "--database-name name", "Database name." do |name|
authd_db_name = name
end
parser.on "-u name", "--database-username user", "Database user." do |name|
authd_db_user = name
end
parser.on "-a host", "--hostname host", "Database host name." do |host|
authd_db_hostname = host
end
parser.on "-P file", "--password-file file", "Password file." do |file_name|
authd_db_password = File.read(file_name).chomp
end
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
authd_jwt_key = File.read(file_name).chomp
end
end
class AuthD::Message
enum RequestTypes
TOKEN_REQUEST
end
enum ResponseTypes
ALL_OK
INVALID_FORMAT
INVALID_CREDENTIALS
end
JSON.mapping({
username: String,
password: String,
})
end
module DataBase
extend Crecto::Repo
end
DataBase.config do |conf|
conf.adapter = Crecto::Adapters::Postgres
conf.hostname = authd_db_hostname
conf.database = authd_db_name
conf.username = authd_db_user
conf.password = authd_db_password
end
# Dummy query to check DB connection is possible.
begin
DataBase.all AuthD::User, Crecto::Repo::Query.new
rescue e
puts "Database connection failed: #{e.message}"
exit 0
end
authd = IPC::Service.new("authd").loop do |event|
p event.class
if event.is_a? IPC::Event::Message
payload = JSON.parse(event.message.payload).as_h?
if payload.nil?
event.client.send AuthD::Message::ResponseTypes::INVALID_FORMAT.to_u8, "Input is not a JSON object."
next
end
username = payload["username"]?.try &.as_s?
password = payload["password"]?.try &.as_s?
if username.nil?
event.client.send AuthD::Message::ResponseTypes::INVALID_FORMAT.to_u8, "Missing username."
next
end
if password.nil?
event.client.send AuthD::Message::ResponseTypes::INVALID_FORMAT.to_u8, "Missing password."
next
end
# FIXME: Huge pitfall here. Somehow doesnt compile without, but that should be its type already, so…?
username = username.as String
password = password.as String
user = DataBase.get_by AuthD::User, username: username, password: password
if user.nil?
event.client.send AuthD::Message::ResponseTypes::INVALID_CREDENTIALS.to_u8, "Invalid credentials.."
next
end
event.client.send AuthD::Message::ResponseTypes::ALL_OK.to_u8, JWT.encode(user.to_h, authd_jwt_key, "HS255")
end
end
authd.close # Not exactly required because an at_exit{} is added, but…

View File

@ -1,12 +1,16 @@
require "uuid"
require "option_parser"
require "kemal"
require "jwt"
require "pg"
require "crecto"
require "./user.cr"
require "ipc"
require "./authd.cr"
extend AuthD
authd_db_name = "authd"
authd_db_hostname = "localhost"
@ -14,7 +18,7 @@ authd_db_user = "user"
authd_db_password = "nico-nico-nii"
authd_jwt_key = "nico-nico-nii"
Kemal.config.extra_options do |parser|
OptionParser.parse! do |parser|
parser.on "-d name", "--database-name name", "Database name." do |name|
authd_db_name = name
end
@ -34,53 +38,68 @@ Kemal.config.extra_options do |parser|
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
authd_jwt_key = File.read(file_name).chomp
end
end
post "/token" do |env|
env.response.content_type = "application/json"
parser.on "-h", "--help", "Show this help" do
puts parser
username = env.params.json["username"]?
password = env.params.json["password"]?
if ! username.is_a? String
next halt env, status_code: 400, response: ({error: "Missing username."}.to_json)
exit 0
end
if ! password.is_a? String
next halt env, status_code: 400, response: ({error: "Missing password."}.to_json)
end
user = DataBase.get_by AuthD::User, username: username, password: password
if ! user
next halt env, status_code: 400, response: ({error: "Invalid user or password."}.to_json)
end
{
"status" => "success",
"token" => JWT.encode user.to_h, authd_jwt_key, "HS256"
}.to_json
end
module DataBase
extend Crecto::Repo
end
Kemal.run do
DataBase.config do |conf|
conf.adapter = Crecto::Adapters::Postgres
conf.hostname = authd_db_hostname
conf.database = authd_db_name
conf.username = authd_db_user
conf.password = authd_db_password
end
DataBase.config do |conf|
conf.adapter = Crecto::Adapters::Postgres
conf.hostname = authd_db_hostname
conf.database = authd_db_name
conf.username = authd_db_user
conf.password = authd_db_password
end
# Dummy query to check DB connection is possible.
begin
DataBase.all AuthD::User, Crecto::Repo::Query.new
rescue e
puts "Database connection failed: #{e.message}"
# Dummy query to check DB connection is possible.
begin
DataBase.all User, Crecto::Repo::Query.new
rescue e
puts "Database connection failed: #{e.message}"
Kemal.stop
exit 1
end
##
# Provides a JWT-based authentication scheme for service-specific users.
IPC::Service.new "auth" do |event|
client = event.client
case event
when IPC::Event::Message
message = event.message
payload = message.payload
case RequestTypes.new message.type.to_i
when RequestTypes::GET_TOKEN
begin
request = GetTokenRequest.from_json payload
rescue e
client.send ResponseTypes::MALFORMED_REQUEST.value.to_u8, e.message || ""
next
end
user = DataBase.get_by User,
username: request.username,
password: request.password
if user.nil?
client.send ResponseTypes::INVALID_CREDENTIALS.value.to_u8, ""
next
end
client.send ResponseTypes::OK.value.to_u8,
JWT.encode user.to_h, authd_jwt_key, "HS256"
end
end
end