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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +384 -0
- data/lib/authnz_eleven/identity.rb +210 -0
- data/lib/authnz_eleven/principal.rb +201 -0
- data/lib/authnz_eleven/principals.rb +111 -0
- data/lib/authnz_eleven/version.rb +5 -0
- data/lib/authnz_eleven/wizard.rb +570 -0
- data/lib/authnz_eleven.rb +15 -0
- data/lib/generators/authnz_eleven/USAGE +26 -0
- data/lib/generators/authnz_eleven/authnz_eleven_generator.rb +3040 -0
- data/lib/generators/authnz_eleven/templates/config/forbidden_usernames.LICENSE +22 -0
- data/lib/generators/authnz_eleven/templates/config/forbidden_usernames.txt +558 -0
- data/lib/generators/authnz_eleven/templates/config/initializers/authentication.rb.tt +275 -0
- data/lib/generators/authnz_eleven/templates/config/initializers/omniauth.rb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/config/initializers/team_slug.rb.tt +24 -0
- data/lib/generators/authnz_eleven/templates/config/initializers/webauthn.rb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/config/locales/authentication.en.yml.tt +11 -0
- data/lib/generators/authnz_eleven/templates/controllers/active_teams_controller.rb.tt +6 -0
- data/lib/generators/authnz_eleven/templates/controllers/admin/dashboard_controller.rb.tt +4 -0
- data/lib/generators/authnz_eleven/templates/controllers/admin/sessions_controller.rb.tt +17 -0
- data/lib/generators/authnz_eleven/templates/controllers/admin/users_controller.rb.tt +91 -0
- data/lib/generators/authnz_eleven/templates/controllers/admin_controller.rb.tt +6 -0
- data/lib/generators/authnz_eleven/templates/controllers/base_controller.rb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/api_token_authentication.rb.tt +21 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/easy_dev_login.rb.tt +47 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/registering.rb.tt +25 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/sudoable.rb.tt +117 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication/webauthn_ceremony.rb.tt +51 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authentication.rb.tt +421 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/authorization.rb.tt +48 -0
- data/lib/generators/authnz_eleven/templates/controllers/concerns/captchable.rb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/controllers/impersonations_controller.rb.tt +8 -0
- data/lib/generators/authnz_eleven/templates/controllers/invitations_controller.rb.tt +65 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/api_tokens_controller.rb.tt +54 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/authentications/events_controller.rb.tt +5 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/dashboard_controller.rb.tt +4 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/email_verifications_controller.rb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/emails_controller.rb.tt +93 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/invitations_controller.rb.tt +61 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/authenticators_controller.rb.tt +87 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/recovery_codes_controller.rb.tt +27 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/security_keys_controller.rb.tt +86 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/multi_factor_authentication/sms_controller.rb.tt +84 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/omniauth_identities_controller.rb.tt +27 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/passkeys_controller.rb.tt +70 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/passwords_controller.rb.tt +74 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/phone_verifications_controller.rb.tt +39 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/phones_controller.rb.tt +88 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/sessions_controller.rb.tt +29 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings/users_controller.rb.tt +42 -0
- data/lib/generators/authnz_eleven/templates/controllers/settings_controller.rb.tt +5 -0
- data/lib/generators/authnz_eleven/templates/controllers/teams_controller.rb.tt +38 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments/passkeys_controller.rb.tt +36 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments/passwords_controller.rb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/credential_enrollments_controller.rb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/easy_dev_logins_controller.rb.tt +19 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/email_verifications_controller.rb.tt +36 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/magic_link_controller.rb.tt +53 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/recovery_codes_controller.rb.tt +30 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/security_keys_controller.rb.tt +34 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/sms_controller.rb.tt +45 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/multi_factor_authentication/challenge/totps_controller.rb.tt +42 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/omniauth/registrations_controller.rb.tt +26 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/omniauth_controller.rb.tt +139 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/passkeys/sessions_controller.rb.tt +18 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/password_resets_controller.rb.tt +64 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/registrations/email_verifications_controller.rb.tt +53 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/registrations/phone_verifications_controller.rb.tt +37 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/registrations_controller.rb.tt +112 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/sessions_controller.rb.tt +52 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/sms_sessions_controller.rb.tt +53 -0
- data/lib/generators/authnz_eleven/templates/controllers/users/sudos_controller.rb.tt +42 -0
- data/lib/generators/authnz_eleven/templates/erb/admin/dashboard/index.html.erb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/erb/admin/sessions/index.html.erb.tt +27 -0
- data/lib/generators/authnz_eleven/templates/erb/admin/users/index.html.erb.tt +32 -0
- data/lib/generators/authnz_eleven/templates/erb/admin/users/show.html.erb.tt +70 -0
- data/lib/generators/authnz_eleven/templates/erb/invitations/show.html.erb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/api_tokens/create.html.erb.tt +7 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/api_tokens/index.html.erb.tt +29 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/authentications/events/index.html.erb.tt +18 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/dashboard/index.html.erb.tt +75 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/emails/edit.html.erb.tt +68 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/invitations/new.html.erb.tt +31 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/authenticators/new.html.erb.tt +57 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/recovery_codes/create.html.erb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/recovery_codes/index.html.erb.tt +13 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/edit.html.erb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/index.html.erb.tt +20 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/security_keys/new.html.erb.tt +17 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/multi_factor_authentication/sms/new.html.erb.tt +35 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/omniauth_identities/index.html.erb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/edit.html.erb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/index.html.erb.tt +21 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/passkeys/new.html.erb.tt +13 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/passwords/edit.html.erb.tt +68 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/phone_verifications/new.html.erb.tt +22 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/phones/edit.html.erb.tt +58 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/sessions/index.html.erb.tt +29 -0
- data/lib/generators/authnz_eleven/templates/erb/settings/users/show.html.erb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/erb/shared/_authnz_eleven_nav.html.erb.tt +24 -0
- data/lib/generators/authnz_eleven/templates/erb/shared/_authnz_eleven_sudo_challenge.html.erb.tt +43 -0
- data/lib/generators/authnz_eleven/templates/erb/shared/_captcha.html.erb.tt +8 -0
- data/lib/generators/authnz_eleven/templates/erb/teams/index.html.erb.tt +34 -0
- data/lib/generators/authnz_eleven/templates/erb/teams/new.html.erb.tt +22 -0
- data/lib/generators/authnz_eleven/templates/erb/teams/show.html.erb.tt +5 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/email_change_verification.html.erb.tt +7 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/invitation.html.erb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/magic_link.html.erb.tt +5 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/password_reset.html.erb.tt +6 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/registration_email_verification.html.erb.tt +7 -0
- data/lib/generators/authnz_eleven/templates/erb/user_mailer/security_notification.html.erb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/passkeys/new.html.erb.tt +24 -0
- data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/passwords/new.html.erb.tt +21 -0
- data/lib/generators/authnz_eleven/templates/erb/users/credential_enrollments/show.html.erb.tt +21 -0
- data/lib/generators/authnz_eleven/templates/erb/users/email_verifications/show.html.erb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/erb/users/magic_link/edit.html.erb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/erb/users/magic_link/new.html.erb.tt +22 -0
- data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/recovery_codes/new.html.erb.tt +20 -0
- data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/security_keys/new.html.erb.tt +17 -0
- data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/sms/new.html.erb.tt +25 -0
- data/lib/generators/authnz_eleven/templates/erb/users/multi_factor_authentication/challenge/totps/new.html.erb.tt +21 -0
- data/lib/generators/authnz_eleven/templates/erb/users/omniauth/registrations/new.html.erb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/erb/users/password_resets/edit.html.erb.tt +26 -0
- data/lib/generators/authnz_eleven/templates/erb/users/password_resets/new.html.erb.tt +20 -0
- data/lib/generators/authnz_eleven/templates/erb/users/registrations/email_verifications/new.html.erb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/erb/users/registrations/email_verifications/show.html.erb.tt +34 -0
- data/lib/generators/authnz_eleven/templates/erb/users/registrations/new.html.erb.tt +87 -0
- data/lib/generators/authnz_eleven/templates/erb/users/registrations/phone_verifications/new.html.erb.tt +46 -0
- data/lib/generators/authnz_eleven/templates/erb/users/sessions/new.html.erb.tt +88 -0
- data/lib/generators/authnz_eleven/templates/erb/users/sms_sessions/edit.html.erb.tt +30 -0
- data/lib/generators/authnz_eleven/templates/erb/users/sms_sessions/new.html.erb.tt +22 -0
- data/lib/generators/authnz_eleven/templates/erb/users/sudos/new.html.erb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/javascript/controllers/webauthn_controller.js.tt +142 -0
- data/lib/generators/authnz_eleven/templates/jobs/authnz_eleven_deferred_job.rb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/jobs/authnz_eleven_mail_delivery_job.rb.tt +3 -0
- data/lib/generators/authnz_eleven/templates/jobs/sms_delivery_job.rb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/lib/captcha/null.rb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/lib/captcha/turnstile.rb.tt +39 -0
- data/lib/generators/authnz_eleven/templates/lib/captcha.rb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/lib/sms/log.rb.tt +8 -0
- data/lib/generators/authnz_eleven/templates/lib/sms/twilio.rb.tt +70 -0
- data/lib/generators/authnz_eleven/templates/lib/sms.rb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/mailers/user_mailer.rb.tt +98 -0
- data/lib/generators/authnz_eleven/templates/migrations/add_guest_to_users_migration.rb.tt +6 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_api_tokens_migration.rb.tt +14 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_events_migration.rb.tt +26 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_invitations_migration.rb.tt +20 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_memberships_migration.rb.tt +12 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_omniauth_identities_migration.rb.tt +16 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_password_histories_migration.rb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_pending_registrations_migration.rb.tt +31 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_recovery_codes_migration.rb.tt +11 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_sessions_migration.rb.tt +19 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_teams_migration.rb.tt +9 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_users_migration.rb.tt +70 -0
- data/lib/generators/authnz_eleven/templates/migrations/create_webauthn_credentials_migration.rb.tt +16 -0
- data/lib/generators/authnz_eleven/templates/models/api_token.rb.tt +24 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/adminable.rb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/authenticatable.rb.tt +151 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/bannable.rb.tt +24 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/deadboltable.rb.tt +39 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/email_verifiable.rb.tt +52 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/eventable.rb.tt +22 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/guestable.rb.tt +30 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/invitable.rb.tt +12 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/magic_linkable.rb.tt +34 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/password_historical.rb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/password_policy.rb.tt +38 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/password_rotatable.rb.tt +15 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/phone_verifiable.rb.tt +50 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/principals.rb.tt +67 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/recoverable.rb.tt +19 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/second_factor.rb.tt +52 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/sms_challengeable.rb.tt +38 -0
- data/lib/generators/authnz_eleven/templates/models/concerns/team_member.rb.tt +20 -0
- data/lib/generators/authnz_eleven/templates/models/current.rb.tt +28 -0
- data/lib/generators/authnz_eleven/templates/models/event.rb.tt +32 -0
- data/lib/generators/authnz_eleven/templates/models/invitation.rb.tt +122 -0
- data/lib/generators/authnz_eleven/templates/models/membership.rb.tt +16 -0
- data/lib/generators/authnz_eleven/templates/models/omniauth_identity.rb.tt +12 -0
- data/lib/generators/authnz_eleven/templates/models/password_history.rb.tt +3 -0
- data/lib/generators/authnz_eleven/templates/models/pending_registration.rb.tt +216 -0
- data/lib/generators/authnz_eleven/templates/models/recovery_code.rb.tt +41 -0
- data/lib/generators/authnz_eleven/templates/models/session.rb.tt +46 -0
- data/lib/generators/authnz_eleven/templates/models/team.rb.tt +12 -0
- data/lib/generators/authnz_eleven/templates/models/user.rb.tt +59 -0
- data/lib/generators/authnz_eleven/templates/models/webauthn_credential.rb.tt +12 -0
- data/lib/generators/authnz_eleven/templates/tasks/authnz_eleven.rake.tt +41 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/account_existence_parity_test.rb.tt +158 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/admin/access_test.rb.tt +139 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/admin/multi_factor_authentication_test.rb.tt +65 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/api_token_authentication_test.rb.tt +45 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/credential_enrollment_test.rb.tt +84 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/guest_access_test.rb.tt +113 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/invitations_controller_test.rb.tt +230 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/passkey_ceremony_test.rb.tt +126 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/password_rotation_test.rb.tt +67 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/security_key_ceremony_test.rb.tt +218 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/security_notifications_test.rb.tt +39 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/session_lifecycle_test.rb.tt +102 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/api_tokens_controller_test.rb.tt +61 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/authentications/events_controller_test.rb.tt +17 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/email_verifications_controller_test.rb.tt +39 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/invitations_controller_test.rb.tt +157 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/management_test.rb.tt +256 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/multi_factor_authentication/management_test.rb.tt +222 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/omniauth_identities_controller_test.rb.tt +57 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/settings/phone_verifications_controller_test.rb.tt +146 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/teams_controller_test.rb.tt +83 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/email_verifications_controller_test.rb.tt +74 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/magic_link_controller_test.rb.tt +90 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/multi_factor_authentication/challenge_test.rb.tt +143 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/omniauth/registrations_controller_test.rb.tt +138 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/omniauth_controller_test.rb.tt +97 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/password_resets_controller_test.rb.tt +58 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations/email_verifications_controller_test.rb.tt +102 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations/phone_verifications_controller_test.rb.tt +69 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/registrations_controller_test.rb.tt +180 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sessions_controller_test.rb.tt +124 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sms_sessions_controller_test.rb.tt +111 -0
- data/lib/generators/authnz_eleven/templates/test_unit/controllers/users/sudos_controller_test.rb.tt +110 -0
- data/lib/generators/authnz_eleven/templates/test_unit/fixtures/memberships.yml.tt +13 -0
- data/lib/generators/authnz_eleven/templates/test_unit/fixtures/teams.yml.tt +2 -0
- data/lib/generators/authnz_eleven/templates/test_unit/fixtures/users.yml.tt +68 -0
- data/lib/generators/authnz_eleven/templates/test_unit/mailers/user_mailer_test.rb.tt +88 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/api_token_test.rb.tt +38 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/password_history_test.rb.tt +51 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/session_test.rb.tt +16 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/sms_challengeable_test.rb.tt +108 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/user/guestable_test.rb.tt +78 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/user/recovery_codes_test.rb.tt +32 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/user/sign_in_methods_test.rb.tt +56 -0
- data/lib/generators/authnz_eleven/templates/test_unit/models/user_test.rb.tt +145 -0
- data/lib/generators/authnz_eleven/wizard_generator.rb +52 -0
- metadata +309 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AuthnzEleven
|
|
4
|
+
# One piece of identifying information on the generated account. A Principal
|
|
5
|
+
# derives every name and expression the templates need for one identifying
|
|
6
|
+
# attribute (email, username, phone), so the per-type differences live here
|
|
7
|
+
# instead of being scattered across the templates.
|
|
8
|
+
#
|
|
9
|
+
# Each selected principal is an explicit part of the generated account shape.
|
|
10
|
+
class Principal
|
|
11
|
+
# type: :email | :username | :phone
|
|
12
|
+
attr_reader :type
|
|
13
|
+
|
|
14
|
+
def initialize(type:, required: true, login: false, permanent: false)
|
|
15
|
+
@type = type.to_sym
|
|
16
|
+
@required = required
|
|
17
|
+
@login = login
|
|
18
|
+
@permanent = permanent
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def required? = @required
|
|
22
|
+
def optional? = !@required
|
|
23
|
+
# a key the sign-in form accepts
|
|
24
|
+
def login? = @login
|
|
25
|
+
|
|
26
|
+
# Fixed once the account holds it: no self-serve change surface is generated
|
|
27
|
+
# for it. The column stays writable, so an admin or a console still can.
|
|
28
|
+
def permanent? = @permanent
|
|
29
|
+
def changeable? = !@permanent
|
|
30
|
+
|
|
31
|
+
# A channel can receive a message (verification, reset, magic link, OTP).
|
|
32
|
+
def channel? = type != :username
|
|
33
|
+
|
|
34
|
+
# "email" / "username" / "phone"
|
|
35
|
+
def column = type.to_s
|
|
36
|
+
# Form label text. Every principal type is a single word, so a plain
|
|
37
|
+
# capitalize matches humanize without pulling ActiveSupport into the gem's
|
|
38
|
+
# otherwise dependency-free load path (the host Rails app provides it at
|
|
39
|
+
# generation time, but the value object is unit-tested standalone).
|
|
40
|
+
def label = type.to_s.capitalize
|
|
41
|
+
|
|
42
|
+
# Form helper + autocomplete token for generated views.
|
|
43
|
+
def field_helper
|
|
44
|
+
{ email: "email_field", phone: "telephone_field", username: "text_field" }.fetch(type)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def autocomplete
|
|
48
|
+
{ email: "email", phone: "tel", username: "username" }.fetch(type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Model-layer expressions, emitted verbatim into user.rb.tt. Keeping them
|
|
52
|
+
# here (not scattered in the template) keeps the template readable and the
|
|
53
|
+
# per-type differences in one place.
|
|
54
|
+
def validation
|
|
55
|
+
case type
|
|
56
|
+
# disposable_domain, not disposable: the plain option also resolves the
|
|
57
|
+
# domain's MX servers, putting a DNS lookup inside a validation. This one
|
|
58
|
+
# is a lookup against the gem's bundled list, in-process and offline.
|
|
59
|
+
when :email then '"valid_email_2/email": { disposable_domain: true }'
|
|
60
|
+
when :phone then %() # validated by an inline Phonelib validate block
|
|
61
|
+
# Letters (both cases), digits, underscore; 3–20 chars; at least one letter;
|
|
62
|
+
# no "@". The ≥1-letter rule is what makes the sign-in dispatch a total,
|
|
63
|
+
# unambiguous partition: "@" → email, else a letter → username, else
|
|
64
|
+
# → phone — a username always has a letter, a phone never does. Relaxing it
|
|
65
|
+
# to allow all-digit names reopens the username/phone ambiguity. Case is
|
|
66
|
+
# preserved for display; uniqueness folds case via the functional index below.
|
|
67
|
+
when :username then %q(format: { with: /\A(?=.*[a-zA-Z])[a-zA-Z0-9_]{3,20}\z/ })
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Everything after `validates :<column>, ` on the model's validation line.
|
|
72
|
+
# A required principal gets `presence: true, <validation>`; an optional one
|
|
73
|
+
# drops presence and allows nil, so a blank submission (normalized to nil
|
|
74
|
+
# below) skips validation and rides the partial unique index.
|
|
75
|
+
def validates_arguments
|
|
76
|
+
rule = validation.to_s.strip
|
|
77
|
+
[
|
|
78
|
+
("presence: true" if required?),
|
|
79
|
+
(rule unless rule.empty?),
|
|
80
|
+
("allow_nil: true" if optional?)
|
|
81
|
+
].compact.join(", ")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Phone can't ride the generic `validates :col, <format>` line: its validity
|
|
85
|
+
# is a Phonelib.valid? check (a custom validator, not a `format:` matcher), and
|
|
86
|
+
# when guests exist it must skip them (guests carry a nil phone). The template
|
|
87
|
+
# emits its own lines instead.
|
|
88
|
+
def dedicated_validation? = type == :phone
|
|
89
|
+
|
|
90
|
+
# Sign-in dispatch: when one form field accepts several login keys, the
|
|
91
|
+
# lookup is routed to a column by a *structural* test of the submitted string —
|
|
92
|
+
# an email has "@", a username has a letter (guaranteed by the format), a phone
|
|
93
|
+
# has neither. #login_dispatch_test is that test; the lowest-priority present
|
|
94
|
+
# key needs none (it's the fallback). Email must be tested before username
|
|
95
|
+
# because an email address also contains letters.
|
|
96
|
+
def login_dispatch_test
|
|
97
|
+
case type
|
|
98
|
+
when :email then %(identifier.include?("@"))
|
|
99
|
+
when :username then %(identifier.match?(/[a-zA-Z]/))
|
|
100
|
+
when :phone then nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The no-password lookup for this key, as a Ruby expression over a stripped
|
|
105
|
+
# `identifier` local — used by the model's find_by_login. Email and phone pass
|
|
106
|
+
# the raw identifier and lean on `normalizes` being applied inside finders
|
|
107
|
+
# (downcase / E.164); username needs an explicit LOWER() match because its
|
|
108
|
+
# normalization is strip-only (case is folded for uniqueness, not on write).
|
|
109
|
+
def find_by_login_lookup
|
|
110
|
+
case type
|
|
111
|
+
when :email then %(find_by(email: identifier))
|
|
112
|
+
when :username then 'find_by("LOWER(username) = ?", identifier.downcase)'
|
|
113
|
+
when :phone then %(find_by(phone: identifier))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The same lookup, but verifying a password — used by the model's
|
|
118
|
+
# authenticate_with_password. Channel keys go through authenticate_by, which
|
|
119
|
+
# equalizes timing whether or not a row was found. It only hashes a decoy on a
|
|
120
|
+
# miss, though, so a found row without a password would skip the hash entirely:
|
|
121
|
+
# with_password makes that row a miss too. Username can't, because its lookup
|
|
122
|
+
# needs LOWER() to fold case, so that arm finds first and authenticates second.
|
|
123
|
+
# The asymmetry is safe: a username is a public identifier, so it is honest
|
|
124
|
+
# about existence anyway.
|
|
125
|
+
def authenticate_lookup
|
|
126
|
+
case type
|
|
127
|
+
when :email then %(with_password.authenticate_by(email: identifier, password:))
|
|
128
|
+
when :username
|
|
129
|
+
'find_by("LOWER(username) = ?", identifier.downcase)&.then { |user| user if user.authenticate(password) }'
|
|
130
|
+
when :phone then %(with_password.authenticate_by(phone: identifier, password:))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# +config_reference+ is the generated app's config constant ("UserAuth"), which
|
|
135
|
+
# the phone branch reads a default country from. It's threaded in rather than
|
|
136
|
+
# derived here because it belongs to the identity, not to the principal — see
|
|
137
|
+
# Identity#config_reference, the same value the initializer template writes.
|
|
138
|
+
def normalization(config_reference:)
|
|
139
|
+
base =
|
|
140
|
+
case type
|
|
141
|
+
when :email then "it.strip.downcase"
|
|
142
|
+
# Strip only — the chosen case is stored and displayed verbatim.
|
|
143
|
+
when :username then "it.strip"
|
|
144
|
+
# A number typed without a leading "+" is read as though dialled inside
|
|
145
|
+
# config.phone.default_country. Passing that country to parse as an
|
|
146
|
+
# ARGUMENT is what suppresses Phonelib's fallback of reading a bare
|
|
147
|
+
# number's leading digits AS a country code — which would turn a mistyped
|
|
148
|
+
# local number into a valid foreign one ("6494461709" comes back a good NZ
|
|
149
|
+
# number). Setting Phonelib.default_country globally does NOT do this.
|
|
150
|
+
#
|
|
151
|
+
# #to_s returns E.164 when the number parses and the original string
|
|
152
|
+
# otherwise, so an uninterpretable number reaches the validator looking the
|
|
153
|
+
# way the user typed it, and is rejected there.
|
|
154
|
+
when :phone then "Phonelib.parse(it, #{config_reference}.phone.default_country).to_s"
|
|
155
|
+
end
|
|
156
|
+
# An optional principal must collapse a blank submission to nil so the
|
|
157
|
+
# partial unique index (WHERE <col> IS NOT NULL) and allow_nil both apply.
|
|
158
|
+
# Required principals normalize without it.
|
|
159
|
+
base += ".presence" if optional?
|
|
160
|
+
"-> { #{base} }"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Whether flows may reveal that a value is taken. Usernames are public
|
|
164
|
+
# identifiers: sign-up MUST honestly reject a taken username. Channel
|
|
165
|
+
# principals stay coy.
|
|
166
|
+
def publicly_unique? = type == :username
|
|
167
|
+
|
|
168
|
+
# The `t.string` line for create_users_migration. Principals whose uniqueness
|
|
169
|
+
# can't ride an inline `index:` option (username — see
|
|
170
|
+
# #functional_unique_index?) emit a bare column here and their index
|
|
171
|
+
# separately.
|
|
172
|
+
#
|
|
173
|
+
# +nullable+ defaults to requiredness but is overridden for a *required* phone
|
|
174
|
+
# in a --guestable build, where guests carry a nil number so the constraint has
|
|
175
|
+
# to live in the model (unless: :guest?) rather than in NOT NULL.
|
|
176
|
+
def migration_field(nullable: optional?)
|
|
177
|
+
case type
|
|
178
|
+
when :username then "t.string :username, null: false"
|
|
179
|
+
when :email
|
|
180
|
+
# Optional email drops NOT NULL and rides a partial unique index, so
|
|
181
|
+
# multiple rows may hold NULL while set values stay unique.
|
|
182
|
+
if required?
|
|
183
|
+
"t.string :email, null: false, index: { unique: true }"
|
|
184
|
+
else
|
|
185
|
+
%(t.string :email, index: { unique: true, where: "email IS NOT NULL" })
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
null_option = " null: false," unless nullable
|
|
189
|
+
where = %(, where: "#{column} IS NOT NULL") if nullable
|
|
190
|
+
"t.string :#{column},#{null_option} index: { unique: true#{where} }"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Username uniqueness is case-insensitive but its stored value keeps its
|
|
195
|
+
# display case, so it can't use migration_field's inline unique index. It
|
|
196
|
+
# rides a functional unique index on LOWER(column) instead, emitted
|
|
197
|
+
# separately. SQLite and PostgreSQL take it as written; MySQL wants the
|
|
198
|
+
# expression in a second pair of parens.
|
|
199
|
+
def functional_unique_index? = type == :username
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "principal"
|
|
4
|
+
|
|
5
|
+
module AuthnzEleven
|
|
6
|
+
# The ordered set of principals for one generator run. Where Identity names
|
|
7
|
+
# things (class, table, routes), Principals shapes the account's identifying
|
|
8
|
+
# columns and the flows that ride on them.
|
|
9
|
+
#
|
|
10
|
+
# Email and phone are opt-in principals; a bare flag makes the selected
|
|
11
|
+
# principal required. Every valid build has at least one principal.
|
|
12
|
+
class Principals
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
REQUIREDNESS = %w[required optional].freeze
|
|
16
|
+
|
|
17
|
+
# Build the set from the generator's parsed options. --username prepends a
|
|
18
|
+
# required login principal, and --email / --phone add channel principals.
|
|
19
|
+
def self.from_options(options)
|
|
20
|
+
# Ordered username, email, phone so #display prefers them in that order and
|
|
21
|
+
# the sign-in dispatch tests the most specific keys first. Each channel
|
|
22
|
+
# helper returns nil when its flag omits it.
|
|
23
|
+
list = [
|
|
24
|
+
(Principal.new(type: :username, required: true, login: true) if options[:username]),
|
|
25
|
+
email_principal(options[:email]),
|
|
26
|
+
phone_principal(options[:phone])
|
|
27
|
+
].compact
|
|
28
|
+
new(list)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# A --email / --phone value is a requiredness word, optionally followed by the
|
|
32
|
+
# word "permanent" — the only modifier a principal takes:
|
|
33
|
+
# "required,permanent" -> [ "required", [ "permanent" ] ]. The generator
|
|
34
|
+
# validates the words; this only splits them.
|
|
35
|
+
def self.split_mode(value)
|
|
36
|
+
requiredness, *modifiers = value.to_s.split(",").map { it.strip.downcase }.reject(&:empty?)
|
|
37
|
+
[requiredness.to_s, modifiers]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.permanent?(modifiers) = modifiers == %w[permanent]
|
|
41
|
+
|
|
42
|
+
# The email principal for the given --email mode, or nil when email is off.
|
|
43
|
+
def self.email_principal(mode)
|
|
44
|
+
requiredness, modifiers = split_mode(mode)
|
|
45
|
+
return unless %w[required optional].include?(requiredness)
|
|
46
|
+
|
|
47
|
+
Principal.new(type: :email, required: requiredness == "required", login: true,
|
|
48
|
+
permanent: permanent?(modifiers))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The phone principal for the given --phone mode, or nil when phone is off.
|
|
52
|
+
# Phone is a login key too (the sign-in form dispatches to it).
|
|
53
|
+
def self.phone_principal(mode)
|
|
54
|
+
requiredness, modifiers = split_mode(mode)
|
|
55
|
+
return unless %w[required optional].include?(requiredness)
|
|
56
|
+
|
|
57
|
+
Principal.new(type: :phone, required: requiredness == "required", login: true,
|
|
58
|
+
permanent: permanent?(modifiers))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initialize(list)
|
|
62
|
+
@list = list
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def each(&) = @list.each(&)
|
|
66
|
+
|
|
67
|
+
def email = find { |p| p.type == :email }
|
|
68
|
+
def username = find { |p| p.type == :username }
|
|
69
|
+
def phone = find { |p| p.type == :phone }
|
|
70
|
+
|
|
71
|
+
def login_principals = select(&:login?)
|
|
72
|
+
|
|
73
|
+
# Login keys in *dispatch* priority for the sign-in form's shape test:
|
|
74
|
+
# email first (its "@" is the most specific test), then username (a letter),
|
|
75
|
+
# then phone (the fallback). Distinct from #login_principals' list order, which
|
|
76
|
+
# follows display preference (username first).
|
|
77
|
+
def login_dispatch_principals
|
|
78
|
+
login_principals.sort_by { |p| %i[email username phone].index(p.type) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def channels = select(&:channel?)
|
|
82
|
+
def required = select(&:required?)
|
|
83
|
+
|
|
84
|
+
# The principal shown in UI/admin/WebAuthn: the first login principal
|
|
85
|
+
# (username when present, else email, else phone).
|
|
86
|
+
def display
|
|
87
|
+
login_principals.min_by { |p| %i[username email phone].index(p.type) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# #display names one column for the whole build, which is what a table header
|
|
91
|
+
# or a WebAuthn handle wants. Showing a person their own identifier is a
|
|
92
|
+
# record-level question instead: --contactable's Reddit shape
|
|
93
|
+
# (--email=optional --phone=optional) promises each account holds *a* channel
|
|
94
|
+
# without saying which, so the build-time pick is nil on half the rows.
|
|
95
|
+
#
|
|
96
|
+
# Only an OPTIONAL principal can be nil at the front of that precedence, so the
|
|
97
|
+
# fallback stops at the first required one: a required username or email is
|
|
98
|
+
# NOT NULL (guests get a synthesized one), which makes everything after it
|
|
99
|
+
# unreachable. A required phone is the exception — nullable under --guestable
|
|
100
|
+
# and while verification is outstanding — but phone sorts last and so never
|
|
101
|
+
# leads. Most builds therefore emit a bare column read.
|
|
102
|
+
def display_expression(receiver)
|
|
103
|
+
ordered = login_principals.sort_by { |p| %i[username email phone].index(p.type) }
|
|
104
|
+
reachable = ordered.take_while(&:optional?) + ordered.drop_while(&:optional?).take(1)
|
|
105
|
+
reachable.map { |p| "#{receiver}.#{p.column}" }.join(" || ")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Whether the sign-in form must dispatch between several login keys.
|
|
109
|
+
def multi_login? = login_principals.size > 1
|
|
110
|
+
end
|
|
111
|
+
end
|