authnz_eleven 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +384 -0
  5. data/lib/authnz_eleven/identity.rb +210 -0
  6. data/lib/authnz_eleven/principal.rb +201 -0
  7. data/lib/authnz_eleven/principals.rb +111 -0
  8. data/lib/authnz_eleven/version.rb +5 -0
  9. data/lib/authnz_eleven/wizard.rb +570 -0
  10. data/lib/authnz_eleven.rb +15 -0
  11. data/lib/generators/authnz_eleven/USAGE +26 -0
  12. data/lib/generators/authnz_eleven/authnz_eleven_generator.rb +3040 -0
  13. data/lib/generators/authnz_eleven/templates/config/forbidden_usernames.LICENSE +22 -0
  14. data/lib/generators/authnz_eleven/templates/config/forbidden_usernames.txt +558 -0
  15. data/lib/generators/authnz_eleven/templates/config/initializers/authentication.rb.tt +275 -0
  16. data/lib/generators/authnz_eleven/templates/config/initializers/omniauth.rb.tt +9 -0
  17. data/lib/generators/authnz_eleven/templates/config/initializers/team_slug.rb.tt +24 -0
  18. data/lib/generators/authnz_eleven/templates/config/initializers/webauthn.rb.tt +14 -0
  19. data/lib/generators/authnz_eleven/templates/config/locales/authentication.en.yml.tt +11 -0
  20. data/lib/generators/authnz_eleven/templates/controllers/active_teams_controller.rb.tt +6 -0
  21. data/lib/generators/authnz_eleven/templates/controllers/admin/dashboard_controller.rb.tt +4 -0
  22. data/lib/generators/authnz_eleven/templates/controllers/admin/sessions_controller.rb.tt +17 -0
  23. data/lib/generators/authnz_eleven/templates/controllers/admin/users_controller.rb.tt +91 -0
  24. data/lib/generators/authnz_eleven/templates/controllers/admin_controller.rb.tt +6 -0
  25. data/lib/generators/authnz_eleven/templates/controllers/base_controller.rb.tt +11 -0
  26. data/lib/generators/authnz_eleven/templates/controllers/concerns/api_token_authentication.rb.tt +21 -0
  27. data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/easy_dev_login.rb.tt +47 -0
  28. data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/registering.rb.tt +25 -0
  29. data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/sudoable.rb.tt +117 -0
  30. data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/webauthn_ceremony.rb.tt +51 -0
  31. data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication.rb.tt +421 -0
  32. data/lib/generators/authnz_eleven/templates/controllers/concerns/authorization.rb.tt +48 -0
  33. data/lib/generators/authnz_eleven/templates/controllers/concerns/captchable.rb.tt +11 -0
  34. data/lib/generators/authnz_eleven/templates/controllers/impersonations_controller.rb.tt +8 -0
  35. data/lib/generators/authnz_eleven/templates/controllers/invitations_controller.rb.tt +65 -0
  36. data/lib/generators/authnz_eleven/templates/controllers/settings/api_tokens_controller.rb.tt +54 -0
  37. data/lib/generators/authnz_eleven/templates/controllers/settings/authentications/events_controller.rb.tt +5 -0
  38. data/lib/generators/authnz_eleven/templates/controllers/settings/dashboard_controller.rb.tt +4 -0
  39. data/lib/generators/authnz_eleven/templates/controllers/settings/email_verifications_controller.rb.tt +15 -0
  40. data/lib/generators/authnz_eleven/templates/controllers/settings/emails_controller.rb.tt +93 -0
  41. data/lib/generators/authnz_eleven/templates/controllers/settings/invitations_controller.rb.tt +61 -0
  42. data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/authenticators_controller.rb.tt +87 -0
  43. data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/recovery_codes_controller.rb.tt +27 -0
  44. data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/security_keys_controller.rb.tt +86 -0
  45. data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/sms_controller.rb.tt +84 -0
  46. data/lib/generators/authnz_eleven/templates/controllers/settings/omniauth_identities_controller.rb.tt +27 -0
  47. data/lib/generators/authnz_eleven/templates/controllers/settings/passkeys_controller.rb.tt +70 -0
  48. data/lib/generators/authnz_eleven/templates/controllers/settings/passwords_controller.rb.tt +74 -0
  49. data/lib/generators/authnz_eleven/templates/controllers/settings/phone_verifications_controller.rb.tt +39 -0
  50. data/lib/generators/authnz_eleven/templates/controllers/settings/phones_controller.rb.tt +88 -0
  51. data/lib/generators/authnz_eleven/templates/controllers/settings/sessions_controller.rb.tt +29 -0
  52. data/lib/generators/authnz_eleven/templates/controllers/settings/users_controller.rb.tt +42 -0
  53. data/lib/generators/authnz_eleven/templates/controllers/settings_controller.rb.tt +5 -0
  54. data/lib/generators/authnz_eleven/templates/controllers/teams_controller.rb.tt +38 -0
  55. data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments/passkeys_controller.rb.tt +36 -0
  56. data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments/passwords_controller.rb.tt +28 -0
  57. data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments_controller.rb.tt +14 -0
  58. data/lib/generators/authnz_eleven/templates/controllers/users/easy_dev_logins_controller.rb.tt +19 -0
  59. data/lib/generators/authnz_eleven/templates/controllers/users/email_verifications_controller.rb.tt +36 -0
  60. data/lib/generators/authnz_eleven/templates/controllers/users/magic_link_controller.rb.tt +53 -0
  61. data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/recovery_codes_controller.rb.tt +30 -0
  62. data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/security_keys_controller.rb.tt +34 -0
  63. data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/sms_controller.rb.tt +45 -0
  64. data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/totps_controller.rb.tt +42 -0
  65. data/lib/generators/authnz_eleven/templates/controllers/users/omniauth/registrations_controller.rb.tt +26 -0
  66. data/lib/generators/authnz_eleven/templates/controllers/users/omniauth_controller.rb.tt +139 -0
  67. data/lib/generators/authnz_eleven/templates/controllers/users/passkeys/sessions_controller.rb.tt +18 -0
  68. data/lib/generators/authnz_eleven/templates/controllers/users/password_resets_controller.rb.tt +64 -0
  69. data/lib/generators/authnz_eleven/templates/controllers/users/registrations/email_verifications_controller.rb.tt +53 -0
  70. data/lib/generators/authnz_eleven/templates/controllers/users/registrations/phone_verifications_controller.rb.tt +37 -0
  71. data/lib/generators/authnz_eleven/templates/controllers/users/registrations_controller.rb.tt +112 -0
  72. data/lib/generators/authnz_eleven/templates/controllers/users/sessions_controller.rb.tt +52 -0
  73. data/lib/generators/authnz_eleven/templates/controllers/users/sms_sessions_controller.rb.tt +53 -0
  74. data/lib/generators/authnz_eleven/templates/controllers/users/sudos_controller.rb.tt +42 -0
  75. data/lib/generators/authnz_eleven/templates/erb/admin/dashboard/index.html.erb.tt +9 -0
  76. data/lib/generators/authnz_eleven/templates/erb/admin/sessions/index.html.erb.tt +27 -0
  77. data/lib/generators/authnz_eleven/templates/erb/admin/users/index.html.erb.tt +32 -0
  78. data/lib/generators/authnz_eleven/templates/erb/admin/users/show.html.erb.tt +70 -0
  79. data/lib/generators/authnz_eleven/templates/erb/invitations/show.html.erb.tt +9 -0
  80. data/lib/generators/authnz_eleven/templates/erb/settings/api_tokens/create.html.erb.tt +7 -0
  81. data/lib/generators/authnz_eleven/templates/erb/settings/api_tokens/index.html.erb.tt +29 -0
  82. data/lib/generators/authnz_eleven/templates/erb/settings/authentications/events/index.html.erb.tt +18 -0
  83. data/lib/generators/authnz_eleven/templates/erb/settings/dashboard/index.html.erb.tt +75 -0
  84. data/lib/generators/authnz_eleven/templates/erb/settings/emails/edit.html.erb.tt +68 -0
  85. data/lib/generators/authnz_eleven/templates/erb/settings/invitations/new.html.erb.tt +31 -0
  86. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/authenticators/new.html.erb.tt +57 -0
  87. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/recovery_codes/create.html.erb.tt +14 -0
  88. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/recovery_codes/index.html.erb.tt +13 -0
  89. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/edit.html.erb.tt +14 -0
  90. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/index.html.erb.tt +20 -0
  91. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/new.html.erb.tt +17 -0
  92. data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/sms/new.html.erb.tt +35 -0
  93. data/lib/generators/authnz_eleven/templates/erb/settings/omniauth_identities/index.html.erb.tt +28 -0
  94. data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/edit.html.erb.tt +14 -0
  95. data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/index.html.erb.tt +21 -0
  96. data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/new.html.erb.tt +13 -0
  97. data/lib/generators/authnz_eleven/templates/erb/settings/passwords/edit.html.erb.tt +68 -0
  98. data/lib/generators/authnz_eleven/templates/erb/settings/phone_verifications/new.html.erb.tt +22 -0
  99. data/lib/generators/authnz_eleven/templates/erb/settings/phones/edit.html.erb.tt +58 -0
  100. data/lib/generators/authnz_eleven/templates/erb/settings/sessions/index.html.erb.tt +29 -0
  101. data/lib/generators/authnz_eleven/templates/erb/settings/users/show.html.erb.tt +28 -0
  102. data/lib/generators/authnz_eleven/templates/erb/shared/_authnz_eleven_nav.html.erb.tt +24 -0
  103. data/lib/generators/authnz_eleven/templates/erb/shared/_authnz_eleven_sudo_challenge.html.erb.tt +43 -0
  104. data/lib/generators/authnz_eleven/templates/erb/shared/_captcha.html.erb.tt +8 -0
  105. data/lib/generators/authnz_eleven/templates/erb/teams/index.html.erb.tt +34 -0
  106. data/lib/generators/authnz_eleven/templates/erb/teams/new.html.erb.tt +22 -0
  107. data/lib/generators/authnz_eleven/templates/erb/teams/show.html.erb.tt +5 -0
  108. data/lib/generators/authnz_eleven/templates/erb/user_mailer/email_change_verification.html.erb.tt +7 -0
  109. data/lib/generators/authnz_eleven/templates/erb/user_mailer/invitation.html.erb.tt +11 -0
  110. data/lib/generators/authnz_eleven/templates/erb/user_mailer/magic_link.html.erb.tt +5 -0
  111. data/lib/generators/authnz_eleven/templates/erb/user_mailer/password_reset.html.erb.tt +6 -0
  112. data/lib/generators/authnz_eleven/templates/erb/user_mailer/registration_email_verification.html.erb.tt +7 -0
  113. data/lib/generators/authnz_eleven/templates/erb/user_mailer/security_notification.html.erb.tt +15 -0
  114. data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/passkeys/new.html.erb.tt +24 -0
  115. data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/passwords/new.html.erb.tt +21 -0
  116. data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/show.html.erb.tt +21 -0
  117. data/lib/generators/authnz_eleven/templates/erb/users/email_verifications/show.html.erb.tt +9 -0
  118. data/lib/generators/authnz_eleven/templates/erb/users/magic_link/edit.html.erb.tt +9 -0
  119. data/lib/generators/authnz_eleven/templates/erb/users/magic_link/new.html.erb.tt +22 -0
  120. data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/recovery_codes/new.html.erb.tt +20 -0
  121. data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/security_keys/new.html.erb.tt +17 -0
  122. data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/sms/new.html.erb.tt +25 -0
  123. data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/totps/new.html.erb.tt +21 -0
  124. data/lib/generators/authnz_eleven/templates/erb/users/omniauth/registrations/new.html.erb.tt +28 -0
  125. data/lib/generators/authnz_eleven/templates/erb/users/password_resets/edit.html.erb.tt +26 -0
  126. data/lib/generators/authnz_eleven/templates/erb/users/password_resets/new.html.erb.tt +20 -0
  127. data/lib/generators/authnz_eleven/templates/erb/users/registrations/email_verifications/new.html.erb.tt +11 -0
  128. data/lib/generators/authnz_eleven/templates/erb/users/registrations/email_verifications/show.html.erb.tt +34 -0
  129. data/lib/generators/authnz_eleven/templates/erb/users/registrations/new.html.erb.tt +87 -0
  130. data/lib/generators/authnz_eleven/templates/erb/users/registrations/phone_verifications/new.html.erb.tt +46 -0
  131. data/lib/generators/authnz_eleven/templates/erb/users/sessions/new.html.erb.tt +88 -0
  132. data/lib/generators/authnz_eleven/templates/erb/users/sms_sessions/edit.html.erb.tt +30 -0
  133. data/lib/generators/authnz_eleven/templates/erb/users/sms_sessions/new.html.erb.tt +22 -0
  134. data/lib/generators/authnz_eleven/templates/erb/users/sudos/new.html.erb.tt +9 -0
  135. data/lib/generators/authnz_eleven/templates/javascript/controllers/webauthn_controller.js.tt +142 -0
  136. data/lib/generators/authnz_eleven/templates/jobs/authnz_eleven_deferred_job.rb.tt +9 -0
  137. data/lib/generators/authnz_eleven/templates/jobs/authnz_eleven_mail_delivery_job.rb.tt +3 -0
  138. data/lib/generators/authnz_eleven/templates/jobs/sms_delivery_job.rb.tt +11 -0
  139. data/lib/generators/authnz_eleven/templates/lib/captcha/null.rb.tt +9 -0
  140. data/lib/generators/authnz_eleven/templates/lib/captcha/turnstile.rb.tt +39 -0
  141. data/lib/generators/authnz_eleven/templates/lib/captcha.rb.tt +15 -0
  142. data/lib/generators/authnz_eleven/templates/lib/sms/log.rb.tt +8 -0
  143. data/lib/generators/authnz_eleven/templates/lib/sms/twilio.rb.tt +70 -0
  144. data/lib/generators/authnz_eleven/templates/lib/sms.rb.tt +15 -0
  145. data/lib/generators/authnz_eleven/templates/mailers/user_mailer.rb.tt +98 -0
  146. data/lib/generators/authnz_eleven/templates/migrations/add_guest_to_users_migration.rb.tt +6 -0
  147. data/lib/generators/authnz_eleven/templates/migrations/create_api_tokens_migration.rb.tt +14 -0
  148. data/lib/generators/authnz_eleven/templates/migrations/create_events_migration.rb.tt +26 -0
  149. data/lib/generators/authnz_eleven/templates/migrations/create_invitations_migration.rb.tt +20 -0
  150. data/lib/generators/authnz_eleven/templates/migrations/create_memberships_migration.rb.tt +12 -0
  151. data/lib/generators/authnz_eleven/templates/migrations/create_omniauth_identities_migration.rb.tt +16 -0
  152. data/lib/generators/authnz_eleven/templates/migrations/create_password_histories_migration.rb.tt +11 -0
  153. data/lib/generators/authnz_eleven/templates/migrations/create_pending_registrations_migration.rb.tt +31 -0
  154. data/lib/generators/authnz_eleven/templates/migrations/create_recovery_codes_migration.rb.tt +11 -0
  155. data/lib/generators/authnz_eleven/templates/migrations/create_sessions_migration.rb.tt +19 -0
  156. data/lib/generators/authnz_eleven/templates/migrations/create_teams_migration.rb.tt +9 -0
  157. data/lib/generators/authnz_eleven/templates/migrations/create_users_migration.rb.tt +70 -0
  158. data/lib/generators/authnz_eleven/templates/migrations/create_webauthn_credentials_migration.rb.tt +16 -0
  159. data/lib/generators/authnz_eleven/templates/models/api_token.rb.tt +24 -0
  160. data/lib/generators/authnz_eleven/templates/models/concerns/adminable.rb.tt +15 -0
  161. data/lib/generators/authnz_eleven/templates/models/concerns/authenticatable.rb.tt +151 -0
  162. data/lib/generators/authnz_eleven/templates/models/concerns/bannable.rb.tt +24 -0
  163. data/lib/generators/authnz_eleven/templates/models/concerns/deadboltable.rb.tt +39 -0
  164. data/lib/generators/authnz_eleven/templates/models/concerns/email_verifiable.rb.tt +52 -0
  165. data/lib/generators/authnz_eleven/templates/models/concerns/eventable.rb.tt +22 -0
  166. data/lib/generators/authnz_eleven/templates/models/concerns/guestable.rb.tt +30 -0
  167. data/lib/generators/authnz_eleven/templates/models/concerns/invitable.rb.tt +12 -0
  168. data/lib/generators/authnz_eleven/templates/models/concerns/magic_linkable.rb.tt +34 -0
  169. data/lib/generators/authnz_eleven/templates/models/concerns/password_historical.rb.tt +28 -0
  170. data/lib/generators/authnz_eleven/templates/models/concerns/password_policy.rb.tt +38 -0
  171. data/lib/generators/authnz_eleven/templates/models/concerns/password_rotatable.rb.tt +15 -0
  172. data/lib/generators/authnz_eleven/templates/models/concerns/phone_verifiable.rb.tt +50 -0
  173. data/lib/generators/authnz_eleven/templates/models/concerns/principals.rb.tt +67 -0
  174. data/lib/generators/authnz_eleven/templates/models/concerns/recoverable.rb.tt +19 -0
  175. data/lib/generators/authnz_eleven/templates/models/concerns/second_factor.rb.tt +52 -0
  176. data/lib/generators/authnz_eleven/templates/models/concerns/sms_challengeable.rb.tt +38 -0
  177. data/lib/generators/authnz_eleven/templates/models/concerns/team_member.rb.tt +20 -0
  178. data/lib/generators/authnz_eleven/templates/models/current.rb.tt +28 -0
  179. data/lib/generators/authnz_eleven/templates/models/event.rb.tt +32 -0
  180. data/lib/generators/authnz_eleven/templates/models/invitation.rb.tt +122 -0
  181. data/lib/generators/authnz_eleven/templates/models/membership.rb.tt +16 -0
  182. data/lib/generators/authnz_eleven/templates/models/omniauth_identity.rb.tt +12 -0
  183. data/lib/generators/authnz_eleven/templates/models/password_history.rb.tt +3 -0
  184. data/lib/generators/authnz_eleven/templates/models/pending_registration.rb.tt +216 -0
  185. data/lib/generators/authnz_eleven/templates/models/recovery_code.rb.tt +41 -0
  186. data/lib/generators/authnz_eleven/templates/models/session.rb.tt +46 -0
  187. data/lib/generators/authnz_eleven/templates/models/team.rb.tt +12 -0
  188. data/lib/generators/authnz_eleven/templates/models/user.rb.tt +59 -0
  189. data/lib/generators/authnz_eleven/templates/models/webauthn_credential.rb.tt +12 -0
  190. data/lib/generators/authnz_eleven/templates/tasks/authnz_eleven.rake.tt +41 -0
  191. data/lib/generators/authnz_eleven/templates/test_unit/controllers/account_existence_parity_test.rb.tt +158 -0
  192. data/lib/generators/authnz_eleven/templates/test_unit/controllers/admin/access_test.rb.tt +139 -0
  193. data/lib/generators/authnz_eleven/templates/test_unit/controllers/admin/multi_factor_authentication_test.rb.tt +65 -0
  194. data/lib/generators/authnz_eleven/templates/test_unit/controllers/api_token_authentication_test.rb.tt +45 -0
  195. data/lib/generators/authnz_eleven/templates/test_unit/controllers/credential_enrollment_test.rb.tt +84 -0
  196. data/lib/generators/authnz_eleven/templates/test_unit/controllers/guest_access_test.rb.tt +113 -0
  197. data/lib/generators/authnz_eleven/templates/test_unit/controllers/invitations_controller_test.rb.tt +230 -0
  198. data/lib/generators/authnz_eleven/templates/test_unit/controllers/passkey_ceremony_test.rb.tt +126 -0
  199. data/lib/generators/authnz_eleven/templates/test_unit/controllers/password_rotation_test.rb.tt +67 -0
  200. data/lib/generators/authnz_eleven/templates/test_unit/controllers/security_key_ceremony_test.rb.tt +218 -0
  201. data/lib/generators/authnz_eleven/templates/test_unit/controllers/security_notifications_test.rb.tt +39 -0
  202. data/lib/generators/authnz_eleven/templates/test_unit/controllers/session_lifecycle_test.rb.tt +102 -0
  203. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/api_tokens_controller_test.rb.tt +61 -0
  204. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/authentications/events_controller_test.rb.tt +17 -0
  205. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/email_verifications_controller_test.rb.tt +39 -0
  206. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/invitations_controller_test.rb.tt +157 -0
  207. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/management_test.rb.tt +256 -0
  208. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/multi_factor_authentication/management_test.rb.tt +222 -0
  209. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/omniauth_identities_controller_test.rb.tt +57 -0
  210. data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/phone_verifications_controller_test.rb.tt +146 -0
  211. data/lib/generators/authnz_eleven/templates/test_unit/controllers/teams_controller_test.rb.tt +83 -0
  212. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/email_verifications_controller_test.rb.tt +74 -0
  213. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/magic_link_controller_test.rb.tt +90 -0
  214. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/multi_factor_authentication/challenge_test.rb.tt +143 -0
  215. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/omniauth/registrations_controller_test.rb.tt +138 -0
  216. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/omniauth_controller_test.rb.tt +97 -0
  217. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/password_resets_controller_test.rb.tt +58 -0
  218. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations/email_verifications_controller_test.rb.tt +102 -0
  219. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations/phone_verifications_controller_test.rb.tt +69 -0
  220. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations_controller_test.rb.tt +180 -0
  221. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sessions_controller_test.rb.tt +124 -0
  222. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sms_sessions_controller_test.rb.tt +111 -0
  223. data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sudos_controller_test.rb.tt +110 -0
  224. data/lib/generators/authnz_eleven/templates/test_unit/fixtures/memberships.yml.tt +13 -0
  225. data/lib/generators/authnz_eleven/templates/test_unit/fixtures/teams.yml.tt +2 -0
  226. data/lib/generators/authnz_eleven/templates/test_unit/fixtures/users.yml.tt +68 -0
  227. data/lib/generators/authnz_eleven/templates/test_unit/mailers/user_mailer_test.rb.tt +88 -0
  228. data/lib/generators/authnz_eleven/templates/test_unit/models/api_token_test.rb.tt +38 -0
  229. data/lib/generators/authnz_eleven/templates/test_unit/models/password_history_test.rb.tt +51 -0
  230. data/lib/generators/authnz_eleven/templates/test_unit/models/session_test.rb.tt +16 -0
  231. data/lib/generators/authnz_eleven/templates/test_unit/models/sms_challengeable_test.rb.tt +108 -0
  232. data/lib/generators/authnz_eleven/templates/test_unit/models/user/guestable_test.rb.tt +78 -0
  233. data/lib/generators/authnz_eleven/templates/test_unit/models/user/recovery_codes_test.rb.tt +32 -0
  234. data/lib/generators/authnz_eleven/templates/test_unit/models/user/sign_in_methods_test.rb.tt +56 -0
  235. data/lib/generators/authnz_eleven/templates/test_unit/models/user_test.rb.tt +145 -0
  236. data/lib/generators/authnz_eleven/wizard_generator.rb +52 -0
  237. metadata +309 -0
