# frozen-string-literal: truemoduleRodauthFeature.define(:recovery_codes,:RecoveryCodes)dodepends:two_factor_baseadditional_form_tags'recovery_auth'additional_form_tags'recovery_codes'before'add_recovery_codes'before'view_recovery_codes'before'recovery_auth'after'add_recovery_codes'button'Add Authentication Recovery Codes','add_recovery_codes'button'Authenticate via Recovery Code','recovery_auth'button'View Authentication Recovery Codes','view_recovery_codes'error_flash"Error authenticating via recovery code",'invalid_recovery_code'error_flash"Unable to add recovery codes",'add_recovery_codes'error_flash"Unable to view recovery codes",'view_recovery_codes'notice_flash"Additional authentication recovery codes have been added",'recovery_codes_added'redirect(:recovery_auth){recovery_auth_path}redirect(:add_recovery_codes){recovery_codes_path}loaded_templates%w'add-recovery-codes recovery-auth recovery-codes password-field'view'add-recovery-codes','Authentication Recovery Codes','add_recovery_codes'view'recovery-auth','Enter Authentication Recovery Code','recovery_auth'view'recovery-codes','View Authentication Recovery Codes','recovery_codes'auth_value_method:add_recovery_codes_param,'add'translatable_method:add_recovery_codes_heading,'<h2>Add Additional Recovery Codes</h2>'auth_value_method:auto_add_recovery_codes?,falsetranslatable_method:invalid_recovery_code_message,"Invalid recovery code"auth_value_method:recovery_codes_limit,16auth_value_method:recovery_codes_column,:codeauth_value_method:recovery_codes_id_column,:idtranslatable_method:recovery_codes_label,'Recovery Code'auth_value_method:recovery_codes_param,'recovery-code'auth_value_method:recovery_codes_table,:account_recovery_codestranslatable_method:recovery_auth_link_text,"Authenticate Using Recovery Code"translatable_method:recovery_codes_link_text,"View Authentication Recovery Codes"auth_cached_method:recovery_codesauth_value_methods(:recovery_codes_primary?)auth_methods(:add_recovery_code,:can_add_recovery_codes?,:new_recovery_code,:recovery_code_match?,:recovery_codes)route(:recovery_auth)do|r|require_loginrequire_account_sessionrequire_two_factor_setuprequire_two_factor_not_authenticated('recovery_code')before_recovery_auth_router.getdorecovery_auth_viewendr.postdoifrecovery_code_match?(param(recovery_codes_param))before_recovery_authtwo_factor_authenticate('recovery_code')endset_response_error_status(invalid_key_error_status)set_field_error(recovery_codes_param,invalid_recovery_code_message)set_error_flashinvalid_recovery_code_error_flashrecovery_auth_viewendendroute(:recovery_codes)do|r|require_accountunlessrecovery_codes_primary?require_two_factor_setuprequire_two_factor_authenticatedendbefore_recovery_codes_router.getdorecovery_codes_viewendr.postdoiftwo_factor_password_match?(param(password_param))ifcan_add_recovery_codes?ifparam_or_nil(add_recovery_codes_param)transactiondobefore_add_recovery_codesadd_recovery_codes(recovery_codes_limit-recovery_codes.length)after_add_recovery_codesendset_notice_now_flashrecovery_codes_added_notice_flashendself.recovery_codes_button=add_recovery_codes_buttonendbefore_view_recovery_codesadd_recovery_codes_viewelseifparam_or_nil(add_recovery_codes_param)set_error_flashadd_recovery_codes_error_flashelseset_error_flashview_recovery_codes_error_flashendset_response_error_status(invalid_password_error_status)set_field_error(password_param,invalid_password_message)recovery_codes_viewendendendattr_accessor:recovery_codes_buttondeftwo_factor_removesuperrecovery_codes_removeenddefotp_add_keysuperifdefined?(super)auto_add_missing_recovery_codesenddefsms_confirmsuperifdefined?(super)auto_add_missing_recovery_codesenddefadd_webauthn_credential(_)superifdefined?(super)auto_add_missing_recovery_codesenddefrecovery_codes_removerecovery_codes_ds.deleteenddefrecovery_code_match?(code)recovery_codes.eachdo|s|iftiming_safe_eql?(code,s)recovery_codes_ds.where(recovery_codes_column=>code).deleteifrecovery_codes_primary?add_recovery_codeendreturntrueendendfalseenddefcan_add_recovery_codes?recovery_codes.length<recovery_codes_limitenddefadd_recovery_codes(number)returnifnumber<=0transactiondonumber.timesdoadd_recovery_codeendendremove_instance_variable(:@recovery_codes)enddefadd_recovery_code# This should never raise uniqueness violations unless the recovery code is the same, and the odds of that# are 1/256**32 assuming a good random number generator. Still, attempt to handle that case by retrying# on such a uniqueness violation.retry_on_uniqueness_violationdorecovery_codes_ds.insert(recovery_codes_id_column=>session_value,recovery_codes_column=>new_recovery_code)endenddefpossible_authentication_methodsmethods=supermethods<<'recovery_code'unlessrecovery_codes_ds.empty?methodsendprivatedef_two_factor_auth_linkslinks=superlinks<<[40,recovery_auth_path,recovery_auth_link_text]unlessrecovery_codes_ds.empty?linksenddef_two_factor_setup_linkslinks=superlinks<<[40,recovery_codes_path,recovery_codes_link_text]if(recovery_codes_primary?||uses_two_factor_authentication?)linksenddef_two_factor_remove_all_from_sessiontwo_factor_remove_session('recovery_code')superenddefnew_recovery_coderandom_keyenddefrecovery_codes_primary?(features&[:otp,:sms_codes,:webauthn]).empty?enddefauto_add_missing_recovery_codesifauto_add_recovery_codes?add_recovery_codes(recovery_codes_limit-recovery_codes.length)endenddef_recovery_codesrecovery_codes_ds.select_map(recovery_codes_column)enddefrecovery_codes_dsdb[recovery_codes_table].where(recovery_codes_id_column=>session_value)endendend