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,39 @@
1
+ require "net/http"
2
+
3
+ module Captcha
4
+ module Turnstile
5
+ VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify".freeze
6
+
7
+ class << self
8
+ def verify(token, ip: nil)
9
+ return false if token.blank?
10
+
11
+ response = Net::HTTP.post_form(URI(VERIFY_URL), {
12
+ "secret" => secret_key,
13
+ "response" => token,
14
+ "remoteip" => ip
15
+ }.compact)
16
+ response.is_a?(Net::HTTPSuccess) && JSON.parse(response.body)["success"] == true
17
+ rescue => e
18
+ Rails.logger.error("[captcha] Turnstile verification error: #{e.class}: #{e.message}")
19
+ false
20
+ end
21
+
22
+ def site_key
23
+ ENV["TURNSTILE_SITE_KEY"] || Rails.application.credentials.dig(:turnstile, :site_key) || test_key("1x00000000000000000000AA")
24
+ end
25
+
26
+ def secret_key
27
+ ENV["TURNSTILE_SECRET_KEY"] || Rails.application.credentials.dig(:turnstile, :secret_key) || test_key("1x0000000000000000000000000000000AA")
28
+ end
29
+
30
+ private
31
+
32
+ def test_key(key)
33
+ raise "Set TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY (or turnstile.site_key / turnstile.secret_key in credentials)" unless Rails.env.local?
34
+
35
+ key
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Captcha
2
+ class << self
3
+ def verify(token, ip: nil)
4
+ provider.verify(token, ip:)
5
+ end
6
+
7
+ def site_key
8
+ provider.site_key
9
+ end
10
+
11
+ def provider
12
+ Rails.env.test? ? Null : Turnstile
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module Sms
2
+ module Log
3
+ def self.deliver(to:, body:)
4
+ Rails.logger.info("[sms] to=#{to} body=#{body}")
5
+ true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,70 @@
1
+ require "net/http"
2
+
3
+ module Sms
4
+ module Twilio
5
+ class DeliveryError < StandardError; end
6
+ class TransientError < DeliveryError; end
7
+
8
+ API_HOST = "api.twilio.com".freeze
9
+ API_VERSION = "2010-04-01".freeze
10
+ OPEN_TIMEOUT = 5
11
+ READ_TIMEOUT = 10
12
+
13
+ class << self
14
+ def deliver(to:, body:)
15
+ raise DeliveryError, "Twilio credentials are not configured" if account_sid.blank? || auth_token.blank?
16
+
17
+ response = post(to: to, body: body)
18
+ return true if response.is_a?(Net::HTTPSuccess)
19
+ raise TransientError, "Twilio responded #{response.code}" if retryable?(response)
20
+
21
+ raise DeliveryError, "Twilio responded #{response.code} (error #{error_code(response)})"
22
+ end
23
+
24
+ private
25
+
26
+ def post(to:, body:)
27
+ uri = URI("https://#{API_HOST}/#{API_VERSION}/Accounts/#{account_sid}/Messages.json")
28
+ params = { "To" => to, "Body" => body }.merge(sender_param)
29
+
30
+ request = Net::HTTP::Post.new(uri)
31
+ request.basic_auth(account_sid, auth_token)
32
+ request.set_form_data(params)
33
+
34
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true,
35
+ open_timeout: OPEN_TIMEOUT, read_timeout: READ_TIMEOUT) { |http| http.request(request) }
36
+ rescue Net::ReadTimeout
37
+ raise DeliveryError, "Twilio did not answer in time; the message may have been sent"
38
+ rescue Net::OpenTimeout, Net::WriteTimeout, SocketError, SystemCallError => e
39
+ raise TransientError, "Twilio unreachable (#{e.class})"
40
+ end
41
+
42
+ def retryable?(response) = response.is_a?(Net::HTTPServerError) || response.code == "429"
43
+
44
+ def error_code(response)
45
+ JSON.parse(response.body)["code"]
46
+ rescue StandardError
47
+ "unknown"
48
+ end
49
+
50
+ def sender_param
51
+ if messaging_service_sid.present?
52
+ { "MessagingServiceSid" => messaging_service_sid }
53
+ elsif from_number.present?
54
+ { "From" => from_number }
55
+ else
56
+ raise DeliveryError, "Set TWILIO_MESSAGING_SERVICE_SID or TWILIO_FROM_NUMBER"
57
+ end
58
+ end
59
+
60
+ def account_sid = credential("TWILIO_ACCOUNT_SID", :account_sid)
61
+ def auth_token = credential("TWILIO_AUTH_TOKEN", :auth_token)
62
+ def messaging_service_sid = credential("TWILIO_MESSAGING_SERVICE_SID", :messaging_service_sid)
63
+ def from_number = credential("TWILIO_FROM_NUMBER", :from_number)
64
+
65
+ def credential(env_key, credentials_key)
66
+ ENV[env_key].presence || Rails.application.credentials.dig(:twilio, credentials_key)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module Sms
2
+ class << self
3
+ def deliver_later(to:, body:)
4
+ SmsDeliveryJob.perform_later(to: to, body: body)
5
+ end
6
+
7
+ def deliver(to:, body:)
8
+ provider.deliver(to: to, body: body)
9
+ end
10
+
11
+ def provider
12
+ Rails.env.production? ? Twilio : Log
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,98 @@
1
+ class <%= identity.mailer_class %> < ApplicationMailer
2
+ self.delivery_job = AuthnzElevenMailDeliveryJob
3
+ <%- if security_notifications? -%>
4
+
5
+ SECURITY_NOTIFICATION_SUBJECTS = {
6
+ password_set: "Your password was set",
7
+ password_changed: "Your password was changed",
8
+ password_reset: "Your password was reset",
9
+ password_removed: "Your password was removed",
10
+ passkey_added: "A passkey was added",
11
+ passkey_removed: "A passkey was removed",
12
+ security_key_added: "A security key was added",
13
+ security_key_removed: "A security key was removed",
14
+ authenticator_enabled: "An authenticator was enabled",
15
+ authenticator_disabled: "An authenticator was disabled",
16
+ sms_mfa_enabled: "SMS two-factor authentication was enabled",
17
+ sms_mfa_disabled: "SMS two-factor authentication was disabled",
18
+ recovery_codes_generated: "Your recovery codes were generated",
19
+ connected_account_linked: "A connected account was linked",
20
+ connected_account_unlinked: "A connected account was unlinked",
21
+ email_changed: "Your email address was changed",
22
+ phone_changed: "Your phone number was changed",
23
+ session_revoked: "A session was signed out",
24
+ <%- if api_tokens? -%>
25
+ api_token_created: "An API token was created",
26
+ api_token_revoked: "An API token was revoked",
27
+ <%- end -%>
28
+ sessions_revoked_by_admin: "An administrator signed out your sessions",
29
+ second_factor_reset_by_admin: "An administrator reset your two-factor authentication",
30
+ deadbolt_cleared_by_admin: "An administrator restored password sign-in",
31
+ account_suspended: "An administrator suspended your account",
32
+ account_restored: "An administrator restored your account",
33
+ account_deleted: "Your account was deleted"
34
+ }.freeze
35
+
36
+ def self.send_security_notification(event, to: <%= identity.current_class %>.user.email)
37
+ return if to.blank?
38
+
39
+ with(
40
+ to:, event:, occurred_at: Time.current,
41
+ ip_address: <%= identity.current_class %>.ip_address,
42
+ user_agent: <%= identity.current_class %>.user_agent
43
+ ).security_notification.deliver_later
44
+ end
45
+ <%- end -%>
46
+ <%- if email_gated_registration? -%>
47
+
48
+ def registration_email_verification
49
+ @signed_id = params[:signed_id]
50
+
51
+ mail to: params[:to], subject: "Verify your email to finish signing up"
52
+ end
53
+ <%- end -%>
54
+ <%- if email_change_verification? -%>
55
+
56
+ def email_change_verification
57
+ @signed_id = params[:signed_id]
58
+
59
+ mail to: params[:to], subject: "Verify your new email address"
60
+ end
61
+ <%- end -%>
62
+ <%- if recoverable? -%>
63
+
64
+ def password_reset
65
+ @signed_id = params[:signed_id]
66
+
67
+ mail to: params[:to], subject: "Reset your password"
68
+ end
69
+ <%- end -%>
70
+ <%- if magic_link? -%>
71
+
72
+ def magic_link
73
+ @signed_id = params[:signed_id]
74
+
75
+ mail to: params[:to], subject: "Your sign in link"
76
+ end
77
+ <%- end -%>
78
+ <%- if security_notifications? -%>
79
+
80
+ def security_notification
81
+ @subject = SECURITY_NOTIFICATION_SUBJECTS.fetch(params[:event].to_sym)
82
+ @occurred_at = params[:occurred_at]
83
+ @ip_address = params[:ip_address]
84
+ @user_agent = params[:user_agent]
85
+
86
+ mail to: params[:to], subject: @subject
87
+ end
88
+ <%- end -%>
89
+ <%- if email_invitable? -%>
90
+
91
+ def invitation
92
+ @invitation = params[:invitation]
93
+ @signed_id = params[:signed_id]
94
+
95
+ mail to: params[:to], subject: "You've been invited to #{<%= identity.config_reference %>.app_name}"
96
+ end
97
+ <%- end -%>
98
+ end
@@ -0,0 +1,6 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ add_column :<%= identity.table %>, :guest, :boolean, null: false, default: false
4
+ add_index :<%= identity.table %>, :guest, where: "guest", name: "index_<%= identity.table %>_on_guest"
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.api_tokens_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ t.string :name, null: false
6
+ t.string :token_digest, null: false
7
+ t.datetime :expires_at, null: false
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :<%= identity.api_tokens_table %>, :token_digest, unique: true
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.events_table %><%= primary_key_options %> do |t|
4
+ t.string :event_type, null: false
5
+
6
+ t.references :subject, polymorphic: true, null: false, index: false<%= reference_type_options %>
7
+ t.references :actor, polymorphic: true, index: false<%= reference_type_options %>
8
+ t.references :context, polymorphic: true, index: false<%= reference_type_options %>
9
+
10
+ t.datetime :occurred_at, null: false
11
+ t.json :data
12
+
13
+ t.string :ip_address
14
+ t.string :user_agent
15
+ t.string :request_id
16
+
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :<%= identity.events_table %>, :event_type
21
+ add_index :<%= identity.events_table %>, :request_id
22
+ add_index :<%= identity.events_table %>, [ :subject_type, :subject_id, :occurred_at ]
23
+ add_index :<%= identity.events_table %>, [ :context_type, :context_id, :occurred_at ]
24
+ add_index :<%= identity.events_table %>, [ :actor_type, :actor_id ]
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.invitations_table %><%= primary_key_options %> do |t|
4
+ t.string :sent_to, null: false, index: true
5
+ <%- if multi_channel_invitable? -%>
6
+ t.string :sent_to_channel, null: false
7
+ <%- end -%>
8
+ t.datetime :expires_at
9
+ t.datetime :accepted_at
10
+
11
+ t.references :inviter, null: false, foreign_key: { to_table: :<%= identity.table %> }<%= reference_type_options %>
12
+ t.references :invitee, foreign_key: { to_table: :<%= identity.table %> }<%= reference_type_options %>
13
+ <%- if teams? -%>
14
+ t.references :team, foreign_key: <%= team_foreign_key %><%= reference_type_options %>
15
+ <%- end -%>
16
+
17
+ t.timestamps
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.memberships_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ t.references :team, null: false, foreign_key: <%= team_foreign_key %><%= reference_type_options %>
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :<%= identity.memberships_table %>, [ :<%= identity.foreign_key %>, :team_id ], unique: true
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.omniauth_identities_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ t.string :provider, null: false
6
+ t.string :uid, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ # A provider UID is meaningful and unique only within its provider.
12
+ add_index :<%= identity.omniauth_identities_table %>, %i[provider uid], unique: true
13
+
14
+ add_index :<%= identity.omniauth_identities_table %>, %i[<%= identity.foreign_key %> provider]
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.password_histories_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ # Each row stores an old password digest; Authenticatable prunes rows to the configured history depth.
6
+ t.string :password_digest, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.pending_registrations_table %><%= primary_key_options %> do |t|
4
+ <%- principals.each do |principal| -%>
5
+ # Do not add principal uniqueness here: an unverified or abandoned claim must not reserve another person's identifier.
6
+ t.string :<%= principal.column %>
7
+ <%- end -%>
8
+ <%- if password? -%>
9
+ t.string :password_digest
10
+ <%- end -%>
11
+ <%- if email_verifiable? -%>
12
+ t.datetime :email_verified_at
13
+ <%- end -%>
14
+ <%- if phone_verifiable? -%>
15
+ t.datetime :phone_verified_at
16
+ <%- end -%>
17
+ <%- if pre_account_passkey_enrollment? -%>
18
+ t.string :webauthn_handle
19
+ <%- end -%>
20
+ <%- if pending_registration_holds_provider? -%>
21
+ t.string :provider
22
+ t.string :uid
23
+ <%- end -%>
24
+ <%- if invitable? -%>
25
+ t.references :invitation, null: <%= !invite_only? %>, foreign_key: { to_table: :<%= identity.invitations_table %>, on_delete: :cascade }<%= reference_type_options %>
26
+ <%- end -%>
27
+
28
+ t.timestamps
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.recovery_codes_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ # Store only the digest; redemption deletes the row rather than marking it used.
6
+ t.string :code_digest, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.sessions_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ <%- if session_carries_team? -%>
6
+ t.references :team, foreign_key: <%= team_foreign_key %><%= reference_type_options %>
7
+ <%- end -%>
8
+ t.string :via
9
+ t.string :user_agent
10
+ <%- if sudoable? -%>
11
+ # Persist sudo recency on the session record so signing out clears it.
12
+ t.datetime :sudo_at
13
+ <%- end -%>
14
+ t.string :ip_address
15
+
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.teams_table %><%= primary_key_options %> do |t|
4
+ t.string :name, null: false
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,70 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.table %><%= primary_key_options %> do |t|
4
+ <%- principals.each do |principal| -%>
5
+ <%- if principal.type == :phone -%>
6
+ <%- if guest_excused_from_phone? || (guestable? && principal.required?) -%>
7
+ # Nullable because guests and incomplete accounts may not have a phone yet.
8
+ <%- end -%>
9
+ <%= principal.migration_field(nullable: phone_column_nullable?) %>
10
+ <%- else -%>
11
+ <%= principal.migration_field %>
12
+ <%- end -%>
13
+ <%- end -%>
14
+ <%- principals.select(&:functional_unique_index?).each do |principal| -%>
15
+ t.index "(LOWER(<%= principal.column %>))", unique: true, name: "index_<%= identity.table %>_on_lower_<%= principal.column %>"
16
+ <%- end -%>
17
+ <%- if password? -%>
18
+ <%- if password_nullable? -%>
19
+ # Nullable because there are multiple sign in methods.
20
+ t.string :password_digest
21
+ <%- else -%>
22
+ t.string :password_digest, null: false
23
+ <%- end -%>
24
+ <%- end -%>
25
+ <%- if password_rotatable? -%>
26
+ t.datetime :password_changed_at
27
+ <%- end -%>
28
+ <%- if deadboltable? -%>
29
+ t.integer :failed_attempts, null: false, default: 0
30
+ t.datetime :deadbolted_at
31
+ <%- end -%>
32
+ <%- if bannable? -%>
33
+ t.datetime :banned_at
34
+ t.datetime :banned_until
35
+ <%- end -%>
36
+ <%- if email_change_verification? -%>
37
+
38
+ t.string :pending_email
39
+ <%- end -%>
40
+ <%- if phone_change_verification? -%>
41
+
42
+ t.string :pending_phone
43
+ <%- end -%>
44
+ <%- if totp? -%>
45
+
46
+ t.datetime :totp_enrolled_at
47
+ t.string :totp_secret, null: false
48
+ t.datetime :totp_last_used_at
49
+ <%- end -%>
50
+ <%- if sms_second_factor? -%>
51
+
52
+ t.datetime :sms_enrolled_at
53
+ <%- end -%>
54
+ <%- if adminable? -%>
55
+
56
+ t.boolean :admin, null: false, default: false
57
+ <%- end -%>
58
+ <%- if webauthn_credentials? -%>
59
+
60
+ t.string :webauthn_handle, null: false
61
+ <%- end -%>
62
+ <%- if last_seenable? -%>
63
+
64
+ t.datetime :last_seen_at
65
+ <%- end -%>
66
+
67
+ t.timestamps
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,16 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :<%= identity.webauthn_credentials_table %><%= primary_key_options %> do |t|
4
+ t.references :<%= identity.singular %>, null: false, foreign_key: true<%= reference_type_options %>
5
+ t.string :name, null: false
6
+ t.string :external_id, null: false, index: { unique: true }
7
+ t.string :public_key, null: false
8
+ t.bigint :sign_count, null: false, default: 0
9
+ <%- if passkey_and_security_keys? -%>
10
+ t.integer :authentication_factor, null: false
11
+ <%- end -%>
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ class <%= identity.api_token_class %> < ApplicationRecord
2
+ belongs_to :<%= identity.singular %>
3
+ <%- if identity.namespaced? -%>
4
+
5
+ alias_method :user, :<%= identity.singular %>
6
+ <%- end -%>
7
+
8
+ validates :name, presence: true, length: { maximum: 100 }
9
+ validates :token_digest, presence: true, uniqueness: true
10
+ validates :expires_at, presence: true
11
+
12
+ def self.issue!(user:, name:)
13
+ secret = "ae11_#{SecureRandom.urlsafe_base64(32)}"
14
+ token = user.api_tokens.create!(name:, token_digest: Digest::SHA256.hexdigest(secret), expires_at: 90.days.from_now)
15
+ [ token, secret ]
16
+ end
17
+
18
+ def self.authenticate(secret)
19
+ return unless secret&.start_with?("ae11_") && secret.bytesize == 48
20
+
21
+ token = includes(:<%= identity.singular %>).find_by(token_digest: Digest::SHA256.hexdigest(secret))
22
+ token if token && token.expires_at.future?<%= " && !token.user.guest?" if guestable? %>
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module <%= identity.class_name %>::Adminable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ attr_readonly :admin # Prevent mass assignment or ordinary updates from elevating an existing account.
6
+
7
+ scope :admins, -> { where(admin: true) }
8
+ end
9
+ <%- if admin_mfa_gate? -%>
10
+
11
+ def admin_mfa_enrolled?
12
+ !admin? || second_factor_enrolled?
13
+ end
14
+ <%- end -%>
15
+ end