From 9561a180d73648aebe850eb1b209c06956cc04c9 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 19:46:48 +0200 Subject: [PATCH 01/11] Improved configuration CLI. --- src/main.cr | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main.cr b/src/main.cr index 2082b6e..d92206b 100644 --- a/src/main.cr +++ b/src/main.cr @@ -6,27 +6,31 @@ require "jwt" require "pg" require "crecto" -MASTER_KEY = Random::Secure.base64 -authd_db_password_file = "db-password-file" authd_db_name = "authd" authd_db_hostname = "localhost" authd_db_user = "user" +authd_db_password = "nico-nico-nii" +authd_jwt_key = "nico-nico-nii" Kemal.config.extra_options do |parser| - parser.on "-d name", "--database-name name", "database name for authd" do |dbn| - authd_db_name = dbn + 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 for authd" do |u| - authd_db_user = u + parser.on "-u name", "--database-username user", "Database user." do |name| + authd_db_user = name end - parser.on "-a hostname", "--hostname host", "hostname for authd" do |h| - authd_db_hostname = h + parser.on "-a host", "--hostname host", "Database host name." do |host| + authd_db_hostname = host end - parser.on "-P password-file", "--passfile file", "password file for authd" do |f| - authd_db_password_file = f + parser.on "-P file", "--password-file file", "Password file." do |file_name| + authd_db_password = File.read file_name + end + + parser.on "-K file", "--key-file file", "JWT key file" do |file_name| + authd_jwt_key = File.read file_name end end @@ -73,7 +77,7 @@ post "/token" do |env| { "status" => "success", - "token" => JWT.encode(user.to_h, MASTER_KEY, "HS256") + "token" => JWT.encode(user.to_h, authd_jwt_key, "HS256") }.to_json end @@ -87,6 +91,6 @@ Kemal.run do conf.hostname = authd_db_hostname conf.database = authd_db_name conf.username = authd_db_user - conf.password = File.read authd_db_password_file + conf.password = authd_db_password end end From 8275a77576395fbacc25f89e414ab10ab1a3234c Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 21:23:50 +0200 Subject: [PATCH 02/11] Various. - JWT key can be set from command-line (through a file). - User class split to a separate file to allow use by other tools. - Minor style changes. --- src/main.cr | 27 ++++----------------------- src/user.cr | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 src/user.cr diff --git a/src/main.cr b/src/main.cr index d92206b..4c1fea0 100644 --- a/src/main.cr +++ b/src/main.cr @@ -6,6 +6,8 @@ require "jwt" require "pg" require "crecto" +require "./user.cr" + authd_db_name = "authd" authd_db_hostname = "localhost" authd_db_user = "user" @@ -34,27 +36,6 @@ Kemal.config.extra_options do |parser| end end -class User < Crecto::Model - schema "users" do # table name - field :username, String - field :realname, String - field :avatar, String - field :password, String - field :perms, Array(String) - end - - validate_required [:username, :password, :perms] - - def to_h - { - :username => @username, - :realname => @realname, - :perms => @perms, - :avatar => @avatar - } - end -end - post "/token" do |env| env.response.content_type = "application/json" @@ -77,7 +58,7 @@ post "/token" do |env| { "status" => "success", - "token" => JWT.encode(user.to_h, authd_jwt_key, "HS256") + "token" => JWT.encode user.to_h, authd_jwt_key, "HS256" }.to_json end @@ -85,7 +66,7 @@ module MyRepo extend Crecto::Repo end -Kemal.run do +Kemal.run 12051 do MyRepo.config do |conf| conf.adapter = Crecto::Adapters::Postgres conf.hostname = authd_db_hostname diff --git a/src/user.cr b/src/user.cr new file mode 100644 index 0000000..08af4cd --- /dev/null +++ b/src/user.cr @@ -0,0 +1,25 @@ + +require "pg" +require "crecto" + +class User < Crecto::Model + schema "users" do # table name + field :username, String + field :realname, String + field :avatar, String + field :password, String + field :perms, Array(String) + end + + validate_required [:username, :password, :perms] + + def to_h + { + :username => @username, + :realname => @realname, + :perms => @perms, + :avatar => @avatar + } + end +end + From a92b94cccb81a36349131b4f2d98a566fddbef9f Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 21:24:44 +0200 Subject: [PATCH 03/11] Primitive adduser tool added. --- shard.yml | 2 ++ src/adduser.cr | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/adduser.cr diff --git a/shard.yml b/shard.yml index f39318c..4527c78 100644 --- a/shard.yml +++ b/shard.yml @@ -11,6 +11,8 @@ description: | targets: authd: main: src/main.cr + authd-adduser: + main: src/adduser.cr crystal: 0.26 diff --git a/src/adduser.cr b/src/adduser.cr new file mode 100644 index 0000000..9384dbc --- /dev/null +++ b/src/adduser.cr @@ -0,0 +1,89 @@ + +require "option_parser" + +require "./user.cr" + +user_name : String? = nil +user_password : String? = nil +user_perms = Array(String).new + +database_url = "postgres:localhost" +database_name = "authd" +database_user = "nico" +database_password = "nico-nico-nii" + +OptionParser.parse! do |parser| + parser.banner = "usage: #{ARGV[0]} [arguments]" + + parser.on "-a url", "--database url", "URL of authd’s database." do |url| + database_url = url + end + parser.on "-U name", "--database-user name", "Name of the database’s user." do |name| + database_user = name + end + parser.on "-P password", "--database-pasword password", "Password of the database’s user." do |password| + database_password = password + end + parser.on "-N name", "--database-name name", "Name of the database." do |name| + database_name = name + end + + parser.on "-u name", "--username name", "Name of the user to add." do |name| + user_name = name + end + parser.on "-p password", "--password password", "Password of the user." do |password| + user_password = password + end + parser.on "-x perm", "--permission", "Add a permission token to the user." do |token| + user_perms << token + end + + parser.on("-h", "--help", "Show this help") do + puts parser + exit 0 + end + + parser.invalid_option do |flag| + puts "error: #{flag} is not a valid option." + puts parser + + exit 1 + end +end + +module DataBase + extend Crecto::Repo +end + +DataBase.config do |conf| + conf.adapter = Crecto::Adapters::Postgres + conf.hostname = database_url + conf.username = database_user + conf.password = database_password + conf.database = database_name +end + +if user_name.nil? + STDERR.puts "No username specified." + exit 1 +end +if user_password.nil? + STDERR.puts "No user password specified." + exit 1 +end + +user = User.new +user.username = user_name +user.password = user_password +user.perms = user_perms +changeset = DataBase.insert user + +if changeset.valid? + puts "User added successfully." +else + changeset.errors.each do |e| + p e + end +end + + From db209117f85ae7e4a0a8b538b5f015d99c86be3f Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 21:25:03 +0200 Subject: [PATCH 04/11] Authd library added to provide an auto-check middleware. --- src/authd.cr | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/authd.cr diff --git a/src/authd.cr b/src/authd.cr new file mode 100644 index 0000000..59de698 --- /dev/null +++ b/src/authd.cr @@ -0,0 +1,37 @@ + +require "kemal" +require "jwt" + +class HTTP::Server::Context + property authd_user : Hash(String, JSON::Any)? +end + +class AuthD::Middleware < Kemal::Handler + property key : String = "" + + @configured = false + @configurator : Proc(Middleware, Nil) + + def initialize(&block : Proc(Middleware, Nil)) + @configurator = block + end + + def call(context) + unless @configured + @configured = true + @configurator.call self + end + + context.request.headers["X-Token"]?.try do |x_token| + payload, header = JWT.decode x_token, @key, "HS256" + + if payload + context.authd_user = payload + end + end + + call_next context + end +end + + From ee69c365ba935c11cebaa30202da0638808252d9 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 21:42:21 +0200 Subject: [PATCH 05/11] User -> AuthD::User, authd_user : AuthD::User --- src/adduser.cr | 2 +- src/authd.cr | 19 +++++++++++++++++-- src/main.cr | 2 +- src/user.cr | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/adduser.cr b/src/adduser.cr index 9384dbc..17dc19d 100644 --- a/src/adduser.cr +++ b/src/adduser.cr @@ -72,7 +72,7 @@ if user_password.nil? exit 1 end -user = User.new +user = AuthD::User.new user.username = user_name user.password = user_password user.perms = user_perms diff --git a/src/authd.cr b/src/authd.cr index 59de698..889a5f4 100644 --- a/src/authd.cr +++ b/src/authd.cr @@ -2,8 +2,10 @@ require "kemal" require "jwt" +require "./user.cr" + class HTTP::Server::Context - property authd_user : Hash(String, JSON::Any)? + property authd_user : AuthD::User? end class AuthD::Middleware < Kemal::Handler @@ -26,7 +28,20 @@ class AuthD::Middleware < Kemal::Handler payload, header = JWT.decode x_token, @key, "HS256" if payload - context.authd_user = 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 + + payload["perms"].as_a.tap do |perms| + perms.each do |perm| + if perm.class == String + u.perms! << perm.as_s + end + end + end + end end end diff --git a/src/main.cr b/src/main.cr index 4c1fea0..0477ab3 100644 --- a/src/main.cr +++ b/src/main.cr @@ -50,7 +50,7 @@ post "/token" do |env| next halt env, status_code: 400, response: ({error: "Missing password."}.to_json) end - user = MyRepo.get_by(User, username: username, password: password) + user = MyRepo.get_by AuthD::User, username: username, password: password if ! user next halt env, status_code: 400, response: ({error: "Invalid user or password."}.to_json) diff --git a/src/user.cr b/src/user.cr index 08af4cd..df50ac2 100644 --- a/src/user.cr +++ b/src/user.cr @@ -2,7 +2,7 @@ require "pg" require "crecto" -class User < Crecto::Model +class AuthD::User < Crecto::Model schema "users" do # table name field :username, String field :realname, String From 3d5dd30d82344a4fd0fae0976108479895df1033 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sat, 22 Sep 2018 23:10:14 +0200 Subject: [PATCH 06/11] Improved password imports. --- src/main.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cr b/src/main.cr index 0477ab3..54e128e 100644 --- a/src/main.cr +++ b/src/main.cr @@ -28,11 +28,11 @@ Kemal.config.extra_options do |parser| end parser.on "-P file", "--password-file file", "Password file." do |file_name| - authd_db_password = File.read file_name + authd_db_password = File.read(file_name).gsub /\n$/, "" end parser.on "-K file", "--key-file file", "JWT key file" do |file_name| - authd_jwt_key = File.read file_name + authd_jwt_key = File.read file_name.gsub /\n$/, "" end end From 22d392268a337afbcdc90cff6138d5546f320b72 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sun, 23 Sep 2018 16:02:35 +0200 Subject: [PATCH 07/11] Not enforcing port number anymore. --- src/main.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cr b/src/main.cr index 54e128e..68f285e 100644 --- a/src/main.cr +++ b/src/main.cr @@ -66,7 +66,7 @@ module MyRepo extend Crecto::Repo end -Kemal.run 12051 do +Kemal.run do MyRepo.config do |conf| conf.adapter = Crecto::Adapters::Postgres conf.hostname = authd_db_hostname From 20fa7650ddf342ce06756ae95720e8a71c13755f Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sun, 23 Sep 2018 16:17:48 +0200 Subject: [PATCH 08/11] DataBase connection check during startup. --- src/main.cr | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main.cr b/src/main.cr index 68f285e..17c6e06 100644 --- a/src/main.cr +++ b/src/main.cr @@ -50,7 +50,7 @@ post "/token" do |env| next halt env, status_code: 400, response: ({error: "Missing password."}.to_json) end - user = MyRepo.get_by AuthD::User, username: username, password: password + 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) @@ -62,16 +62,25 @@ post "/token" do |env| }.to_json end -module MyRepo +module DataBase extend Crecto::Repo end Kemal.run do - MyRepo.config do |conf| + 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}" + + Kemal.stop + end end From ba5821052c5c9057521e85d975f29bd1337e87d4 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sun, 23 Sep 2018 16:20:42 +0200 Subject: [PATCH 09/11] Database setup SQL query added to README. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f024102 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ + +# authd + +## Database setup + +```sql +create table users(id int, created_at date, updated_at date, username text, realname text, password text, avatar text, perms text[]); +``` + From 34983207216c0bb5ec0e8509da105d0647e73607 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sun, 23 Sep 2018 16:34:56 +0200 Subject: [PATCH 10/11] JWT key import. --- src/main.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cr b/src/main.cr index 17c6e06..8c54cbe 100644 --- a/src/main.cr +++ b/src/main.cr @@ -32,7 +32,7 @@ Kemal.config.extra_options do |parser| end parser.on "-K file", "--key-file file", "JWT key file" do |file_name| - authd_jwt_key = File.read file_name.gsub /\n$/, "" + authd_jwt_key = File.read(file_name).gsub /\n$/, "" end end From abc364e21298c63ed3e51de355b9b9039dc8c6b2 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Mon, 24 Sep 2018 22:10:24 +0200 Subject: [PATCH 11/11] chomp() used instead of gsub where possible. --- src/main.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cr b/src/main.cr index 8c54cbe..1e3b388 100644 --- a/src/main.cr +++ b/src/main.cr @@ -28,11 +28,11 @@ Kemal.config.extra_options do |parser| end parser.on "-P file", "--password-file file", "Password file." do |file_name| - authd_db_password = File.read(file_name).gsub /\n$/, "" + 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).gsub /\n$/, "" + authd_jwt_key = File.read(file_name).chomp end end