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,70 @@
1
+ class <%= identity.settings_module %>::PasskeysController < <%= 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 passkey.", only: :new
5
+ <%- end -%>
6
+ before_action :set_user
7
+ before_action :set_passkey, only: %i[edit update destroy]
8
+
9
+ def index
10
+ @passkeys = @user.webauthn_credentials<%= ".passkeys" if passkey_and_security_keys? %>
11
+ end
12
+
13
+ def new
14
+ respond_to do |format|
15
+ format.html
16
+ format.json do
17
+ issue_webauthn_challenge(WebAuthn::Credential.options_for_create(
18
+ user: { id: @user.webauthn_handle, name: @user.<%= principals.display.column %> },
19
+ exclude: @user.webauthn_credentials.pluck(:external_id),
20
+ authenticator_selection: { resident_key: "required", user_verification: "required" } # Discoverable, user-verified credentials are required because these passkeys perform passwordless sign-in.
21
+ ))
22
+ end
23
+ end
24
+ end
25
+
26
+ def edit
27
+ end
28
+
29
+ def create
30
+ @passkey = @user.webauthn_credentials.create!(
31
+ **verify_webauthn_registration(params.expect(credential: {}), user_verification: true),
32
+ name: "Passkey"<%= factor_attr(:first_factor) %>
33
+ )
34
+ <%- if security_notifications? -%>
35
+ <%= identity.mailer_class %>.send_security_notification(:passkey_added)
36
+ <%- end -%>
37
+
38
+ render json: { status: "ok", location: edit_<%= identity.settings_helper_prefix %>_passkey_url(@passkey) }, status: :created
39
+ rescue WebAuthn::Error
40
+ render json: { error: "We couldn't add that passkey. Please try again." }, status: :unprocessable_entity
41
+ end
42
+
43
+ def update
44
+ @passkey.update!(name: params[:name])
45
+ redirect_to <%= identity.settings_helper_prefix %>_passkeys_path, notice: "Renamed your passkey.", status: :see_other
46
+ end
47
+
48
+ def destroy
49
+ if @user.last_sign_in_method?(:passkey)
50
+ return redirect_to <%= identity.settings_helper_prefix %>_passkeys_path,
51
+ alert: "This is the only way into your account. Add another passkey before removing this one.", status: :see_other
52
+ end
53
+
54
+ @passkey.destroy!
55
+ <%- if security_notifications? -%>
56
+ <%= identity.mailer_class %>.send_security_notification(:passkey_removed)
57
+ <%- end -%>
58
+ redirect_to <%= identity.settings_helper_prefix %>_passkeys_path, notice: "Removed #{@passkey.name}.", status: :see_other
59
+ end
60
+
61
+ private
62
+
63
+ def set_user
64
+ @user = <%= identity.current_class %>.user
65
+ end
66
+
67
+ def set_passkey
68
+ @passkey = @user.webauthn_credentials<%= ".passkeys" if passkey_and_security_keys? %>.find(params[:id]) # Scope the lookup to the signed-in user and first-factor passkeys to prevent cross-account or wrong-factor changes.
69
+ end
70
+ end
@@ -0,0 +1,74 @@
1
+ class <%= identity.settings_module %>::PasswordsController < <%= identity.settings_controller %>
2
+ <%- if password_rotatable? -%>
3
+ skip_impediments
4
+ <%- end -%>
5
+ rate_limit(**<%= identity.config_reference %>.rate_limits.password_challenge, only: :update, name: "password_challenge",
6
+ with: -> { redirect_to edit_<%= identity.settings_helper_prefix %>_password_path, alert: "Too many attempts. Please try again later.", status: :see_other })
7
+ before_action :set_user
8
+
9
+ def edit
10
+ end
11
+
12
+ def update
13
+ if @user.update(user_params)
14
+ <%- if security_notifications? -%>
15
+ <%= identity.mailer_class %>.send_security_notification(:password_changed)
16
+ <%- end -%>
17
+ redirect_to interrupted_destination(otherwise: edit_<%= identity.settings_helper_prefix %>_password_path),
18
+ notice: "Your password has been changed.", status: :see_other
19
+ else
20
+ render :edit, status: :unprocessable_entity
21
+ end
22
+ end
23
+ <%- if password_nullable? -%>
24
+
25
+ def create
26
+ if @user.password_set?
27
+ return redirect_to edit_<%= identity.settings_helper_prefix %>_password_path,
28
+ alert: "You already have a password. Change it instead.", status: :see_other
29
+ end
30
+
31
+ if @user.update(new_password_params)
32
+ <%- if security_notifications? -%>
33
+ <%= identity.mailer_class %>.send_security_notification(:password_set)
34
+ <%- end -%>
35
+ redirect_to edit_<%= identity.settings_helper_prefix %>_password_path, notice: "Your password has been set.",
36
+ status: :see_other
37
+ else
38
+ render :edit, status: :unprocessable_entity
39
+ end
40
+ end
41
+ <%- end -%>
42
+ <%- if password_removable? -%>
43
+
44
+ def destroy
45
+ if @user.last_sign_in_method?(:password)
46
+ return redirect_to edit_<%= identity.settings_helper_prefix %>_password_path,
47
+ alert: "This is the only way into your account. Add another way to sign in before removing it.", status: :see_other
48
+ end
49
+
50
+ @user.update_columns(password_digest: nil<%= password_rotatable? ? ", password_changed_at: nil" : "" %>)
51
+ <%- if security_notifications? -%>
52
+ <%= identity.mailer_class %>.send_security_notification(:password_removed)
53
+ <%- end -%>
54
+ redirect_to edit_<%= identity.settings_helper_prefix %>_password_path, notice: "Your password has been removed.",
55
+ status: :see_other
56
+ end
57
+ <%- end -%>
58
+
59
+ private
60
+
61
+ def set_user
62
+ @user = <%= identity.current_class %>.user
63
+ end
64
+
65
+ def user_params
66
+ params.permit(:password, :password_confirmation, :password_challenge).with_defaults(password_challenge: "")
67
+ end
68
+ <%- if password_nullable? -%>
69
+
70
+ def new_password_params
71
+ params.permit(:password, :password_confirmation)
72
+ end
73
+ <%- end -%>
74
+ end
@@ -0,0 +1,39 @@
1
+ class <%= identity.settings_module %>::PhoneVerificationsController < <%= identity.settings_controller %>
2
+ rate_limit(**<%= identity.config_reference %>.rate_limits.phone_verification, only: :create,
3
+ with: -> { redirect_to new_<%= identity.settings_helper_prefix %>_phone_verification_path, alert: "Too many codes requested. Please try again later." })
4
+ before_action :set_user
5
+ before_action :require_something_to_verify
6
+
7
+ def new
8
+ end
9
+
10
+ def create
11
+ <%= deferred_call("@user", :request_phone_change_verification) %>
12
+
13
+ redirect_to new_<%= identity.settings_helper_prefix %>_phone_verification_path, notice: "We sent you another code."
14
+ end
15
+
16
+ def update
17
+ if @user.verify_sms_challenge(params[:code], to: @user.pending_phone) && @user.verify_pending_phone
18
+ <%- if security_notifications? -%>
19
+ <%= identity.mailer_class %>.send_security_notification(:phone_changed)
20
+ <%- end -%>
21
+ redirect_to <%= identity.settings_helper_prefix %>_path, notice: "Your phone number has been verified.", status: :see_other
22
+ else
23
+ flash.now[:alert] = "That code is incorrect or has expired."
24
+ render :new, status: :unprocessable_entity
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def set_user
31
+ @user = <%= identity.current_class %>.user
32
+ end
33
+
34
+ def require_something_to_verify
35
+ return if @user.pending_phone.present?
36
+
37
+ redirect_to edit_<%= identity.settings_helper_prefix %>_phone_path, alert: "Add a phone number first."
38
+ end
39
+ end
@@ -0,0 +1,88 @@
1
+ class <%= identity.settings_module %>::PhonesController < <%= identity.settings_controller %>
2
+ <%- if sudoable? -%>
3
+ require_sudo message: "Please confirm it's you before changing your phone number.", only: :update
4
+ <%- end -%>
5
+ <%- if phone_verifiable? -%>
6
+ rate_limit(**<%= identity.config_reference %>.rate_limits.phone_verification, only: :update,
7
+ with: -> { redirect_to edit_<%= identity.settings_helper_prefix %>_phone_path, alert: "Too many codes 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 %>_phone_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 phone_verifiable? -%>
18
+
19
+ def update
20
+ <%- if password? && !sudoable? -%>
21
+ @user.assign_attributes(user_params.except(:phone))
22
+ <%- end -%>
23
+ @user.stage_phone(user_params[:phone])
24
+ return render :edit, status: :unprocessable_entity unless @user.save
25
+ unless @user.pending_phone
26
+ <%- if security_notifications? -%>
27
+ <%= identity.mailer_class %>.send_security_notification(:phone_changed) if @user.phone_previously_changed?
28
+ <%- end -%>
29
+ return redirect_to_settled
30
+ end
31
+
32
+ <%= deferred_call("@user", :request_phone_change_verification) %>
33
+ redirect_to new_<%= identity.settings_helper_prefix %>_phone_verification_path, notice: "Check your phone for a code to confirm your new number.",
34
+ status: :see_other
35
+ end
36
+ <%- else -%>
37
+
38
+ def update
39
+ if @user.update(user_params)
40
+ <%- if security_notifications? -%>
41
+ <%= identity.mailer_class %>.send_security_notification(:phone_changed) if @user.phone_previously_changed?
42
+ <%- end -%>
43
+ redirect_to_settled
44
+ else
45
+ render :edit, status: :unprocessable_entity
46
+ end
47
+ rescue ActiveRecord::RecordNotUnique
48
+ respond_to_taken_phone
49
+ end
50
+ <%- end -%>
51
+
52
+ private
53
+ <%- unless phone_verifiable? -%>
54
+
55
+ def respond_to_taken_phone
56
+ @user.errors.add(:phone, :taken)
57
+ render :edit, status: :unprocessable_entity
58
+ end
59
+ <%- end -%>
60
+
61
+ def set_user
62
+ @user = <%= identity.current_class %>.user
63
+ end
64
+
65
+ def user_params
66
+ <%- if password? && !sudoable? -%>
67
+ params.permit(:phone, :password_challenge).with_defaults(password_challenge: "")
68
+ <%- else -%>
69
+ params.permit(:phone)
70
+ <%- end -%>
71
+ end
72
+
73
+ def redirect_to_settled
74
+ <%- if principals.phone.optional? -%>
75
+ return redirect_to_edit("Your phone has been removed.") if @user.phone.blank?
76
+
77
+ <%- end -%>
78
+ <%- unless phone_verifiable? -%>
79
+ return redirect_to_edit("Your phone has been changed.") if @user.phone_previously_changed?
80
+
81
+ <%- end -%>
82
+ redirect_to_edit("That's already your phone number.")
83
+ end
84
+
85
+ def redirect_to_edit(notice)
86
+ redirect_to edit_<%= identity.settings_helper_prefix %>_phone_path, notice: notice, status: :see_other
87
+ end
88
+ end
@@ -0,0 +1,29 @@
1
+ class <%= identity.settings_module %>::SessionsController < <%= identity.settings_controller %>
2
+ <%- if max_sessions_prompt? -%>
3
+ skip_impediments
4
+ <%- end -%>
5
+ before_action :set_sessions
6
+
7
+ def index
8
+ @sessions = @sessions.order(created_at: :desc)
9
+ end
10
+
11
+ def destroy
12
+ @sessions.find(params[:id]).destroy!
13
+ <%- if security_notifications? -%>
14
+ <%= identity.mailer_class %>.send_security_notification(:session_revoked)
15
+ <%- end -%>
16
+ redirect_to interrupted_destination(otherwise: <%= identity.settings_helper_prefix %>_sessions_path),
17
+ notice: "That session has been signed out.", status: :see_other
18
+ end
19
+
20
+ private
21
+
22
+ def set_sessions
23
+ <%- if timeoutable? -%>
24
+ @sessions = <%= identity.current_class %>.user.sessions.active
25
+ <%- else -%>
26
+ @sessions = <%= identity.current_class %>.user.sessions
27
+ <%- end -%>
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ class <%= identity.settings_module %>::UsersController < <%= identity.settings_controller %>
2
+ <%- if sudoable? -%>
3
+ require_sudo message: "Please confirm it's you before deleting your account.", only: :destroy
4
+ <%- elsif password? -%>
5
+ rate_limit(**<%= identity.config_reference %>.rate_limits.password_challenge, only: :destroy, name: "password_challenge",
6
+ with: -> { redirect_to <%= identity.settings_helper_prefix %>_user_path, alert: "Too many attempts. Please try again later.", status: :see_other })
7
+ before_action :require_password_challenge, only: :destroy
8
+ <%- end -%>
9
+ before_action :require_confirmation, only: :destroy
10
+
11
+ def show
12
+ end
13
+
14
+ def destroy
15
+ user = <%= identity.current_class %>.user
16
+ <%- if security_notifications? -%>
17
+ email = user.email
18
+ <%- end -%>
19
+ user.destroy!
20
+ <%- if security_notifications? -%>
21
+ <%= identity.mailer_class %>.send_security_notification(:account_deleted, to: email)
22
+ <%- end -%>
23
+ <%= identity.current_class %>.session = nil
24
+ redirect_to root_path, notice: "Your account has been deleted.", status: :see_other
25
+ end
26
+
27
+ private
28
+
29
+ def require_confirmation
30
+ return if params[:confirmation] == "DELETE ACCOUNT"
31
+
32
+ redirect_to <%= identity.settings_helper_prefix %>_user_path, alert: "Type DELETE ACCOUNT to confirm.", status: :see_other
33
+ end
34
+ <%- if !sudoable? && password? -%>
35
+
36
+ def require_password_challenge
37
+ return if <%= identity.current_class %>.user.authenticate(params[:password_challenge].to_s)
38
+
39
+ redirect_to <%= identity.settings_helper_prefix %>_user_path, alert: "Password challenge is invalid.", status: :see_other
40
+ end
41
+ <%- end -%>
42
+ end
@@ -0,0 +1,5 @@
1
+ class <%= identity.settings_controller %> < <%= identity.controller_superclass %>
2
+ <%- if teams? -%>
3
+ skip_before_action :require_team
4
+ <%- end -%>
5
+ end
@@ -0,0 +1,38 @@
1
+ class <%= identity.nested_module("TeamsController") %> < <%= identity.controller_superclass %>
2
+ skip_impediments<%= " only: %i[index new create]" if scope_teams? %>
3
+
4
+ def index
5
+ @teams = <%= identity.current_class %>.user.teams
6
+ end
7
+ <%- if scope_teams? -%>
8
+
9
+ def show
10
+ end
11
+ <%- end -%>
12
+
13
+ def new
14
+ @team = <%= identity.team_class %>.new
15
+ end
16
+
17
+ def create
18
+ @team = <%= identity.team_class %>.new(team_params)
19
+ @team.memberships.build(<%= identity.singular %>: <%= identity.current_class %>.user)
20
+
21
+ if @team.save
22
+ <%- if session_carries_team? -%>
23
+ set_session_team!(@team)
24
+ redirect_to interrupted_destination(otherwise: <%= identity.nested_helper("root_path") %>), notice: "#{@team.name} created.", status: :see_other
25
+ <%- elsif scope_teams? -%>
26
+ redirect_to <%= team_root_helper %>(team_id: @team), notice: "#{@team.name} created."
27
+ <%- else -%>
28
+ redirect_to root_url(script_name: "/#{@team.id}"), notice: "#{@team.name} created."
29
+ <%- end -%>
30
+ else
31
+ render :new, status: :unprocessable_entity
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def team_params = params.expect(<%= identity.teams_table.singularize %>: [ :name ])
38
+ end
@@ -0,0 +1,36 @@
1
+ class <%= identity.module_name %>::CredentialEnrollments::PasskeysController < <%= identity.controller_superclass %>
2
+ include <%= identity.authentication_concern %>::Registering
3
+
4
+ require_unauthenticated_access
5
+ rate_limit(**<%= identity.config_reference %>.rate_limits.sign_up, only: :create,
6
+ with: -> { redirect_to <%= identity.route_scope %>_new_registration_passkey_path, alert: "Too many attempts. Please try again later." })
7
+ before_action :set_registration
8
+
9
+ def new
10
+ respond_to do |format|
11
+ format.html
12
+ format.json do
13
+ options = WebAuthn::Credential.options_for_create(
14
+ user: { id: @registration.webauthn_handle, name: @registration.<%= principals.display.column %> },
15
+ authenticator_selection: { resident_key: "required", user_verification: "required" }
16
+ )
17
+ issue_webauthn_challenge(options)
18
+ end
19
+ end
20
+ end
21
+
22
+ def create
23
+ @registration.passkey = verify_webauthn_registration(JSON.parse(params[:credential].to_s), user_verification: true)
24
+ advance(@registration)
25
+ rescue WebAuthn::Error, JSON::ParserError
26
+ flash.now[:alert] = "We couldn't add that passkey. Please try again."
27
+ render :new, status: :unprocessable_entity
28
+ end
29
+
30
+ private
31
+
32
+ def set_registration
33
+ @registration = <%= identity.pending_registration_class %>.unexpired.find_by(id: session[:<%= pending_registration_session_key %>])
34
+ redirect_to <%= registration_entry_path %> unless @registration
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ class <%= identity.module_name %>::CredentialEnrollments::PasswordsController < <%= identity.controller_superclass %>
2
+ include <%= identity.authentication_concern %>::Registering
3
+
4
+ require_unauthenticated_access
5
+ rate_limit(**<%= identity.config_reference %>.rate_limits.sign_up, only: :create,
6
+ with: -> { redirect_to <%= identity.route_scope %>_new_registration_password_path, alert: "Too many attempts. Please try again later." })
7
+ before_action :set_registration
8
+
9
+ def new
10
+ end
11
+
12
+ def create
13
+ return render :new, status: :unprocessable_entity unless @registration.update(password_params)
14
+
15
+ advance(@registration)
16
+ end
17
+
18
+ private
19
+
20
+ def set_registration
21
+ @registration = <%= identity.pending_registration_class %>.unexpired.find_by(id: session[:<%= pending_registration_session_key %>])
22
+ redirect_to <%= identity.route_scope %>_sign_up_path unless @registration
23
+ end
24
+
25
+ def password_params
26
+ params.permit(:password, :password_confirmation)
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ class <%= identity.module_name %>::CredentialEnrollmentsController < <%= identity.controller_superclass %>
2
+ require_unauthenticated_access
3
+ before_action :set_registration
4
+
5
+ def show
6
+ end
7
+
8
+ private
9
+
10
+ def set_registration
11
+ @registration = <%= identity.pending_registration_class %>.unexpired.find_by(id: session[:<%= pending_registration_session_key %>])
12
+ redirect_to <%= identity.route_scope %>_sign_up_path unless @registration
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ class <%= identity.module_name %>::EasyDevLoginsController < <%= identity.controller_superclass %>
2
+ allow_unauthenticated_access
3
+ before_action :ensure_development
4
+
5
+ def create
6
+ user = <%= find_by_login_expr("params[:#{sign_in_field}]") %>
7
+ return redirect_to <%= identity.route_scope %>_sign_in_path(<%= login_hint_param %>: params[:<%= sign_in_field %>]), alert: "No user with that <%= login_keys_phrase %>." unless user
8
+
9
+ <%= sign_in_call("user", via: :easy_dev_login, may_challenge: false) %>
10
+ redirect_to after_authentication_url, notice: "Now signed in as #{user.<%= principals.display.column %>}.", status: :see_other
11
+ end
12
+
13
+ private
14
+
15
+ def ensure_development
16
+ return if Rails.env.development?
17
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "Easy dev login is only available in development."
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ class <%= identity.module_name %>::EmailVerificationsController < <%= identity.controller_superclass %>
2
+ allow_unauthenticated_access
3
+ before_action :set_<%= identity.singular %>
4
+
5
+ def show
6
+ end
7
+
8
+ def update
9
+ <%- if security_notifications? -%>
10
+ old_email = @<%= identity.singular %>.email
11
+ <%- end -%>
12
+ return redirect_to_rejection unless @<%= identity.singular %>.verify_pending_email
13
+ <%- if security_notifications? -%>
14
+
15
+ <%= identity.mailer_class %>.send_security_notification(:email_changed, to: old_email)
16
+ <%- end -%>
17
+
18
+ if authenticated?
19
+ redirect_to edit_<%= identity.settings_helper_prefix %>_email_path, notice: "Your email has been confirmed."
20
+ else
21
+ redirect_to <%= identity.route_scope %>_sign_in_path, notice: "Your email has been confirmed. Please sign in."
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def set_<%= identity.singular %>
28
+ @<%= identity.singular %> = <%= identity.class_name %>.find_by_token_for(:email_change_verification, params.expect(:sid))
29
+
30
+ redirect_to_rejection unless @<%= identity.singular %>
31
+ end
32
+
33
+ def redirect_to_rejection
34
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "That confirmation link is invalid or has expired."
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ class <%= identity.module_name %>::MagicLinkController < <%= identity.controller_superclass %>
2
+ require_unauthenticated_access
3
+ rate_limit(**<%= identity.config_reference %>.rate_limits.magic_link, only: :create,
4
+ with: -> { redirect_to <%= identity.route_scope %>_new_magic_link_path, alert: "Too many sign-in links requested. Please try again later." })
5
+ before_action :set_user, only: :edit
6
+
7
+ def new
8
+ end
9
+
10
+ def edit
11
+ end
12
+
13
+ def update
14
+ @user = <%= identity.class_name %>.consume_magic_link(params.expect(:sid))
15
+ return redirect_to_rejection unless @user
16
+
17
+ <%= sign_in_call("@user", via: :magic_link) %>
18
+ redirect_to after_authentication_url, status: :see_other
19
+ end
20
+
21
+ def create
22
+ <%- if captchable? -%>
23
+ unless captcha_verified?
24
+ flash.now[:alert] = "Captcha verification failed. Please try again."
25
+ return render :new, status: :unprocessable_entity
26
+ end
27
+
28
+ <%- end -%>
29
+ <%- if coy? -%>
30
+ <%= deferred_call(identity.class_name, :request_magic_link, "params[:email].to_s") %>
31
+ redirect_to <%= identity.route_scope %>_sign_in_path, notice: "Check your email for a sign in link."
32
+ <%- else -%>
33
+ if <%= identity.class_name %>.request_magic_link(params[:email])
34
+ redirect_to <%= identity.route_scope %>_sign_in_path, notice: "Check your email for a sign in link."
35
+ else
36
+ redirect_to <%= identity.route_scope %>_new_magic_link_path, alert: "No account was found for that email."
37
+ end
38
+ <%- end -%>
39
+ end
40
+
41
+ private
42
+
43
+ def set_user
44
+ token = params.expect(:sid)
45
+ @user = <%= identity.class_name %>.find_by_token_for(:magic_link, token)
46
+
47
+ redirect_to_rejection unless @user && Rails.cache.exist?(@user.magic_link_nonce_key)
48
+ end
49
+
50
+ def redirect_to_rejection
51
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "That magic link is invalid or has expired."
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ class <%= identity.module_name %>::MultiFactorAuthentication::Challenge::RecoveryCodesController < <%= identity.controller_superclass %>
2
+ require_unauthenticated_access
3
+ rate_limit(**<%= identity.config_reference %>.rate_limits.recovery_code_challenge, only: :create,
4
+ with: -> { redirect_to new_<%= identity.route_scope %>_mfa_challenge_recovery_codes_path, alert: "Too many attempts. Please try again later." })
5
+ before_action :set_user
6
+
7
+ def new
8
+ end
9
+
10
+ def create
11
+ if <%= identity.recovery_code_class %>.redeem(user: @user, code: params[:code])
12
+ <%- if rememberable? -%>
13
+ start_new_session_for(@user, via: :second_factor, remember: session.delete(:<%= session_key(:remember_me) %>)) # Keep recovery-code sign-in in the shared session funnel so bans, invitation claims, and session policy still apply.
14
+ <%- else -%>
15
+ start_new_session_for(@user, via: :second_factor) # Keep recovery-code sign-in in the shared session funnel so bans, invitation claims, and session policy still apply.
16
+ <%- end -%>
17
+ redirect_to after_authentication_url, status: :see_other
18
+ else
19
+ redirect_to new_<%= identity.route_scope %>_mfa_challenge_recovery_codes_path, alert: "That code is incorrect."
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def set_user
26
+ @user = <%= identity.class_name %>.find_by_token_for!(:authentication_challenge, session[:<%= session_key(:challenge_token) %>])
27
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveRecord::RecordNotFound
28
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "Your sign-in challenge has expired. Please sign in again."
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ class <%= identity.module_name %>::MultiFactorAuthentication::Challenge::SecurityKeysController < <%= identity.controller_superclass %>
2
+ require_unauthenticated_access
3
+ before_action :set_user
4
+
5
+ def new
6
+ respond_to do |format|
7
+ format.html
8
+ format.json do
9
+ issue_webauthn_challenge(WebAuthn::Credential.options_for_get(allow: <%= second_factor_keys_expr("@user") %>.pluck(:external_id)))
10
+ end
11
+ end
12
+ end
13
+
14
+ def create
15
+ verify_webauthn_assertion(params.expect(credential: {}), from: <%= second_factor_keys_expr("@user") %>)
16
+
17
+ <%- if rememberable? -%>
18
+ start_new_session_for(@user, via: :second_factor, remember: session.delete(:<%= session_key(:remember_me) %>))
19
+ <%- else -%>
20
+ start_new_session_for(@user, via: :second_factor)
21
+ <%- end -%>
22
+ render json: { status: "ok", location: after_authentication_url }, status: :created
23
+ rescue WebAuthn::Error, ActiveRecord::RecordNotFound
24
+ render json: { error: "We couldn't verify that security key. Please try again." }, status: :unprocessable_entity
25
+ end
26
+
27
+ private
28
+
29
+ def set_user
30
+ @user = <%= identity.class_name %>.find_by_token_for!(:authentication_challenge, session[:<%= session_key(:challenge_token) %>])
31
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveRecord::RecordNotFound
32
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "Your sign-in challenge has expired. Please sign in again."
33
+ end
34
+ end