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,117 @@
1
+ module <%= identity.authentication_concern %>::Sudoable
2
+ extend ActiveSupport::Concern
3
+
4
+ <%- unless single_sudo_bar? -%>
5
+ included do
6
+ helper_method :sudo_strategy
7
+ end
8
+
9
+ <%- end -%>
10
+ class_methods do
11
+ def require_sudo(message: "Please confirm it's you before continuing.", **options)
12
+ before_action(**options) { enforce_sudo(message:) }
13
+ end
14
+
15
+ def require_sudo_within(window, message: "Please confirm it's you before continuing.", **options)
16
+ before_action(**options) { enforce_sudo_within(window, message:) }
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def enforce_sudo(message:)
23
+ return if sudo_confirmed?
24
+
25
+ redirect_back_or_to <%= identity.settings_helper_prefix %>_path, alert: message, status: :see_other
26
+ end
27
+
28
+ def enforce_sudo_within(window, message:)
29
+ <%- if fail_open_sudo? -%>
30
+ return if sudo_strategy == :open
31
+ <%- end -%>
32
+ return if <%= identity.current_class %>.session&.sudo_at&.> window.ago
33
+
34
+ session[:<%= session_key(:sudo_proceed_to) %>] = request.url
35
+ session[:<%= session_key(:sudo_message) %>] = message
36
+ redirect_to new_<%= identity.route_scope %>_sudo_path
37
+ end
38
+
39
+ def sudo_confirmed?
40
+ return false unless sudo_attempts_left?
41
+
42
+ <%- if single_sudo_bar? -%>
43
+ confirmed = <%= sudo_subject %>.authenticate(params[:sudo_password].to_s)
44
+ <%- else -%>
45
+ confirmed =
46
+ case sudo_strategy
47
+ <%- if sudo_bars.include?(:webauthn) -%>
48
+ when :webauthn then verify_sudo_assertion(params[:sudo_credential])
49
+ <%- end -%>
50
+ <%- if sudo_bars.include?(:password) -%>
51
+ when :password then <%= sudo_subject %>.authenticate(params[:sudo_password].to_s)
52
+ <%- end -%>
53
+ <%- if sudo_bars.include?(:totp) -%>
54
+ when :totp then verify_sudo_totp(params[:sudo_code])
55
+ <%- end -%>
56
+ else true
57
+ end
58
+ <%- end -%>
59
+
60
+ if confirmed
61
+ <%= identity.current_class %>.session.touch(:sudo_at)
62
+ Rails.cache.delete(sudo_attempt_key)
63
+ end
64
+
65
+ confirmed
66
+ end
67
+ <%- unless single_sudo_bar? -%>
68
+
69
+ def sudo_strategy
70
+ <%- if sudo_bars.include?(:webauthn) -%>
71
+ return :webauthn if <%= sudo_subject %>.webauthn_credentials.any?
72
+ <%- end -%>
73
+ <%- if unconditional_password_bar? -%>
74
+ :password
75
+ <%- else -%>
76
+ <%- if sudo_bars.include?(:password) -%>
77
+ return :password if <%= sudo_subject %>.password_set?
78
+ <%- end -%>
79
+ <%- if sudo_bars.include?(:totp) -%>
80
+ return :totp if <%= sudo_subject %>.totp_enrolled_at?
81
+ <%- end -%>
82
+ :open
83
+ <%- end -%>
84
+ end
85
+ <%- end -%>
86
+
87
+ def sudo_attempts_left?
88
+ attempts = Rails.cache.increment(sudo_attempt_key, 1, expires_in: <%= identity.config_reference %>.rate_limits.sudo[:within]).to_i
89
+ attempts <= <%= identity.config_reference %>.rate_limits.sudo[:to]
90
+ end
91
+
92
+ def sudo_attempt_key
93
+ "sudo-attempts:#{<%= identity.current_class %>.session.cache_key}"
94
+ end
95
+ <%- if sudo_bars.include?(:totp) -%>
96
+
97
+ def verify_sudo_totp(code)
98
+ return false if code.blank?
99
+
100
+ totp = ROTP::TOTP.new(<%= sudo_subject %>.totp_secret, issuer: <%= identity.config_reference %>.app_name)
101
+ used_at = totp.verify(code, drift_behind: <%= identity.config_reference %>.second_factor.totp_drift_behind,
102
+ after: <%= sudo_subject %>.totp_last_used_at)
103
+ return false unless used_at
104
+
105
+ <%= sudo_subject %>.update!(totp_last_used_at: Time.at(used_at))
106
+ true
107
+ end
108
+ <%- end -%>
109
+ <%- if sudo_bars.include?(:webauthn) -%>
110
+
111
+ def verify_sudo_assertion(credential_json)
112
+ verify_webauthn_assertion(JSON.parse(credential_json.to_s), from: <%= sudo_subject %>.webauthn_credentials).present?
113
+ rescue WebAuthn::Error, ActiveRecord::RecordNotFound, JSON::ParserError
114
+ false
115
+ end
116
+ <%- end -%>
117
+ end
@@ -0,0 +1,51 @@
1
+ module <%= identity.authentication_concern %>::WebauthnCeremony
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ def issue_webauthn_challenge(options)
7
+ session[:<%= session_key(:webauthn_challenge) %>] = options.challenge
8
+ render json: options
9
+ end
10
+
11
+ def verify_webauthn_assertion(credential, from:)
12
+ challenge = consume_webauthn_challenge
13
+
14
+ assertion = WebAuthn::Credential.from_get(credential)
15
+ stored = from.find_by!(external_id: assertion.id)
16
+
17
+ assertion.verify(
18
+ challenge,
19
+ public_key: stored.public_key,
20
+ sign_count: stored.sign_count,
21
+ <%- if passkey_and_security_keys? -%>
22
+ user_verification: stored.first_factor? # Only a passkey signs in on its own; a security key here is the second factor.
23
+ <%- elsif passkey? -%>
24
+ user_verification: true # A passkey is the whole sign-in, so possession alone is not enough.
25
+ <%- else -%>
26
+ user_verification: false # A security key here is the second factor, so presence is all it owes.
27
+ <%- end -%>
28
+ )
29
+ stored.update!(sign_count: assertion.sign_count)
30
+ stored
31
+ end
32
+
33
+ def verify_webauthn_registration(credential, user_verification:)
34
+ challenge = consume_webauthn_challenge
35
+ registration = WebAuthn::Credential.from_create(credential)
36
+ registration.verify(challenge, user_verification: user_verification)
37
+
38
+ {
39
+ external_id: registration.id,
40
+ public_key: registration.public_key,
41
+ sign_count: registration.sign_count
42
+ }
43
+ end
44
+
45
+ def consume_webauthn_challenge
46
+ challenge = session.delete(:<%= session_key(:webauthn_challenge) %>)
47
+ raise WebAuthn::ChallengeVerificationError unless challenge
48
+
49
+ challenge
50
+ end
51
+ end
@@ -0,0 +1,421 @@
1
+ module <%= identity.authentication_concern %>
2
+ extend ActiveSupport::Concern
3
+ <%- if webauthn_credentials? || sudoable? -%>
4
+
5
+ <%- if webauthn_credentials? -%>
6
+ include WebauthnCeremony
7
+ <%- end -%>
8
+ <%- if sudoable? -%>
9
+ include Sudoable
10
+ <%- end -%>
11
+ <%- end -%>
12
+ <%- if bannable? -%>
13
+
14
+ class <%= identity.class_name %>Banned < StandardError; end
15
+ <%- end -%>
16
+
17
+ included do
18
+ <%- if bannable? -%>
19
+ rescue_from <%= identity.class_name %>Banned, with: :handle_<%= identity.singular %>_banned
20
+
21
+ <%- end -%>
22
+ before_action :set_current_request_details
23
+ before_action :resume_session
24
+ before_action :require_authentication
25
+ <%- impediments.each do |callback, *| -%>
26
+ before_action :<%= callback %>, <%= impediment_conditions %>
27
+ <%- end -%>
28
+ <%- if impeded? -%>
29
+
30
+ <%- end -%>
31
+ helper_method :authenticated?
32
+ <%- if impersonatable? -%>
33
+ helper_method :impersonating?
34
+ <%- end -%>
35
+ <%- if easy_dev_login? && password? -%>
36
+
37
+ if Rails.env.development?
38
+ include EasyDevLogin
39
+ end
40
+ <%- end -%>
41
+ end
42
+
43
+ class_methods do
44
+ def allow_unauthenticated_access(**options)
45
+ skip_before_action :require_authentication, **options
46
+ end
47
+
48
+ def require_unauthenticated_access(**options)
49
+ allow_unauthenticated_access(**options)
50
+ before_action :redirect_authenticated_user, **options
51
+ end
52
+ <%- if adminable? -%>
53
+
54
+ def not_found_unless(predicate = nil, &block)
55
+ block ||= -> { send(predicate) }
56
+
57
+ define_method(:require_authentication) do
58
+ raise ActiveRecord::RecordNotFound unless authenticated? && instance_exec(&block)
59
+ end
60
+
61
+ private :require_authentication
62
+ end
63
+ <%- end -%>
64
+ <%- if guestable? -%>
65
+
66
+ def allow_guest_access(**options)
67
+ allow_unauthenticated_access(**options)
68
+ before_action :ensure_guest_session, **options
69
+ end
70
+ <%- end -%>
71
+ <%- if impeded? -%>
72
+
73
+ def skip_impediments(**options)
74
+ skip_before_action <%= impediment_callbacks %>, **options, raise: false
75
+ end
76
+ <%- end -%>
77
+ end
78
+
79
+ private
80
+
81
+ def set_current_request_details
82
+ <%= identity.current_class %>.user_agent = request.user_agent
83
+ <%= identity.current_class %>.ip_address = request.ip
84
+ <%= identity.current_class %>.request_id = request.request_id
85
+ end
86
+
87
+ def resume_session
88
+ <%= identity.current_class %>.session ||= find_session_by_cookie
89
+ return unless <%= identity.current_class %>.session
90
+ <%- if impersonatable? -%>
91
+
92
+ <%= identity.current_class %>.impersonated_user = find_impersonated_user
93
+
94
+ if impersonation_timed_out?
95
+ expire_impersonation!
96
+ return
97
+ end
98
+ <%- end -%>
99
+ <%- if timeoutable? -%>
100
+
101
+ if current_session_timed_out?
102
+ expire_current_session!
103
+ return
104
+ end
105
+ <%- end -%>
106
+
107
+ refresh_current_session_activity!
108
+ end
109
+
110
+ def find_session_by_cookie
111
+ if (token = cookies.signed[:<%= identity.cookie_name %>])
112
+ <%= identity.session_class %>.find_by(id: token)
113
+ end
114
+ end
115
+
116
+ <%- if timeoutable? -%>
117
+ def current_session_timed_out?
118
+ <%- if guestable? -%>
119
+ return false if <%= identity.current_class %>.user.guest?
120
+
121
+ <%- end -%>
122
+ <%= identity.current_class %>.session.updated_at < <%= identity.config_reference %>.session.idle_timeout.ago
123
+ end
124
+
125
+ def expire_current_session!
126
+ terminate_current_session
127
+ flash[:alert] = "Your session has expired. Please sign in again."
128
+ end
129
+
130
+ <%- end -%>
131
+ def refresh_current_session_activity!
132
+ return if <%= identity.current_class %>.session.updated_at >= <%= identity.config_reference %>.session.activity_write_interval.ago
133
+
134
+ <%= identity.current_class %>.session.touch
135
+ <%- if last_seenable? && impersonatable? -%>
136
+ <%= identity.current_class %>.true_user.mark_last_seen!
137
+ <%- elsif last_seenable? -%>
138
+ <%= identity.current_class %>.user.mark_last_seen!
139
+ <%- end -%>
140
+ end
141
+
142
+ def authenticated?
143
+ <%- if guestable? -%>
144
+ <%= identity.current_class %>.session.present? && !<%= identity.current_class %>.user.guest?
145
+ <%- else -%>
146
+ <%= identity.current_class %>.session.present?
147
+ <%- end -%>
148
+ end
149
+
150
+ def require_authentication
151
+ return if authenticated?
152
+ request_authentication
153
+ end
154
+
155
+ def request_authentication
156
+ remember_interrupted_destination
157
+ redirect_to <%= identity.route_scope %>_sign_in_path, status: :see_other
158
+ end
159
+ <%- impediments.each do |callback, predicate, path, message| -%>
160
+
161
+ def <%= callback %>
162
+ return if <%= identity.current_class %>.user.<%= predicate %>
163
+
164
+ remember_interrupted_destination
165
+ redirect_to <%= path %>, alert: "<%= message %>", status: :see_other
166
+ end
167
+ <%- end -%>
168
+
169
+ def redirect_authenticated_user
170
+ return unless authenticated?
171
+
172
+ redirect_to after_authentication_url, status: :see_other
173
+ end
174
+
175
+ # All sessions should be created through this method
176
+ <%- if rememberable? -%>
177
+ def start_new_session_for(user, via:, remember: false)
178
+ <%- else -%>
179
+ def start_new_session_for(user, via:)
180
+ <%- end -%>
181
+ <%- if bannable? -%>
182
+ raise <%= identity.class_name %>Banned, ban_message(user) if user.banned?
183
+
184
+ <%- end -%>
185
+ <%- if second_factor? -%>
186
+ if second_factor_required?(user, via:)
187
+ store_mfa_challenge_token(user, via)
188
+ <%- if rememberable? -%>
189
+ session[:<%= session_key(:remember_me) %>] = remember
190
+ <%- end -%>
191
+ <%- if multiple_second_factors? -%>
192
+ redirect_to second_factor_challenge_path(user)
193
+ <%- else -%>
194
+ redirect_to <%= second_factor_challenge_lines.first %>
195
+ <%- end -%>
196
+ return false
197
+ end
198
+
199
+ via = session.delete(:<%= session_key(:sign_in_via) %>) if via == :second_factor
200
+ session.delete(:<%= session_key(:challenge_token) %>)
201
+
202
+ <%- end -%>
203
+ <%- if guestable? -%>
204
+ previous_guest = <%= identity.current_class %>.user if <%= identity.current_class %>.session && <%= identity.current_class %>.user.guest?
205
+
206
+ <%- end -%>
207
+ <%- if max_sessions_evict? -%>
208
+ evict_sessions_over_limit!(user)
209
+ <%- end -%>
210
+ <%= identity.current_class %>.session = user.sessions.create!(via:)
211
+ set_session_cookie<%= rememberable? ? "(expires: (#{identity.config_reference}.session.remember_for.from_now if remember))" : "" %>
212
+ <%- if last_seenable? -%>
213
+ user.mark_last_seen!
214
+ <%- end -%>
215
+ <%- if guestable? -%>
216
+
217
+ user.absorb_guest(previous_guest) if previous_guest
218
+ <%- end -%>
219
+ <%- if invitable? -%>
220
+
221
+ session.delete(:<%= session_key(:pending_invitation_token) %>)
222
+ <%- end -%>
223
+ <%- if session_carries_team? -%>
224
+ <%= identity.current_class %>.session.update!(team: landing_team)
225
+ <%- end -%>
226
+ <%- if second_factor? -%>
227
+ true
228
+ <%- end -%>
229
+ end
230
+
231
+ <%- if second_factor? -%>
232
+ def second_factor_required?(user, via:)
233
+ user.second_factor_enrolled? && <%= identity.config_reference %>.second_factor.needed_after.include?(via)
234
+ end
235
+ <%- if multiple_second_factors? -%>
236
+
237
+ def second_factor_challenge_path(user)
238
+ <%- second_factor_challenge_lines[0..-2].each do |line| -%>
239
+ <%= line %>
240
+ <%- end -%>
241
+
242
+ <%= second_factor_challenge_lines.last %>
243
+ end
244
+ <%- end -%>
245
+
246
+ def store_mfa_challenge_token(user, via)
247
+ session[:<%= session_key(:challenge_token) %>] = user.generate_token_for(:authentication_challenge)
248
+ session[:<%= session_key(:sign_in_via) %>] = via
249
+ end
250
+
251
+ <%- end -%>
252
+ <%- if max_sessions_evict? -%>
253
+ def evict_sessions_over_limit!(user)
254
+ limit = <%= identity.config_reference %>.session.concurrent_limit
255
+ return unless limit
256
+
257
+ surplus = user.sessions.count - (limit - 1)
258
+ user.sessions.order(updated_at: :asc).limit(surplus).destroy_all if surplus.positive?
259
+ end
260
+
261
+ <%- end -%>
262
+ def set_session_cookie(expires: nil)
263
+ cookies.signed[:<%= identity.cookie_name %>] = {
264
+ value: <%= identity.current_class %>.session.id,
265
+ expires:,
266
+ secure: Rails.env.production?,
267
+ httponly: true,
268
+ same_site: :lax
269
+ }
270
+ end
271
+ <%- if bannable? -%>
272
+
273
+ def ban_message(user)
274
+ return "Your account is suspended until #{user.banned_until.to_date.to_fs(:long)}." if user.banned_until
275
+
276
+ "Your account has been suspended."
277
+ end
278
+
279
+ def handle_<%= identity.singular %>_banned(exception)
280
+ if request.format.json?
281
+ render json: { error: exception.message }, status: :forbidden
282
+ else
283
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: exception.message
284
+ end
285
+ end
286
+ <%- end -%>
287
+ <%- if guestable? -%>
288
+
289
+ def ensure_guest_session
290
+ return if <%= identity.current_class %>.session
291
+
292
+ guest = <%= identity.class_name %>.create_guest!
293
+ <%= identity.current_class %>.session = guest.sessions.create!(via: :guest)
294
+ <%- if last_seenable? -%>
295
+ guest.mark_last_seen!
296
+ <%- end -%>
297
+ set_session_cookie(expires: guest.created_at + <%= identity.config_reference %>.guest.ttl)
298
+ end
299
+ <%- end -%>
300
+
301
+ def terminate_current_session
302
+ <%- if impersonatable? -%>
303
+ stop_impersonating_user
304
+ <%- end -%>
305
+ <%= identity.current_class %>.session&.destroy
306
+ <%= identity.current_class %>.session = nil
307
+ cookies.delete(:<%= identity.cookie_name %>)
308
+ reset_session
309
+ end
310
+
311
+ def remember_interrupted_destination
312
+ return unless request.get? && request.format.html?
313
+
314
+ session[:<%= interrupted_destination_key %>] ||= request.fullpath
315
+ end
316
+
317
+ def interrupted_destination(otherwise:)
318
+ session.delete(:<%= interrupted_destination_key %>) || otherwise
319
+ end
320
+
321
+ def after_authentication_url
322
+ <%- if session_carries_team? -%>
323
+ set_session_team!(landing_team) if landing_team
324
+ <%- end -%>
325
+ interrupted_destination(otherwise: default_post_sign_in_path)
326
+ end
327
+
328
+ def default_post_sign_in_path
329
+ <%- if admin_dashboard? -%>
330
+ return <%= admin_helper("root_path") %> if <%= identity.current_class %>.user.admin?
331
+ <%- end -%>
332
+ <%- if scope_teams? -%>
333
+ return <%= team_root_helper %>(team_id: landing_team) if landing_team
334
+
335
+ <%- elsif middleware_teams? -%>
336
+ return root_url(script_name: "/#{landing_team.id}") if landing_team
337
+
338
+ <%- end -%>
339
+ <%= identity.namespaced? ? "#{identity.route_scope}_root_path" : "root_path" %>
340
+ end
341
+ <%- if teams? -%>
342
+
343
+ def landing_team
344
+ <%= identity.current_class %>.user.primary_team
345
+ end
346
+ <%- end -%>
347
+ <%- if session_carries_team? -%>
348
+
349
+ def set_session_team!(team)
350
+ raise ActiveRecord::RecordNotFound, "Team not accessible" unless <%= identity.current_class %>.user.member_of?(team)
351
+ <%= identity.current_class %>.session&.update!(team:)
352
+ end
353
+ <%- end -%>
354
+ <%- if invitable? -%>
355
+
356
+ def pending_invitation
357
+ # Caches either an invitation or nil.
358
+ return @pending_invitation if defined?(@pending_invitation)
359
+
360
+ @pending_invitation = <%= identity.invitation_class %>.find_by_token_for(:invitation, session[:<%= session_key(:pending_invitation_token) %>])
361
+ end
362
+ <%- if omniauth? -%>
363
+
364
+ def pending_invitation_for(address)
365
+ pending_invitation if pending_invitation&.email_addressed? && pending_invitation.sent_to == address&.strip&.downcase
366
+ end
367
+ <%- end -%>
368
+ <%- if invite_only? -%>
369
+
370
+ def require_invitation_to_register
371
+ return if pending_invitation
372
+
373
+ redirect_to <%= identity.route_scope %>_sign_in_path, alert: "Registration is invite-only."
374
+ end
375
+ <%- end -%>
376
+ <%- end -%>
377
+ <%- if impersonatable? -%>
378
+
379
+ def impersonating?
380
+ <%= identity.current_class %>.impersonated_user.present?
381
+ end
382
+
383
+ def find_impersonated_user
384
+ return unless <%= identity.current_class %>.true_user.admin?
385
+
386
+ <%= identity.class_name %>.find_by(id: session[:<%= session_key(:impersonated_user_id) %>]) # .find raises, .find_by returns nil
387
+ end
388
+
389
+ def impersonation_timed_out?
390
+ return false unless impersonating?
391
+
392
+ session[:<%= session_key(:impersonation_started_at) %>].to_i < <%= identity.config_reference %>.impersonation.duration.ago.to_i
393
+ end
394
+
395
+ def expire_impersonation!
396
+ stop_impersonating_user
397
+ redirect_to <%= admin_helper("root_path") %>, alert: "Impersonation timed out.", status: :see_other
398
+ end
399
+
400
+ def impersonate_user(user)
401
+ <%= identity.current_class %>.impersonated_user = user
402
+ <%- if session_carries_team? -%>
403
+ <%= identity.current_class %>.session&.update!(team: user.primary_team)
404
+ <%- end -%>
405
+ session[:<%= session_key(:impersonated_user_id) %>] = user.id
406
+ session[:<%= session_key(:impersonation_started_at) %>] = Time.current.to_i
407
+ <%- if trackable? -%>
408
+ user.record_event("<%= identity.singular %>.impersonation_started", actor: <%= identity.current_class %>.true_user)
409
+ <%- end -%>
410
+ end
411
+
412
+ def stop_impersonating_user
413
+ <%- if trackable? -%>
414
+ <%= identity.current_class %>.impersonated_user&.record_event("<%= identity.singular %>.impersonation_stopped", actor: <%= identity.current_class %>.true_user)
415
+ <%- end -%>
416
+ <%= identity.current_class %>.impersonated_user = nil
417
+ session.delete(:<%= session_key(:impersonated_user_id) %>)
418
+ session.delete(:<%= session_key(:impersonation_started_at) %>)
419
+ end
420
+ <%- end -%>
421
+ end
@@ -0,0 +1,48 @@
1
+ module <%= identity.authorization_concern %>
2
+ extend ActiveSupport::Concern
3
+ <%- if teams? -%>
4
+
5
+ included do
6
+ <%- unless middleware_teams? -%>
7
+ before_action :set_current_team, if: :authenticated?
8
+ <%- end -%>
9
+ before_action :team_members_only, if: :authenticated?
10
+ end
11
+ <%- end -%>
12
+ <%- if adminable? -%>
13
+
14
+ class_methods do
15
+ def admins_only
16
+ not_found_unless { <%= identity.current_class %>.user.admin? }
17
+ end
18
+ end
19
+ <%- end -%>
20
+ <%- if scope_teams? -%>
21
+
22
+ def default_url_options
23
+ super.merge(path_params: { team_id: <%= identity.current_class %>.team&.id }.compact)
24
+ end
25
+ <%- end -%>
26
+ <%- if teams? -%>
27
+
28
+ private
29
+ <%- if scope_teams? -%>
30
+
31
+ def set_current_team
32
+ <%= identity.current_class %>.team = <%= identity.team_class %>.find(request.path_parameters[:team_id]) if request.path_parameters[:team_id]
33
+ end
34
+ <%- elsif session_carries_team? -%>
35
+
36
+ def set_current_team
37
+ <%= identity.current_class %>.team = <%= identity.current_class %>.session.team
38
+ end
39
+ <%- end -%>
40
+
41
+ def team_members_only
42
+ return unless <%= identity.current_class %>.team
43
+ return if <%= identity.current_class %>.user.member_of?(<%= identity.current_class %>.team)
44
+
45
+ raise ActiveRecord::RecordNotFound
46
+ end
47
+ <%- end -%>
48
+ end
@@ -0,0 +1,11 @@
1
+ module Captchable
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ def captcha_verified?
7
+ return @captcha_verified if defined?(@captcha_verified)
8
+
9
+ @captcha_verified = Captcha.verify(params[:captcha_token], ip: request.remote_ip)
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class <%= identity.nested_module("ImpersonationsController") %> < <%= identity.controller_superclass %>
2
+ not_found_unless :impersonating?
3
+
4
+ def destroy
5
+ stop_impersonating_user
6
+ redirect_to <%= admin_helper("root_path") %>, status: :see_other
7
+ end
8
+ end