From bd68148924023b27e79abec289040cb84c12fa6a Mon Sep 17 00:00:00 2001
From: Philippe PITTOLI
Date: Sun, 23 Feb 2020 20:37:50 +0100
Subject: [PATCH] Password Recovery.
---
src/authd.cr | 55 ++++++++++++++++++++++-
src/main.cr | 57 ++++++++++++++++++++++++
src/user.cr | 1 +
utils/authd-user-ask-for-new-password.cr | 45 +++++++++++++++++++
4 files changed, 157 insertions(+), 1 deletion(-)
create mode 100644 utils/authd-user-ask-for-new-password.cr
diff --git a/src/authd.cr b/src/authd.cr
index 2114598..a68126e 100644
--- a/src/authd.cr
+++ b/src/authd.cr
@@ -99,6 +99,18 @@ class AuthD::Response
initialize :user, :service, :resource, :permission
end
+ class PasswordRecoverySent < Response
+ property user : ::AuthD::User::Public
+
+ initialize :user
+ end
+
+ class PasswordRecovered < Response
+ property user : ::AuthD::User::Public
+
+ initialize :user
+ end
+
# This creates a Request::Type enumeration. One entry for each request type.
{% begin %}
enum Type
@@ -273,6 +285,21 @@ class AuthD::Request
initialize :shared_key, :user, :service, :resource, :permission
end
+ class PasswordRecovery < Request
+ property shared_key : String
+ property user : Int32 | String
+ property password_renew_key : String
+ property new_password : String
+
+ initialize :shared_key, :user, :password_renew_key, :new_password
+ end
+
+ class AskPasswordRecovery < Request
+ property user : Int32 | String
+
+ initialize :user
+ end
+
# This creates a Request::Type enumeration. One entry for each request type.
{% begin %}
enum Type
@@ -309,7 +336,7 @@ class AuthD::Request
requests.find(&.type.==(type)).try &.from_json(payload)
rescue e : JSON::ParseException
- raise Exception.new "malformed request"
+ raise Exception.new "malformed request: #{e}"
end
end
@@ -419,6 +446,32 @@ module AuthD
end
end
+ def ask_password_recovery(uid_or_login : String | Int32)
+ send Request::AskPasswordRecovery.new uid_or_login
+ response = Response.from_ipc read
+
+ case response
+ when Response::PasswordRecoverySent
+ when Response::Error
+ raise Exception.new response.reason
+ else
+ Exception.new
+ end
+ end
+
+ def change_password(uid_or_login : String | Int32, new_pass : String, renew_key : String)
+ send Request::PasswordRecovery.new @key, uid_or_login, renew_key, new_pass
+ response = Response.from_ipc read
+
+ case response
+ when Response::PasswordRecovered
+ when Response::Error
+ raise Exception.new response.reason
+ else
+ Exception.new
+ end
+ end
+
def register(login : String,
password : String,
email : String?,
diff --git a/src/main.cr b/src/main.cr
index 072f218..5109c85 100644
--- a/src/main.cr
+++ b/src/main.cr
@@ -209,6 +209,7 @@ class AuthD::Service
user.profile = profile
end
+
@users << user
# Once the user is created and stored, we try to contact him
@@ -309,6 +310,62 @@ class AuthD::Service
@users_per_uid.update user.uid.to_s, user
Response::PermissionSet.new user.uid, service, request.resource, request.permission
+ when Request::AskPasswordRecovery
+
+ uid_or_login = request.user
+ user = if uid_or_login.is_a? Int32
+ @users_per_uid.get? uid_or_login.to_s
+ else
+ @users_per_login.get? uid_or_login
+ end
+
+ if user.nil?
+ return Response::Error.new "user not found"
+ end
+
+ user.password_renew_key = UUID.random.to_s
+
+ @users_per_uid.update user.uid.to_s, user
+
+ # Once the user is created and stored, we try to contact him
+ unless Process.run("password-recovery-mailer", [
+ "-l", user.login,
+ "-e", user.contact.email.not_nil!,
+ "-t", "Password recovery email",
+ "-f", "karchnu@localhost",
+ "-a", user.password_renew_key.not_nil!
+ ]).success?
+ return Response::Error.new "cannot contact the user for password recovery"
+ end
+
+ Response::PasswordRecoverySent.new user.to_public
+ when Request::PasswordRecovery
+ if request.shared_key != @jwt_key
+ return Response::Error.new "invalid authentication key"
+ end
+
+ uid_or_login = request.user
+ user = if uid_or_login.is_a? Int32
+ @users_per_uid.get? uid_or_login.to_s
+ else
+ @users_per_login.get? uid_or_login
+ end
+
+ if user.nil?
+ return Response::Error.new "user not found"
+ end
+
+ if user.password_renew_key == request.password_renew_key
+ user.password_hash = hash_password request.new_password
+ else
+ return Response::Error.new "renew key not valid"
+ end
+
+ user.password_renew_key = nil
+
+ @users_per_uid.update user.uid.to_s, user
+
+ Response::PasswordRecoverySent.new user.to_public
else
Response::Error.new "unhandled request type"
end
diff --git a/src/user.cr b/src/user.cr
index e05111b..d5aaf10 100644
--- a/src/user.cr
+++ b/src/user.cr
@@ -39,6 +39,7 @@ class AuthD::User
# Private.
property contact : Contact
property password_hash : String
+ property password_renew_key : String?
# service => resource => permission level
property permissions : Hash(String, Hash(String, PermissionLevel))
property configuration : Hash(String, Hash(String, JSON::Any))
diff --git a/utils/authd-user-ask-for-new-password.cr b/utils/authd-user-ask-for-new-password.cr
new file mode 100644
index 0000000..8911fe5
--- /dev/null
+++ b/utils/authd-user-ask-for-new-password.cr
@@ -0,0 +1,45 @@
+require "option_parser"
+
+require "../src/authd.cr"
+
+key_file : String? = nil
+cli_login : String? = nil
+
+OptionParser.parse do |parser|
+ parser.unknown_args do |args|
+ if args.size != 1
+ puts "usage: #{PROGRAM_NAME} [options]"
+ puts parser
+ exit 1
+ end
+
+ cli_login = args[0]
+ end
+
+ parser.on "-K file", "--key-file file", "Read the authd shared key from a file." do |file|
+ key_file = file
+ end
+
+ parser.on "-h", "--help", "Prints this help message." do
+ puts "usage: #{PROGRAM_NAME} [options]"
+ puts parser
+ exit 0
+ end
+end
+
+begin
+ authd = IPC::Connection.new "auth"
+
+ authd = AuthD::Client.new
+ authd.key = File.read(key_file.not_nil!).chomp
+
+ login = cli_login.not_nil!
+
+ # AskPasswordRecovery => PasswordRecoverySent
+ # PasswordRecovery =>
+
+ pp! authd.ask_password_recovery login
+rescue e
+ puts "Error: #{e}"
+ exit 1
+end