require_dependency "cally/application_controller" module Cally class UsersController < ApplicationController require 'omw_random_string' require 'omw_mailgun' before_action :set_user, only: [:show, :edit, :update, :destroy, :toggle_admin] before_action :user_params, only: [:create, :edit, :update, :destroy, :ask_security_question, :reset_password] before_action :is_invitation_only?, only: [:new, :create] before_action :same_user?, only: [:show, :edit, :update] before_action :logged_in_as_admin?, only: [:index, :toggle_admin] before_action :admin_or_same_user?, only: [:destroy] def index @users = User.paginate(page: params[:page], per_page: 20) end # This method shows the signup form if users count is 0. # If this is invitation only then there must be a valid key def new # Checks if it is the first user or valid invitation key is provided # If the website is not invitation only then you can create a new user here if User.count == 0 || @invitation_only != 'true' || (@invitation_only == 'true' && valid_invitation_key?) @user = User.new else flash[:error] = 'This is invitation only, enter your email for asking for invitation.' redirect_to invitation_path end end # This method creates a user if the user.count is 0. # if the user count >= 0 and there's an valid invitation key then you can also create your account # in all other cases this outputs an error saying there's no valid invitation key def create if User.count == 0 || @invitation_only != 'true' || (@invitation_only == 'true' && valid_invitation_key?) @user = User.new(username: user_params[:username], email: user_params[:email], password: user_params[:password], security_question: user_params[:security_question], security_answer: user_params[:security_answer]) # creates a rondow string of 32 characters which will be the user's key @user.user_key = OmwRandomString.generate(32) # If this is the first user created then automatically set him/her as administrator if User.count == 0 @user.admin = true end if @user.save flash[:success] = "User '#{@user.username}' successfully created." session[:user_id] = @user.id redirect_to user_path(@user) # if this creation is because of an invite then remove the invitation from the database if defined? @invitation_key Invitation.find_by(invitation_key: @invitation_key).destroy end else if defined? @invitation_key render 'new', locals: { invitation_key: @invitation_key } else flash[:error] = 'Error creating account' render 'new' end end else flash[:error] = 'This is invitation only' redirect_to invitation_path end end def login if User.count == 0 redirect_to new_user_path end end def show end def edit end def update if @user.update(user_params) flash[:success] = 'Your profile is updated successfully.' redirect_to user_path(@user) else flash[:error] = 'Error updating your profile.' redirect_to edit_user_path(@user) end end def destroy if @user == current_user session[:user_id] = nil end if @user.destroy flash[:success] = 'User successfully deleted.' redirect_to login_path else flash[:error] = 'Error deleting user.' if logged_in? redirect_to user_path(current_user) else redirect_to login_path end end end def toggle_admin # Here you can toggle a user administration rights if it's not the first user. # The first user will always be an administrator. if @user != User.first && @user.update(admin: !@user.admin) flash[:success] = 'Admin status updated successfully.' redirect_to users_path else flash[:error] = 'Error updating admin status.' redirect_to users_path end end def forgot_password end def ask_security_question # When a user want to reset his/her password then he/she have to # answer the security question given by him/her on signup # If the user doesn't exists it throws an error. user = User.find_by(email: user_params[:email]) if user @user_email = user.email @security_question = user.security_question else flash[:error] = "User with email: '#{user_params[:email]}' not found." redirect_to forgot_password_path end end def reset @reset_key = params[:reset_key] @user_key = params[:user_key] end def reset_password user = User.find_by(email: user_params[:email], security_answer: user_params[:security_answer].downcase) if user if retrieable?(user) # when the user haven't used up his/her retry attempts set as an environment variable. # This will create a random string and sends this to the user's email # When the user clicks on this url in the received email he/she will be # able to reset his/her password reset_key = OmwRandomString.generate(32) user.reset_key = reset_key added_retry = add_retry(user) if user.save && added_retry && send_reset_key(reset_key, user) flash[:success] = "Reset key is sent to '#{user.email}'." redirect_to login_path else if @error_sent_message flash[:error] = "error sending reset key with message: '#{@error_sent_message}'." redirect_to forgot_password_path else flash[:error] = 'Error saving reset_key.' redirect_to forgot_password_path end end else flash[:error] = 'You has reached the limit of allowed password resets.' redirect_to forgot_password_path end else flash[:error] = 'The security answer is incorrect.' redirect_to forgot_password_path end end def execute_reset # this is called when resetting the user's password # If successfull it will remove the reset_key in the database # else it will show an error 'No valid reset password key' # which means the reset key is not found in the database @user = User.find_by(user_key: user_params[:user_key], reset_key: user_params[:reset_key]) if @user && (@user.reset_key != nil) && @user.update(user_params) @user.reset_key = nil if @user.save flash[:success] = 'Password successfully changed' session[:user_id] = @user.id redirect_to user_path(@user) else flash[:error] = 'Error deleting reset_key' redirect_to login_path end else flash[:error] = 'No valid reset password key' render 'reset', locals: { user_key: params[:user_key], reset_key: params[:reset_key] } #redirect_to reset_path(user_key: user_params[:user_key], reset_key: user_params[:reset_key]) end end private # get the params for user (create) def user_params if params[:user].present? params.require(:user).permit(:username, :email, :password, :invitation_key, :user_key, :reset_key, :security_question, :security_answer) end end # This checks if the env var is set to true, if not then everyone can signup def is_invitation_only? @invitation_only = ENV['INVITATION_ONLY'] || 'false' end def set_user if (!User.exists?(id: params[:id])) redirect_to login_path else @user = User.find(params[:id]) end end def add_retry(user) # when a user hasn't burned all his/her retries for the day # it will add 1 retry to the database if user.retry_date == Date.today && user.retry < ENV['MAILGUN_RETRIES'].to_i user.update(retry: user.retry+1) user.update(retry_date: Date.current) return true elsif user.retry_date < Date.today user.update(retry: 0) user.update(retry_date: Date.current) return true else return false end end def retrieable?(user) # In this bit of code we will check if a user hasn't burned all # his/her retries for resetting the password. # When he/she burned their retries it will be for this day. if user && (user.retry < ENV['MAILGUN_RETRIES'].to_i || user.retry_date < Date.today) return true else return false end end def send_reset_key(reset_key, user) # Here the reset key is send to the user's email address. # When clicking on the url in the received e-mail the user # will be able to reset his/her password if !test_env? set_mailgun_prefix url = "#{request.base_url}/#{@mailgun_prefix}reset/#{user.user_key}/#{reset_key}" res = Mailgun.post("/#{ENV['MAILGUN_DOMAIN']}/messages", body: { from: "#{ENV['MAILGUN_SENDER_NAME']} <#{ENV['MAILGUN_SENDER_EMAIL']}>", to: "#{user.email}", subject: 'Reset your password', text: "the reset url is: #{url}" }) if res.code == 200 return true else @error_sent_message = res.message return false end elsif test_env? return true else return false end end def valid_invitation_key? # This will check if there's a valid invitation key submitted as a parameter.. if ((params[:invitation_key] && Invitation.exists?(invitation_key: params[:invitation_key])) || (user_params && user_params[:invitation_key] && Invitation.exists?(invitation_key: user_params[:invitation_key]))) @invitation_key = params[:invitation_key] || user_params[:invitation_key] return true else return false end end end end