module Users class SessionsController < Devise::SessionsController include Devise::Controllers::Rememberable include RecaptchaVerifiable before_action :must_have_current_school layout "session" # GET /users/sign_in?referrer def new store_location_for(:user, params[:referrer]) if params[:referrer].present? if current_user.present? flash[:notice] = t("shared.already_signed_in") redirect_to after_sign_in_path_for(current_user) end @show_checkbox_recaptcha = params[:visible_recaptcha].present? end # POST /user/send_reset_password_email def send_reset_password_email @form = Users::Sessions::SendResetPasswordEmailForm.new(Reform::OpenForm.new) @form.current_school = current_school recaptcha_success = recaptcha_success?(@form, action: "user_password_reset") unless recaptcha_success redirect_to sign_in_with_password_path(visible_recaptcha: 1) return end if @form.validate(params) @form.save redirect_to session_email_sent_path( kind: "reset_password_link", email_address: @form.email ) else flash[:error] = @form.errors.full_messages.join(", ") redirect_to request_password_reset_path end end # GET /user/token?referrer - link to sign_in user with token in params def token user = Users::AuthenticationService.new( current_school, params[:token] ).authenticate store_location_for(:user, params[:referrer]) if params[:referrer].present? if user.present? sign_in user remember_me(user) unless params[:shared_device] == "true" Users::ConfirmationService.new(user).execute user.update!(account_deletion_notification_sent_at: nil) redirect_to after_sign_in_path_for(user) else flash[:error] = t(".link_expired") redirect_to new_user_session_path end end # GET /user/reset_password?token= def reset_password @user = Users::ValidateResetTokenService.new(params[:token]).authenticate if @user.present? @token = params[:token] else flash[:error] = t(".link_used") redirect_to new_user_session_path end end # POST /users/update_password def update_password @form = Users::Sessions::ResetPasswordForm.new(Reform::OpenForm.new) if @form.validate(params) @form.save @form.user.update!(account_deletion_notification_sent_at: nil) sign_in @form.user render json: { error: nil, path: after_sign_in_path_for(current_user) } else render json: { error: @form.errors.full_messages.join(", "), path: nil } end end # POST /user/sign_in def create @form, recaptcha_action = if params[:password_sign_in] [ Users::Sessions::SignInWithPasswordForm.new(Reform::OpenForm.new), "user_password_login" ] elsif params[:email_link] [ Users::Sessions::SignInWithEmailForm.new(Reform::OpenForm.new), "user_magic_link_request" ] end @form&.current_school = current_school recaptcha_success = recaptcha_success?(@form, action: recaptcha_action) unless recaptcha_success if params[:password_sign_in] redirect_to sign_in_with_password_path(visible_recaptcha: 1) else redirect_to new_user_session_path(visible_recaptcha: 1) end return end if params[:password_sign_in] process_password_login elsif params[:email_link] process_link_login else redirect_to new_user_session_path end end # GET /users/sign_in_with_password def sign_in_with_password if current_user.present? flash[:notice] = t("shared.already_signed_in") redirect_to after_sign_in_path_for(current_user) return end @show_checkbox_recaptcha = params[:visible_recaptcha].present? end # POST /users/sign_in_with_otp def sign_in_with_otp @form = Users::Sessions::SignInWithInputTokenForm.new(Reform::OpenForm.new) @form.current_school = current_school recaptcha_success = recaptcha_success?(@form, action: "sign_in_with_otp") unless recaptcha_success redirect_to session_email_sent_path( kind: "magic_link", email_address: params[:email], visible_recaptcha: 1 ) return end if @form.validate(params) @form.save sign_in(@form.user) remember_me(@form.user) unless @form.shared_device? redirect_to after_sign_in_path_for(@form.user) else flash[:error] = @form.errors.full_messages.join(", ") redirect_to( session_email_sent_path( kind: "magic_link", email_address: params[:email], input_tokens_deleted: @form.input_tokens_deleted ) ) end end # GET /users/request_password_reset def request_password_reset if current_user.present? redirect_to edit_user_path return end @show_checkbox_recaptcha = params[:visible_recaptcha].present? end # GET /users/auth_callback?encrypted_token=xxx def auth_callback begin crypt = EncryptorService.new data = crypt.decrypt( Base64.urlsafe_decode64(params[:encrypted_token].presence || "") ) session_id = Base64.urlsafe_decode64(data[:session_id]) # Abort if the session is invalid if session.id.nil? || session_id.to_s != session.id.private_id.to_s flash[:error] = t(".invalid_session") redirect_to new_user_session_path return end # Link discord account to user if the request has discord data if data[:login_token].blank? && current_user.present? && data[:auth_hash]&.dig(:discord)&.dig(:uid).present? if current_school.users.exists?( discord_user_id: data[:auth_hash][:discord][:uid] ) flash[:error] = t(".discord_already_linked") redirect_to edit_user_path return end onboard_user = Discord::AddMemberService.new(current_user).execute( data[:auth_hash][:discord][:uid], data[:auth_hash][:discord][:tag], data[:auth_hash][:discord][:access_token] ) if onboard_user flash[:success] = t(".success") else flash[:error] = t(".discord_link_error") end redirect_to edit_user_path return end user = Users::AuthenticationService.new( current_school, data[:login_token] ).authenticate if user.present? sign_in user remember_me(user) redirect_to after_sign_in_path_for(user) else flash[:error] = t(".error") redirect_to new_user_session_path end rescue ActiveSupport::MessageEncryptor::InvalidMessage flash[:error] = t(".error") redirect_to new_user_session_path end end # GET /users/email_sent?kind=magic_link/reset_password_link def email_sent if current_user.present? flash[:notice] = t("shared.already_signed_in") redirect_to after_sign_in_path_for(current_user) return end @kind = params[:kind] @show_checkbox_recaptcha = params[:visible_recaptcha].present? end private def process_password_login if @form.validate(params) sign_in @form.user @form.user.update!(account_deletion_notification_sent_at: nil) remember_me(@form.user) unless @form.shared_device? redirect_to after_sign_in_path_for(current_user) else flash[:error] = @form.errors.full_messages.join(", ") redirect_to sign_in_with_password_path end end def process_link_login if @form.validate(params.merge(referrer: stored_location_for(:user))) @form.save redirect_to session_email_sent_path( kind: "magic_link", email_address: @form.email ) else flash[:error] = @form.errors.full_messages.join(", ") redirect_to new_user_session_path end end def must_have_current_school raise_not_found if current_school.blank? end end end