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,143 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::MultiFactorAuthentication::ChallengeTest < ActionDispatch::IntegrationTest
4
+ <%- if sms_second_factor? -%>
5
+ include ActiveJob::TestHelper
6
+
7
+ <%- end -%>
8
+ setup do
9
+ @user = <%= identity.plural %>(:bob)
10
+ <%= enroll_second_factor_expr("@user") %>
11
+ end
12
+ <%- if password? -%>
13
+
14
+ test "signing in with a second factor enrolled diverts to the challenge" do
15
+ sign_in_with_password
16
+ assert_redirected_to <%= enrolled_second_factor_challenge_path %>
17
+ assert cookies[:<%= identity.cookie_name %>].blank?, "no session until the second factor is cleared"
18
+ end
19
+ <%- if totp? -%>
20
+
21
+ test "a correct TOTP code clears the challenge and signs in" do
22
+ sign_in_with_password
23
+ post <%= identity.route_scope %>_mfa_challenge_totp_path, params: { code: ROTP::TOTP.new(@user.totp_secret).now }
24
+ assert cookies[:<%= identity.cookie_name %>].present?
25
+ end
26
+
27
+ test "a wrong TOTP code does not sign in" do
28
+ sign_in_with_password
29
+ post <%= identity.route_scope %>_mfa_challenge_totp_path, params: { code: "000000" }
30
+ assert_redirected_to new_<%= identity.route_scope %>_mfa_challenge_totp_path
31
+ assert cookies[:<%= identity.cookie_name %>].blank?
32
+ end
33
+
34
+ test "the authenticator challenge refuses an account that is not enrolled" do
35
+ sign_in_with_password
36
+ @user.update!(totp_enrolled_at: nil)
37
+
38
+ post <%= identity.route_scope %>_mfa_challenge_totp_path, params: { code: ROTP::TOTP.new(@user.totp_secret).now }
39
+
40
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
41
+ assert cookies[:<%= identity.cookie_name %>].blank?,
42
+ "every account carries a totp_secret from birth, enrolled or not"
43
+ end
44
+ <%- end -%>
45
+
46
+ <%- if sms_second_factor? -%>
47
+
48
+ test "the texted code clears the challenge and signs in" do
49
+ enroll_sms
50
+ sign_in_with_password
51
+ get new_<%= identity.route_scope %>_mfa_challenge_sms_path
52
+
53
+ patch <%= identity.route_scope %>_mfa_challenge_sms_path, params: { code: issued_code }
54
+
55
+ assert cookies[:<%= identity.cookie_name %>].present?
56
+ end
57
+
58
+ test "a wrong texted code does not sign in" do
59
+ enroll_sms
60
+ sign_in_with_password
61
+ get new_<%= identity.route_scope %>_mfa_challenge_sms_path
62
+
63
+ patch <%= identity.route_scope %>_mfa_challenge_sms_path, params: { code: "000000" }
64
+
65
+ assert_response :unprocessable_entity
66
+ assert cookies[:<%= identity.cookie_name %>].blank?
67
+ end
68
+
69
+ test "the challenge refuses a factor the account no longer holds" do
70
+ enroll_sms
71
+ sign_in_with_password
72
+ get new_<%= identity.route_scope %>_mfa_challenge_sms_path
73
+ code = issued_code
74
+ @user.update!(sms_enrolled_at: nil)
75
+
76
+ patch <%= identity.route_scope %>_mfa_challenge_sms_path, params: { code: code }
77
+
78
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
79
+ assert cookies[:<%= identity.cookie_name %>].blank?,
80
+ "a handoff says the first factor was met, not which second factor to accept"
81
+ end
82
+
83
+ test "landing on the challenge twice does not text a second code" do
84
+ enroll_sms
85
+ sign_in_with_password
86
+
87
+ assert_enqueued_jobs 1, only: SmsDeliveryJob do
88
+ get new_<%= identity.route_scope %>_mfa_challenge_sms_path
89
+ get new_<%= identity.route_scope %>_mfa_challenge_sms_path
90
+ end
91
+ end
92
+ <%- end -%>
93
+
94
+ test "a valid recovery code clears the challenge and signs in" do
95
+ @user.recovery_codes.create!(code: "recovery-code-1")
96
+ sign_in_with_password
97
+ post <%= identity.route_scope %>_mfa_challenge_recovery_codes_path, params: { code: "recovery-code-1" }
98
+ assert cookies[:<%= identity.cookie_name %>].present?
99
+ end
100
+
101
+ test "a recovery code is single-use" do
102
+ @user.recovery_codes.create!(code: "recovery-code-1")
103
+ sign_in_with_password
104
+ post <%= identity.route_scope %>_mfa_challenge_recovery_codes_path, params: { code: "recovery-code-1" }
105
+ delete <%= identity.route_scope %>_sign_out_path
106
+
107
+ sign_in_with_password
108
+ post <%= identity.route_scope %>_mfa_challenge_recovery_codes_path, params: { code: "recovery-code-1" }
109
+ assert cookies[:<%= identity.cookie_name %>].blank?, "a spent code must not sign in again"
110
+ end
111
+
112
+ test "recovery codes are never stored in a readable form" do
113
+ @user.recovery_codes.create!(code: "recovery-code-1")
114
+ assert_not_includes @user.recovery_codes.pluck(:code_digest), "recovery-code-1"
115
+ end
116
+ <%- if bannable? -%>
117
+
118
+ test "a suspended account is barred before the challenge is issued" do
119
+ @user.ban!
120
+ sign_in_with_password
121
+
122
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
123
+ assert_equal "Your account has been suspended.", flash[:alert]
124
+ end
125
+ <%- end -%>
126
+
127
+ private
128
+
129
+ def sign_in_with_password
130
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: @user.<%= login_field %>, password: "quilted-lantern-moss-97" }
131
+ end
132
+ <%- if sms_second_factor? -%>
133
+
134
+ def enroll_sms
135
+ @user.update!(sms_enrolled_at: Time.current)
136
+ end
137
+
138
+ def issued_code
139
+ enqueued_jobs.reverse.filter_map { |job| job.to_s[/(\d{6}) is your/, 1] }.first
140
+ end
141
+ <%- end -%>
142
+ <%- end -%>
143
+ end
@@ -0,0 +1,138 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::Omniauth::RegistrationsControllerTest < ActionDispatch::IntegrationTest
4
+ <%- if omniauth_registration_verifies_phone? -%>
5
+ include ActiveJob::TestHelper
6
+
7
+ <%- end -%>
8
+ setup do
9
+ OmniAuth.config.test_mode = true
10
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(
11
+ provider: "developer", uid: "first-login",
12
+ info: { email: "provider-user@example.com" }
13
+ )
14
+ <%- if invite_only? -%>
15
+ invitation = <%= identity.invitation_class %>.create!(sent_to: "provider-user@example.com"<%= %(, sent_to_channel: "email") if multi_channel_invitable? %>, inviter: <%= identity.plural %>(:alice))
16
+ get <%= identity.namespaced? ? "#{identity.route_scope}_accept_invitation_path" : "accept_invitation_path" %>(token: invitation.generate_token_for(:invitation))
17
+ <%- end -%>
18
+ end
19
+
20
+ teardown do
21
+ OmniAuth.config.mock_auth[:developer] = nil
22
+ OmniAuth.config.test_mode = false
23
+ end
24
+
25
+ test "a first-time provider login is sent to the completion form, no account yet" do
26
+ assert_no_difference [ "<%= identity.class_name %>.count", "<%= identity.omniauth_identity_class %>.count" ] do
27
+ post "<%= omniauth_callback_test_path %>"
28
+ end
29
+ assert_redirected_to new_<%= identity.route_scope %>_omniauth_registration_path
30
+ end
31
+
32
+ <%- if omniauth_registration_verifies_phone? -%>
33
+ test "the number the form collects is verified before the account exists" do
34
+ post "<%= omniauth_callback_test_path %>"
35
+
36
+ assert_no_difference "<%= identity.class_name %>.count" do
37
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
38
+ end
39
+ assert_redirected_to <%= identity.route_scope %>_verify_phone_path
40
+
41
+ code = enqueued_jobs.reverse_each.filter_map { |job| job.to_s[/(\d{6}) is your/, 1] }.first
42
+ assert_difference [ "<%= identity.class_name %>.count", "<%= identity.omniauth_identity_class %>.count" ], 1 do
43
+ patch <%= identity.route_scope %>_verify_phone_path, params: { code: code }
44
+ end
45
+
46
+ user = <%= identity.class_name %>.last
47
+ assert user.phone.present?
48
+ assert_equal "developer", user.omniauth_identities.sole.provider
49
+ assert cookies[:<%= identity.cookie_name %>].present?
50
+ end
51
+ <%- else -%>
52
+ test "completing the form creates the account with the provider's email" do
53
+ post "<%= omniauth_callback_test_path %>"
54
+ assert_difference [ "<%= identity.class_name %>.count", "<%= identity.omniauth_identity_class %>.count" ], 1 do
55
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
56
+ end
57
+ user = <%= identity.class_name %>.last
58
+ assert_equal "developer", user.omniauth_identities.sole.provider
59
+ <%- if principals.email -%>
60
+ assert_equal "provider-user@example.com", user.email
61
+ <%- end -%>
62
+ assert cookies[:<%= identity.cookie_name %>].present?
63
+ end
64
+ <%- if principals.email -%>
65
+
66
+ test "completing the form with an address that already has an account is refused" do
67
+ <%= identity.plural %>(:alice).update_column(:email, "provider-user@example.com")
68
+
69
+ post "<%= omniauth_callback_test_path %>"
70
+ assert_no_difference "<%= identity.class_name %>.count" do
71
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
72
+ end
73
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
74
+ assert_nil cookies[:<%= identity.cookie_name %>].presence
75
+ end
76
+ <%- end -%>
77
+
78
+ test "a provider account that finished signing up elsewhere is sent to sign in" do
79
+ post "<%= omniauth_callback_test_path %>"
80
+ <%= identity.plural %>(:alice).omniauth_identities.create!(provider: "developer", uid: "first-login")
81
+
82
+ assert_no_difference "<%= identity.class_name %>.count" do
83
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
84
+ end
85
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
86
+ end
87
+ <%- end -%>
88
+ <%- if principals.username -%>
89
+
90
+ test "a taken username is refused on the form, and the sign-up waits" do
91
+ post "<%= omniauth_callback_test_path %>"
92
+
93
+ assert_no_difference "<%= identity.class_name %>.count" do
94
+ post <%= identity.route_scope %>_omniauth_registration_path,
95
+ params: <%= omniauth_profile_params_literal %>.merge(username: <%= identity.plural %>(:alice).username)
96
+ end
97
+ assert_response :unprocessable_entity
98
+ end
99
+ <%- end -%>
100
+
101
+ <%- if principals.email&.required? -%>
102
+ test "a provider that reports no address is turned away at the callback" do
103
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(
104
+ provider: "developer", uid: "addressless", info: {}
105
+ )
106
+
107
+ assert_no_difference "<%= identity.class_name %>.count" do
108
+ post "<%= omniauth_callback_test_path %>"
109
+ end
110
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
111
+ assert_equal "That provider didn't share an email address, try signing up another way.", flash[:alert]
112
+ end
113
+
114
+ <%- end -%>
115
+ test "the form without a provider sign-up in progress bounces to sign in" do
116
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
117
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
118
+ end
119
+ <%- if channel_less_registration? -%>
120
+
121
+ test "a provider that reports no address completes the sign-up outright" do
122
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(
123
+ provider: "developer", uid: "addressless", info: {}
124
+ )
125
+ post "<%= omniauth_callback_test_path %>"
126
+
127
+ assert_difference [ "<%= identity.class_name %>.count", "<%= identity.omniauth_identity_class %>.count" ], 1 do
128
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
129
+ end
130
+
131
+ user = <%= identity.class_name %>.last
132
+ assert_nil user.email
133
+ assert_equal "developer", user.omniauth_identities.sole.provider
134
+ assert_equal [ :omniauth ], user.sign_in_methods
135
+ assert cookies[:<%= identity.cookie_name %>].present?
136
+ end
137
+ <%- end -%>
138
+ end
@@ -0,0 +1,97 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::OmniauthControllerTest < ActionDispatch::IntegrationTest
4
+ setup do
5
+ OmniAuth.config.test_mode = true
6
+ mock_provider(uid: "returning-user", email: "bob@example.com")
7
+ @user = <%= identity.plural %>(:bob)
8
+ @user.omniauth_identities.create!(provider: "developer", uid: "returning-user")
9
+ end
10
+
11
+ teardown do
12
+ OmniAuth.config.mock_auth[:developer] = nil
13
+ OmniAuth.config.test_mode = false
14
+ end
15
+
16
+ test "a returning provider account signs in" do
17
+ post "<%= omniauth_callback_test_path %>"
18
+ assert cookies[:<%= identity.cookie_name %>].present?
19
+ end
20
+ <%- if invite_only? && phone_invitable? -%>
21
+
22
+ test "a provider email cannot accept a phone invitation" do
23
+ invitation = <%= identity.invitation_class %>.create!(sent_to: "+14155552671", inviter: @user)
24
+ get <%= identity.namespaced? ? "#{identity.route_scope}_accept_invitation_path" : "accept_invitation_path" %>(token: invitation.generate_token_for(:invitation))
25
+ mock_provider(uid: "phone-invite", email: "phone-invite@example.com")
26
+
27
+ assert_no_difference "<%= identity.class_name %>.count" do
28
+ post "<%= omniauth_callback_test_path %>"
29
+ end
30
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
31
+ refute_predicate invitation.reload, :accepted?
32
+ end
33
+ <%- end -%>
34
+ <%- if !omniauth_registration? && !invite_only? -%>
35
+
36
+ test "a provider account nobody has seen registers" do
37
+ mock_provider(uid: "brand-new", email: "brand-new@example.com")
38
+
39
+ assert_difference [ "<%= identity.class_name %>.count", "<%= identity.omniauth_identity_class %>.count" ], 1 do
40
+ post "<%= omniauth_callback_test_path %>"
41
+ end
42
+ assert cookies[:<%= identity.cookie_name %>].present?
43
+ end
44
+ <%- if principals.email&.required? -%>
45
+
46
+ test "a provider that reports no address is told so, not told the address is taken" do
47
+ mock_provider(uid: "no-address", email: nil)
48
+
49
+ assert_no_difference "<%= identity.class_name %>.count" do
50
+ post "<%= omniauth_callback_test_path %>"
51
+ end
52
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
53
+ assert_equal "That provider didn't share an email address, try signing up another way.", flash[:alert]
54
+ end
55
+ <%- end -%>
56
+ <%- end -%>
57
+
58
+ test "a signed-in <%= identity.singular %> links a new provider account instead of signing in" do
59
+ sign_in_as @user
60
+ mock_provider(uid: "a-second-login", email: "whatever@example.com")
61
+
62
+ assert_difference "@user.omniauth_identities.count", 1 do
63
+ post "<%= omniauth_callback_test_path %>"
64
+ end
65
+ assert_redirected_to <%= identity.settings_helper_prefix %>_omniauth_identities_path
66
+ end
67
+
68
+ test "linking a provider account owned by someone else is refused, not moved" do
69
+ owner = <%= identity.plural %>(:alice)
70
+ owner.omniauth_identities.create!(provider: "developer", uid: "someone-elses-login")
71
+ mock_provider(uid: "someone-elses-login", email: "whatever@example.com")
72
+ sign_in_as @user
73
+
74
+ assert_no_difference [ "@user.omniauth_identities.count", "owner.omniauth_identities.count" ] do
75
+ post "<%= omniauth_callback_test_path %>"
76
+ end
77
+ assert_redirected_to <%= identity.settings_helper_prefix %>_omniauth_identities_path
78
+ assert_equal owner.id, <%= identity.omniauth_identity_class %>.find_by!(provider: "developer", uid: "someone-elses-login").<%= identity.foreign_key %>
79
+ end
80
+
81
+ test "the failure endpoint returns to sign in" do
82
+ get "<%= omniauth_failure_test_path %>", params: { message: "invalid_credentials" }
83
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
84
+ end
85
+
86
+ private
87
+
88
+ def mock_provider(uid:, email:)
89
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(
90
+ provider: "developer", uid: uid, info: { email: email }
91
+ )
92
+ end
93
+
94
+ def sign_in_as(user)
95
+ <%= test_sign_in_body %>
96
+ end
97
+ end
@@ -0,0 +1,58 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::PasswordResetsControllerTest < ActionDispatch::IntegrationTest
4
+ include ActionMailer::TestHelper
5
+
6
+ setup { @user = <%= identity.plural %>(:alice) }
7
+
8
+ test "requesting a reset for a known account emails a link" do
9
+ assert_enqueued_emails 1 do
10
+ post <%= identity.route_scope %>_reset_password_path, params: { email: @user.email }
11
+ end
12
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
13
+ end
14
+
15
+ <%- if coy? -%>
16
+ test "requesting a reset for an unknown email gives nothing away" do
17
+ assert_no_enqueued_emails do
18
+ post <%= identity.route_scope %>_reset_password_path, params: { email: "nobody-#{SecureRandom.hex(4)}@example.com" }
19
+ end
20
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
21
+ end
22
+ <%- else -%>
23
+ test "requesting a reset for an unknown email is told no account exists" do
24
+ assert_no_enqueued_emails do
25
+ post <%= identity.route_scope %>_reset_password_path, params: { email: "nobody-#{SecureRandom.hex(4)}@example.com" }
26
+ end
27
+ assert_redirected_to <%= identity.route_scope %>_forgot_password_path
28
+ end
29
+ <%- end -%>
30
+
31
+ test "a valid reset token opens the change-password form" do
32
+ token = @user.generate_token_for(:password_reset)
33
+ get <%= identity.route_scope %>_reset_password_path(sid: token)
34
+ assert_response :success
35
+ end
36
+
37
+ test "a valid reset token changes the password" do
38
+ token = @user.generate_token_for(:password_reset)
39
+ patch <%= identity.route_scope %>_reset_password_path(sid: token),
40
+ params: { password: "heron-basalt-drift-41", password_confirmation: "heron-basalt-drift-41" }
41
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
42
+ assert @user.reload.authenticate("heron-basalt-drift-41"), "the new password should verify"
43
+ end
44
+
45
+ test "a reset link is dead once it has been used" do
46
+ token = @user.generate_token_for(:password_reset)
47
+ patch <%= identity.route_scope %>_reset_password_path(sid: token),
48
+ params: { password: "heron-basalt-drift-41", password_confirmation: "heron-basalt-drift-41" }
49
+
50
+ get <%= identity.route_scope %>_reset_password_path(sid: token)
51
+ assert_redirected_to <%= identity.route_scope %>_forgot_password_path
52
+ end
53
+
54
+ test "an invalid or expired reset link is refused" do
55
+ get <%= identity.route_scope %>_reset_password_path(sid: "not-a-real-token")
56
+ assert_redirected_to <%= identity.route_scope %>_forgot_password_path
57
+ end
58
+ end
@@ -0,0 +1,102 @@
1
+ require "test_helper"
2
+ <%- confirming_completes = sign_up_fixture_can_sign_in? &&(invite_only? || !phone_gated_registration?) -%>
3
+
4
+ class <%= identity.module_name %>::Registrations::EmailVerificationsControllerTest < ActionDispatch::IntegrationTest
5
+ test "opening the link is inert" do
6
+ registration, token = begin_registration
7
+
8
+ assert_no_changes -> { registration.reload.email_verified_at } do
9
+ get <%= identity.route_scope %>_verify_email_path(sid: token)
10
+ end
11
+
12
+ assert_response :success
13
+ assert <%= identity.pending_registration_class %>.exists?(registration.id)
14
+ end
15
+
16
+ test "only the browser that started the registration can confirm it" do
17
+ registration, token = begin_registration
18
+ other_browser = open_session
19
+
20
+ assert_no_changes -> { registration.reload.email_verified_at } do
21
+ other_browser.patch <%= identity.route_scope %>_verify_email_path(sid: token)
22
+ end
23
+ assert_equal <%= identity.route_scope %>_sign_up_url, other_browser.response.location
24
+ end
25
+
26
+ test "a sign-up left untouched past its lifetime cannot be confirmed" do
27
+ registration, token = begin_registration
28
+ registration.update_column(:updated_at, <%= identity.config_reference %>.tokens.pending_registration.ago - 1.minute)
29
+
30
+ assert_no_changes -> { registration.reload.email_verified_at } do
31
+ patch <%= identity.route_scope %>_verify_email_path(sid: token)
32
+ end
33
+ assert_redirected_to <%= registration_entry_path %>
34
+ end
35
+
36
+ test "confirmation timestamps the verified address before completing or enrolling" do
37
+ registration, token = begin_registration
38
+
39
+ <%- if confirming_completes -%>
40
+ assert_difference -> { <%= identity.class_name %>.count }, 1 do
41
+ patch <%= identity.route_scope %>_verify_email_path(sid: token)
42
+ end
43
+ assert_not <%= identity.pending_registration_class %>.exists?(registration.id)
44
+ assert cookies[:<%= identity.cookie_name %>].present?
45
+ <%- else -%>
46
+ assert_no_difference -> { <%= identity.class_name %>.count } do
47
+ patch <%= identity.route_scope %>_verify_email_path(sid: token)
48
+ end
49
+ assert registration.reload.email_verified_at?
50
+ assert <%= identity.pending_registration_class %>.exists?(registration.id)
51
+ <%- end -%>
52
+ end
53
+ <%- if confirming_completes -%>
54
+
55
+ test "an address taken since sign-up is sent to sign in, not into that account" do
56
+ registration, token = begin_registration
57
+ <%= identity.plural %>(:alice).update_column(:email, registration.email)
58
+
59
+ assert_no_difference -> { <%= identity.class_name %>.count } do
60
+ patch <%= identity.route_scope %>_verify_email_path(sid: token)
61
+ end
62
+ assert_redirected_to <%= identity.route_scope %>_sign_in_path
63
+ assert_not <%= identity.pending_registration_class %>.exists?(registration.id)
64
+ assert cookies[:<%= identity.cookie_name %>].blank?
65
+ end
66
+ <%- end -%>
67
+ <%- if principals.username -%>
68
+
69
+ test "a username taken before confirming can be replaced on the confirmation page" do
70
+ registration, token = begin_registration
71
+ <%= identity.plural %>(:alice).update_column(:username, registration.username)
72
+
73
+ patch <%= identity.route_scope %>_verify_email_path(sid: token), params: { username: registration.username }
74
+ assert_response :unprocessable_entity
75
+ assert_not registration.reload.email_verified_at?
76
+
77
+ replacement = "#{registration.username}_2"
78
+ patch <%= identity.route_scope %>_verify_email_path(sid: token), params: { username: replacement }
79
+ <%- if confirming_completes -%>
80
+ assert <%= identity.class_name %>.exists?(username: replacement)
81
+ <%- else -%>
82
+ assert_equal replacement, registration.reload.username
83
+ assert registration.email_verified_at?
84
+ <%- end -%>
85
+ end
86
+ <%- end -%>
87
+
88
+ private
89
+
90
+ def begin_registration
91
+ attributes = <%= sign_up_fixture_literal(indent: 4) %>
92
+ <%- if password_form_optional? -%>
93
+ attributes.merge!(password: "", password_confirmation: "")
94
+ <%- end -%>
95
+ <%- if invite_only? -%>
96
+ <%= accept_test_invitation(%("+14158264077")) %>
97
+ <%- end -%>
98
+ post <%= identity.route_scope %>_sign_up_path, params: attributes
99
+ registration = <%= identity.pending_registration_class %>.last
100
+ [ registration, registration.generate_token_for(:registration_email_verification) ]
101
+ end
102
+ end
@@ -0,0 +1,69 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::Registrations::PhoneVerificationsControllerTest < ActionDispatch::IntegrationTest
4
+ include ActiveJob::TestHelper
5
+ <%- unless typed_phone_gated_registration? -%>
6
+
7
+ teardown do
8
+ OmniAuth.config.mock_auth[:developer] = nil
9
+ OmniAuth.config.test_mode = false
10
+ end
11
+ <%- end -%>
12
+
13
+ test "the code verifies its exact pending number" do
14
+ registration, code = begin_phone_verification
15
+
16
+ <%- if !typed_phone_gated_registration? || sign_up_fixture_can_sign_in? -%>
17
+ assert_difference -> { <%= identity.class_name %>.count }, 1 do
18
+ patch <%= identity.route_scope %>_verify_phone_path, params: { code: code }
19
+ end
20
+ assert_not <%= identity.pending_registration_class %>.exists?(registration.id)
21
+ <%- else -%>
22
+ assert_no_difference -> { <%= identity.class_name %>.count } do
23
+ patch <%= identity.route_scope %>_verify_phone_path, params: { code: code }
24
+ end
25
+ assert registration.reload.phone_verified_at?
26
+ <%- end -%>
27
+ end
28
+
29
+ test "a wrong code creates no account" do
30
+ begin_phone_verification
31
+
32
+ assert_no_difference -> { <%= identity.class_name %>.count } do
33
+ patch <%= identity.route_scope %>_verify_phone_path, params: { code: "000000" }
34
+ end
35
+ assert_response :unprocessable_entity
36
+ end
37
+
38
+ private
39
+
40
+ def begin_phone_verification
41
+ clear_enqueued_jobs
42
+ <%- if typed_phone_gated_registration? -%>
43
+ <%- if invite_only? -%>
44
+ <%= accept_test_invitation(%("invitee@example.com")) %>
45
+ <%- end -%>
46
+ post <%= identity.route_scope %>_sign_up_path, params: <%= sign_up_fixture_literal(indent: 4) %>
47
+ registration = <%= identity.pending_registration_class %>.last
48
+ <%- if email_verifiable? -%>
49
+ registration.update!(email_verified_at: Time.current)
50
+ <%- end -%>
51
+ registration.request_phone_verification
52
+ <%- else -%>
53
+ OmniAuth.config.test_mode = true
54
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(
55
+ provider: "developer", uid: "phone-verification", info: { email: "provider-user@example.com" }
56
+ )
57
+ <%- if invite_only? -%>
58
+ <%= accept_test_invitation(%("provider-user@example.com")) %>
59
+ <%- end -%>
60
+ post "<%= omniauth_callback_test_path %>"
61
+ post <%= identity.route_scope %>_omniauth_registration_path, params: <%= omniauth_profile_params_literal %>
62
+ registration = <%= identity.pending_registration_class %>.last
63
+ <%- end -%>
64
+ code = enqueued_jobs.reverse_each.filter_map { |job| job.to_s[/(\d{6}) is your/, 1] }.first
65
+ raise "verification code was not enqueued" unless code
66
+
67
+ [ registration, code ]
68
+ end
69
+ end