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,20 @@
|
|
|
1
|
+
module <%= identity.class_name %>::TeamMember
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
has_many :memberships, dependent: :destroy
|
|
6
|
+
has_many :teams, through: :memberships
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def on_a_team?
|
|
10
|
+
memberships.any?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def primary_team
|
|
14
|
+
teams.first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def member_of?(team)
|
|
18
|
+
memberships.exists?(team:)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class <%= identity.current_class %> < ActiveSupport::CurrentAttributes
|
|
2
|
+
attribute :session
|
|
3
|
+
attribute :user_agent, :ip_address, :request_id
|
|
4
|
+
<%- if impersonatable? -%>
|
|
5
|
+
attribute :impersonated_user
|
|
6
|
+
|
|
7
|
+
def true_user
|
|
8
|
+
session&.<%= identity.singular %>
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def user
|
|
12
|
+
impersonated_user || true_user
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def user=(user)
|
|
16
|
+
self.impersonated_user = user
|
|
17
|
+
end
|
|
18
|
+
<%- else -%>
|
|
19
|
+
|
|
20
|
+
def user
|
|
21
|
+
session&.<%= identity.singular %>
|
|
22
|
+
end
|
|
23
|
+
<%- end -%>
|
|
24
|
+
<%- if teams? -%>
|
|
25
|
+
|
|
26
|
+
attribute :team
|
|
27
|
+
<%- end -%>
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class <%= identity.event_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :subject, polymorphic: true
|
|
3
|
+
belongs_to :actor, polymorphic: true, optional: true
|
|
4
|
+
belongs_to :context, polymorphic: true, optional: true
|
|
5
|
+
|
|
6
|
+
scope :chronological, -> { order(occurred_at: :asc, id: :asc) }
|
|
7
|
+
scope :recent_first, -> { order(occurred_at: :desc, id: :desc) }
|
|
8
|
+
|
|
9
|
+
def self.record!(
|
|
10
|
+
event_type:,
|
|
11
|
+
subject:,
|
|
12
|
+
actor: nil,
|
|
13
|
+
context: nil,
|
|
14
|
+
occurred_at: nil,
|
|
15
|
+
data: nil,
|
|
16
|
+
ip_address: nil,
|
|
17
|
+
user_agent: nil,
|
|
18
|
+
request_id: nil
|
|
19
|
+
)
|
|
20
|
+
create!(
|
|
21
|
+
event_type: event_type,
|
|
22
|
+
subject: subject,
|
|
23
|
+
actor: actor || <%= identity.current_class %>.user,
|
|
24
|
+
context: context || subject.try(:event_context) || subject,
|
|
25
|
+
occurred_at: occurred_at || Time.current,
|
|
26
|
+
data: data || {},
|
|
27
|
+
ip_address: ip_address || <%= identity.current_class %>.ip_address,
|
|
28
|
+
user_agent: user_agent || <%= identity.current_class %>.user_agent,
|
|
29
|
+
request_id: request_id || <%= identity.current_class %>.request_id
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
class <%= identity.invitation_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :inviter, class_name: "<%= identity.class_name %>", inverse_of: :sent_invitations
|
|
3
|
+
belongs_to :invitee, class_name: "<%= identity.class_name %>", optional: true, inverse_of: :<%= teams? ? "accepted_invitations" : "invitation" %>
|
|
4
|
+
<%- if teams? -%>
|
|
5
|
+
belongs_to :team, optional: true
|
|
6
|
+
<%- end -%>
|
|
7
|
+
|
|
8
|
+
generates_token_for :invitation, expires_in: <%= identity.config_reference %>.tokens.invitation do
|
|
9
|
+
<%- if multi_channel_invitable? -%>
|
|
10
|
+
[ sent_to, sent_to_channel, accepted_at ]
|
|
11
|
+
<%- else -%>
|
|
12
|
+
[ sent_to, accepted_at ]
|
|
13
|
+
<%- end -%>
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
<%- if multi_channel_invitable? -%>
|
|
17
|
+
normalizes :sent_to, with: -> {
|
|
18
|
+
it.include?("@") ? it.strip.downcase : Phonelib.parse(it, <%= identity.config_reference %>.phone.default_country).to_s
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
before_validation do
|
|
22
|
+
self.sent_to_channel = sent_to&.include?("@") ? "email" : "phone" if will_save_change_to_sent_to?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
validates :sent_to, presence: true
|
|
26
|
+
validates :sent_to_channel, inclusion: { in: %w[email phone] }
|
|
27
|
+
validates :sent_to, <%= principals.email.validation %>, if: :email_addressed?
|
|
28
|
+
validates :sent_to, phone: { countries: <%= identity.config_reference %>.phone.allowed_countries, format: :e164 }, if: :phone_addressed?
|
|
29
|
+
<%- elsif email_invitable? -%>
|
|
30
|
+
normalizes :sent_to, with: <%= principals.email.normalization(config_reference: identity.config_reference) %>
|
|
31
|
+
validates :sent_to, presence: true, <%= principals.email.validation %>
|
|
32
|
+
<%- else -%>
|
|
33
|
+
normalizes :sent_to, with: <%= principals.phone.normalization(config_reference: identity.config_reference) %>
|
|
34
|
+
validates :sent_to, presence: true
|
|
35
|
+
validates :sent_to, phone: { countries: <%= identity.config_reference %>.phone.allowed_countries, format: :e164 }
|
|
36
|
+
<%- end -%>
|
|
37
|
+
<%- if encrypted_pii? -%>
|
|
38
|
+
|
|
39
|
+
encrypts :sent_to, deterministic: true
|
|
40
|
+
<%- end -%>
|
|
41
|
+
<%- if coy? -%>
|
|
42
|
+
|
|
43
|
+
def offer_unless_redundant
|
|
44
|
+
offer unless redundant?
|
|
45
|
+
end
|
|
46
|
+
<%- else -%>
|
|
47
|
+
|
|
48
|
+
validate on: :create do
|
|
49
|
+
<%- if teams? -%>
|
|
50
|
+
errors.add(:base, team ? "#{sent_to} is already a member of #{team.name}." : "#{sent_to} already has an account.") if redundant?
|
|
51
|
+
<%- else -%>
|
|
52
|
+
errors.add(:base, "#{sent_to} already has an account.") if redundant?
|
|
53
|
+
<%- end -%>
|
|
54
|
+
end
|
|
55
|
+
<%- end -%>
|
|
56
|
+
|
|
57
|
+
def offer
|
|
58
|
+
update!(expires_at: <%= identity.config_reference %>.tokens.invitation.from_now)
|
|
59
|
+
signed_id = generate_token_for(:invitation)
|
|
60
|
+
<%- if multi_channel_invitable? -%>
|
|
61
|
+
|
|
62
|
+
if email_addressed?
|
|
63
|
+
<%= identity.mailer_class %>.with(invitation: self, signed_id:, to: sent_to).invitation.deliver_later
|
|
64
|
+
else
|
|
65
|
+
Sms.deliver_later(to: sent_to, body: sms_message(signed_id))
|
|
66
|
+
end
|
|
67
|
+
<%- elsif email_invitable? -%>
|
|
68
|
+
<%= identity.mailer_class %>.with(invitation: self, signed_id:, to: sent_to).invitation.deliver_later
|
|
69
|
+
<%- else -%>
|
|
70
|
+
Sms.deliver_later(to: sent_to, body: sms_message(signed_id))
|
|
71
|
+
<%- end -%>
|
|
72
|
+
signed_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def accepted?
|
|
76
|
+
accepted_at.present?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
<%- unless multi_channel_invitable? -%>
|
|
80
|
+
def sent_to_channel = "<%= email_invitable? ? "email" : "phone" %>"
|
|
81
|
+
|
|
82
|
+
<%- end -%>
|
|
83
|
+
def email_addressed? = sent_to_channel == "email"
|
|
84
|
+
def phone_addressed? = sent_to_channel == "phone"
|
|
85
|
+
|
|
86
|
+
def addressed_to?(user)
|
|
87
|
+
sent_to == user[sent_to_channel]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def redundant?
|
|
91
|
+
<%- if teams? -%>
|
|
92
|
+
(team ? team.<%= identity.plural %> : <%= identity.class_name %>).exists?(sent_to_channel => sent_to)
|
|
93
|
+
<%- else -%>
|
|
94
|
+
<%= identity.class_name %>.exists?(sent_to_channel => sent_to)
|
|
95
|
+
<%- end -%>
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def accept(user)
|
|
99
|
+
return false unless addressed_to?(user)
|
|
100
|
+
|
|
101
|
+
with_lock do
|
|
102
|
+
return false if accepted?
|
|
103
|
+
|
|
104
|
+
update!(invitee: user, accepted_at: Time.current)
|
|
105
|
+
<%- if teams? -%>
|
|
106
|
+
team.memberships.find_or_create_by!(<%= identity.singular %>: user) if team
|
|
107
|
+
<%- end -%>
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
true
|
|
111
|
+
end
|
|
112
|
+
<%- if phone_invitable? -%>
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def sms_message(signed_id)
|
|
117
|
+
url = Rails.application.routes.url_helpers.<%= identity.namespaced? ? "#{identity.route_scope}_accept_invitation_url" : "accept_invitation_url" %>(
|
|
118
|
+
token: signed_id, **Rails.application.config.action_mailer.default_url_options)
|
|
119
|
+
"#{inviter.<%= principals.display.column %>} invited you to #{<%= identity.config_reference %>.app_name}: #{url}"
|
|
120
|
+
end
|
|
121
|
+
<%- end -%>
|
|
122
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class <%= identity.membership_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :<%= identity.singular %>
|
|
3
|
+
belongs_to :team
|
|
4
|
+
|
|
5
|
+
validates :<%= identity.foreign_key %>, uniqueness: { scope: :team_id }
|
|
6
|
+
<%- if session_carries_team? -%>
|
|
7
|
+
|
|
8
|
+
after_destroy :clear_active_team_from_sessions
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def clear_active_team_from_sessions
|
|
13
|
+
<%= identity.session_class %>.where(<%= identity.foreign_key %>:, team_id:).update_all(team_id: nil)
|
|
14
|
+
end
|
|
15
|
+
<%- end -%>
|
|
16
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class <%= identity.omniauth_identity_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :<%= identity.singular %>
|
|
3
|
+
|
|
4
|
+
validates :provider, presence: true
|
|
5
|
+
validates :uid, presence: true, uniqueness: { scope: :provider }
|
|
6
|
+
|
|
7
|
+
# The provider named by a sign-in, e.g. :google for google_oauth2.
|
|
8
|
+
def self.via(provider) = provider.to_s.split("_").first.downcase.to_sym
|
|
9
|
+
|
|
10
|
+
def via = self.class.via(provider)
|
|
11
|
+
def provider_name = via.to_s.titleize
|
|
12
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
class <%= identity.pending_registration_class %> < ApplicationRecord
|
|
2
|
+
include <%= identity.principals_concern %>
|
|
3
|
+
<%- if password? -%>
|
|
4
|
+
include <%= identity.password_policy_concern %>
|
|
5
|
+
<%- end -%>
|
|
6
|
+
<%- if sms? -%>
|
|
7
|
+
include <%= identity.class_name %>::SmsChallengeable
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%- if invitable? -%>
|
|
10
|
+
|
|
11
|
+
belongs_to :invitation<%= invite_only? ? "" : ", optional: true" %>
|
|
12
|
+
<%- end -%>
|
|
13
|
+
|
|
14
|
+
Completion = Struct.new(:status, :user, :via)
|
|
15
|
+
<%- if pre_account_passkey_enrollment? -%>
|
|
16
|
+
|
|
17
|
+
attr_accessor :passkey
|
|
18
|
+
<%- end -%>
|
|
19
|
+
|
|
20
|
+
<%- if email_gated_registration? -%>
|
|
21
|
+
generates_token_for :registration_email_verification, expires_in: <%= identity.config_reference %>.tokens.registration_email_verification do
|
|
22
|
+
[ email, email_verified_at ]
|
|
23
|
+
end
|
|
24
|
+
<%- end -%>
|
|
25
|
+
|
|
26
|
+
scope :expired, -> { where(updated_at: ...<%= identity.config_reference %>.tokens.pending_registration.ago) }
|
|
27
|
+
scope :unexpired, -> { where(updated_at: <%= identity.config_reference %>.tokens.pending_registration.ago..) }
|
|
28
|
+
<%- if pre_account_passkey_enrollment? -%>
|
|
29
|
+
|
|
30
|
+
before_validation on: :create do
|
|
31
|
+
self.webauthn_handle ||= WebAuthn.generate_user_id
|
|
32
|
+
end
|
|
33
|
+
<%- end -%>
|
|
34
|
+
<%- if invitable? && (email_verifiable? || phone_verifiable?) -%>
|
|
35
|
+
|
|
36
|
+
before_create :verify_invited_channel
|
|
37
|
+
<%- end -%>
|
|
38
|
+
<%- if principals.username -%>
|
|
39
|
+
|
|
40
|
+
validate :username_not_taken
|
|
41
|
+
<%- end -%>
|
|
42
|
+
<%- if guest_excused_from_phone? -%>
|
|
43
|
+
|
|
44
|
+
def guest? = false
|
|
45
|
+
<%- end -%>
|
|
46
|
+
<%- if password_nullable? && !password_blank_rows_all? -%>
|
|
47
|
+
|
|
48
|
+
def password_may_be_blank? = <%= staged_password_blank_rows_expression %>
|
|
49
|
+
<%- end -%>
|
|
50
|
+
|
|
51
|
+
def next_step
|
|
52
|
+
<%- if omniauth_registration? -%>
|
|
53
|
+
return :complete_profile if <%= omniauth_profile_missing_condition %>
|
|
54
|
+
<%- end -%>
|
|
55
|
+
<%- if email_verifiable? -%>
|
|
56
|
+
return :verify_email if email.present? && email_verified_at.blank?
|
|
57
|
+
<%- end -%>
|
|
58
|
+
<%- if phone_gated_registration? -%>
|
|
59
|
+
return :verify_phone if phone.present? && phone_verified_at.blank?
|
|
60
|
+
<%- end -%>
|
|
61
|
+
<%- if credential_staged_registration? -%>
|
|
62
|
+
return :enroll_credential unless sign_in_methods.any?
|
|
63
|
+
<%- end -%>
|
|
64
|
+
:complete
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def sign_in_methods
|
|
68
|
+
methods = []
|
|
69
|
+
<%- if password? -%>
|
|
70
|
+
methods << :password if password_digest.present?
|
|
71
|
+
<%- end -%>
|
|
72
|
+
<%- if pre_account_passkey_enrollment? -%>
|
|
73
|
+
methods << :passkey if passkey
|
|
74
|
+
<%- end -%>
|
|
75
|
+
<%- if pending_registration_holds_provider? -%>
|
|
76
|
+
methods << :omniauth if provider.present?
|
|
77
|
+
<%- end -%>
|
|
78
|
+
<%- if magic_link? -%>
|
|
79
|
+
methods << :magic_link if email.present?<%= email_verifiable? ? " && email_verified_at.present?" : "" %>
|
|
80
|
+
<%- end -%>
|
|
81
|
+
<%- if sms_code? -%>
|
|
82
|
+
methods << :sms_code if phone.present?<%= phone_verifiable? ? " && phone_verified_at.present?" : "" %>
|
|
83
|
+
<%- end -%>
|
|
84
|
+
methods
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def complete
|
|
88
|
+
return Completion.new(:invalid) unless valid?
|
|
89
|
+
|
|
90
|
+
user = <%= identity.class_name %>.new(account_attributes)
|
|
91
|
+
<%- if pre_account_passkey_enrollment? -%>
|
|
92
|
+
user.webauthn_credentials.build(**passkey, name: "Passkey"<%= factor_attr(:first_factor) %>) if passkey
|
|
93
|
+
<%- end -%>
|
|
94
|
+
<%- if pending_registration_holds_provider? -%>
|
|
95
|
+
user.omniauth_identities.build(provider:, uid:) if provider
|
|
96
|
+
<%- end -%>
|
|
97
|
+
|
|
98
|
+
unless user.save
|
|
99
|
+
errors.merge!(user.errors)
|
|
100
|
+
return collision_result || Completion.new(:invalid)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
<%- if invitable? -%>
|
|
104
|
+
invitation&.accept(user)
|
|
105
|
+
<%- end -%>
|
|
106
|
+
destroy!
|
|
107
|
+
<%- if pending_registration_holds_provider? -%>
|
|
108
|
+
Completion.new(:created, user, provider ? <%= identity.omniauth_identity_class %>.via(provider) : :registration)
|
|
109
|
+
<%- else -%>
|
|
110
|
+
Completion.new(:created, user, :registration)
|
|
111
|
+
<%- end -%>
|
|
112
|
+
rescue ActiveRecord::RecordNotUnique
|
|
113
|
+
collision_result || raise
|
|
114
|
+
end
|
|
115
|
+
<%- if email_gated_registration? -%>
|
|
116
|
+
|
|
117
|
+
def request_email_verification
|
|
118
|
+
touch # keeps :expired from reaping this while verification is in progress
|
|
119
|
+
return if <%= identity.class_name %>.exists?(email: email)
|
|
120
|
+
|
|
121
|
+
signed_id = generate_token_for(:registration_email_verification)
|
|
122
|
+
<%= identity.mailer_class %>.with(signed_id: signed_id, to: email).registration_email_verification.deliver_later
|
|
123
|
+
end
|
|
124
|
+
<%- end -%>
|
|
125
|
+
<%- if phone_gated_registration? -%>
|
|
126
|
+
|
|
127
|
+
def request_phone_verification
|
|
128
|
+
touch # keeps :expired from reaping this while verification is in progress
|
|
129
|
+
return if <%= identity.class_name %>.exists?(phone: phone)
|
|
130
|
+
|
|
131
|
+
issue_sms_challenge
|
|
132
|
+
end
|
|
133
|
+
<%- end -%>
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
<%- if invitable? && (email_verifiable? || phone_verifiable?) -%>
|
|
137
|
+
|
|
138
|
+
def verify_invited_channel
|
|
139
|
+
return unless invitation&.addressed_to?(self)
|
|
140
|
+
|
|
141
|
+
<%- if multi_channel_invitable? && email_verifiable? && phone_verifiable? -%>
|
|
142
|
+
if invitation.email_addressed?
|
|
143
|
+
self.email_verified_at = Time.current
|
|
144
|
+
<%- if principals.phone.optional? -%>
|
|
145
|
+
self.phone = nil
|
|
146
|
+
<%- end -%>
|
|
147
|
+
else
|
|
148
|
+
self.phone_verified_at = Time.current
|
|
149
|
+
<%- if principals.email.optional? -%>
|
|
150
|
+
self.email = nil
|
|
151
|
+
<%- end -%>
|
|
152
|
+
end
|
|
153
|
+
<%- elsif email_invitable? && email_verifiable? -%>
|
|
154
|
+
self.email_verified_at = Time.current
|
|
155
|
+
<%- elsif phone_invitable? && phone_verifiable? -%>
|
|
156
|
+
self.phone_verified_at = Time.current
|
|
157
|
+
<%- end -%>
|
|
158
|
+
end
|
|
159
|
+
<%- end -%>
|
|
160
|
+
|
|
161
|
+
def account_attributes
|
|
162
|
+
attributes = {}
|
|
163
|
+
<%- principals.each do |principal| -%>
|
|
164
|
+
<%- if principal.type == :email && email_verifiable? -%>
|
|
165
|
+
attributes[:email] = email if email_verified_at.present?
|
|
166
|
+
<%- elsif principal.type == :phone && phone_verifiable? -%>
|
|
167
|
+
attributes[:phone] = phone if phone_verified_at.present?
|
|
168
|
+
<%- else -%>
|
|
169
|
+
attributes[:<%= principal.column %>] = <%= principal.column %>
|
|
170
|
+
<%- end -%>
|
|
171
|
+
<%- end -%>
|
|
172
|
+
<%- if password? -%>
|
|
173
|
+
attributes[:password_digest] = password_digest
|
|
174
|
+
<%- end -%>
|
|
175
|
+
<%- if pre_account_passkey_enrollment? -%>
|
|
176
|
+
attributes[:webauthn_handle] = webauthn_handle
|
|
177
|
+
<%- end -%>
|
|
178
|
+
attributes
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def collision_result
|
|
182
|
+
<%- if pending_registration_holds_provider? -%>
|
|
183
|
+
if provider.present? && <%= identity.omniauth_identity_class %>.exists?(provider:, uid:)
|
|
184
|
+
destroy!
|
|
185
|
+
return Completion.new(:taken)
|
|
186
|
+
end
|
|
187
|
+
<%- end -%>
|
|
188
|
+
<%- if principals.username -%>
|
|
189
|
+
if username.present? && <%= identity.class_name %>.where("LOWER(username) = ?", username.downcase).exists?
|
|
190
|
+
errors.add(:username, "has already been taken")
|
|
191
|
+
return Completion.new(:invalid)
|
|
192
|
+
end
|
|
193
|
+
<%- end -%>
|
|
194
|
+
<%- if principals.email -%>
|
|
195
|
+
if email.present? && <%= identity.class_name %>.exists?(email: email)
|
|
196
|
+
destroy!
|
|
197
|
+
return Completion.new(:taken)
|
|
198
|
+
end
|
|
199
|
+
<%- end -%>
|
|
200
|
+
<%- if principals.phone -%>
|
|
201
|
+
if <%= phone_verifiable? ? "phone_verified_at.present?" : "phone.present?" %> && <%= identity.class_name %>.exists?(phone: phone)
|
|
202
|
+
destroy!
|
|
203
|
+
return Completion.new(:taken)
|
|
204
|
+
end
|
|
205
|
+
<%- end -%>
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
<%- if principals.username -%>
|
|
209
|
+
|
|
210
|
+
def username_not_taken
|
|
211
|
+
return if username.blank?
|
|
212
|
+
|
|
213
|
+
errors.add(:username, "has already been taken") if <%= identity.class_name %>.where("LOWER(username) = ?", username.downcase).exists?
|
|
214
|
+
end
|
|
215
|
+
<%- end -%>
|
|
216
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class <%= identity.recovery_code_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :<%= identity.singular %>
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
def regenerate_for(user)
|
|
6
|
+
codes = Array.new(<%= identity.config_reference %>.second_factor.recovery_code_count) { generate_code }
|
|
7
|
+
|
|
8
|
+
transaction do
|
|
9
|
+
user.recovery_codes.delete_all
|
|
10
|
+
user.recovery_codes.create!(codes.map { |code| { code: code } })
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
codes
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def redeem(user:, code:)
|
|
17
|
+
return false unless user.recovery_codes.where(code_digest: digest(code)).delete_all == 1
|
|
18
|
+
|
|
19
|
+
<%- if trackable? -%>
|
|
20
|
+
user.record_event(
|
|
21
|
+
"<%= identity.singular %>.recovery_code_used",
|
|
22
|
+
actor: user,
|
|
23
|
+
data: { remaining: user.recovery_codes.count }
|
|
24
|
+
)
|
|
25
|
+
<%- end -%>
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate_code
|
|
30
|
+
SecureRandom.alphanumeric(<%= identity.config_reference %>.second_factor.recovery_code_length).downcase
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def digest(code)
|
|
34
|
+
OpenSSL::HMAC.hexdigest("SHA256", Rails.application.secret_key_base, code.to_s) # An HMAC prevents a database leak from exposing recovery codes that can be used to bypass the second factor.
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def code=(plaintext)
|
|
39
|
+
self.code_digest = self.class.digest(plaintext)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class <%= identity.session_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :<%= identity.singular %>
|
|
3
|
+
<%- if session_carries_team? -%>
|
|
4
|
+
belongs_to :team, optional: true
|
|
5
|
+
<%- end -%>
|
|
6
|
+
<%- if identity.namespaced? -%>
|
|
7
|
+
|
|
8
|
+
alias_method :user, :<%= identity.singular %>
|
|
9
|
+
<%- end -%>
|
|
10
|
+
<%- if timeoutable? -%>
|
|
11
|
+
|
|
12
|
+
def self.idle_timeout
|
|
13
|
+
<%= identity.config_reference %>.session.idle_timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
scope :active, -> { where(updated_at: idle_timeout.ago..) }
|
|
17
|
+
<%- if guestable? -%>
|
|
18
|
+
scope :timed_out, -> { where(updated_at: ...idle_timeout.ago).where.not(<%= identity.singular %>: <%= identity.class_name %>.guests) }
|
|
19
|
+
<%- else -%>
|
|
20
|
+
scope :timed_out, -> { where(updated_at: ...idle_timeout.ago) }
|
|
21
|
+
<%- end -%>
|
|
22
|
+
<%- end -%>
|
|
23
|
+
|
|
24
|
+
before_create do
|
|
25
|
+
self.user_agent = <%= identity.current_class %>.user_agent
|
|
26
|
+
self.ip_address = <%= identity.current_class %>.ip_address
|
|
27
|
+
end
|
|
28
|
+
<%- if trackable? -%>
|
|
29
|
+
|
|
30
|
+
after_create do
|
|
31
|
+
<%- if guestable? -%>
|
|
32
|
+
<%= identity.singular %>.record_event("<%= identity.singular %>.signed_in", actor: <%= identity.singular %>, data: { via:, user_agent:, ip_address: }) unless <%= identity.singular %>.guest?
|
|
33
|
+
<%- else -%>
|
|
34
|
+
<%= identity.singular %>.record_event("<%= identity.singular %>.signed_in", actor: <%= identity.singular %>, data: { via:, user_agent:, ip_address: })
|
|
35
|
+
<%- end -%>
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
after_destroy do
|
|
39
|
+
<%- if guestable? -%>
|
|
40
|
+
<%= identity.singular %>.record_event("<%= identity.singular %>.signed_out", actor: <%= identity.singular %>, data: { via:, user_agent:, ip_address: }) unless <%= identity.singular %>.guest?
|
|
41
|
+
<%- else -%>
|
|
42
|
+
<%= identity.singular %>.record_event("<%= identity.singular %>.signed_out", actor: <%= identity.singular %>, data: { via:, user_agent:, ip_address: })
|
|
43
|
+
<%- end -%>
|
|
44
|
+
end
|
|
45
|
+
<%- end -%>
|
|
46
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class <%= identity.team_class %> < ApplicationRecord
|
|
2
|
+
has_many :memberships, dependent: :destroy
|
|
3
|
+
has_many :<%= identity.plural %>, through: :memberships
|
|
4
|
+
<%- if invitable? -%>
|
|
5
|
+
has_many :invitations, dependent: :destroy
|
|
6
|
+
<%- end -%>
|
|
7
|
+
<%- if session_carries_team? -%>
|
|
8
|
+
has_many :sessions, dependent: :nullify
|
|
9
|
+
<%- end -%>
|
|
10
|
+
|
|
11
|
+
validates :name, presence: true
|
|
12
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class <%= identity.class_name %> < ApplicationRecord
|
|
2
|
+
include <%= identity.principals_concern %>
|
|
3
|
+
include Authenticatable
|
|
4
|
+
<%- if password? -%>
|
|
5
|
+
include <%= identity.password_policy_concern %>
|
|
6
|
+
<%- end -%>
|
|
7
|
+
<%- if password_rotatable? -%>
|
|
8
|
+
include PasswordRotatable
|
|
9
|
+
<%- end -%>
|
|
10
|
+
<%- if password_historical? -%>
|
|
11
|
+
include PasswordHistorical
|
|
12
|
+
<%- end -%>
|
|
13
|
+
<%- if recoverable? -%>
|
|
14
|
+
include Recoverable
|
|
15
|
+
<%- end -%>
|
|
16
|
+
<%- if magic_link? -%>
|
|
17
|
+
include MagicLinkable
|
|
18
|
+
<%- end -%>
|
|
19
|
+
<%- if second_factor? -%>
|
|
20
|
+
include SecondFactor
|
|
21
|
+
<%- end -%>
|
|
22
|
+
<%- if deadboltable? -%>
|
|
23
|
+
include Deadboltable
|
|
24
|
+
<%- end -%>
|
|
25
|
+
<%- if bannable? -%>
|
|
26
|
+
include Bannable
|
|
27
|
+
<%- end -%>
|
|
28
|
+
<%- if sms? -%>
|
|
29
|
+
include SmsChallengeable
|
|
30
|
+
<%- end -%>
|
|
31
|
+
<%- if email_change_verification? -%>
|
|
32
|
+
include EmailVerifiable
|
|
33
|
+
<%- end -%>
|
|
34
|
+
<%- if phone_change_verification? -%>
|
|
35
|
+
include PhoneVerifiable
|
|
36
|
+
<%- end -%>
|
|
37
|
+
<%- if teams? -%>
|
|
38
|
+
include TeamMember
|
|
39
|
+
<%- end -%>
|
|
40
|
+
<%- if adminable? -%>
|
|
41
|
+
include Adminable
|
|
42
|
+
<%- end -%>
|
|
43
|
+
<%- if trackable? -%>
|
|
44
|
+
include Eventable
|
|
45
|
+
<%- end -%>
|
|
46
|
+
<%- if guestable? -%>
|
|
47
|
+
include Guestable
|
|
48
|
+
<%- end -%>
|
|
49
|
+
<%- if invitable? -%>
|
|
50
|
+
include Invitable
|
|
51
|
+
<%- end -%>
|
|
52
|
+
<%- unique_principals = principals.reject { |principal| principal.channel? && coy? } -%>
|
|
53
|
+
<%- if unique_principals.any? -%>
|
|
54
|
+
|
|
55
|
+
<%- unique_principals.each do |principal| -%>
|
|
56
|
+
validates :<%= principal.column %>, uniqueness: <%= principal.channel? ? "true, allow_nil: true" : "{ case_sensitive: false }" %>
|
|
57
|
+
<%- end -%>
|
|
58
|
+
<%- end -%>
|
|
59
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class <%= identity.webauthn_credential_class %> < ApplicationRecord
|
|
2
|
+
belongs_to :<%= identity.singular %>
|
|
3
|
+
|
|
4
|
+
validates :name, presence: true
|
|
5
|
+
<%- if passkey_and_security_keys? -%>
|
|
6
|
+
|
|
7
|
+
enum :authentication_factor, { first_factor: 0, second_factor: 1 }
|
|
8
|
+
|
|
9
|
+
scope :passkeys, -> { first_factor }
|
|
10
|
+
scope :second_factors, -> { second_factor }
|
|
11
|
+
<%- end -%>
|
|
12
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
namespace :authnz_eleven do
|
|
2
|
+
<%- if identity.namespaced? -%>
|
|
3
|
+
namespace :<%= identity.task_namespace %> do
|
|
4
|
+
<%- end -%>
|
|
5
|
+
<%- if guestable? -%>
|
|
6
|
+
desc "Delete stale guest users (and their sessions) older than config.guest.ttl"
|
|
7
|
+
task delete_expired_guests: :environment do
|
|
8
|
+
scope = <%= identity.class_name %>.expired_guests
|
|
9
|
+
|
|
10
|
+
count = scope.count
|
|
11
|
+
scope.find_each(&:destroy)
|
|
12
|
+
|
|
13
|
+
puts "Deleted #{count} expired guest user(s)."
|
|
14
|
+
end
|
|
15
|
+
<%- end -%>
|
|
16
|
+
<%- if guestable? && timeoutable? -%>
|
|
17
|
+
|
|
18
|
+
<%- end -%>
|
|
19
|
+
<%- if pending_registration? -%>
|
|
20
|
+
desc "Delete sign-ups abandoned for longer than config.tokens.pending_registration"
|
|
21
|
+
task delete_expired_pending_registrations: :environment do
|
|
22
|
+
count = <%= identity.pending_registration_class %>.expired.destroy_all.size
|
|
23
|
+
|
|
24
|
+
puts "Deleted #{count} expired pending registration(s)."
|
|
25
|
+
end
|
|
26
|
+
<%- end -%>
|
|
27
|
+
<%- if pending_registration? && timeoutable? -%>
|
|
28
|
+
|
|
29
|
+
<%- end -%>
|
|
30
|
+
<%- if timeoutable? -%>
|
|
31
|
+
desc "Delete sessions that have timed out (idle past config.session.idle_timeout)"
|
|
32
|
+
task delete_timed_out_sessions: :environment do
|
|
33
|
+
count = <%= identity.session_class %>.timed_out.delete_all
|
|
34
|
+
|
|
35
|
+
puts "Deleted #{count} timed-out session(s)."
|
|
36
|
+
end
|
|
37
|
+
<%- end -%>
|
|
38
|
+
<%- if identity.namespaced? -%>
|
|
39
|
+
end
|
|
40
|
+
<%- end -%>
|
|
41
|
+
end
|