From d1e9445a11c7ac3260a5ed7fec856003bd02f286 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Wed, 10 Oct 2018 04:46:34 +0900 Subject: [PATCH 1/3] WIP of prototype authd using IPC instead of HTTP. --- src/main-ipc.cr | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main-ipc.cr diff --git a/src/main-ipc.cr b/src/main-ipc.cr new file mode 100644 index 0000000..797d357 --- /dev/null +++ b/src/main-ipc.cr @@ -0,0 +1,117 @@ +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… + From 621135ce5a413c87bc04ba6ade4fd3bc96bd5005 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Tue, 13 Nov 2018 02:51:21 +0900 Subject: [PATCH 2/3] Finished conversion to a libIPC-based microservice. --- shard.yml | 10 ++--- src/authd.cr | 77 ++++++++++++++++--------------- src/main-ipc.cr | 117 ------------------------------------------------ src/main.cr | 99 +++++++++++++++++++++++----------------- 4 files changed, 105 insertions(+), 198 deletions(-) delete mode 100644 src/main-ipc.cr diff --git a/shard.yml b/shard.yml index 4527c78..1476dfb 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,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 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-ipc.cr b/src/main-ipc.cr deleted file mode 100644 index 797d357..0000000 --- a/src/main-ipc.cr +++ /dev/null @@ -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… - 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 + From 2cbb515ab0376ac5282390c4e72736a38954ccf1 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 1 Dec 2018 07:31:01 +0900 Subject: [PATCH 3/3] Shard update. --- shard.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 1476dfb..ed510ae 100644 --- a/shard.yml +++ b/shard.yml @@ -17,7 +17,9 @@ targets: crystal: 0.27 dependencies: - # FIXME: Missing ipc. + ipc: + git: https://git.karchnu.fr/JunkOS/ipc.cr + branch: master jwt: github: crystal-community/jwt branch: master