Finished conversion to a libIPC-based microservice.
parent
37fdc6332d
commit
621135ce5a
10
shard.yml
10
shard.yml
|
@ -1,12 +1,12 @@
|
||||||
name: authd
|
name: authd
|
||||||
version: 0.1.0
|
version: 0.2.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Karchnu <karchnu@karchnu.fr>
|
- Karchnu <karchnu@karchnu.fr>
|
||||||
- Luka Vandervelden <lukc@upyum.com>
|
- Luka Vandervelden <lukc@upyum.com>
|
||||||
|
|
||||||
description: |
|
description: |
|
||||||
Web authentication daemon.
|
JWT-based authentication daemon.
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
authd:
|
authd:
|
||||||
|
@ -14,12 +14,10 @@ targets:
|
||||||
authd-adduser:
|
authd-adduser:
|
||||||
main: src/adduser.cr
|
main: src/adduser.cr
|
||||||
|
|
||||||
crystal: 0.26
|
crystal: 0.27
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
kemal:
|
# FIXME: Missing ipc.
|
||||||
github: kemalcr/kemal
|
|
||||||
branch: master
|
|
||||||
jwt:
|
jwt:
|
||||||
github: crystal-community/jwt
|
github: crystal-community/jwt
|
||||||
branch: master
|
branch: master
|
||||||
|
|
77
src/authd.cr
77
src/authd.cr
|
@ -1,52 +1,59 @@
|
||||||
|
|
||||||
require "kemal"
|
|
||||||
require "jwt"
|
require "jwt"
|
||||||
|
|
||||||
|
require "ipc"
|
||||||
|
|
||||||
require "./user.cr"
|
require "./user.cr"
|
||||||
|
|
||||||
class HTTP::Server::Context
|
module AuthD
|
||||||
property authd_user : AuthD::User?
|
enum RequestTypes
|
||||||
end
|
GET_TOKEN
|
||||||
|
|
||||||
class AuthD::Middleware < Kemal::Handler
|
|
||||||
property key : String = ""
|
|
||||||
|
|
||||||
@configured = false
|
|
||||||
@configurator : Proc(Middleware, Nil)
|
|
||||||
|
|
||||||
def initialize(&block : Proc(Middleware, Nil))
|
|
||||||
@configurator = block
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(context)
|
enum ResponseTypes
|
||||||
unless @configured
|
OK
|
||||||
@configured = true
|
MALFORMED_REQUEST
|
||||||
@configurator.call self
|
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
|
end
|
||||||
|
|
||||||
context.request.headers["X-Token"]?.try do |x_token|
|
def get_token?(username : String, password : String)
|
||||||
payload, header = JWT.decode x_token, @key, "HS256"
|
send RequestTypes::GET_TOKEN.value.to_u8, {
|
||||||
|
:username => username,
|
||||||
|
:password => password
|
||||||
|
}.to_json
|
||||||
|
|
||||||
if payload
|
response = read
|
||||||
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
|
|
||||||
|
|
||||||
payload["perms"].as_a.tap do |perms|
|
if response.type == ResponseTypes::OK.value.to_u8
|
||||||
perms.each do |perm|
|
response.payload
|
||||||
if perm.class == String
|
else
|
||||||
u.perms! << perm.as_s
|
nil
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
117
src/main-ipc.cr
117
src/main-ipc.cr
|
@ -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 doesn’t 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…
|
|
||||||
|
|
99
src/main.cr
99
src/main.cr
|
@ -1,12 +1,16 @@
|
||||||
require "uuid"
|
require "uuid"
|
||||||
|
require "option_parser"
|
||||||
|
|
||||||
require "kemal"
|
|
||||||
require "jwt"
|
require "jwt"
|
||||||
|
|
||||||
require "pg"
|
require "pg"
|
||||||
require "crecto"
|
require "crecto"
|
||||||
|
|
||||||
require "./user.cr"
|
require "ipc"
|
||||||
|
|
||||||
|
require "./authd.cr"
|
||||||
|
|
||||||
|
extend AuthD
|
||||||
|
|
||||||
authd_db_name = "authd"
|
authd_db_name = "authd"
|
||||||
authd_db_hostname = "localhost"
|
authd_db_hostname = "localhost"
|
||||||
|
@ -14,7 +18,7 @@ authd_db_user = "user"
|
||||||
authd_db_password = "nico-nico-nii"
|
authd_db_password = "nico-nico-nii"
|
||||||
authd_jwt_key = "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|
|
parser.on "-d name", "--database-name name", "Database name." do |name|
|
||||||
authd_db_name = name
|
authd_db_name = name
|
||||||
end
|
end
|
||||||
|
@ -34,53 +38,68 @@ Kemal.config.extra_options do |parser|
|
||||||
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
|
parser.on "-K file", "--key-file file", "JWT key file" do |file_name|
|
||||||
authd_jwt_key = File.read(file_name).chomp
|
authd_jwt_key = File.read(file_name).chomp
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
post "/token" do |env|
|
parser.on "-h", "--help", "Show this help" do
|
||||||
env.response.content_type = "application/json"
|
puts parser
|
||||||
|
|
||||||
username = env.params.json["username"]?
|
exit 0
|
||||||
password = env.params.json["password"]?
|
|
||||||
|
|
||||||
if ! username.is_a? String
|
|
||||||
next halt env, status_code: 400, response: ({error: "Missing username."}.to_json)
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
module DataBase
|
module DataBase
|
||||||
extend Crecto::Repo
|
extend Crecto::Repo
|
||||||
end
|
end
|
||||||
|
|
||||||
Kemal.run do
|
DataBase.config do |conf|
|
||||||
DataBase.config do |conf|
|
conf.adapter = Crecto::Adapters::Postgres
|
||||||
conf.adapter = Crecto::Adapters::Postgres
|
conf.hostname = authd_db_hostname
|
||||||
conf.hostname = authd_db_hostname
|
conf.database = authd_db_name
|
||||||
conf.database = authd_db_name
|
conf.username = authd_db_user
|
||||||
conf.username = authd_db_user
|
conf.password = authd_db_password
|
||||||
conf.password = authd_db_password
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Dummy query to check DB connection is possible.
|
# Dummy query to check DB connection is possible.
|
||||||
begin
|
begin
|
||||||
DataBase.all AuthD::User, Crecto::Repo::Query.new
|
DataBase.all User, Crecto::Repo::Query.new
|
||||||
rescue e
|
rescue e
|
||||||
puts "Database connection failed: #{e.message}"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue