classRodamoduleRodaPluginsmoduleRodauthLockout=Feature.define(:lockout)dodepends:loginroute'unlock-account'auth_value_methods(:account_lockouts_id_column,:account_lockouts_deadline_column,:account_lockouts_key_column,:account_lockouts_table,:account_login_failures_id_column,:account_login_failures_number_column,:account_login_failures_table,:max_invalid_logins,:unlock_account_additional_form_tags,:unlock_account_autologin?,:unlock_account_button,:unlock_account_email_subject,:unlock_account_key_param,:unlock_account_notice_flash,:unlock_account_redirect,:unlock_account_request_additional_form_tags,:unlock_account_request_button,:unlock_account_request_notice_flash,:unlock_account_request_redirect,:unlock_account_route)auth_methods(:after_unlock_account,:after_unlock_account_request,:clear_invalid_login_attempts,:create_unlock_account_email,:generate_unlock_account_key,:get_unlock_account_key,:invalid_login_attempted,:locked_out?,:send_unlock_account_email,:unlock_account_request_view,:unlock_account_email_body,:unlock_account_email_link,:unlock_account,:unlock_account_key,:unlock_account_view)get_blockdo|r,auth|ifauth._account_from_unlock_key(r[auth.unlock_account_key_param].to_s)auth.unlock_account_viewelseauth.set_redirect_error_flashauth.no_matching_unlock_account_key_messager.redirectauth.require_login_redirectendendpost_blockdo|r,auth|iflogin=r[auth.login_param]ifauth._account_from_login(login.to_s)auth.transactiondoauth.send_unlock_account_emailauth.after_unlock_account_requestendauth.set_notice_flashauth.unlock_account_request_notice_flashr.redirectauth.unlock_account_request_redirectendelsifkey=r[auth.unlock_account_key_param]ifauth._account_from_unlock_key(key.to_s)auth.unlock_accountauth.after_unlock_accountifauth.unlock_account_autologin?auth.update_sessionendauth.set_notice_flashauth.unlock_account_notice_flashr.redirect(auth.unlock_account_redirect)endendenddefbefore_login_attemptsuperiflocked_out?set_error_flashlogin_error_flashresponse.writeunlock_account_request_viewrequest.haltendenddefafter_loginsuperclear_invalid_login_attemptsenddefafter_login_failuresuperinvalid_login_attemptedenddefafter_unlock_accountenddefafter_unlock_account_requestendaliasunlock_account_routelockout_routedefunlock_account_autologin?falseenddefunlock_account_notice_flash"Your account has been unlocked"enddefunlock_account_redirectdefault_redirectenddefunlock_account_button'Unlock Account'enddefunlock_account_additional_form_tagsenddefunlock_account_request_notice_flash"An email has been sent to you with a link to unlock your account"enddefunlock_account_request_redirectdefault_redirectenddefunlock_account_request_button'Request Account Unlock'enddefunlock_account_request_additional_form_tagsend# This is solely for bruteforce protection, so we allow 100 tries.defmax_invalid_logins100enddefaccount_login_failures_table:account_login_failuresenddefaccount_login_failures_id_column:idenddefaccount_login_failures_number_column:numberenddefaccount_login_failures_datasetdb[account_login_failures_table].where(account_login_failures_id_column=>account_id_value)enddefaccount_lockouts_table:account_lockoutsenddefaccount_lockouts_id_column:idenddefaccount_lockouts_key_column:keyenddefaccount_lockouts_deadline_column:deadlineenddefaccount_lockouts_datasetdb[account_lockouts_table].where(account_lockouts_id_column=>account_id_value)enddeflocked_out?iflockout=account_lockouts_dataset.firstifTime.now<lockout[account_lockouts_deadline_column]trueelseunlock_accountfalseendelsefalseendenddefunlock_accounttransactiondoaccount_login_failures_dataset.deleteaccount_lockouts_dataset.deleteendenddefunlock_account_request_viewview('unlock-account-request','Request Account Unlock')enddefunlock_account_viewview('unlock-account','Unlock Account')enddefno_matching_unlock_account_key_message'No matching unlock account key'enddefclear_invalid_login_attemptsunlock_accountenddefinvalid_login_attemptednumber=account_login_failures_dataset.returning(account_login_failures_number_column).where(account_login_failures_id_column=>account_id_value).with_sql(:update_sql,account_login_failures_number_column=>Sequel.expr(account_login_failures_number_column)+1).single_valueunlessnumberaccount_login_failures_dataset.insert(account_login_failures_id_column=>account_id_value)number=1endifnumber>=max_invalid_logins@unlock_account_key_value=generate_unlock_account_keyaccount_lockouts_dataset.insert(account_lockouts_id_column=>account_id_value,account_lockouts_key_column=>unlock_account_key_value)endenddefget_unlock_account_keyaccount_lockouts_dataset.get(account_lockouts_key_column)enddefgenerate_unlock_account_keyrandom_keyendattr_reader:unlock_account_key_valuedef_account_from_unlock_key(key)@account=account_from_unlock_key(key)enddefaccount_from_unlock_key(key)id,key=key.split('_',2)id_column=account_lockouts_id_columnds=db[account_lockouts_table].select(account_lockouts_id_column).where(account_lockouts_id_column=>id,account_lockouts_key_column=>key)account_model.where(account_id=>ds).firstenddefunlock_account_key_param'key'enddefcreate_unlock_account_emailcreate_email(unlock_account_email_subject,unlock_account_email_body)enddefsend_unlock_account_email@unlock_account_key_value=get_unlock_account_keycreate_unlock_account_email.deliver!enddefunlock_account_email_bodyrender('unlock-account-email')enddefunlock_account_email_link"#{request.base_url}#{prefix}/#{unlock_account_route}?#{unlock_account_key_param}=#{account_id_value}_#{unlock_account_key_value}"enddefunlock_account_email_subject'Unlock Account'enddefafter_close_accountsuperaccount_login_failures_dataset.deleteaccount_lockouts_dataset.deleteendendendendend