diff --git a/shard.yml b/shard.yml index 4527c78..ed510ae 100644 --- a/shard.yml +++ b/shard.yml @@ -1,12 +1,12 @@ name: authd -version: 0.1.0 +version: 0.2.0 authors: - Karchnu - Luka Vandervelden description: | - Web authentication daemon. + JWT-based authentication daemon. targets: authd: @@ -14,11 +14,11 @@ targets: authd-adduser: main: src/adduser.cr -crystal: 0.26 +crystal: 0.27 dependencies: - kemal: - github: kemalcr/kemal + ipc: + git: https://git.karchnu.fr/JunkOS/ipc.cr branch: master jwt: github: crystal-community/jwt diff --git a/src/authd.cr b/src/authd.cr index 889a5f4..f94c718 100644 --- a/src/authd.cr +++ b/src/authd.cr @@ -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 - diff --git a/src/main.cr b/src/main.cr index 1e3b388..6ab8a6a 100644 --- a/src/main.cr +++ b/src/main.cr @@ -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 +