diff --git a/makefile b/makefile index 54a826b..263232e 100644 --- a/makefile +++ b/makefile @@ -49,6 +49,9 @@ validate: get-user: ./bin/authc user get $(NAME) $(LOGIN_OPT) +migrate-user: + ./bin/authc user migrate $(NAME) $(PASSWORD_HASH) $(LOGIN_OPT) + SERVICE ?= 'auth' RESOURCE ?= '*' UID ?= 1000 diff --git a/src/authd/client.cr b/src/authd/client.cr index 733fdc2..d5858ea 100644 --- a/src/authd/client.cr +++ b/src/authd/client.cr @@ -70,6 +70,17 @@ module AuthD ], read end + # Migration of a user from old code base (dnsmanager v1). + def migrate_user(login : String, password_hash_brkn : String) + send_now Request::MigrateUser.new login, password_hash_brkn + parse_message [ + Response::UserAdded, + Response::ErrorMustBeAuthenticated, + Response::ErrorAlreadyUsedLogin, + Response::ErrorMailRequired + ], read + end + def bootstrap(login : String, password : String, email : String, diff --git a/src/authd/user.cr b/src/authd/user.cr index dd1b058..e6b98ba 100644 --- a/src/authd/user.cr +++ b/src/authd/user.cr @@ -38,6 +38,7 @@ class AuthD::User # Private. property contact : Contact + property password_hash_brkn : String? = nil # Old, broken algorithm. property password_hash : String property password_renew_key : String? # service => resource => permission level diff --git a/src/better-parser.cr b/src/better-parser.cr index cf1a2b0..1b82fbf 100644 --- a/src/better-parser.cr +++ b/src/better-parser.cr @@ -93,6 +93,17 @@ parser = OptionParser.new do |parser| unrecognized_args_to_context_args.call parser, 2 end + parser.on "migrate", "Adding a user from old code base." do + parser.banner = "usage: user add login password-hash-brkn" + Baguette::Log.info "Adding a user to the DB." + Context.command = "user-migrate" + opt_authd_login.call parser + opt_profile.call parser + opt_help.call parser + # login password-hash-brkn + unrecognized_args_to_context_args.call parser, 2 + end + parser.on "mod", "Modify a user account." do parser.banner = "Usage: user mod userid [-e email|-P profile] [opt]" Baguette::Log.info "Modify a user account." diff --git a/src/client.cr b/src/client.cr index db90810..9804d53 100644 --- a/src/client.cr +++ b/src/client.cr @@ -62,6 +62,7 @@ class Actions # Require admin privileges. @the_call["user-add"] = ->user_add + @the_call["user-migrate"] = ->user_migrate @the_call["user-mod"] = ->user_mod @the_call["permission-set"] = ->permission_set @@ -87,6 +88,17 @@ class Actions puts "error: #{e.message}" end + # Migrate a user from old code base (dnsmanager v1). + def user_migrate + args = Context.args.not_nil! + login, password_hash_brkn = args[0..1] + profile = Context.user_profile + + pp! authd.migrate_user login, password_hash_brkn + rescue e : AuthD::Exception + puts "error: #{e.message}" + end + def user_registration args = Context.args.not_nil! login, email = args[0..1] @@ -143,7 +155,6 @@ class Actions puts "error: #{e.message}" end - # TODO def user_mod args = Context.args.not_nil! diff --git a/src/requests/login.cr b/src/requests/login.cr index a95c1c6..f4e2ff5 100644 --- a/src/requests/login.cr +++ b/src/requests/login.cr @@ -40,6 +40,25 @@ class AuthD::Request return Response::ErrorInvalidCredentials.new end + # MIGRATION + # The migration involves old (broken) hash algorithm. + # On first connection, the user is authenticated with the old algorithm then a new hash is generated. + if brkn_hash = user.password_hash_brkn + # Authenticates the user with its old password hash algo. + if brkn_hash != authd.obsolete_hash_password @password + Baguette::Log.error "cannot authenticate the user with his old password hash" + return Response::ErrorInvalidCredentials.new + end + + # FYI: there is no need to clone the user since there are no indexes on passwords. + user.password_hash = authd.hash_password @password # Adding new password hash. + user.password_hash_brkn = nil # Removing old password hash. + Baguette::Log.info "updating password hash for #{user.login} to newer algorithm" + authd.users_per_login.update user + + return AuthD::Request.perform_login authd, fd, user.not_nil! + end + pwhash = Sodium::Password::Hash.new hash = Base64.decode user.password_hash diff --git a/src/requests/migration.cr b/src/requests/migration.cr new file mode 100644 index 0000000..98db8d8 --- /dev/null +++ b/src/requests/migration.cr @@ -0,0 +1,46 @@ +class AuthD::Request + # Migration involves users with a broken password hash algorithm. + IPC::JSON.message MigrateUser, 16 do + property login : String + property password_hash_brkn : String # Old, broken algorithm. Will be changed on first authentication. + property admin : Bool = false + property email : String? = nil + property profile : Hash(String, JSON::Any)? = nil + + def initialize(@login, @password_hash_brkn, @admin = false, @email = nil, @profile = nil) + end + + def handle(authd : AuthD::Service, fd : Int32) + logged_user = authd.get_logged_user_full? fd + return Response::ErrorMustBeAuthenticated.new if logged_user.nil? + + logged_user.assert_permission("authd", "*", User::PermissionLevel::Admin) + + if authd.users_per_login.get? @login + return Response::ErrorAlreadyUsedLogin.new + end + + # No mail verification since there were no mail stored in dnsmanager v1. + + uid = authd.new_uid + + user = User.new uid, @login, "" # Current password is voluntarily not set. + + user.password_hash_brkn = @password_hash_brkn + user.contact.email = @email unless @email.nil? + user.admin = @admin + + @profile.try do |profile| + user.profile = profile + end + + # We consider adding the user as a registration. + user.date_registration = Time.local + + authd.users << user + authd.new_uid_commit uid + Response::UserAdded.new user.to_public + end + end + AuthD.requests << MigrateUser +end