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,570 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../authnz_eleven"
|
|
4
|
+
|
|
5
|
+
module AuthnzEleven
|
|
6
|
+
# An interactive flag picker for the main generator.
|
|
7
|
+
#
|
|
8
|
+
# The generator has forty-odd flags and a dozen validators explaining which
|
|
9
|
+
# combinations don't work. Those validators exist because a command line
|
|
10
|
+
# arrives all at once. A wizard asks in dependency order instead, so a
|
|
11
|
+
# combination the generator would refuse is simply never offered: the answers
|
|
12
|
+
# to one screen decide what the next screen contains.
|
|
13
|
+
#
|
|
14
|
+
# That ordering is the whole design. Principals before doors, because a door
|
|
15
|
+
# needs something to deliver to. Verification before the password mode,
|
|
16
|
+
# because `deferred` waits on a proved channel. Teams and admin last, because
|
|
17
|
+
# nothing upstream depends on them.
|
|
18
|
+
class Wizard
|
|
19
|
+
# Flags that turn another one on by themselves, so naming both in the
|
|
20
|
+
# emitted command would be noise.
|
|
21
|
+
IMPLIED = {
|
|
22
|
+
"admin_dashboard" => "adminable",
|
|
23
|
+
"impersonatable" => "adminable"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# The answers are seeded with the recommended explicit principal and the
|
|
27
|
+
# generator's behavioral defaults.
|
|
28
|
+
class Answers
|
|
29
|
+
attr_accessor :user_class, :namespaced,
|
|
30
|
+
:email, :phone, :username,
|
|
31
|
+
:contactable, :verify, :coy, :encrypted,
|
|
32
|
+
:doors, :registration, :permanent, :password_mode, :password_extras,
|
|
33
|
+
:second_factor, :protections,
|
|
34
|
+
:sessions, :max_sessions, :api_tokens, :teams, :admin
|
|
35
|
+
|
|
36
|
+
def initialize
|
|
37
|
+
@user_class = "User"
|
|
38
|
+
@namespaced = false
|
|
39
|
+
@email = "required"
|
|
40
|
+
@phone = "none"
|
|
41
|
+
@username = false
|
|
42
|
+
@contactable = true
|
|
43
|
+
@verify = true
|
|
44
|
+
@coy = false
|
|
45
|
+
@encrypted = false
|
|
46
|
+
@doors = []
|
|
47
|
+
@registration = "open"
|
|
48
|
+
@permanent = []
|
|
49
|
+
@password_mode = "required"
|
|
50
|
+
@password_extras = []
|
|
51
|
+
@second_factor = []
|
|
52
|
+
@protections = []
|
|
53
|
+
@sessions = []
|
|
54
|
+
@max_sessions = "none"
|
|
55
|
+
@api_tokens = false
|
|
56
|
+
@teams = "none"
|
|
57
|
+
@admin = []
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def email? = email != "none"
|
|
61
|
+
def phone? = phone != "none"
|
|
62
|
+
|
|
63
|
+
# The requiredness words of the channels this build actually has, so the
|
|
64
|
+
# channel rules can be asked of the set rather than of each column.
|
|
65
|
+
def channels = [email, phone].reject { |mode| mode == "none" }
|
|
66
|
+
def channels? = channels.any?
|
|
67
|
+
def optional_channel? = channels.include?("optional")
|
|
68
|
+
|
|
69
|
+
# One channel, and it may be absent: nothing for "optional" to be optional
|
|
70
|
+
# against, so an account could hold no way to be reached at all.
|
|
71
|
+
def sole_optional_channel? = channels == ["optional"]
|
|
72
|
+
|
|
73
|
+
def password? = doors.include?("password")
|
|
74
|
+
def sms_code? = doors.include?("sms_code")
|
|
75
|
+
def other_doors? = doors.any? { |d| d != "password" }
|
|
76
|
+
|
|
77
|
+
# Mirrors the generator's validate_password_deferred!.
|
|
78
|
+
def password_deferrable? = doors.include?("passkey") || (contactable && verify && channels?)
|
|
79
|
+
|
|
80
|
+
# Mirrors the generator's validate_sudoable!.
|
|
81
|
+
def sudo_answerable? = password? || doors.include?("passkey") || second_factor.intersect?(%w[totp webauthn])
|
|
82
|
+
|
|
83
|
+
def email_verifiable? = verify && email?
|
|
84
|
+
def phone_verifiable? = verify && phone?
|
|
85
|
+
|
|
86
|
+
# Mirrors the generator's validate_coy!.
|
|
87
|
+
def coyable? = verify && channels?
|
|
88
|
+
|
|
89
|
+
# "Permanent" removes the settings form for a value, which only reads as a
|
|
90
|
+
# rule when the value was there from the start: every sign-up path collects
|
|
91
|
+
# a required phone before the account exists.
|
|
92
|
+
def phone_permanent? = phone == "required"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def initialize
|
|
96
|
+
@answers = Answers.new
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
attr_reader :answers
|
|
100
|
+
|
|
101
|
+
def run
|
|
102
|
+
require "huh"
|
|
103
|
+
|
|
104
|
+
errors = form.run
|
|
105
|
+
raise Error, errors.map(&:message).join("; ") if errors.any?
|
|
106
|
+
|
|
107
|
+
to_flags
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_flags
|
|
111
|
+
[
|
|
112
|
+
*identity_flags,
|
|
113
|
+
*principal_flags,
|
|
114
|
+
*channel_flags,
|
|
115
|
+
*door_flags,
|
|
116
|
+
*feature_flags
|
|
117
|
+
]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def command = ["bin/rails generate authnz_eleven", *to_flags].join(" ")
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Every screen's options are plain [label, value] pairs, so which options a
|
|
125
|
+
# build offers can be asked without a terminal — or the form library — in
|
|
126
|
+
# the room. `menu` is the only place they become Huh objects.
|
|
127
|
+
def menu(pairs) = pairs.map { |label, value| Huh.option(label, value) }
|
|
128
|
+
|
|
129
|
+
EMAIL_MODES = [
|
|
130
|
+
%w[Required required],
|
|
131
|
+
%w[Optional optional],
|
|
132
|
+
%w[None none]
|
|
133
|
+
].freeze
|
|
134
|
+
|
|
135
|
+
PHONE_MODES = [
|
|
136
|
+
%w[Required required],
|
|
137
|
+
%w[Optional optional],
|
|
138
|
+
%w[None none]
|
|
139
|
+
].freeze
|
|
140
|
+
|
|
141
|
+
SECOND_FACTORS = [
|
|
142
|
+
["Authenticator app (TOTP)", "totp"],
|
|
143
|
+
["Hardware security key (WebAuthn)", "webauthn"],
|
|
144
|
+
["Texted code (SMS)", "sms"]
|
|
145
|
+
].freeze
|
|
146
|
+
|
|
147
|
+
PROTECTIONS = [
|
|
148
|
+
["Captcha on signed-out forms (Cloudflare Turnstile)", "captchable"],
|
|
149
|
+
["Ask users to authenticate again before dangerous actions (sudo)", "sudoable"],
|
|
150
|
+
["Email users when security settings change", "security_notifications"]
|
|
151
|
+
].freeze
|
|
152
|
+
|
|
153
|
+
SESSIONS = [
|
|
154
|
+
["'Remember me' checkbox", "rememberable"],
|
|
155
|
+
["Sign out inactive users", "timeoutable"],
|
|
156
|
+
["Record when users were last seen", "last_seenable"],
|
|
157
|
+
["Audit trail of authentication activity", "trackable"],
|
|
158
|
+
["Anonymous guest users", "guestable"],
|
|
159
|
+
["Sign in as anyone (development only)", "easy_dev_login"]
|
|
160
|
+
].freeze
|
|
161
|
+
|
|
162
|
+
MAX_SESSIONS = [
|
|
163
|
+
["No limit", "none"],
|
|
164
|
+
["Sign out the oldest", "evict"],
|
|
165
|
+
["Ask the user to sign one out", "prompt"]
|
|
166
|
+
].freeze
|
|
167
|
+
|
|
168
|
+
TEAMS = [
|
|
169
|
+
["No teams", "none"],
|
|
170
|
+
["Team stored in the session", "session"],
|
|
171
|
+
["Team in the URL (/:team_id)", "scope"],
|
|
172
|
+
["Team in the URL, via middleware (37signals-style)", "middleware"]
|
|
173
|
+
].freeze
|
|
174
|
+
|
|
175
|
+
ADMIN = [
|
|
176
|
+
["Admin users", "adminable"],
|
|
177
|
+
["An /admin area", "admin_dashboard"],
|
|
178
|
+
["Admins can impersonate users", "impersonatable"],
|
|
179
|
+
["Admins can ban users", "bannable"]
|
|
180
|
+
].freeze
|
|
181
|
+
|
|
182
|
+
def form
|
|
183
|
+
Huh.form(
|
|
184
|
+
identity_group,
|
|
185
|
+
namespaced_group,
|
|
186
|
+
principals_group,
|
|
187
|
+
contactable_group,
|
|
188
|
+
channels_group,
|
|
189
|
+
coy_group,
|
|
190
|
+
doors_group,
|
|
191
|
+
registration_group,
|
|
192
|
+
permanence_group,
|
|
193
|
+
password_group,
|
|
194
|
+
protections_group,
|
|
195
|
+
sessions_group,
|
|
196
|
+
api_tokens_group,
|
|
197
|
+
teams_group,
|
|
198
|
+
admin_group
|
|
199
|
+
).with_theme(Huh::Themes.base16)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# ---- screens -------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
def identity_group
|
|
205
|
+
Huh.group(
|
|
206
|
+
Huh.input
|
|
207
|
+
.key("user_class")
|
|
208
|
+
.title("What is the signed-in model called?")
|
|
209
|
+
.description("The table, routes and controllers are named after it.")
|
|
210
|
+
.placeholder("User")
|
|
211
|
+
.value(answers, :user_class)
|
|
212
|
+
).title("Identity")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Only a second identity needs a URL prefix, and a second identity is the
|
|
216
|
+
# only reason to name the model something other than User.
|
|
217
|
+
def namespaced_group
|
|
218
|
+
Huh.group(
|
|
219
|
+
Huh.confirm
|
|
220
|
+
.key("namespaced")
|
|
221
|
+
.title("Give this identity its own URL prefix and namespace?")
|
|
222
|
+
.description("Required if your app already has one.")
|
|
223
|
+
.value(answers, :namespaced)
|
|
224
|
+
).title("Namespace").with_hide_func { answers.user_class == "User" }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def principals_group
|
|
228
|
+
Huh.group(
|
|
229
|
+
Huh.select
|
|
230
|
+
.key("email")
|
|
231
|
+
.title("Email")
|
|
232
|
+
.options(*menu(EMAIL_MODES))
|
|
233
|
+
.value(answers, :email),
|
|
234
|
+
Huh.select
|
|
235
|
+
.key("phone")
|
|
236
|
+
.title("Phone")
|
|
237
|
+
.options(*menu(PHONE_MODES))
|
|
238
|
+
.value(answers, :phone),
|
|
239
|
+
Huh.confirm
|
|
240
|
+
.key("username")
|
|
241
|
+
.title("Username?")
|
|
242
|
+
.value(answers, :username)
|
|
243
|
+
.validate { |username| validate_identifier(username) }
|
|
244
|
+
).title("Identifiers")
|
|
245
|
+
.description("What users sign in with.")
|
|
246
|
+
.with_validate_on_submit(true)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Drop the email column and something else has to name the account at the
|
|
250
|
+
# sign-in form. Asked here rather than left to the generator because the
|
|
251
|
+
# answer is two fields up on the screen the user is already looking at.
|
|
252
|
+
def validate_identifier(username)
|
|
253
|
+
return if answers.email? || username || answers.phone.start_with?("required")
|
|
254
|
+
|
|
255
|
+
raise Huh::ValidationError,
|
|
256
|
+
"without email, add a username or make phone required"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# "Optional" only means something when another channel covers the gap.
|
|
260
|
+
# With every channel required the promise is already kept, so don't ask.
|
|
261
|
+
def contactable_group
|
|
262
|
+
Huh.group(
|
|
263
|
+
Huh.confirm
|
|
264
|
+
.key("contactable")
|
|
265
|
+
.title("Must every account have an email or phone?")
|
|
266
|
+
.description("Say no for old-school accounts you can't contact or recover.")
|
|
267
|
+
.value(answers, :contactable)
|
|
268
|
+
.validate { |contactable| validate_contactable(contactable) }
|
|
269
|
+
).title("Contact")
|
|
270
|
+
.with_hide_func { !answers.optional_channel? }
|
|
271
|
+
.with_validate_on_submit(true)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# With a second channel to fall back on, "optional" and "must hold one" agree.
|
|
275
|
+
# With only one, they contradict each other and the flag would be a lie.
|
|
276
|
+
def validate_contactable(contactable)
|
|
277
|
+
return unless contactable && answers.sole_optional_channel?
|
|
278
|
+
|
|
279
|
+
raise Huh::ValidationError,
|
|
280
|
+
"your only email/phone is optional, so answer no or make it required"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def channels_group
|
|
284
|
+
Huh.group(
|
|
285
|
+
Huh.confirm
|
|
286
|
+
.key("verify")
|
|
287
|
+
.title("Verify emails and phone numbers?")
|
|
288
|
+
.description("By emailed link or texted code.")
|
|
289
|
+
.value(answers, :verify),
|
|
290
|
+
Huh.confirm
|
|
291
|
+
.key("encrypted")
|
|
292
|
+
.title("Encrypt emails and phone numbers at rest?")
|
|
293
|
+
.value(answers, :encrypted)
|
|
294
|
+
).title("Email and phone").with_hide_func { !answers.channels? }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def coy_group
|
|
298
|
+
Huh.group(
|
|
299
|
+
Huh.confirm
|
|
300
|
+
.key("coy")
|
|
301
|
+
.title("Hide whether an account exists?")
|
|
302
|
+
.description("Forms respond the same whether or not an email or phone is registered.")
|
|
303
|
+
.value(answers, :coy)
|
|
304
|
+
).title("Account privacy").with_hide_func { !answers.coyable? }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def doors_group
|
|
308
|
+
Huh.group(
|
|
309
|
+
Huh.multi_select
|
|
310
|
+
.key("doors")
|
|
311
|
+
.title("How do users sign in?")
|
|
312
|
+
.options_func { menu(door_options) }
|
|
313
|
+
.value(answers, :doors)
|
|
314
|
+
.validate { |chosen| validate_doors(chosen) }
|
|
315
|
+
).title("Sign-in").with_validate_on_submit(true)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# A door needs somewhere to deliver to: magic links ride email, codes ride SMS.
|
|
319
|
+
def door_options
|
|
320
|
+
[
|
|
321
|
+
%w[Password password],
|
|
322
|
+
%w[Passkey passkey],
|
|
323
|
+
["Social / SSO (OmniAuth)", "omniauth"],
|
|
324
|
+
(["Emailed link", "magic_link"] if answers.email?),
|
|
325
|
+
(["Texted code", "sms_code"] if answers.phone?)
|
|
326
|
+
].compact
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# A texted second factor needs a number to text, and is pointless beside the
|
|
330
|
+
# texted door: same proof of the same phone, so whoever holds the number has
|
|
331
|
+
# already opened the door.
|
|
332
|
+
def second_factor_options
|
|
333
|
+
return SECOND_FACTORS if answers.phone? && !answers.sms_code?
|
|
334
|
+
|
|
335
|
+
SECOND_FACTORS.reject { |_, value| value == "sms" }
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def protection_options
|
|
339
|
+
PROTECTIONS.reject do |_, value|
|
|
340
|
+
value == "sudoable" && !answers.sudo_answerable? ||
|
|
341
|
+
value == "security_notifications" && !answers.email?
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def validate_doors(chosen)
|
|
346
|
+
raise Huh::ValidationError, "pick at least one way to sign in" if chosen.empty?
|
|
347
|
+
|
|
348
|
+
return unless chosen == ["sms_code"] && answers.phone == "optional"
|
|
349
|
+
|
|
350
|
+
raise Huh::ValidationError,
|
|
351
|
+
"texted codes are the only sign-in method, so make phone required or add another method"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Registration is one choice with four possible account-creation policies.
|
|
355
|
+
# An invitation has to be sent somewhere, so the two invite answers need a
|
|
356
|
+
# channel.
|
|
357
|
+
def registration_group
|
|
358
|
+
Huh.group(
|
|
359
|
+
Huh.select
|
|
360
|
+
.key("registration")
|
|
361
|
+
.title("Who can create an account?")
|
|
362
|
+
.options_func { menu(registration_options) }
|
|
363
|
+
.value(answers, :registration)
|
|
364
|
+
).title("Registration")
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def registration_options
|
|
368
|
+
[
|
|
369
|
+
["Nobody. You create accounts yourself", "closed"],
|
|
370
|
+
%w[Anyone open],
|
|
371
|
+
(["Anyone, and users can invite others", "open-and-invites"] if answers.channels?),
|
|
372
|
+
(["Invited users only", "invite-only"] if answers.channels?)
|
|
373
|
+
].compact
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Asked here, well after the principals themselves, because whether a number
|
|
377
|
+
# CAN be permanent depends on the doors and the registration mode: a build
|
|
378
|
+
# that mints the account before the number arrives has to keep the settings
|
|
379
|
+
# form that permanence removes.
|
|
380
|
+
def permanence_group
|
|
381
|
+
Huh.group(
|
|
382
|
+
Huh.multi_select
|
|
383
|
+
.key("permanent")
|
|
384
|
+
.title("Which can users never change?")
|
|
385
|
+
.description("Admins can still change them.")
|
|
386
|
+
.options_func { menu(permanent_options) }
|
|
387
|
+
.value(answers, :permanent)
|
|
388
|
+
).title("Permanent").with_hide_func { permanent_options.empty? }
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def permanent_options
|
|
392
|
+
[
|
|
393
|
+
(%w[Email email] if answers.email == "required"),
|
|
394
|
+
(%w[Phone phone] if answers.phone_permanent?)
|
|
395
|
+
].compact
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def password_group
|
|
399
|
+
Huh.group(
|
|
400
|
+
Huh.select
|
|
401
|
+
.key("password_mode")
|
|
402
|
+
.title("Password on the sign-up form")
|
|
403
|
+
.options_func { menu(password_mode_options) }
|
|
404
|
+
.value(answers, :password_mode),
|
|
405
|
+
Huh.multi_select
|
|
406
|
+
.key("password_extras")
|
|
407
|
+
.title("Password features")
|
|
408
|
+
.options_func { menu(password_extra_options) }
|
|
409
|
+
.value(answers, :password_extras)
|
|
410
|
+
).title("Password").with_hide_func { !answers.password? }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# A password reset is an emailed link, so it rides the email channel.
|
|
414
|
+
def password_extra_options
|
|
415
|
+
[
|
|
416
|
+
(["Password reset by email", "recoverable"] if answers.email?),
|
|
417
|
+
["Reject breached passwords", "pwned"],
|
|
418
|
+
["Reject weak passwords", "strong_passwords"],
|
|
419
|
+
["Change passwords every 90 days", "password_rotatable"],
|
|
420
|
+
["Reject reused passwords", "password_historical"],
|
|
421
|
+
["Shut password sign-in for a while after too many failures", "deadboltable"]
|
|
422
|
+
].compact
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def password_mode_options
|
|
426
|
+
[
|
|
427
|
+
%w[Required required],
|
|
428
|
+
(%w[Optional optional] if answers.other_doors?),
|
|
429
|
+
(["Asked for on a second page", "deferred"] if answers.password_deferrable?)
|
|
430
|
+
].compact
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def protections_group
|
|
434
|
+
Huh.group(
|
|
435
|
+
Huh.multi_select
|
|
436
|
+
.key("second_factor")
|
|
437
|
+
.title("Two-factor authentication")
|
|
438
|
+
.options_func { menu(second_factor_options) }
|
|
439
|
+
.value(answers, :second_factor),
|
|
440
|
+
Huh.multi_select
|
|
441
|
+
.key("protections")
|
|
442
|
+
.title("Other protections")
|
|
443
|
+
.options_func { menu(protection_options) }
|
|
444
|
+
.value(answers, :protections)
|
|
445
|
+
).title("Protections")
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def sessions_group
|
|
449
|
+
Huh.group(
|
|
450
|
+
Huh.multi_select
|
|
451
|
+
.key("sessions")
|
|
452
|
+
.title("Session features")
|
|
453
|
+
.options(*menu(SESSIONS))
|
|
454
|
+
.value(answers, :sessions),
|
|
455
|
+
Huh.select
|
|
456
|
+
.key("max_sessions")
|
|
457
|
+
.title("Limit sessions per user?")
|
|
458
|
+
.options(*menu(MAX_SESSIONS))
|
|
459
|
+
.value(answers, :max_sessions)
|
|
460
|
+
).title("Sessions")
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def api_tokens_group
|
|
464
|
+
Huh.group(
|
|
465
|
+
Huh.confirm
|
|
466
|
+
.key("api_tokens")
|
|
467
|
+
.title("Issue personal access tokens for your application's API?")
|
|
468
|
+
.description("Adds browser settings to manage tokens and a separate concern for API controllers.")
|
|
469
|
+
.value(answers, :api_tokens)
|
|
470
|
+
).title("API tokens")
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def teams_group
|
|
474
|
+
Huh.group(
|
|
475
|
+
Huh.select
|
|
476
|
+
.key("teams")
|
|
477
|
+
.title("Teams")
|
|
478
|
+
.options(*menu(TEAMS))
|
|
479
|
+
.value(answers, :teams)
|
|
480
|
+
).title("Teams")
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def admin_group
|
|
484
|
+
Huh.group(
|
|
485
|
+
Huh.multi_select
|
|
486
|
+
.key("admin")
|
|
487
|
+
.title("Administration")
|
|
488
|
+
.options(*menu(ADMIN))
|
|
489
|
+
.value(answers, :admin)
|
|
490
|
+
).title("Administration")
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# ---- emitting ------------------------------------------------------
|
|
494
|
+
|
|
495
|
+
def identity_flags
|
|
496
|
+
[
|
|
497
|
+
("--user-class=#{answers.user_class}" unless answers.user_class == "User"),
|
|
498
|
+
("--namespaced" if answers.namespaced)
|
|
499
|
+
].compact
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def principal_flags
|
|
503
|
+
[
|
|
504
|
+
("--email=#{mode(:email)}" unless mode(:email) == "none"),
|
|
505
|
+
("--phone=#{mode(:phone)}" unless mode(:phone) == "none"),
|
|
506
|
+
("--username" if answers.username)
|
|
507
|
+
].compact
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def mode(channel)
|
|
511
|
+
value = answers.public_send(channel)
|
|
512
|
+
answers.permanent.include?(channel.to_s) ? "#{value},permanent" : value
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def channel_flags
|
|
516
|
+
[
|
|
517
|
+
("--no-verifiable" unless answers.verify),
|
|
518
|
+
("--coy" if answers.coy && answers.coyable?),
|
|
519
|
+
("--no-contactable" unless answers.contactable),
|
|
520
|
+
("--encrypted-pii" if answers.encrypted)
|
|
521
|
+
].compact
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def door_flags
|
|
525
|
+
answers.doors.map { |door| door == "password" ? password_flag : flag(door) }
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def password_flag
|
|
529
|
+
answers.password_mode == "required" ? "--password" : "--password=#{answers.password_mode}"
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
REGISTRATION = {
|
|
533
|
+
"closed" => ["--registration=closed"],
|
|
534
|
+
"open" => [],
|
|
535
|
+
"open-and-invites" => ["--registration=open-and-invites"],
|
|
536
|
+
"invite-only" => ["--registration=invite-only"]
|
|
537
|
+
}.freeze
|
|
538
|
+
|
|
539
|
+
def feature_flags
|
|
540
|
+
[
|
|
541
|
+
*REGISTRATION.fetch(answers.registration),
|
|
542
|
+
*(answers.password? ? answers.password_extras.map { |e| flag(e) } : []),
|
|
543
|
+
("--second-factor=#{answers.second_factor.join(",")}" if answers.second_factor.any?),
|
|
544
|
+
*answers.protections.map { |p| flag(p) },
|
|
545
|
+
*answers.sessions.map { |s| flag(s) },
|
|
546
|
+
*max_sessions_flag,
|
|
547
|
+
("--api-tokens" if answers.api_tokens),
|
|
548
|
+
*teams_flag,
|
|
549
|
+
*admin_flags
|
|
550
|
+
].compact
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def max_sessions_flag
|
|
554
|
+
return [] if answers.max_sessions == "none"
|
|
555
|
+
|
|
556
|
+
[answers.max_sessions == "evict" ? "--max-sessionable" : "--max-sessionable=prompt"]
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def teams_flag
|
|
560
|
+
{ "none" => [], "session" => ["--teams=session"], "scope" => ["--teams"],
|
|
561
|
+
"middleware" => ["--teams=middleware"] }.fetch(answers.teams)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def admin_flags
|
|
565
|
+
(answers.admin - answers.admin.filter_map { |a| IMPLIED[a] }).map { |a| flag(a) }
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def flag(name) = "--#{name.tr("_", "-")}"
|
|
569
|
+
end
|
|
570
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "authnz_eleven/version"
|
|
4
|
+
require_relative "authnz_eleven/principal"
|
|
5
|
+
require_relative "authnz_eleven/principals"
|
|
6
|
+
|
|
7
|
+
module AuthnzEleven
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# authnz_eleven ships no runtime code: it is simply a Rails generator. The
|
|
11
|
+
# generator is discovered automatically from lib/generators when the gem
|
|
12
|
+
# is in your Gemfile. See:
|
|
13
|
+
#
|
|
14
|
+
# bin/rails generate authnz_eleven --help
|
|
15
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates authentication into your app. Only the features you pick are
|
|
3
|
+
generated, and the code is yours to change.
|
|
4
|
+
|
|
5
|
+
Pick at least one identifier (--email, --phone or --username) and one
|
|
6
|
+
sign-in method (--password, --magic-link, --sms-code, --passkey or
|
|
7
|
+
--omniauth).
|
|
8
|
+
|
|
9
|
+
Not sure which flags you want? The wizard will ask you:
|
|
10
|
+
|
|
11
|
+
bin/rails generate authnz_eleven:wizard
|
|
12
|
+
|
|
13
|
+
Settings live in config/initializers/authentication.rb.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
bin/rails generate authnz_eleven --email --password
|
|
17
|
+
|
|
18
|
+
Passwordless:
|
|
19
|
+
bin/rails generate authnz_eleven --email --magic-link --passkey
|
|
20
|
+
|
|
21
|
+
GitHub-style:
|
|
22
|
+
bin/rails generate authnz_eleven --username --email --password \
|
|
23
|
+
--recoverable --passkey --second-factor=totp,webauthn --sudoable
|
|
24
|
+
|
|
25
|
+
A second, separate identity:
|
|
26
|
+
bin/rails generate authnz_eleven --user-class=Realtor --namespaced --email --password
|