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,158 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::AccountExistenceParityTest < ActionDispatch::IntegrationTest
4
+ def queue_adapter_for_test = ActiveJob::QueueAdapters::TestAdapter.new
5
+
6
+ setup { @user = <%= identity.plural %>(:alice) }
7
+ <%- if password? -%>
8
+
9
+ test "a failed sign-in does the same work for any address" do
10
+ assert_same_work(@user.<%= login_field %>, unknown_email) do |login|
11
+ post <%= identity.route_scope %>_sign_in_path, params: { <%= sign_in_field %>: login, password: "wrong-password-0" }
12
+ end
13
+ end
14
+ <%- end -%>
15
+ <%- if recoverable? -%>
16
+
17
+ test "a password reset request does the same work for any address" do
18
+ assert_same_work(@user.email, unknown_email) do |email|
19
+ post <%= identity.route_scope %>_reset_password_path, params: { email: }
20
+ end
21
+ end
22
+ <%- end -%>
23
+ <%- if magic_link? -%>
24
+
25
+ test "a magic link request does the same work for any address" do
26
+ assert_same_work(@user.email, unknown_email) do |email|
27
+ post <%= identity.route_scope %>_magic_link_path, params: { email: }
28
+ end
29
+ end
30
+ <%- end -%>
31
+ <%- if sms_code? -%>
32
+
33
+ test "an SMS sign-in request does the same work for any number" do
34
+ assert_same_work(@user.phone, unknown_phone) do |phone|
35
+ post <%= identity.route_scope %>_sms_sign_in_path, params: { phone: }
36
+ end
37
+ end
38
+
39
+ test "an SMS sign-in code check does the same work for any number" do
40
+ works = [ @user.phone, unknown_phone ].map do |phone|
41
+ perform_enqueued_jobs(only: AuthnzElevenDeferredJob) do
42
+ post <%= identity.route_scope %>_sms_sign_in_path, params: { phone: }
43
+ end
44
+ work_of { patch <%= identity.route_scope %>_sms_sign_in_code_path, params: { code: "000000" } }
45
+ end
46
+
47
+ assert_equal(*works)
48
+ end
49
+ <%- end -%>
50
+ <%- if email_gated_registration? && !invite_only? -%>
51
+
52
+ test "signing up does the same work for any address" do
53
+ assert_same_work(@user.email, unknown_email) do |email|
54
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email:)
55
+ end
56
+ end
57
+
58
+ test "resending a sign-up link does the same work for any address" do
59
+ works = [ @user.email, unknown_email ].map do |email|
60
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params(email:)
61
+ work_of { post <%= identity.route_scope %>_verify_email_path }
62
+ end
63
+
64
+ assert_equal(*works)
65
+ end
66
+ <%- end -%>
67
+ <%- if email_change_verification? -%>
68
+
69
+ test "changing your email does the same work for any address" do
70
+ sign_in_as <%= identity.plural %>(:bob)
71
+ assert_same_work(@user.email, unknown_email) do |email|
72
+ patch <%= identity.settings_helper_prefix %>_email_path, params: { email:<%= settings_credential_pair %> }
73
+ end
74
+ end
75
+
76
+ test "resending an email change link does the same work for any address" do
77
+ sign_in_as <%= identity.plural %>(:bob)
78
+ works = [ @user.email, unknown_email ].map do |email|
79
+ patch <%= identity.settings_helper_prefix %>_email_path, params: { email:<%= settings_credential_pair %> }
80
+ work_of { post <%= identity.settings_helper_prefix %>_email_verification_path }
81
+ end
82
+
83
+ assert_equal(*works)
84
+ end
85
+ <%- end -%>
86
+ <%- if phone_change_verification? -%>
87
+
88
+ test "changing your phone does the same work for any number" do
89
+ sign_in_as <%= identity.plural %>(:bob)
90
+ assert_same_work(@user.phone, unknown_phone) do |phone|
91
+ patch <%= identity.settings_helper_prefix %>_phone_path, params: { phone:<%= settings_credential_pair %> }
92
+ end
93
+ end
94
+
95
+ test "resending a phone change code does the same work for any number" do
96
+ sign_in_as <%= identity.plural %>(:bob)
97
+ works = [ @user.phone, unknown_phone ].map do |phone|
98
+ patch <%= identity.settings_helper_prefix %>_phone_path, params: { phone:<%= settings_credential_pair %> }
99
+ work_of { post <%= identity.settings_helper_prefix %>_phone_verification_path }
100
+ end
101
+
102
+ assert_equal(*works)
103
+ end
104
+ <%- end -%>
105
+ <%- if invitable? -%>
106
+
107
+ test "inviting does the same work for anyone" do
108
+ sign_in_as <%= identity.plural %>(:bob)
109
+ <%- if email_invitable? -%>
110
+ assert_same_work(@user.email, unknown_email) do |sent_to|
111
+ <%- else -%>
112
+ assert_same_work(@user.phone, unknown_phone) do |sent_to|
113
+ <%- end -%>
114
+ post <%= identity.settings_helper_prefix %>_invitation_path, params: { sent_to: }
115
+ end
116
+ end
117
+ <%- end -%>
118
+
119
+ private
120
+
121
+ def assert_same_work(known, unknown, &request)
122
+ assert_equal work_of { request.(known) }, work_of { request.(unknown) }
123
+ end
124
+
125
+ def work_of
126
+ work = []
127
+ record = lambda do |event, *, payload|
128
+ if event == "sql.active_record"
129
+ work << [ without_literals(payload[:sql]), payload[:row_count] ] unless payload[:sql].start_with?("SELECT") || payload[:name] == "SCHEMA"
130
+ elsif !payload[:key].to_s.start_with?("rate-limit:")
131
+ work << [ event, payload[:job]&.class ]
132
+ end
133
+ end
134
+ ActiveSupport::Notifications.subscribed(record, /\A(sql\.active_record|cache_\w+\.active_support|enqueue\.active_job)\z/) { yield }
135
+ work
136
+ end
137
+
138
+ def without_literals(sql) = sql.gsub(/'(?:[^'\\]|\\.)*'|\b\d+\b/, "?")
139
+
140
+ def unknown_email = "nobody-#{SecureRandom.hex(4)}@example.com"
141
+ <%- if principals.phone -%>
142
+ def unknown_phone = "+14158264010"
143
+ <%- end -%>
144
+ <%- if email_change_verification? || phone_change_verification? || invitable? -%>
145
+
146
+ def sign_in_as(user)
147
+ perform_enqueued_jobs(only: AuthnzElevenDeferredJob) do
148
+ <%= test_sign_in_body.gsub(/^(?=.)/, " ") %>
149
+ end
150
+ end
151
+ <%- end -%>
152
+ <%- if email_gated_registration? && !invite_only? -%>
153
+
154
+ def sign_up_params(**overrides)
155
+ <%= sign_up_fixture_literal(indent: 4) %>.merge(overrides)
156
+ end
157
+ <%- end -%>
158
+ end
@@ -0,0 +1,139 @@
1
+ require "test_helper"
2
+
3
+ class <%= admin_module %>::AccessTest < ActionDispatch::IntegrationTest
4
+ test "the admin area is a 404 for anonymous visitors" do
5
+ get <%= admin_helper("root_path") %>
6
+ assert_response :not_found
7
+ end
8
+ <%- if password? -%>
9
+
10
+ test "the admin area is a 404 for a signed-in non-admin" do
11
+ sign_in_as <%= identity.plural %>(:bob)
12
+ get <%= admin_helper("root_path") %>
13
+ assert_response :not_found
14
+ end
15
+
16
+ <%- if password_rotatable? -%>
17
+
18
+ test "the admin area is a 404 for a non-admin held at a completion gate" do
19
+ sign_in_as <%= identity.plural %>(:bob)
20
+ <%- if password_rotatable? -%>
21
+ <%= identity.plural %>(:bob).update!(password_changed_at: 1.year.ago)
22
+ <%- end -%>
23
+
24
+ get <%= admin_helper("root_path") %>
25
+ assert_response :not_found
26
+ end
27
+ <%- end -%>
28
+
29
+ test "an admin can reach the dashboard" do
30
+ sign_in_as <%= identity.plural %>(:alice)
31
+ <%- if admin_mfa_gate? -%>
32
+ <%= enroll_second_factor_expr("#{identity.plural}(:alice)") %>
33
+ <%- end -%>
34
+ get <%= admin_helper("root_path") %>
35
+ assert_response :success
36
+ end
37
+
38
+ test "the destructive actions refuse an admin target" do
39
+ sign_in_as <%= identity.plural %>(:alice)
40
+ <%- if admin_mfa_gate? -%>
41
+ <%= enroll_second_factor_expr("#{identity.plural}(:alice)") %>
42
+ <%- end -%>
43
+
44
+ [ <%= identity.plural %>(:carol), <%= identity.plural %>(:alice) ].each do |admin|
45
+ delete <%= admin_helper("user_path") %>(admin)
46
+ assert_redirected_to <%= admin_helper("users_path") %>
47
+ assert <%= identity.class_name %>.exists?(admin.id), "an admin must not be deleted here"
48
+ <%- if bannable? -%>
49
+
50
+ post ban_<%= admin_helper("user_path") %>(admin)
51
+ assert_not admin.reload.banned?, "an admin must not be suspended here"
52
+ <%- end -%>
53
+ <%- if second_factor? -%>
54
+
55
+ <%= enroll_second_factor_expr("admin") %> unless admin.second_factor_enrolled?
56
+ post reset_second_factor_<%= admin_helper("user_path") %>(admin)
57
+ assert admin.reload.second_factor_enrolled?, "an admin's second factor must survive"
58
+ <%- end -%>
59
+ end
60
+ end
61
+ <%- if impersonatable? && !admin_mfa_gate? -%>
62
+
63
+ test "an admin can impersonate another user, and stop" do
64
+ sign_in_as <%= identity.plural %>(:alice)
65
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:bob))
66
+ assert_redirected_to <%= identity.nested_helper("root_path") %>
67
+
68
+ get <%= admin_helper("root_path") %>
69
+ assert_response :not_found
70
+
71
+ delete <%= identity.nested_helper("impersonation_path") %>
72
+ assert_redirected_to <%= admin_helper("root_path") %>
73
+
74
+ get <%= admin_helper("root_path") %>
75
+ assert_response :success
76
+ end
77
+
78
+ test "impersonation expires on its own" do
79
+ sign_in_as <%= identity.plural %>(:alice)
80
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:bob))
81
+
82
+ travel <%= identity.config_reference %>.impersonation.duration + 1.minute do
83
+ get <%= identity.nested_helper("root_path") %>
84
+ assert_redirected_to <%= admin_helper("root_path") %>
85
+
86
+ get <%= admin_helper("root_path") %>
87
+ assert_response :success, "alice should be herself again"
88
+ end
89
+ end
90
+
91
+ test "an admin cannot impersonate an admin, including themselves" do
92
+ sign_in_as <%= identity.plural %>(:alice)
93
+
94
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:carol))
95
+ assert_redirected_to <%= admin_helper("users_path") %>
96
+
97
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:alice))
98
+ assert_redirected_to <%= admin_helper("users_path") %>
99
+
100
+ get <%= admin_helper("root_path") %>
101
+ assert_response :success, "alice should still be herself, with her admin access intact"
102
+ end
103
+ <%- if trackable? -%>
104
+
105
+ test "impersonation is recorded at both ends" do
106
+ sign_in_as <%= identity.plural %>(:alice)
107
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:bob))
108
+ delete <%= identity.nested_helper("impersonation_path") %>
109
+
110
+ events = <%= identity.plural %>(:bob).events.chronological.last(2)
111
+ assert_equal [ "<%= identity.singular %>.impersonation_started", "<%= identity.singular %>.impersonation_stopped" ],
112
+ events.map(&:event_type)
113
+ assert_equal [ <%= identity.plural %>(:alice), <%= identity.plural %>(:alice) ], events.map(&:actor)
114
+ end
115
+ <%- end -%>
116
+
117
+ test "a non-admin cannot impersonate" do
118
+ sign_in_as <%= identity.plural %>(:bob)
119
+ post <%= admin_helper("impersonate_user_path") %>(<%= identity.plural %>(:alice))
120
+ assert_response :not_found
121
+ end
122
+
123
+ test "the stop-impersonating route is a 404 when nobody is impersonating" do
124
+ delete <%= identity.nested_helper("impersonation_path") %>
125
+ assert_response :not_found
126
+
127
+ sign_in_as <%= identity.plural %>(:bob)
128
+ delete <%= identity.nested_helper("impersonation_path") %>
129
+ assert_response :not_found
130
+ end
131
+ <%- end -%>
132
+
133
+ private
134
+
135
+ def sign_in_as(user)
136
+ <%= test_sign_in_body %>
137
+ end
138
+ <%- end -%>
139
+ end
@@ -0,0 +1,65 @@
1
+ require "test_helper"
2
+
3
+ class <%= admin_module %>::MultiFactorAuthenticationTest < ActionDispatch::IntegrationTest
4
+ setup { @admin = <%= identity.plural %>(:alice) }
5
+
6
+ test "an MFA-less admin is held: an admin page bounces to the enrollment screen" do
7
+ assert_not @admin.second_factor_enrolled?
8
+ sign_in_as @admin
9
+
10
+ get <%= admin_helper("users_path") %>
11
+ assert_redirected_to <%= admin_mfa_hold_path %>
12
+ end
13
+
14
+ test "the enrollment screen stays reachable while held" do
15
+ sign_in_as @admin
16
+ <%- if sudo_test_mode == :answer -%>
17
+ post <%= identity.route_scope %>_sudo_path, params: { <%= sudo_test_answer %> }
18
+ <%- end -%>
19
+
20
+ get <%= admin_mfa_hold_path %>
21
+ assert_response :success
22
+ end
23
+ <%- if sudo_test_mode == :answer -%>
24
+
25
+ test "the sudo page stays reachable while held, so the enrollment screen can ask for confirmation" do
26
+ sign_in_as @admin
27
+
28
+ get new_<%= identity.route_scope %>_sudo_path
29
+ assert_response :success
30
+ end
31
+ <%- end -%>
32
+
33
+ test "enrolling a second factor clears the gate for the rest of the admin area" do
34
+ sign_in_as @admin
35
+ get <%= admin_helper("users_path") %>
36
+ assert_redirected_to <%= admin_mfa_hold_path %>, "held before a second factor is enrolled"
37
+
38
+ <%= enroll_second_factor_expr("@admin") %>
39
+ assert @admin.reload.second_factor_enrolled?
40
+
41
+ get <%= admin_helper("users_path") %>
42
+ assert_response :success, "cleared once a second factor is enrolled"
43
+ end
44
+
45
+
46
+ test "an admin clears every second factor a user holds" do
47
+ stranded = <%= identity.plural %>(:bob)
48
+ <%= enroll_second_factor_expr("stranded") %>
49
+ <%= identity.recovery_code_class %>.regenerate_for(stranded)
50
+
51
+ sign_in_as @admin
52
+ <%= enroll_second_factor_expr("@admin") %>
53
+ post reset_second_factor_<%= admin_helper("user_path") %>(stranded)
54
+
55
+ assert_not stranded.reload.second_factor_enrolled?
56
+ assert_empty stranded.recovery_codes.reload,
57
+ "codes guarding nothing are live sign-in secrets"
58
+ end
59
+
60
+ private
61
+
62
+ def sign_in_as(user)
63
+ <%= test_sign_in_body %>
64
+ end
65
+ end
@@ -0,0 +1,45 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.class_name %>ApiTokenProbeController < ActionController::API
4
+ include <%= identity.nested_module("ApiTokenAuthentication") %>
5
+
6
+ def index
7
+ render json: { user_id: current_api_user.id, token_id: current_api_token.id }
8
+ end
9
+ end
10
+
11
+ class <%= identity.class_name %>ApiTokenAuthenticationTest < ActionController::TestCase
12
+ tests <%= identity.class_name %>ApiTokenProbeController
13
+
14
+ setup do
15
+ @routes = ActionDispatch::Routing::RouteSet.new
16
+ @routes.draw { get "api_token_probe", to: "<%= identity.class_name.underscore %>_api_token_probe#index" }
17
+ @user = <%= identity.plural %>(:bob)
18
+ @token, @secret = <%= identity.api_token_class %>.issue!(user: @user, name: "Integration")
19
+ end
20
+
21
+ test "bearer token authenticates without a browser session" do
22
+ @request.headers["Authorization"] = "Bearer #{@secret}"
23
+ get :index
24
+
25
+ assert_response :success
26
+ assert_equal @user.id, response.parsed_body["user_id"]
27
+ assert_equal @token.id, response.parsed_body["token_id"]
28
+ assert_nil <%= identity.current_class %>.session
29
+ end
30
+
31
+ test "missing expired and revoked tokens are rejected" do
32
+ get :index
33
+ assert_response :unauthorized
34
+
35
+ @request.headers["Authorization"] = "Bearer #{@secret}"
36
+ @token.update!(expires_at: 1.second.ago)
37
+ get :index
38
+ assert_response :unauthorized
39
+
40
+ @token.update!(expires_at: 1.day.from_now)
41
+ @token.destroy!
42
+ get :index
43
+ assert_response :unauthorized
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::CredentialEnrollmentTest < ActionDispatch::IntegrationTest
4
+ test "an unfinished registration does not create an account" do
5
+ assert_no_difference -> { <%= identity.class_name %>.count } do
6
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
7
+ end
8
+
9
+ <%- if invite_only? -%>
10
+ assert_not <%= identity.pending_registration_class %>.exists?, "admission is required before staging"
11
+ <%- else -%>
12
+ assert <%= identity.pending_registration_class %>.exists?
13
+ <%- end -%>
14
+ end
15
+
16
+ <%- if pre_account_password_enrollment? -%>
17
+ test "password and account commit together" do
18
+ registration = stage_registration_awaiting_credential
19
+
20
+ assert_difference -> { <%= identity.class_name %>.count }, 1 do
21
+ post <%= identity.route_scope %>_registration_password_path,
22
+ params: { password: "Correct horse battery staple 42!", password_confirmation: "Correct horse battery staple 42!" }
23
+ end
24
+
25
+ assert_not <%= identity.pending_registration_class %>.exists?(registration.id)
26
+ end
27
+
28
+ test "the password step enforces the password rules" do
29
+ stage_registration_awaiting_credential
30
+
31
+ assert_no_difference -> { <%= identity.class_name %>.count } do
32
+ post <%= identity.route_scope %>_registration_password_path, params: { password: "a", password_confirmation: "a" }
33
+ end
34
+ assert_response :unprocessable_entity
35
+
36
+ assert_no_difference -> { <%= identity.class_name %>.count } do
37
+ post <%= identity.route_scope %>_registration_password_path,
38
+ params: { password: "Correct horse battery staple 42!", password_confirmation: "Something else entirely 42!" }
39
+ end
40
+ assert_response :unprocessable_entity
41
+ end
42
+ <%- end -%>
43
+ <%- if pre_account_provider_enrollment? -%>
44
+
45
+ test "connecting a provider from the finish-setup page completes the sign-up" do
46
+ request_validation = OmniAuth.config.request_validation_phase
47
+ OmniAuth.config.request_validation_phase = nil
48
+ OmniAuth.config.test_mode = true
49
+ OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new(provider: "developer", uid: "finishing-setup")
50
+ stage_registration_awaiting_credential
51
+
52
+ assert_difference -> { <%= identity.class_name %>.count }, 1 do
53
+ post "<%= omniauth_path_prefix %>/developer?registration_intent=1"
54
+ follow_redirect!
55
+ end
56
+ assert <%= identity.omniauth_identity_class %>.exists?(uid: "finishing-setup")
57
+ assert cookies[:<%= identity.cookie_name %>].present?
58
+ ensure
59
+ OmniAuth.config.mock_auth[:developer] = nil
60
+ OmniAuth.config.test_mode = false
61
+ OmniAuth.config.request_validation_phase = request_validation
62
+ end
63
+ <%- end -%>
64
+
65
+ private
66
+ <%- if pre_account_password_enrollment? || pre_account_provider_enrollment? -%>
67
+
68
+ def stage_registration_awaiting_credential
69
+ post <%= identity.route_scope %>_sign_up_path, params: sign_up_params
70
+ registration = <%= identity.pending_registration_class %>.last
71
+ <%- if email_verifiable? -%>
72
+ registration.update!(email_verified_at: Time.current)
73
+ <%- end -%>
74
+ <%- if phone_verifiable? -%>
75
+ registration.update!(phone_verified_at: Time.current)
76
+ <%- end -%>
77
+ registration
78
+ end
79
+ <%- end -%>
80
+
81
+ def sign_up_params
82
+ <%= sign_up_fixture_literal(indent: 4) %>
83
+ end
84
+ end
@@ -0,0 +1,113 @@
1
+ require "test_helper"
2
+
3
+ class <%= identity.module_name %>::GuestAccessTest < ActionDispatch::IntegrationTest
4
+ <%- if timeoutable? -%>
5
+ include ActiveSupport::Testing::TimeHelpers
6
+
7
+ <%- end -%>
8
+ class ProbeController < <%= identity.controller_superclass %>
9
+ allow_guest_access
10
+
11
+ def show
12
+ render plain: "#{<%= identity.current_class %>.user&.id}:#{<%= identity.current_class %>.user&.guest?}:#{authenticated?}"
13
+ end
14
+
15
+ def sign_in
16
+ <%= sign_in_call("#{identity.class_name}.find(params[:id])", via: :second_factor, may_challenge: false) %>
17
+ head :ok
18
+ end
19
+ end
20
+
21
+ setup { @<%= identity.singular %> = <%= identity.plural %>(:bob) }
22
+
23
+ test "an anonymous visitor is admitted and given a guest identity" do
24
+ with_probe_routes do
25
+ assert_difference -> { <%= identity.class_name %>.guests.count }, +1 do
26
+ get "/guest_probe"
27
+ end
28
+
29
+ assert_response :success
30
+ assert_equal "#{<%= identity.class_name %>.guests.last.id}:true:false", response.body
31
+ assert_equal "guest", <%= identity.class_name %>.guests.last.sessions.sole.via,
32
+ "creating a guest does not sign them in"
33
+ end
34
+ end
35
+
36
+ test "a guest cookie persists until the guest expires" do
37
+ with_probe_routes do
38
+ get "/guest_probe"
39
+ guest = <%= identity.class_name %>.guests.last
40
+ expires = Time.httpdate(session_cookie_header[/expires=([^;]+)/i, 1])
41
+
42
+ assert_in_delta (guest.created_at + <%= identity.config_reference %>.guest.ttl).to_f,
43
+ expires.to_f, 1
44
+ end
45
+ end
46
+
47
+ test "the same guest is reused across requests" do
48
+ with_probe_routes do
49
+ get "/guest_probe"
50
+ first = response.body
51
+
52
+ assert_no_difference -> { <%= identity.class_name %>.guests.count } do
53
+ get "/guest_probe"
54
+ end
55
+
56
+ assert_equal first, response.body
57
+ end
58
+ end
59
+ <%- if timeoutable? -%>
60
+
61
+ test "a guest outlives the idle timeout that would expire a signed-in session" do
62
+ with_probe_routes do
63
+ get "/guest_probe"
64
+ guest = response.body
65
+
66
+ travel <%= identity.config_reference %>.session.idle_timeout + 1.day do
67
+ get "/guest_probe"
68
+
69
+ assert_equal guest, response.body, "the guest should still be the same row"
70
+ assert_nil flash[:alert], "a guest was never signed in, so nothing expired"
71
+ end
72
+ end
73
+ end
74
+ <%- end -%>
75
+
76
+ test "signing in absorbs the guest and destroys it" do
77
+ with_probe_routes do
78
+ get "/guest_probe"
79
+ guest = <%= identity.class_name %>.guests.last
80
+
81
+ assert_difference -> { <%= identity.class_name %>.guests.count }, -1 do
82
+ post "/guest_probe/sign_in/#{@<%= identity.singular %>.id}"
83
+ end
84
+
85
+ assert_no_match(/expires=/i, session_cookie_header,
86
+ "signing in must not inherit the guest cookie's persistence")
87
+ assert_nil <%= identity.class_name %>.find_by(id: guest.id),
88
+ "the guest row is destroyed once its state has been handed to the account"
89
+
90
+ get "/guest_probe"
91
+ assert_equal "#{@<%= identity.singular %>.id}:false:true", response.body
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def session_cookie_header
98
+ Array(response.headers["Set-Cookie"])
99
+ .flat_map { |line| line.to_s.split("\n") }
100
+ .find { |line| line.start_with?("<%= identity.cookie_name %>=") }.to_s
101
+ end
102
+
103
+ def with_probe_routes
104
+ # Keep requests and assertions inside this block; with_routing uses a separate integration session and discards its cookies afterward.
105
+ with_routing do |set|
106
+ set.draw do
107
+ get "guest_probe", to: ProbeController.action(:show)
108
+ post "guest_probe/sign_in/:id", to: ProbeController.action(:sign_in)
109
+ end
110
+ yield
111
+ end
112
+ end
113
+ end