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,180 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::RegistrationsControllerTest < ActionDispatch::IntegrationTest
4
+ include ActionMailer::TestHelper
5
+
6
+ setup { @existing = <%= identity.plural %>(:alice) }
7
+ <%- if invite_only? -%>
8
+
9
+ test "sign up is gated behind an invitation" do
10
+ get <%= identity.route_scope %>_sign_up_path
11
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
12
+
13
+ assert_no_difference -> { <%= identity.class_name %>.count } do
14
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
15
+ end
16
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
17
+ end
18
+ <%- else -%>
19
+
20
+ test "sign up page renders" do
21
+ get <%= identity.route_scope %>_sign_up_path
22
+ assert_response :success
23
+ end
24
+
25
+ <%- if typed_staged_registration? -%>
26
+ test "a valid sign up stages a claim without creating an account" do
27
+ assert_difference -> { <%= identity.pending_registration_class %>.count }, 1 do
28
+ assert_no_difference -> { <%= identity.class_name %>.count } do
29
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
30
+ end
31
+ end
32
+ end
33
+ <%- if password_on_sign_up_form? -%>
34
+
35
+ # A claim carries the password typed beside it. Superseding would hand whoever
36
+ # signs up last the credential the first person's link installs, so claims stand
37
+ # on their own: your link always mints the password you typed.
38
+ test "a second claim on one identifier leaves the first standing" do
39
+ claimed = sign_up_params
40
+ post <%= identity.route_scope %>_sign_up_path, params: claimed
41
+ first = <%= identity.pending_registration_class %>.last
42
+
43
+ assert_difference -> { <%= identity.pending_registration_class %>.count }, 1 do
44
+ post <%= identity.route_scope %>_sign_up_path, params: claimed
45
+ end
46
+ assert <%= identity.pending_registration_class %>.exists?(first.id)
47
+ end
48
+ <%- end -%>
49
+ <%- else -%>
50
+ test "a valid sign up creates the account" do
51
+ assert_difference -> { <%= identity.class_name %>.count }, 1 do
52
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
53
+ end
54
+ end
55
+ <%- end -%>
56
+ <%- if principals.phone -%>
57
+ <%- if typed_phone_gated_registration? && coy? && principals.email.nil? -%>
58
+
59
+ test "an already-registered phone is answered like a free one" do
60
+ assert_no_difference -> { <%= identity.class_name %>.count } do
61
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(phone: @existing.phone)
62
+ end
63
+ assert_redirected_to <%= identity.route_scope %>_verify_phone_path
64
+ assert cookies[:<%= identity.cookie_name %>].blank?
65
+ end
66
+ <%- else -%>
67
+
68
+ <%- if typed_staged_registration? && coy? -%>
69
+ test "an already-registered phone is staged like any other" do
70
+ assert_difference -> { <%= identity.pending_registration_class %>.count }, 1 do
71
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(phone: @existing.phone)
72
+ end
73
+ assert cookies[:<%= identity.cookie_name %>].blank?
74
+ end
75
+ <%- else -%>
76
+ test "an already-registered phone is rejected" do
77
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(phone: @existing.phone)
78
+ assert_response :unprocessable_entity
79
+ end
80
+ <%- end -%>
81
+ <%- end -%>
82
+ <%- if typed_phone_gated_registration? && coy? && principals.email&.optional? -%>
83
+
84
+ test "an already-registered phone is answered like a free one when no email is given" do
85
+ assert_no_difference -> { <%= identity.class_name %>.count } do
86
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(phone: @existing.phone, email: "")
87
+ end
88
+ assert_redirected_to <%= identity.route_scope %>_verify_phone_path
89
+ assert cookies[:<%= identity.cookie_name %>].blank?
90
+ end
91
+ <%- end -%>
92
+ <%- end -%>
93
+ <%- if email_verifiable? && coy? -%>
94
+
95
+ test "a fresh sign up does not sign in until the email is verified" do
96
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
97
+ assert_redirected_to <%= identity.route_scope %>_new_verify_email_path
98
+ assert cookies[:<%= identity.cookie_name %>].blank?
99
+ end
100
+
101
+ test "signing up with an already-registered email mails nobody" do
102
+ assert_no_enqueued_emails do
103
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email: @existing.email)
104
+ end
105
+ end
106
+ <%- elsif email_verifiable? -%>
107
+
108
+ test "a fresh sign up does not sign in until the email is verified" do
109
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
110
+ assert_redirected_to <%= identity.route_scope %>_<%= email_gated_registration? ? "new_verify_email" : "sign_up" %>_path
111
+ assert cookies[:<%= identity.cookie_name %>].blank?
112
+ end
113
+
114
+ test "signing up with an already-registered email is rejected" do
115
+ assert_no_difference -> { <%= identity.class_name %>.count } do
116
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email: @existing.email)
117
+ end
118
+ assert_response :unprocessable_entity
119
+ assert_includes response.body, "has already been taken"
120
+ assert cookies[:<%= identity.cookie_name %>].blank?
121
+ end
122
+ <%- else -%>
123
+
124
+ <%- if typed_phone_gated_registration? && principals.email.nil? -%>
125
+ test "a valid sign up withholds the session until the phone is confirmed" do
126
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
127
+ assert_redirected_to <%= identity.route_scope %>_verify_phone_path
128
+ assert cookies[:<%= identity.cookie_name %>].blank?
129
+ end
130
+ <%- else -%>
131
+ test "a valid sign up signs in immediately" do
132
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
133
+ assert cookies[:<%= identity.cookie_name %>].present?
134
+ end
135
+ <%- end -%>
136
+ <%- if principals.email -%>
137
+
138
+ test "an already-registered email is rejected" do
139
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email: @existing.email)
140
+ assert_response :unprocessable_entity
141
+ end
142
+ <%- end -%>
143
+ <%- end -%>
144
+ <%- end -%>
145
+ <%- if channel_less_registration? -%>
146
+
147
+ test "a sign up with no channel at all skips verification" do
148
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(<%= principals.email ? %(email: "", ) : "" %>phone: "")
149
+ <%- if credential_staged_registration? -%>
150
+ assert_redirected_to <%= credential_enrollment_path %>
151
+ assert cookies[:<%= identity.cookie_name %>].blank?
152
+ <%- else -%>
153
+ assert cookies[:<%= identity.cookie_name %>].present?
154
+ <%- end -%>
155
+ end
156
+ <%- end -%>
157
+ <%- if contactable_validation? -%>
158
+
159
+ test "a sign up with no channel at all is refused" do
160
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email: "", phone: "")
161
+
162
+ assert_response :unprocessable_entity
163
+ assert cookies[:<%= identity.cookie_name %>].blank?
164
+ end
165
+ <%- end -%>
166
+ <%- if typed_phone_gated_registration? && principals.phone.optional? && principals.email&.optional? -%>
167
+
168
+ test "a sign up with a phone and no email must verify the phone first" do
169
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(<%= principals.email ? %(email: "") : "" %>)
170
+ assert_redirected_to <%= identity.route_scope %>_verify_phone_path
171
+ assert cookies[:<%= identity.cookie_name %>].blank?
172
+ end
173
+ <%- end -%>
174
+
175
+ private
176
+
177
+ def sign_up_params(**overrides)
178
+ <%= sign_up_fixture_literal(indent: 4) %>.merge(overrides)
179
+ end
180
+ end
@@ -0,0 +1,124 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::SessionsControllerTest < ActionDispatch::IntegrationTest
4
+ setup { @user = <%= identity.plural %>(:alice) }
5
+
6
+ <%- if password? -%>
7
+ test "signs in with valid credentials" do
8
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
9
+ assert cookies[:<%= identity.cookie_name %>].present?
10
+ end
11
+
12
+ test "rejects invalid credentials without revealing existence" do
13
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "wrong-password" }
14
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path(<%= login_hint_param %>: @user.<%= login_field %>)
15
+ assert cookies[:<%= identity.cookie_name %>].blank?
16
+ end
17
+ <%- if deadboltable? -%>
18
+
19
+ test "deadbolts the password sign-in door after too many failed attempts" do
20
+ @user.update!(failed_attempts: <%= identity.config_reference %>.deadbolt.maximum_attempts - 1)
21
+
22
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "wrong-password" }
23
+ assert @user.reload.deadbolted?
24
+
25
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
26
+ assert cookies[:<%= identity.cookie_name %>].blank?, "the right password must not open a deadbolted door"
27
+ end
28
+
29
+ test "the deadbolt lifts itself once the window passes" do
30
+ @user.deadbolt!
31
+
32
+ travel <%= identity.config_reference %>.deadbolt.duration + 1.minute
33
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
34
+ assert cookies[:<%= identity.cookie_name %>].present?
35
+ end
36
+
37
+ test "an expired deadbolt starts the count over" do
38
+ @user.update!(failed_attempts: <%= identity.config_reference %>.deadbolt.maximum_attempts - 1)
39
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "wrong-password" }
40
+ assert @user.reload.deadbolted?
41
+
42
+ travel <%= identity.config_reference %>.deadbolt.duration + 1.minute
43
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "wrong-password" }
44
+ assert_not @user.reload.deadbolted?
45
+ assert_equal 1, @user.failed_attempts
46
+ end
47
+
48
+ test "deadbolting leaves the sessions the account already holds alone" do
49
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
50
+ assert @user.sessions.any?
51
+
52
+ @user.deadbolt!
53
+ assert @user.sessions.reload.any?
54
+ end
55
+
56
+ test "resets failed attempts after a successful sign in" do
57
+ @user.update!(failed_attempts: 3)
58
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
59
+ assert_equal 0, @user.reload.failed_attempts
60
+ end
61
+ <%- end -%>
62
+ <%- if bannable? && password? -%>
63
+
64
+ test "a suspended account cannot sign in and is told so" do
65
+ @user.ban!
66
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
67
+ assert cookies[:<%= identity.cookie_name %>].blank?
68
+ assert_equal "Your account has been suspended.", flash[:alert]
69
+ end
70
+
71
+ test "suspending an account ends the sessions it already holds" do
72
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
73
+ assert @user.sessions.any?
74
+
75
+ @user.ban!
76
+ assert_empty @user.sessions.reload
77
+ end
78
+ <%- end -%>
79
+
80
+ test "signs out" do
81
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
82
+ delete <%= identity.route_scope %>_sign_out_path
83
+ assert_redirected_to root_url
84
+ end
85
+ <%- if multi_login? && password? -%>
86
+
87
+ <%- if principals.email -%>
88
+ test "signs in by email through the shared login field" do
89
+ post <%= identity.route_scope %>_sign_in_path, params: { login: @user.email, password: "quilted-lantern-moss-97" }
90
+ assert cookies[:<%= identity.cookie_name %>].present?
91
+ end
92
+ <%- end -%>
93
+ <%- if principals.phone -%>
94
+
95
+ test "signs in by phone through the shared login field" do
96
+ post <%= identity.route_scope %>_sign_in_path, params: { login: @user.phone, password: "quilted-lantern-moss-97" }
97
+ assert cookies[:<%= identity.cookie_name %>].present?
98
+ end
99
+ <%- end -%>
100
+ <%- if principals.username -%>
101
+
102
+ test "username sign-in folds case" do
103
+ post <%= identity.route_scope %>_sign_in_path, params: { login: @user.username.upcase, password: "quilted-lantern-moss-97" }
104
+ assert cookies[:<%= identity.cookie_name %>].present?
105
+ end
106
+ <%- end -%>
107
+ <%- end -%>
108
+ <%- else -%>
109
+ test "signs out" do
110
+ sign_in_as @user
111
+
112
+ delete <%= identity.route_scope %>_sign_out_path
113
+
114
+ assert_redirected_to root_url
115
+ assert cookies[:<%= identity.cookie_name %>].blank?
116
+ end
117
+
118
+ private
119
+
120
+ def sign_in_as(user)
121
+ <%= test_sign_in_body %>
122
+ end
123
+ <%- end -%>
124
+ end
@@ -0,0 +1,111 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::SmsSessionsControllerTest < ActionDispatch::IntegrationTest
4
+ include ActiveJob::TestHelper
5
+
6
+ setup { @user = <%= identity.plural %>(:alice) }
7
+
8
+ test "requesting a code for a known number texts one" do
9
+ assert_enqueued_jobs 1, only: SmsDeliveryJob do
10
+ request_code @user.phone
11
+ end
12
+
13
+ assert_redirected_to <%= identity.route_scope %>_sms_sign_in_code_path
14
+ assert cookies[:<%= identity.cookie_name %>].blank?, "asking for a code must not sign anyone in"
15
+ end
16
+
17
+ test "the texted code signs them in" do
18
+ request_code <%= identity.plural %>(:bob).phone
19
+
20
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: issued_code }
21
+
22
+ assert cookies[:<%= identity.cookie_name %>].present?
23
+ <%- unless teams? -%>
24
+ assert_redirected_to <%= identity.nested_helper("root_url") %>
25
+ <%- end -%>
26
+ end
27
+
28
+ test "a wrong code signs nobody in" do
29
+ request_code @user.phone
30
+
31
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: "000000" }
32
+
33
+ assert_response :unprocessable_entity
34
+ assert cookies[:<%= identity.cookie_name %>].blank?
35
+ end
36
+
37
+ test "a redeemed code cannot be used twice" do
38
+ request_code @user.phone
39
+ code = issued_code
40
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: code }
41
+
42
+ delete <%= identity.route_scope %>_sign_out_path
43
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: code }
44
+
45
+ assert cookies[:<%= identity.cookie_name %>].blank?
46
+ end
47
+
48
+ test "guessing past the attempt cap destroys the code" do
49
+ request_code @user.phone
50
+ code = issued_code
51
+
52
+ (<%= identity.config_reference %>.sms.max_attempts + 1).times do
53
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: "000000" }
54
+ end
55
+
56
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: code }
57
+ assert cookies[:<%= identity.cookie_name %>].blank?, "the real code must be dead once the budget is spent"
58
+ end
59
+
60
+ test "an expired code signs nobody in" do
61
+ request_code @user.phone
62
+ code = issued_code
63
+
64
+ travel <%= identity.config_reference %>.tokens.sms_code + 1.minute
65
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: code }
66
+
67
+ assert cookies[:<%= identity.cookie_name %>].blank?
68
+ end
69
+ <%- if coy? -%>
70
+
71
+ test "requesting a code for an unknown number gives nothing away" do
72
+ assert_no_enqueued_jobs only: SmsDeliveryJob do
73
+ request_code "+14158264010"
74
+ end
75
+
76
+ assert_redirected_to <%= identity.route_scope %>_sms_sign_in_code_path
77
+ end
78
+
79
+ test "the code page behaves the same with nothing pending" do
80
+ request_code "+14158264010"
81
+
82
+ get <%= identity.route_scope %>_sms_sign_in_code_path
83
+ assert_response :success
84
+
85
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: "000000" }
86
+ assert_response :unprocessable_entity
87
+ assert cookies[:<%= identity.cookie_name %>].blank?
88
+ end
89
+
90
+ test "an unknown number discards a parked code just as a known one does" do
91
+ request_code @user.phone
92
+ parked = issued_code
93
+
94
+ # Replace any parked code for every request; retaining it would reveal whether the submitted number has an account.
95
+ request_code "+14158264010"
96
+
97
+ patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: parked }
98
+ assert cookies[:<%= identity.cookie_name %>].blank?, "the parked code must not still sign anyone in"
99
+ end
100
+ <%- end -%>
101
+
102
+ private
103
+
104
+ def request_code(phone)
105
+ post <%= identity.route_scope %>_sms_sign_in_path, params: { phone: phone }
106
+ end
107
+
108
+ def issued_code
109
+ enqueued_jobs.reverse.filter_map { |job| job.to_s[/(\d{6}) is your/, 1] }.first
110
+ end
111
+ end
@@ -0,0 +1,110 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::SudosControllerTest < ActionDispatch::IntegrationTest
4
+ setup { @user = <%= identity.plural %>(:bob) }
5
+ <%- if sudo_test_mode == :open -%>
6
+
7
+ test "a challenge this account cannot answer fails open" do
8
+ sign_in_as @user
9
+
10
+ assert_difference -> { <%= identity.class_name %>.count }, -1 do
11
+ delete <%= identity.settings_helper_prefix %>_user_path, params: { confirmation: "DELETE ACCOUNT" }
12
+ end
13
+ end
14
+ <%- else -%>
15
+
16
+ test "the sudo page presents the challenge" do
17
+ sign_in_as @user
18
+ get new_<%= identity.route_scope %>_sudo_path
19
+ assert_response :success
20
+ end
21
+
22
+ test "a wrong answer keeps you on the sudo page and stamps nothing" do
23
+ sign_in_as @user
24
+ session = <%= identity.session_class %>.last
25
+
26
+ post <%= identity.route_scope %>_sudo_path, params: { sudo_password: "wrong-password" }
27
+
28
+ assert_redirected_to new_<%= identity.route_scope %>_sudo_path
29
+ assert_nil session.reload.sudo_at
30
+ end
31
+
32
+ test "the right answer stamps the session and proceeds" do
33
+ sign_in_as @user
34
+ session = <%= identity.session_class %>.last
35
+
36
+ post <%= identity.route_scope %>_sudo_path, params: { <%= sudo_test_answer %> }
37
+
38
+ assert_redirected_to <%= identity.settings_helper_prefix %>_url
39
+ assert_not_nil session.reload.sudo_at
40
+ end
41
+
42
+ test "the request cannot choose where confirming sends you" do
43
+ sign_in_as @user
44
+
45
+ post <%= identity.route_scope %>_sudo_path,
46
+ params: { <%= sudo_test_answer %>, proceed_to_url: root_url, message: "Confirm to claim your refund." }
47
+
48
+ assert_redirected_to <%= identity.settings_helper_prefix %>_url
49
+ end
50
+
51
+ test "a guarded action with no answer changes nothing" do
52
+ sign_in_as @user
53
+
54
+ assert_no_difference -> { <%= identity.class_name %>.count } do
55
+ delete <%= identity.settings_helper_prefix %>_user_path, params: { confirmation: "DELETE ACCOUNT" }
56
+ end
57
+ assert_response :redirect
58
+ end
59
+
60
+ test "a guarded action with a wrong answer changes nothing" do
61
+ sign_in_as @user
62
+
63
+ assert_no_difference -> { <%= identity.class_name %>.count } do
64
+ delete <%= identity.settings_helper_prefix %>_user_path,
65
+ params: { confirmation: "DELETE ACCOUNT", sudo_password: "wrong-password" }
66
+ end
67
+ assert_response :redirect
68
+ end
69
+
70
+ test "a guarded action with the right answer runs in one request" do
71
+ sign_in_as @user
72
+
73
+ assert_difference -> { <%= identity.class_name %>.count }, -1 do
74
+ delete <%= identity.settings_helper_prefix %>_user_path,
75
+ params: { confirmation: "DELETE ACCOUNT", <%= sudo_test_answer %> }
76
+ end
77
+ assert_redirected_to root_path
78
+ end
79
+
80
+ test "an action answered inline still stamps, so an area opens too" do
81
+ sign_in_as @user
82
+ session = <%= identity.session_class %>.last
83
+ <%- if email_changeable? -%>
84
+
85
+ patch <%= identity.settings_helper_prefix %>_email_path,
86
+ params: { email: "changed-#{SecureRandom.hex(4)}@example.com", <%= sudo_test_answer %> }
87
+
88
+ assert_not_nil session.reload.sudo_at
89
+ <%- elsif phone_changeable? -%>
90
+
91
+ patch <%= identity.settings_helper_prefix %>_phone_path,
92
+ params: { phone: "+14158264010", <%= sudo_test_answer %> }
93
+
94
+ assert_not_nil session.reload.sudo_at
95
+ <%- else -%>
96
+
97
+ delete <%= identity.settings_helper_prefix %>_user_path,
98
+ params: { confirmation: "DELETE ACCOUNT", <%= sudo_test_answer %> }
99
+
100
+ assert_not_nil session.reload.sudo_at
101
+ <%- end -%>
102
+ end
103
+ <%- end -%>
104
+
105
+ private
106
+
107
+ def sign_in_as(user)
108
+ <%= test_sign_in_body %>
109
+ end
110
+ end
@@ -0,0 +1,13 @@
1
+ alice_acme:
2
+ <%= identity.singular %>: alice
3
+ team: acme
4
+
5
+ bob_acme:
6
+ <%= identity.singular %>: bob
7
+ team: acme
8
+ <%- if adminable? -%>
9
+
10
+ carol_acme:
11
+ <%= identity.singular %>: carol
12
+ team: acme
13
+ <%- end -%>
@@ -0,0 +1,68 @@
1
+ alice:
2
+ <%- if principals.email -%>
3
+ email: alice@example.com
4
+ <%- end -%>
5
+ <%- if principals.phone -%>
6
+ phone: "+14155550141"
7
+ <%- end -%>
8
+ <%- if principals.username -%>
9
+ username: alice
10
+ <%- end -%>
11
+ <%- if password? -%>
12
+ password_digest: <%%= BCrypt::Password.create("quilted-lantern-moss-97", cost: 4) %>
13
+ <%- end -%>
14
+ <%- if totp? -%>
15
+ totp_secret: <%%= ROTP::Base32.random %>
16
+ <%- end -%>
17
+ <%- if adminable? -%>
18
+ admin: true
19
+ <%- end -%>
20
+ <%- if webauthn_credentials? -%>
21
+ webauthn_handle: <%%= WebAuthn.generate_user_id %>
22
+ <%- end -%>
23
+
24
+ bob:
25
+ <%- if principals.email -%>
26
+ email: bob@example.com
27
+ <%- end -%>
28
+ <%- if principals.phone -%>
29
+ phone: "+16505550142"
30
+ <%- end -%>
31
+ <%- if principals.username -%>
32
+ username: bob
33
+ <%- end -%>
34
+ <%- if password? -%>
35
+ password_digest: <%%= BCrypt::Password.create("quilted-lantern-moss-97", cost: 4) %>
36
+ <%- end -%>
37
+ <%- if totp? -%>
38
+ totp_secret: <%%= ROTP::Base32.random %>
39
+ <%- end -%>
40
+ <%- if adminable? -%>
41
+ admin: false
42
+ <%- end -%>
43
+ <%- if webauthn_credentials? -%>
44
+ webauthn_handle: <%%= WebAuthn.generate_user_id %>
45
+ <%- end -%>
46
+ <%- if adminable? -%>
47
+
48
+ carol:
49
+ <%- if principals.email -%>
50
+ email: carol@example.com
51
+ <%- end -%>
52
+ <%- if principals.phone -%>
53
+ phone: "+12125550143"
54
+ <%- end -%>
55
+ <%- if principals.username -%>
56
+ username: carol
57
+ <%- end -%>
58
+ <%- if password? -%>
59
+ password_digest: <%%= BCrypt::Password.create("quilted-lantern-moss-97", cost: 4) %>
60
+ <%- end -%>
61
+ <%- if totp? -%>
62
+ totp_secret: <%%= ROTP::Base32.random %>
63
+ <%- end -%>
64
+ admin: true
65
+ <%- if webauthn_credentials? -%>
66
+ webauthn_handle: <%%= WebAuthn.generate_user_id %>
67
+ <%- end -%>
68
+ <%- end -%>