Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
Philippe PITTOLI | b7a724b5d6 | |
Philippe PITTOLI | dbb4486fb1 | |
Philippe PITTOLI | 8f7c3f5b0d | |
Philippe PITTOLI | d24cb5d94e | |
Philippe PITTOLI | e2e77af4fa | |
Philippe PITTOLI | 3d44c7c6e8 | |
Philippe PITTOLI | 68f8b141c0 | |
Philippe PITTOLI | a64a3291ce | |
Philippe PITTOLI | 2b33f362dd | |
Philippe PITTOLI | 11f5b0872b | |
Philippe PITTOLI | 16b2869827 | |
Philippe PITTOLI | bbf7a9a80e | |
Philippe PITTOLI | ca4cb9e231 | |
Philippe PITTOLI | 111adae98f |
5
TODO.md
5
TODO.md
|
@ -8,6 +8,11 @@ A combinaison of both is fine as long as the logic is comprehensively documented
|
|||
A simple error message is given instead of specific messages for each recurring error.
|
||||
In the same time, some exceptions (such as **AdminAuthenticationException**) are used a few times for the same kind of errors.
|
||||
|
||||
### New features
|
||||
|
||||
- On login: inform the user he doesn't have an email address.
|
||||
This happens when the user was migrated.
|
||||
|
||||
### Structures, not classes
|
||||
|
||||
Maybe in some cases, it could be great to use structures instead of classes.
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/gawk -f
|
||||
|
||||
BEGIN {
|
||||
OFS="\t"
|
||||
should_print = 0
|
||||
}
|
||||
|
||||
$1 ~ /^[-_ %ùÙêÊçÇéÉàÀ+a-zA-Z0-9'@.,;&]+$/ {
|
||||
should_print = 1
|
||||
}
|
||||
|
||||
should_print == 0 {
|
||||
print "INVALID:", $1, $2
|
||||
}
|
||||
|
||||
should_print == 1 {
|
||||
print $1 "\t" $2
|
||||
should_print = 0
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Should we run the build?
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
exec >& 2
|
||||
echo "Usage: $0 <exe>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exe=$1
|
||||
|
||||
# If the binary hasn't already be compiled.
|
||||
[ -f "${exe}" ] || exit 0
|
||||
|
||||
v=`find src/ -type f -newer "${exe}" | wc -l`
|
||||
test "${v}" != "0"
|
56
makefile
56
makefile
|
@ -1,7 +1,6 @@
|
|||
all: build-server
|
||||
|
||||
Q ?= @
|
||||
SHOULD_UPDATE = ./bin/should-update
|
||||
OPTS ?= --progress
|
||||
|
||||
NAME ?= John
|
||||
|
@ -15,6 +14,8 @@ else
|
|||
LOGIN_OPT = -l $(LOGIN)
|
||||
endif
|
||||
|
||||
SOURCE_FILES = $(wildcard src/*.cr src/*/*.cr src/*/*/*.cr)
|
||||
|
||||
##################
|
||||
### SETUP COMMANDS
|
||||
##################
|
||||
|
@ -36,39 +37,34 @@ add-first-user:
|
|||
### REQUEST EXAMPLES
|
||||
####################
|
||||
|
||||
add-user:
|
||||
./bin/authc user add $(NAME) $(EMAIL) $(LOGIN_OPT)
|
||||
|
||||
register:
|
||||
./bin/authc user register $(NAME) $(EMAIL)
|
||||
|
||||
ACTIVATION_KEY ?= put-your-key-here
|
||||
validate:
|
||||
./bin/authc user validate $(NAME) $(ACTIVATION_KEY)
|
||||
add-user:; $(Q)./bin/authc user add $(NAME) $(EMAIL) $(LOGIN_OPT)
|
||||
register:; $(Q)./bin/authc user register $(NAME) $(EMAIL)
|
||||
validate:; $(Q)./bin/authc user validate $(NAME) $(ACTIVATION_KEY)
|
||||
get-user:; $(Q)./bin/authc user get $(NAME) $(LOGIN_OPT)
|
||||
|
||||
get-user:
|
||||
./bin/authc user get $(NAME) $(LOGIN_OPT)
|
||||
USER_DB ?= /tmp/migration-authd-user-db.txt
|
||||
$(USER_DB): ; ./bin/migration-filter.awk < /tmp/usrdb | grep -a -v "^INVALID" | sort | uniq > $(USER_DB)
|
||||
migration-file: $(USER_DB)
|
||||
migrate-user:; ./bin/authc user migrate $(NAME) $(PASSWORD_HASH) $(LOGIN_OPT)
|
||||
migrate-all-users:; ./bin/authc migration-script $(USER_DB) $(LOGIN_OPT)
|
||||
|
||||
SERVICE ?= 'auth'
|
||||
RESOURCE ?= '*'
|
||||
UID ?= 1000
|
||||
permission-check:
|
||||
./bin/authc permission check $(UID) $(SERVICE) $(RESOURCE) $(LOGIN_OPT)
|
||||
|
||||
PERMISSION ?= Read
|
||||
permission-set:
|
||||
./bin/authc permission set $(UID) $(SERVICE) $(RESOURCE) $(PERMISSION) $(LOGIN_OPT)
|
||||
permission-check:; ./bin/authc permission check $(UID) $(SERVICE) $(RESOURCE) $(LOGIN_OPT)
|
||||
permission-set:; ./bin/authc permission set $(UID) $(SERVICE) $(RESOURCE) $(PERMISSION) $(LOGIN_OPT)
|
||||
|
||||
|
||||
###################
|
||||
### DEVELOPER TOOLS
|
||||
###################
|
||||
|
||||
build-server:
|
||||
$(Q)-$(SHOULD_UPDATE) bin/authd && shards build authd $(OPTS)
|
||||
build-client:
|
||||
$(Q)-$(SHOULD_UPDATE) bin/authc && shards build authc $(OPTS)
|
||||
|
||||
bin/authd: $(SOURCE_FILES); $(Q)shards build authd $(OPTS)
|
||||
bin/authc: $(SOURCE_FILES); $(Q)shards build authc $(OPTS)
|
||||
build-server: bin/authd
|
||||
build-client: bin/authc
|
||||
build: build-server build-client
|
||||
|
||||
doc:
|
||||
|
@ -81,18 +77,12 @@ DIR ?= docs
|
|||
serve-doc:
|
||||
darkhttpd $(DIR) --addr $(HTTPD_ADDR) --port $(HTTPD_PORT) --log $(HTTPD_ACCESS_LOGS)
|
||||
|
||||
print-messages:
|
||||
cat src/requests/*.cr | ./bin/get-messages.awk
|
||||
print-message-numbers:
|
||||
make -s print-messages | grep -E "^[0-9]" | sort -n
|
||||
print-messages-without-comments:
|
||||
make -s print-messages | grep -vE '^[[:blank:]]+#'
|
||||
print-response-messages:
|
||||
cat src/responses/*.cr | ./bin/get-messages.awk
|
||||
print-response-message-numbers:
|
||||
make -s print-response-messages | grep -E "^[0-9]" | sort -n
|
||||
print-response-messages-without-comments:
|
||||
make -s print-response-messages | grep -vE '^[[:blank:]]+#'
|
||||
print-messages:; cat src/requests/*.cr | ./bin/get-messages.awk
|
||||
print-message-numbers:; make -s print-messages | grep -E "^[0-9]" | sort -n
|
||||
print-messages-no-comments:; make -s print-messages | grep -vE '^[[:blank:]]+#'
|
||||
print-response-messages:; cat src/responses/*.cr | ./bin/get-messages.awk
|
||||
print-response-message-numbers:; make -s print-response-messages | grep -E "^[0-9]" | sort -n
|
||||
print-response-messages-no-comments:; make -s print-response-messages | grep -vE '^[[:blank:]]+#'
|
||||
|
||||
wipe-db:
|
||||
rm -r $(DATA_DIRECTORY)
|
||||
|
|
|
@ -39,5 +39,8 @@ require "./authd/exceptions"
|
|||
# Requests and responses.
|
||||
require "./network"
|
||||
|
||||
# Run child processes.
|
||||
require "./process"
|
||||
|
||||
# Functions to request the authd server.
|
||||
require "./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,
|
||||
|
|
|
@ -4,6 +4,8 @@ require "uuid"
|
|||
class AuthD::User
|
||||
include JSON::Serializable
|
||||
|
||||
def_clone
|
||||
|
||||
enum PermissionLevel
|
||||
None
|
||||
Read
|
||||
|
@ -18,10 +20,16 @@ class AuthD::User
|
|||
class Contact
|
||||
include JSON::Serializable
|
||||
|
||||
def_clone
|
||||
|
||||
# the activation key is removed once the user is validated
|
||||
property activation_key : String? = nil
|
||||
property email : String?
|
||||
|
||||
# Not yet validated email address: useful to keep a the previous validated email address
|
||||
# until the new address is validated.
|
||||
property pending_email : String? = nil
|
||||
|
||||
def initialize(@email = nil)
|
||||
end
|
||||
|
||||
|
@ -38,6 +46,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
|
||||
|
|
|
@ -79,6 +79,17 @@ parser = OptionParser.new do |parser|
|
|||
unrecognized_args_to_context_args.call parser, 2
|
||||
end
|
||||
|
||||
parser.on "migration-script", "Adding a batch of users from old code base." do
|
||||
parser.banner = "usage: migration-script user-db.txt"
|
||||
Baguette::Log.info "Adding a batch of users."
|
||||
Context.command = "migration-script"
|
||||
opt_authd_login.call parser
|
||||
opt_profile.call parser
|
||||
opt_help.call parser
|
||||
# user-db.txt
|
||||
unrecognized_args_to_context_args.call parser, 1
|
||||
end
|
||||
|
||||
parser.on "user", "Operations on users." do
|
||||
parser.banner = "Usage: user [add | mod | delete | validate | search | get | recover | register ]"
|
||||
|
||||
|
@ -93,6 +104,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."
|
||||
|
|
|
@ -62,6 +62,8 @@ class Actions
|
|||
|
||||
# Require admin privileges.
|
||||
@the_call["user-add"] = ->user_add
|
||||
@the_call["user-migrate"] = ->user_migrate
|
||||
@the_call["migration-script"] = ->migration_script
|
||||
@the_call["user-mod"] = ->user_mod
|
||||
|
||||
@the_call["permission-set"] = ->permission_set
|
||||
|
@ -87,6 +89,52 @@ 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
|
||||
|
||||
# Migrate a batch of users from dnsmanager v1.
|
||||
#
|
||||
# Usage: authc migration-script user-db.txt
|
||||
#
|
||||
# user-db.txt should be formated as "login <TAB> old-hash".
|
||||
def migration_script
|
||||
args = Context.args.not_nil!
|
||||
filename = args[0]
|
||||
profile = Context.user_profile
|
||||
|
||||
File.each_line(filename) do |line|
|
||||
login, password_hash_brkn = line.split("\t")
|
||||
STDOUT.write ((" " * 150) + "\r").to_slice
|
||||
STDOUT.write "adding user '#{login}'\r".to_slice
|
||||
response = authd.migrate_user login, password_hash_brkn
|
||||
|
||||
case response
|
||||
when AuthD::Response::UserAdded
|
||||
pp! response.user
|
||||
when AuthD::Response::ErrorMustBeAuthenticated
|
||||
Baguette::Log.error "ErrorMustBeAuthenticated"
|
||||
exit 1
|
||||
when AuthD::Response::ErrorAlreadyUsedLogin
|
||||
#Baguette::Log.error "ErrorAlreadyUsedLogin"
|
||||
when AuthD::Response::ErrorMailRequired
|
||||
Baguette::Log.error "ErrorMailRequired"
|
||||
else
|
||||
Baguette::Log.error "unknown error"
|
||||
end
|
||||
end
|
||||
|
||||
rescue e : AuthD::Exception
|
||||
puts "error: #{e.message}"
|
||||
end
|
||||
|
||||
def user_registration
|
||||
args = Context.args.not_nil!
|
||||
login, email = args[0..1]
|
||||
|
@ -143,7 +191,6 @@ class Actions
|
|||
puts "error: #{e.message}"
|
||||
end
|
||||
|
||||
|
||||
# TODO
|
||||
def user_mod
|
||||
args = Context.args.not_nil!
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
require "baguette-crystal-base"
|
||||
|
||||
class Baguette::Configuration
|
||||
class Auth < IPC
|
||||
property service_name : String = "auth"
|
||||
property recreate_indexes : Bool = false
|
||||
property storage : String = "storage"
|
||||
property registrations : Bool = false
|
||||
property require_email : Bool = false
|
||||
property activation_template : String = "email-activation"
|
||||
property recovery_template : String = "email-recovery"
|
||||
property mailer_exe : String = "/usr/local/bin/mailer"
|
||||
property read_only_profile_keys : Array(String) = Array(String).new
|
||||
|
||||
property print_password_recovery_parameters : Bool = false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
def run_process(cmd : String, params : Array(String), env : Hash(String, String))
|
||||
unless Process.run(cmd, params, env,
|
||||
true # clear environment
|
||||
# input: Process::Redirect::Inherit,
|
||||
# output: Process::Redirect::Inherit,
|
||||
# error: Process::Redirect::Inherit
|
||||
).success?
|
||||
raise "cannot run #{cmd} #{params.join(" ")}"
|
||||
end
|
||||
end
|
||||
|
||||
# Send a token to recovery the user's password.
|
||||
def send_recovery_token(authd : AuthD::Service, login : String, email : String, token : String)
|
||||
run_process(authd.configuration.mailer_exe,
|
||||
[ "send", authd.configuration.recovery_template, email ],
|
||||
{ "HOME" => "/", "LOGIN" => login, "TOKEN" => token })
|
||||
end
|
||||
|
||||
# Send a token to validate the user's email address.
|
||||
def send_activation_token(authd : AuthD::Service, login : String, email : String, token : String)
|
||||
run_process(authd.configuration.mailer_exe,
|
||||
[ "send", authd.configuration.activation_template, email ],
|
||||
{ "HOME" => "/", "LOGIN" => login, "TOKEN" => token })
|
||||
end
|
|
@ -11,7 +11,8 @@ class AuthD::Request
|
|||
# On successuful connection: store the authenticated user in a hash.
|
||||
authd.logged_users[fd] = user.to_public
|
||||
|
||||
Response::Login.new (token.to_s authd.configuration.secret_key), user.uid
|
||||
Response::Login.new (token.to_s authd.configuration.secret_key), user.uid,
|
||||
user.contact.email, user.contact.pending_email
|
||||
end
|
||||
|
||||
IPC::JSON.message Login, 0 do
|
||||
|
@ -34,10 +35,29 @@ class AuthD::Request
|
|||
# No user means DODB::MissingEntry, so it's already covered.
|
||||
return Response::ErrorInvalidCredentials.new if user.nil?
|
||||
|
||||
# In case the user hasn't validated his email address,
|
||||
# In case the user hasn't validated his email address (no email address but a token is present),
|
||||
# authentication shouldn't be possible.
|
||||
if user.contact.activation_key
|
||||
return Response::ErrorInvalidCredentials.new
|
||||
if user.contact.email.nil? && user.contact.activation_key
|
||||
return Response::ErrorEmailAddressNotValidated.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
|
||||
|
|
|
@ -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
|
|
@ -12,6 +12,7 @@ class AuthD::Request
|
|||
logged_user = authd.get_logged_user_full? fd
|
||||
return Response::ErrorMustBeAuthenticated.new if logged_user.nil?
|
||||
|
||||
# The user will be modified, we should get a COPY of the user.
|
||||
user = if u = @user
|
||||
logged_user.assert_permission("authd", "*", User::PermissionLevel::Edit)
|
||||
authd.user? u
|
||||
|
@ -20,23 +21,49 @@ class AuthD::Request
|
|||
end
|
||||
return Response::ErrorUserNotFound.new if user.nil?
|
||||
|
||||
cloned_user : AuthD::User = user.clone
|
||||
|
||||
# Only an admin can uprank or downrank someone.
|
||||
if admin = @admin
|
||||
logged_user.assert_permission("authd", "*", User::PermissionLevel::Admin)
|
||||
user.admin = admin
|
||||
cloned_user.admin = admin
|
||||
end
|
||||
|
||||
@password.try do |s|
|
||||
user.password_hash = authd.hash_password s
|
||||
cloned_user.password_hash = authd.hash_password s
|
||||
end
|
||||
|
||||
@email.try do |email|
|
||||
user.contact.email = email
|
||||
# In case of a new email address:
|
||||
# 1. the new address is stored as "pending_email"
|
||||
# 2. the new address has to be validated before being used as primary email address
|
||||
if email = @email
|
||||
# Verify the email address isn't already in the database.
|
||||
if authd.users_per_email.get? Base64.encode(email).chomp
|
||||
return Response::ErrorEmailAddressAlreadyUsed.new
|
||||
end
|
||||
cloned_user.contact.pending_email = email
|
||||
cloned_user.contact.new_activation_key
|
||||
|
||||
begin
|
||||
u_login = cloned_user.login
|
||||
u_email = cloned_user.contact.pending_email.not_nil!
|
||||
u_activation_token = cloned_user.contact.activation_key.not_nil!
|
||||
|
||||
# Once the user is created and stored, we try to contact him.
|
||||
send_activation_token authd, u_login, u_email, u_activation_token
|
||||
rescue e
|
||||
Baguette::Log.error "mailer: #{e}"
|
||||
return Response::ErrorCannotContactUser.new
|
||||
end
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
begin
|
||||
authd.users_per_uid.update cloned_user.uid.to_s, cloned_user
|
||||
rescue e
|
||||
return Response::Error.new "could not update the user (email may already be used)"
|
||||
end
|
||||
|
||||
Response::UserEdited.new user.uid
|
||||
Response::UserEdited.new cloned_user.uid
|
||||
end
|
||||
end
|
||||
AuthD.requests << ModUser
|
||||
|
|
|
@ -35,26 +35,11 @@ class AuthD::Request
|
|||
user.password_renew_key.not_nil!
|
||||
end
|
||||
|
||||
mailer_exe = authd.configuration.mailer_exe
|
||||
template_name = authd.configuration.recovery_template
|
||||
|
||||
u_login = user.login
|
||||
u_email = user.contact.email.not_nil!
|
||||
u_token = user.password_renew_key.not_nil!
|
||||
|
||||
# Once the user is created and stored, we try to contact him.
|
||||
unless Process.run(mailer_exe,
|
||||
# PARAMETERS
|
||||
[ "send", template_name, u_email ],
|
||||
# ENV
|
||||
{ "HOME" => "/", "LOGIN" => u_login, "TOKEN" => u_token },
|
||||
true # clear environment
|
||||
# input: Process::Redirect::Inherit,
|
||||
# output: Process::Redirect::Inherit,
|
||||
# error: Process::Redirect::Inherit
|
||||
).success?
|
||||
raise "cannot contact user #{u_login} address #{u_email}"
|
||||
end
|
||||
send_recovery_token authd, u_login, u_email, u_token
|
||||
|
||||
Response::PasswordRecoverySent.new
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ class AuthD::Request
|
|||
return Response::ErrorAlreadyUsedLogin.new
|
||||
end
|
||||
|
||||
acceptable_login_regex = "[a-zA-Z][-_ a-zA-Z0-9']+"
|
||||
acceptable_login_regex = "[-_ %ùÙêÊçÇéÉàÀ+a-zA-Z0-9'@.,;&]+"
|
||||
pattern = Regex.new acceptable_login_regex, Regex::Options::IGNORE_CASE
|
||||
return Response::ErrorInvalidLoginFormat.new unless pattern =~ @login
|
||||
|
||||
|
@ -25,24 +25,27 @@ class AuthD::Request
|
|||
return Response::ErrorMailRequired.new
|
||||
end
|
||||
|
||||
if ! @email.nil?
|
||||
if m = @email
|
||||
# Test on the email address format.
|
||||
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
|
||||
result = grok.parse @email.not_nil!
|
||||
result = grok.parse m
|
||||
email = result["email"]?
|
||||
|
||||
return Response::ErrorInvalidEmailFormat.new if email.nil?
|
||||
|
||||
# Verify the email address isn't already in the database.
|
||||
return Response::ErrorEmailAddressAlreadyUsed.new if authd.users_per_email.get? Base64.encode(m).chomp
|
||||
end
|
||||
|
||||
# In this case we should not accept its registration.
|
||||
return Response::ErrorPasswordTooShort.new if @password.size < 20
|
||||
return Response::ErrorPasswordTooShort.new if @password.size < 15
|
||||
return Response::ErrorPasswordTooLong.new if @password.size > 100
|
||||
|
||||
uid = authd.new_uid
|
||||
password = authd.hash_password @password
|
||||
|
||||
user = User.new uid, @login, password
|
||||
user.contact.email = @email unless @email.nil?
|
||||
user.contact.pending_email = @email unless @email.nil?
|
||||
user.contact.new_activation_key
|
||||
|
||||
@profile.try do |profile|
|
||||
|
@ -52,26 +55,12 @@ class AuthD::Request
|
|||
user.date_registration = Time.local
|
||||
|
||||
begin
|
||||
mailer_exe = authd.configuration.mailer_exe
|
||||
template_name = authd.configuration.activation_template
|
||||
|
||||
u_login = user.login
|
||||
u_email = user.contact.email.not_nil!
|
||||
u_activation_key = user.contact.activation_key.not_nil!
|
||||
u_login = user.login
|
||||
u_email = user.contact.pending_email.not_nil!
|
||||
u_activation_token = user.contact.activation_key.not_nil!
|
||||
|
||||
# Once the user is created and stored, we try to contact him.
|
||||
unless Process.run(mailer_exe,
|
||||
# PARAMETERS
|
||||
[ "send", template_name, u_email ],
|
||||
# ENV
|
||||
{ "HOME" => "/", "LOGIN" => u_login, "TOKEN" => u_activation_key },
|
||||
true # clear environment
|
||||
# input: Process::Redirect::Inherit,
|
||||
# output: Process::Redirect::Inherit,
|
||||
# error: Process::Redirect::Inherit
|
||||
).success?
|
||||
raise "cannot contact user #{u_login} address #{u_email}"
|
||||
end
|
||||
send_activation_token authd, u_login, u_email, u_activation_token
|
||||
rescue e
|
||||
Baguette::Log.error "mailer: #{e}"
|
||||
return Response::ErrorCannotContactUser.new
|
||||
|
|
|
@ -21,14 +21,17 @@ class AuthD::Request
|
|||
result = if regex = @regex
|
||||
pattern = Regex.new regex, Regex::Options::IGNORE_CASE
|
||||
users.each do |u|
|
||||
puts "trying to match user #{u.login}"
|
||||
if pattern =~ u.login || u.profile.try do |profile|
|
||||
full_name = profile["full_name"]?
|
||||
puts "login didn't work, trying to match its full name: #{full_name}"
|
||||
if full_name.nil?
|
||||
false
|
||||
else
|
||||
pattern =~ full_name.as_s
|
||||
end
|
||||
end || u.contact.email.try do |email|
|
||||
puts "full name didn't work, trying to match its email: #{email}"
|
||||
pattern =~ email
|
||||
end
|
||||
Baguette::Log.debug "#{u.login} matches #{pattern}"
|
||||
|
|
|
@ -17,13 +17,17 @@ class AuthD::Request
|
|||
end
|
||||
|
||||
# Remove the user contact activation key: the email is validated.
|
||||
if user.contact.activation_key == @activation_key
|
||||
user.contact.activation_key = nil
|
||||
else
|
||||
if user.contact.activation_key != @activation_key
|
||||
return Response::ErrorInvalidActivationKey.new
|
||||
end
|
||||
|
||||
authd.users_per_uid.update user.uid.to_s, user
|
||||
cloned_user = user.clone
|
||||
|
||||
cloned_user.contact.activation_key = nil
|
||||
cloned_user.contact.email = cloned_user.contact.pending_email
|
||||
cloned_user.contact.pending_email = nil
|
||||
|
||||
authd.users_per_uid.update cloned_user
|
||||
|
||||
Response::UserValidated.new user.to_public
|
||||
end
|
||||
|
|
|
@ -102,4 +102,16 @@ class AuthD::Response
|
|||
end
|
||||
end
|
||||
AuthD.responses << ErrorPasswordTooLong
|
||||
|
||||
IPC::JSON.message ErrorEmailAddressNotValidated, 36 do
|
||||
def initialize()
|
||||
end
|
||||
end
|
||||
AuthD.responses << ErrorEmailAddressNotValidated
|
||||
|
||||
IPC::JSON.message ErrorEmailAddressAlreadyUsed, 37 do
|
||||
def initialize()
|
||||
end
|
||||
end
|
||||
AuthD.responses << ErrorEmailAddressAlreadyUsed
|
||||
end
|
||||
|
|
|
@ -2,7 +2,9 @@ class AuthD::Response
|
|||
IPC::JSON.message Login, 1 do
|
||||
property uid : UInt32
|
||||
property token : String
|
||||
def initialize(@token, @uid)
|
||||
property current_email : String? = nil
|
||||
property pending_email : String? = nil
|
||||
def initialize(@token, @uid, @current_email, @pending_email)
|
||||
end
|
||||
end
|
||||
AuthD.responses << Login
|
||||
|
|
|
@ -3,20 +3,7 @@ require "sodium"
|
|||
|
||||
extend AuthD
|
||||
|
||||
class Baguette::Configuration
|
||||
class Auth < IPC
|
||||
property recreate_indexes : Bool = false
|
||||
property storage : String = "storage"
|
||||
property registrations : Bool = false
|
||||
property require_email : Bool = false
|
||||
property activation_template : String = "email-activation"
|
||||
property recovery_template : String = "email-recovery"
|
||||
property mailer_exe : String = "mailer"
|
||||
property read_only_profile_keys : Array(String) = Array(String).new
|
||||
|
||||
property print_password_recovery_parameters : Bool = false
|
||||
end
|
||||
end
|
||||
require "./configuration"
|
||||
|
||||
# Provides a JWT-based authentication scheme for service-specific users.
|
||||
class AuthD::Service < IPC
|
||||
|
@ -57,7 +44,7 @@ class AuthD::Service < IPC
|
|||
end
|
||||
|
||||
self.timer @configuration.ipc_timer
|
||||
self.service_init "auth"
|
||||
self.service_init @configuration.service_name
|
||||
end
|
||||
|
||||
def obsolete_hash_password(password : String) : String
|
||||
|
@ -173,7 +160,7 @@ class AuthD::Service < IPC
|
|||
end
|
||||
|
||||
def run
|
||||
Baguette::Log.title "Starting authd"
|
||||
Baguette::Log.title "Starting #{@configuration.service_name}"
|
||||
|
||||
Baguette::Log.info "(mailer) Email activation template: #{@configuration.activation_template}"
|
||||
Baguette::Log.info "(mailer) Email recovery template: #{@configuration.recovery_template}"
|
||||
|
|
Loading…
Reference in New Issue