@@ -0,0 +1,65 @@
1
+ class <%= identity.namespaced? ? "#{identity.module_name}::InvitationsController" : "InvitationsController" %> < <%= identity.controller_superclass %>
2
+ allow_unauthenticated_access only: :show
3
+ <%- if teams? -%>
4
+ skip_before_action :require_team
5
+ <%- end -%>
6
+
7
+ before_action :set_invitation
8
+
9
+ <%- if teams? -%>
10
+ def show
11
+ return if authenticated?
12
+
13
+ if invited_account_exists?
14
+ remember_interrupted_destination
15
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "Sign in to accept your invitation."
16
+ else
17
+ session[:<%= session_key(:pending_invitation_token) %>] = params.expect(:token)
18
+ redirect_to <%= registration_entry_path %>, notice: "Create an account to accept your invitation."
19
+ end
20
+ end
21
+
22
+ def update
23
+ unless @invitation.accept(<%= identity.current_class %>.user)
24
+ return redirect_to <%= identity.nested_helper("root_path") %>, alert: "That invitation was sent to a different <%= multi_channel_invitable? ? "email address or phone number" : (email_invitable? ? "email address" : "phone number") %>."
25
+ end
26
+
27
+ team = @invitation.team
28
+ <%- if session_carries_team? -%>
29
+ set_session_team!(team) if team
30
+ redirect_to <%= identity.nested_helper("root_path") %>, notice: team && "You've joined #{team.name}.", status: :see_other
31
+ <%- elsif scope_teams? -%>
32
+ redirect_to team ? <%= team_root_helper %>(team_id: team) : <%= identity.nested_helper("root_path") %>,
33
+ notice: team && "You've joined #{team.name}.", status: :see_other
34
+ <%- else -%>
35
+ redirect_to root_url(script_name: team && "/#{team.id}"),
36
+ notice: team && "You've joined #{team.name}.", status: :see_other
37
+ <%- end -%>
38
+ end
39
+ <%- else -%>
40
+ def show
41
+ if authenticated?
42
+ redirect_to <%= identity.namespaced? ? "#{identity.route_scope}_root_path" : "root_path" %>, notice: "You already have an account."
43
+ else
44
+ session[:<%= session_key(:pending_invitation_token) %>] = params.expect(:token)
45
+ redirect_to <%= registration_entry_path %>, notice: "Create an account to accept your invitation."
46
+ end
47
+ end
48
+ <%- end -%>
49
+
50
+ private
51
+
52
+ def set_invitation
53
+ @invitation = <%= identity.invitation_class %>.find_by_token_for(:invitation, params.expect(:token))
54
+
55
+ unless @invitation
56
+ redirect_to <%= identity.namespaced? ? "#{identity.route_scope}_root_path" : "root_path" %>, alert: "That invitation is invalid or has expired."
57
+ end
58
+ end
59
+ <%- if teams? -%>
60
+
61
+ def invited_account_exists?
62
+ <%= identity.class_name %>.exists?(@invitation.sent_to_channel => @invitation.sent_to)
63
+ end
64
+ <%- end -%>
65
+ end
@@ -0,0 +1,54 @@
1
+ class <%= identity.settings_module %>::ApiTokensController < <%= identity.settings_controller %>
2
+ <%- if sudoable? -%>
3
+ require_sudo message: "Please confirm it's you before creating an API token.", only: :create
4
+ <%- end -%>
5
+ <%- if impersonatable? -%>
6
+ before_action :refuse_impersonation
7
+ <%- end -%>
8
+ <%- if guestable? -%>
9
+ before_action :refuse_guest
10
+ <%- end -%>
11
+ <%- if sudoable? || impersonatable? || guestable? -%>
12
+
13
+ <%- end -%>
14
+ def index
15
+ @api_tokens = <%= identity.current_class %>.user.api_tokens.order(created_at: :desc)
16
+ end
17
+
18
+ def create
19
+ @api_token, @secret = <%= identity.api_token_class %>.issue!(user: <%= identity.current_class %>.user, name: params[:name].to_s.strip)
20
+ <%- if security_notifications? -%>
21
+ <%= identity.mailer_class %>.send_security_notification(:api_token_created)
22
+ <%- end -%>
23
+ response.headers["Cache-Control"] = "no-store"
24
+ render :create, status: :created
25
+ rescue ActiveRecord::RecordInvalid => error
26
+ @api_tokens = <%= identity.current_class %>.user.api_tokens.order(created_at: :desc)
27
+ @error = error.record.errors.full_messages.to_sentence
28
+ render :index, status: :unprocessable_entity
29
+ end
30
+
31
+ def destroy
32
+ <%= identity.current_class %>.user.api_tokens.find(params[:id]).destroy!
33
+ <%- if security_notifications? -%>
34
+ <%= identity.mailer_class %>.send_security_notification(:api_token_revoked)
35
+ <%- end -%>
36
+ redirect_to <%= identity.settings_helper_prefix %>_api_tokens_path, notice: "API token revoked.", status: :see_other
37
+ end
38
+ <%- if impersonatable? || guestable? -%>
39
+
40
+ private
41
+ <%- if impersonatable? -%>
42
+
43
+ def refuse_impersonation
44
+ head :forbidden if <%= identity.current_class %>.impersonated_user
45
+ end
46
+ <%- end -%>
47
+ <%- if guestable? -%>
48
+
49
+ def refuse_guest
50
+ head :forbidden if <%= identity.current_class %>.user.guest?
51
+ end
52
+ <%- end -%>
53
+ <%- end -%>
54
+ end
@@ -0,0 +1,5 @@
1
+ class <%= identity.settings_module %>::Authentications::EventsController < <%= identity.settings_controller %>
2
+ def index
3
+ @events = <%= identity.current_class %>.user.events.recent_first
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ class <%= identity.settings_module %>::DashboardController < <%= identity.settings_controller %>
2
+ def index
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ class <%= identity.settings_module %>::EmailVerificationsController < <%= identity.settings_controller %>
2
+ rate_limit(**<%= identity.config_reference %>.rate_limits.email_verification, only: :create,
3
+ with: -> { redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, alert: "Too many links requested. Please try again later." })
4
+
5
+ def create
6
+ <%- unless coy? -%>
7
+ unless <%= identity.current_class %>.user.pending_email
8
+ return redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, alert: "You have no email confirmation waiting."
9
+ end
10
+
11
+ <%- end -%>
12
+ <%= deferred_call("#{identity.current_class}.user", :send_email_change_verification) %>
13
+ redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, notice: "We sent you another link."
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ class <%= identity.settings_module %>::EmailsController < <%= identity.settings_controller %>
2
+ <%- if sudoable? -%>
3
+ require_sudo message: "Please confirm it's you before changing your email address.", only: :update
4
+ <%- end -%>
5
+ <%- if email_verifiable? -%>
6
+ rate_limit(**<%= identity.config_reference %>.rate_limits.email_verification, only: :update,
7
+ with: -> { redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, alert: "Too many links requested. Please try again later.", status: :see_other })
8
+ <%- end -%>
9
+ <%- if password? && !sudoable? -%>
10
+ rate_limit(**<%= identity.config_reference %>.rate_limits.password_challenge, only: :update, name: "password_challenge",
11
+ with: -> { redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, alert: "Too many attempts. Please try again later.", status: :see_other })
12
+ <%- end -%>
13
+ before_action :set_user
14
+
15
+ def edit
16
+ end
17
+ <%- if email_verifiable? -%>
18
+
19
+ def update
20
+ <%- if security_notifications? -%>
21
+ old_email = @user.email
22
+ <%- end -%>
23
+ <%- if password? && !sudoable? -%>
24
+ @user.assign_attributes(user_params.except(:email))
25
+ <%- end -%>
26
+ @user.stage_email(user_params[:email])
27
+ return render :edit, status: :unprocessable_entity unless @user.save
28
+ unless @user.pending_email
29
+ <%- if security_notifications? -%>
30
+ <%= identity.mailer_class %>.send_security_notification(:email_changed, to: old_email) if @user.email_previously_changed?
31
+ <%- end -%>
32
+ return redirect_to_settled
33
+ end
34
+
35
+ <%= deferred_call("@user", :send_email_change_verification) %>
36
+ redirect_to_edit("Check your email for a link to confirm your new address.")
37
+ end
38
+ <%- else -%>
39
+
40
+ def update
41
+ <%- if security_notifications? -%>
42
+ old_email = @user.email
43
+ <%- end -%>
44
+ if @user.update(user_params)
45
+ <%- if security_notifications? -%>
46
+ <%= identity.mailer_class %>.send_security_notification(:email_changed, to: old_email) if @user.email_previously_changed?
47
+ <%- end -%>
48
+ redirect_to_settled
49
+ else
50
+ render :edit, status: :unprocessable_entity
51
+ end
52
+ rescue ActiveRecord::RecordNotUnique
53
+ respond_to_taken_email
54
+ end
55
+ <%- end -%>
56
+
57
+ private
58
+ <%- unless email_verifiable? -%>
59
+
60
+ def respond_to_taken_email
61
+ @user.errors.add(:email, :taken)
62
+ render :edit, status: :unprocessable_entity
63
+ end
64
+ <%- end -%>
65
+
66
+ def set_user
67
+ @user = <%= identity.current_class %>.user
68
+ end
69
+
70
+ def user_params
71
+ <%- if password? && !sudoable? -%>
72
+ params.permit(:email, :password_challenge).with_defaults(password_challenge: "")
73
+ <%- else -%>
74
+ params.permit(:email)
75
+ <%- end -%>
76
+ end
77
+
78
+ def redirect_to_settled
79
+ <%- if principals.email.optional? -%>
80
+ return redirect_to_edit("Your email has been removed.") if @user.email.blank?
81
+
82
+ <%- end -%>
83
+ <%- unless email_verifiable? -%>
84
+ return redirect_to_edit("Your email has been changed.") if @user.email_previously_changed?
85
+
86
+ <%- end -%>
87
+ redirect_to_edit("That's already your email address.")
88
+ end
89
+
90
+ def redirect_to_edit(notice)
91
+ redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, notice: notice, status: :see_other
92
+ end
93
+ end
@@ -0,0 +1,61 @@
1
+ class <%= identity.settings_module %>::InvitationsController < <%= identity.settings_controller %>
2
+ rate_limit(**<%= identity.config_reference %>.rate_limits.invitation, only: :create,
3
+ with: -> { redirect_to new_<%= identity.settings_helper_prefix %>_invitation_path, alert: "Too many invitations. Please try again later." })
4
+
5
+ def new
6
+ @invitation = <%= identity.invitation_class %>.new
7
+ <%- if teams? -%>
8
+ @teams = <%= identity.current_class %>.user.teams
9
+ <%- end -%>
10
+ end
11
+
12
+ def create
13
+ @invitation = <%= identity.current_class %>.user.sent_invitations.new(invitation_params)
14
+ <%- if teams? -%>
15
+ @invitation.team = invited_team
16
+ <%- end -%>
17
+
18
+ unless @invitation.save
19
+ <%- if teams? -%>
20
+ @teams = <%= identity.current_class %>.user.teams
21
+ <%- end -%>
22
+ return render :new, status: :unprocessable_entity
23
+ end
24
+
25
+ <%- if coy? -%>
26
+ <%= deferred_call("@invitation", :offer_unless_redundant) %>
27
+
28
+ redirect_to new_<%= identity.settings_helper_prefix %>_invitation_path, notice: "Invitation sent to #{@invitation.sent_to}."
29
+ <%- elsif teams? -%>
30
+ @invitation.offer
31
+ if invited_account_exists?
32
+ redirect_to new_<%= identity.settings_helper_prefix %>_invitation_path, notice: "Invitation sent to #{@invitation.sent_to}."
33
+ else
34
+ redirect_to new_<%= identity.settings_helper_prefix %>_invitation_path,
35
+ notice: "Invitation sent to #{@invitation.sent_to}. They'll join when they confirm their #{@invitation.email_addressed? ? "email" : "phone"}."
36
+ end
37
+ <%- else -%>
38
+ @invitation.offer
39
+ redirect_to new_<%= identity.settings_helper_prefix %>_invitation_path,
40
+ notice: "Invitation sent to #{@invitation.sent_to}. They'll join when they confirm their #{@invitation.email_addressed? ? "email" : "phone"}."
41
+ <%- end -%>
42
+ end
43
+
44
+ private
45
+ <%- if teams? -%>
46
+
47
+ def invited_team
48
+ <%= identity.current_class %>.user.teams.find(params[:team_id]) if params[:team_id].present?
49
+ end
50
+ <%- end -%>
51
+ <%- if teams? && !coy? -%>
52
+
53
+ def invited_account_exists?
54
+ <%= identity.class_name %>.exists?(@invitation.sent_to_channel => @invitation.sent_to)
55
+ end
56
+ <%- end -%>
57
+
58
+ def invitation_params
59
+ params.permit(:sent_to)
60
+ end
61
+ end
@@ -0,0 +1,87 @@
1
+ class <%= identity.settings_module %>::MultiFactorAuthentication::AuthenticatorsController < <%= identity.settings_controller %>
2
+ <%- if admin_mfa_gate? -%>
3
+ skip_impediments
4
+ <%- end -%>
5
+ before_action :no_store, only: %i[new create]
6
+ before_action :set_user
7
+ before_action :set_totp, only: %i[new create]
8
+
9
+ def new
10
+ @provisioning_uri = @totp.provisioning_uri(@user.<%= principals.display.column %>)
11
+ @qr_svg = RQRCode::QRCode.new(@provisioning_uri).as_svg(
12
+ module_size: 4,
13
+ standalone: true,
14
+ use_path: true
15
+ )
16
+ end
17
+
18
+ def create
19
+ return just_check_code if @user.totp_enrolled_at? # Do not regenerate recovery codes if just checking code.
20
+
21
+ unless @totp.verify(params[:code], drift_behind: <%= identity.config_reference %>.second_factor.totp_drift_behind)
22
+ return redirect_to new_<%= identity.settings_helper_prefix %>_mfa_authenticator_path, alert: "That code is incorrect."
23
+ end
24
+
25
+ should_mint = @user.recovery_codes.none?
26
+
27
+ @user.transaction do
28
+ @user.update!(totp_enrolled_at: Time.current)
29
+ @codes = <%= identity.recovery_code_class %>.regenerate_for(@user) if should_mint
30
+ <%- if trackable? -%>
31
+ @user.record_event("<%= identity.singular %>.two_factor_enabled")
32
+ <%- end -%>
33
+ end
34
+ <%- if security_notifications? -%>
35
+ <%= identity.mailer_class %>.send_security_notification(:authenticator_enabled)
36
+ <%- end -%>
37
+
38
+ if should_mint
39
+ flash.delete(:alert)
40
+ flash.now[:notice] = "Two-factor authentication is on."
41
+ render "<%= identity.settings_path_prefix %>/multi_factor_authentication/recovery_codes/create"
42
+ else
43
+ redirect_to interrupted_destination(
44
+ otherwise: new_<%= identity.settings_helper_prefix %>_mfa_authenticator_path
45
+ ), notice: "Two-factor authentication is on. Your existing recovery codes still work.",
46
+ status: :see_other
47
+ end
48
+ end
49
+
50
+ def show
51
+ redirect_to <%= identity.settings_helper_prefix %>_mfa_recovery_codes_path
52
+ end
53
+
54
+ def destroy
55
+ @user.transaction do
56
+ @user.update!(totp_enrolled_at: nil, totp_secret: ROTP::Base32.random)
57
+ @user.discard_orphaned_recovery_codes!
58
+ <%- if trackable? -%>
59
+ @user.record_event("<%= identity.singular %>.two_factor_disabled")
60
+ <%- end -%>
61
+ end
62
+ <%- if security_notifications? -%>
63
+ <%= identity.mailer_class %>.send_security_notification(:authenticator_disabled)
64
+ <%- end -%>
65
+
66
+ redirect_to <%= identity.settings_helper_prefix %>_path, notice: "Two-factor authentication is off.", status: :see_other
67
+ end
68
+
69
+ private
70
+
71
+ def just_check_code
72
+ if @totp.verify(params[:code], drift_behind: <%= identity.config_reference %>.second_factor.totp_drift_behind)
73
+ redirect_to new_<%= identity.settings_helper_prefix %>_mfa_authenticator_path, notice: "That code is correct."
74
+ else
75
+ redirect_to new_<%= identity.settings_helper_prefix %>_mfa_authenticator_path,
76
+ alert: "That code didn't match. Check you scanned the whole QR code, then try again."
77
+ end
78
+ end
79
+
80
+ def set_user
81
+ @user = <%= identity.current_class %>.user
82
+ end
83
+
84
+ def set_totp
85
+ @totp = ROTP::TOTP.new(@user.totp_secret, issuer: <%= identity.config_reference %>.app_name)
86
+ end
87
+ end
@@ -0,0 +1,27 @@
1
+ class <%= identity.settings_module %>::MultiFactorAuthentication::RecoveryCodesController < <%= identity.settings_controller %>
2
+ before_action :no_store, only: :create
3
+ before_action :set_user
4
+
5
+ def index
6
+ @remaining = @user.recovery_codes.count
7
+ end
8
+
9
+ def create
10
+ @codes = <%= identity.recovery_code_class %>.regenerate_for(@user)
11
+ <%- if security_notifications? -%>
12
+ <%= identity.mailer_class %>.send_security_notification(:recovery_codes_generated)
13
+ <%- end -%>
14
+ end
15
+
16
+ def complete
17
+ redirect_to interrupted_destination(
18
+ otherwise: <%= identity.settings_helper_prefix %>_mfa_recovery_codes_path
19
+ ), status: :see_other
20
+ end
21
+
22
+ private
23
+
24
+ def set_user
25
+ @user = <%= identity.current_class %>.user
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ class <%= identity.settings_module %>::MultiFactorAuthentication::SecurityKeysController < <%= identity.settings_controller %>
2
+ <%- if sudoable? -%>
3
+ require_sudo_within <%= identity.config_reference %>.sudo.window,
4
+ message: "Please confirm it's you before adding a security key.", only: :new
5
+ <%- end -%>
6
+ <%- if admin_mfa_gate? -%>
7
+ skip_impediments
8
+ <%- end -%>
9
+ before_action :set_user
10
+ before_action :set_security_key, only: %i[edit update destroy]
11
+
12
+ def index
13
+ @security_keys = <%= second_factor_keys_expr("@user") %>
14
+ end
15
+
16
+ def new
17
+ respond_to do |format|
18
+ format.html
19
+ format.json do
20
+ issue_webauthn_challenge(WebAuthn::Credential.options_for_create(
21
+ user: { id: @user.webauthn_handle, name: @user.<%= principals.display.column %> },
22
+ exclude: @user.webauthn_credentials.pluck(:external_id)
23
+ ))
24
+ end
25
+ end
26
+ end
27
+
28
+ def edit
29
+ end
30
+
31
+ def create
32
+ @user.transaction do
33
+ @security_key = @user.webauthn_credentials.create!(
34
+ **verify_webauthn_registration(JSON.parse(params[:credential].to_s), user_verification: false),
35
+ name: "Security key"<%= factor_attr(:second_factor) %>
36
+ )
37
+ @codes = <%= identity.recovery_code_class %>.regenerate_for(@user) if @user.recovery_codes.none?
38
+ end
39
+ <%- if security_notifications? -%>
40
+ <%= identity.mailer_class %>.send_security_notification(:security_key_added)
41
+ <%- end -%>
42
+
43
+ if @codes
44
+ flash.delete(:alert)
45
+ flash.now[:notice] = "Your security key has been added. Save your recovery codes."
46
+ render "<%= identity.settings_path_prefix %>/multi_factor_authentication/recovery_codes/create"
47
+ else
48
+ redirect_to interrupted_destination(
49
+ otherwise: edit_<%= identity.settings_helper_prefix %>_mfa_security_key_path(@security_key)
50
+ ), notice: "Your security key has been added. Your existing recovery codes still work.",
51
+ status: :see_other
52
+ end
53
+ rescue WebAuthn::Error, JSON::ParserError
54
+ flash.now[:alert] = "We couldn't add that security key. Please try again."
55
+ render :new, status: :unprocessable_entity
56
+ end
57
+
58
+ def update
59
+ @security_key.update!(name: params[:name])
60
+ redirect_to <%= identity.settings_helper_prefix %>_mfa_security_keys_path, notice: "Renamed your security key.",
61
+ status: :see_other
62
+ end
63
+
64
+ def destroy
65
+ @user.transaction do
66
+ @security_key.destroy!
67
+ @user.discard_orphaned_recovery_codes!
68
+ end
69
+ <%- if security_notifications? -%>
70
+ <%= identity.mailer_class %>.send_security_notification(:security_key_removed)
71
+ <%- end -%>
72
+
73
+ redirect_to <%= identity.settings_helper_prefix %>_mfa_security_keys_path, notice: "Removed #{@security_key.name}.",
74
+ status: :see_other
75
+ end
76
+
77
+ private
78
+
79
+ def set_user
80
+ @user = <%= identity.current_class %>.user
81
+ end
82
+
83
+ def set_security_key
84
+ @security_key = <%= second_factor_keys_expr("@user") %>.find(params[:id])
85
+ end
86
+ end
@@ -0,0 +1,84 @@
1
+ class <%= identity.settings_module %>::MultiFactorAuthentication::SmsController < <%= identity.settings_controller %>
2
+ <%- if admin_mfa_gate? -%>
3
+ skip_impediments
4
+ <%- end -%>
5
+ rate_limit(**<%= identity.config_reference %>.rate_limits.sms_second_factor, only: :create,
6
+ with: -> { redirect_to new_<%= identity.settings_helper_prefix %>_mfa_sms_path, alert: "Too many codes requested. Please try again later." })
7
+ before_action :no_store, only: :update
8
+ before_action :set_user
9
+ <%- if phone_column_nullable? -%>
10
+ before_action :require_a_number_to_text, only: %i[create update]
11
+ <%- end -%>
12
+
13
+ def new
14
+ end
15
+
16
+ def create
17
+ @user.issue_sms_challenge
18
+
19
+ redirect_to new_<%= identity.settings_helper_prefix %>_mfa_sms_path, notice: "We sent a code to your phone."
20
+ end
21
+
22
+ def show
23
+ redirect_to <%= identity.settings_helper_prefix %>_mfa_recovery_codes_path
24
+ end
25
+
26
+ def update
27
+ unless @user.verify_sms_challenge(params[:code])
28
+ return redirect_to new_<%= identity.settings_helper_prefix %>_mfa_sms_path, alert: "That code is incorrect or has expired."
29
+ end
30
+
31
+ should_mint = @user.recovery_codes.none?
32
+
33
+ @user.transaction do
34
+ @user.update!(sms_enrolled_at: Time.current)
35
+ @codes = <%= identity.recovery_code_class %>.regenerate_for(@user) if should_mint
36
+ <%- if trackable? -%>
37
+ @user.record_event("<%= identity.singular %>.two_factor_enabled")
38
+ <%- end -%>
39
+ end
40
+ <%- if security_notifications? -%>
41
+ <%= identity.mailer_class %>.send_security_notification(:sms_mfa_enabled)
42
+ <%- end -%>
43
+
44
+ if should_mint
45
+ flash.delete(:alert)
46
+ flash.now[:notice] = "Two-factor authentication is on."
47
+ render "<%= identity.settings_path_prefix %>/multi_factor_authentication/recovery_codes/create"
48
+ else
49
+ redirect_to interrupted_destination(otherwise: new_<%= identity.settings_helper_prefix %>_mfa_sms_path),
50
+ notice: "Two-factor authentication is on. Your existing recovery codes still work.",
51
+ status: :see_other
52
+ end
53
+ end
54
+
55
+ def destroy
56
+ @user.transaction do
57
+ @user.update!(sms_enrolled_at: nil)
58
+ @user.clear_sms_challenge
59
+ @user.discard_orphaned_recovery_codes!
60
+ <%- if trackable? -%>
61
+ @user.record_event("<%= identity.singular %>.two_factor_disabled")
62
+ <%- end -%>
63
+ end
64
+ <%- if security_notifications? -%>
65
+ <%= identity.mailer_class %>.send_security_notification(:sms_mfa_disabled)
66
+ <%- end -%>
67
+
68
+ redirect_to <%= identity.settings_helper_prefix %>_path, notice: "Two-factor authentication is off.", status: :see_other
69
+ end
70
+
71
+ private
72
+
73
+ def set_user
74
+ @user = <%= identity.current_class %>.user
75
+ end
76
+ <%- if phone_column_nullable? -%>
77
+
78
+ def require_a_number_to_text
79
+ return if @user.phone.present?
80
+
81
+ redirect_to edit_<%= identity.settings_helper_prefix %>_phone_path, alert: "Add a phone number first."
82
+ end
83
+ <%- end -%>
84
+ end
@@ -0,0 +1,27 @@
1
+ class <%= identity.settings_module %>::OmniauthIdentitiesController < <%= identity.settings_controller %>
2
+ before_action :set_identity, only: :destroy
3
+
4
+ def index
5
+ @omniauth_identities = <%= identity.current_class %>.user.omniauth_identities.order(:provider, :created_at)
6
+ end
7
+
8
+ def destroy
9
+ if <%= identity.current_class %>.user.last_sign_in_method?(:omniauth)
10
+ return redirect_to <%= identity.settings_helper_prefix %>_omniauth_identities_path,
11
+ alert: "This is the only way into your account. Link another before removing this one.", status: :see_other
12
+ end
13
+
14
+ @omniauth_identity.destroy!
15
+ <%- if security_notifications? -%>
16
+ <%= identity.mailer_class %>.send_security_notification(:connected_account_unlinked)
17
+ <%- end -%>
18
+ redirect_to <%= identity.settings_helper_prefix %>_omniauth_identities_path, notice: "Unlinked #{@omniauth_identity.provider_name}.",
19
+ status: :see_other
20
+ end
21
+
22
+ private
23
+
24
+ def set_identity
25
+ @omniauth_identity = <%= identity.current_class %>.user.omniauth_identities.find(params[:id]) # Scope the lookup to the signed-in user to prevent unlinking another account's provider identity.
26
+ end
27
+ end