seams 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 +335 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
- data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
- data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
- data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
- data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
- data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
- data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
- data/lib/generators/seams/admin/admin_generator.rb +852 -0
- data/lib/generators/seams/admin/templates/README.md.tt +266 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
- data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
- data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
- data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
- data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
- data/lib/generators/seams/auth/auth_generator.rb +311 -0
- data/lib/generators/seams/auth/templates/README.md.tt +289 -0
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
- data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
- data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
- data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
- data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
- data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
- data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
- data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
- data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
- data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
- data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
- data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
- data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
- data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
- data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
- data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
- data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
- data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
- data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
- data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
- data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
- data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
- data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
- data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
- data/lib/generators/seams/billing/billing_generator.rb +476 -0
- data/lib/generators/seams/billing/templates/README.md.tt +355 -0
- data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
- data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
- data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
- data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
- data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
- data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
- data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
- data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
- data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
- data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
- data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
- data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
- data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
- data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
- data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
- data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
- data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
- data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
- data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
- data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
- data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
- data/lib/generators/seams/core/core_generator.rb +191 -0
- data/lib/generators/seams/core/templates/README.md.tt +45 -0
- data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
- data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
- data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
- data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
- data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
- data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
- data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
- data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
- data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
- data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
- data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
- data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
- data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
- data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
- data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
- data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
- data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
- data/lib/generators/seams/engine/engine_generator.rb +165 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
- data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
- data/lib/generators/seams/engine/templates/README.md.tt +40 -0
- data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
- data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
- data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
- data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
- data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
- data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
- data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
- data/lib/generators/seams/install/install_generator.rb +211 -0
- data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
- data/lib/generators/seams/install/templates/Procfile.tt +14 -0
- data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
- data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
- data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
- data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
- data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
- data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
- data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
- data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
- data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
- data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
- data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
- data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
- data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
- data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
- data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
- data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
- data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
- data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
- data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
- data/lib/generators/seams/remove/remove_generator.rb +259 -0
- data/lib/generators/seams/teams/teams_generator.rb +298 -0
- data/lib/generators/seams/teams/templates/README.md.tt +88 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
- data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
- data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
- data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
- data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
- data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
- data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
- data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
- data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
- data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
- data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
- data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
- data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
- data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
- data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
- data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
- data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
- data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
- data/lib/seams/cli/list.rb +111 -0
- data/lib/seams/cli/quality.rb +99 -0
- data/lib/seams/cli/resolve.rb +276 -0
- data/lib/seams/cli/test_changed.rb +116 -0
- data/lib/seams/cli.rb +48 -0
- data/lib/seams/configuration.rb +19 -0
- data/lib/seams/cops/known_queue_names.rb +42 -0
- data/lib/seams/cops/migration_comments.rb +68 -0
- data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
- data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
- data/lib/seams/cops.rb +18 -0
- data/lib/seams/event_registry.rb +49 -0
- data/lib/seams/events/adapter.rb +24 -0
- data/lib/seams/events/adapters/active_support.rb +31 -0
- data/lib/seams/events/publisher.rb +178 -0
- data/lib/seams/events.rb +39 -0
- data/lib/seams/generators/dummy_app_writer.rb +424 -0
- data/lib/seams/generators/eject_aware.rb +102 -0
- data/lib/seams/generators/follow_up_generator.rb +148 -0
- data/lib/seams/generators/host_injector.rb +124 -0
- data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
- data/lib/seams/generators/splicer.rb +217 -0
- data/lib/seams/observability/adapter.rb +33 -0
- data/lib/seams/observability/adapters/rails_logger.rb +59 -0
- data/lib/seams/observability.rb +34 -0
- data/lib/seams/runtime.rb +23 -0
- data/lib/seams/version.rb +5 -0
- data/lib/seams.rb +23 -0
- metadata +493 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Auth
|
|
2
|
+
|
|
3
|
+
> Email + password authentication for a Seams-powered host application.
|
|
4
|
+
> The canonical "human" record is `Auth::Identity` (post-Wave-9). Other
|
|
5
|
+
> engines address the human via Identity, not via a host User.
|
|
6
|
+
|
|
7
|
+
## Events emitted
|
|
8
|
+
|
|
9
|
+
| Event name | Payload | Emitted when |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `identity.signed_up.auth` | `{ identity_id:, email: }` | RegistrationsController#create succeeds |
|
|
12
|
+
| `identity.signed_in.auth` | `{ identity_id:, session_id: }` | SessionsController#create succeeds |
|
|
13
|
+
| `identity.signed_out.auth` | `{ identity_id:, session_id: }` | SessionsController#destroy runs |
|
|
14
|
+
| `session.expired.auth` | `{ identity_id:, session_id: }` | sweeper job revokes a stale session |
|
|
15
|
+
|
|
16
|
+
> `identity_id` is the row id in the engine's `auth_identities` table.
|
|
17
|
+
> Subscribers that resolve identities should use `identity_id`.
|
|
18
|
+
|
|
19
|
+
## Events consumed
|
|
20
|
+
|
|
21
|
+
This engine does not subscribe to any other engine's events.
|
|
22
|
+
|
|
23
|
+
## Exposed concerns
|
|
24
|
+
|
|
25
|
+
| Concern | Purpose |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `Auth::Authenticatable` | Optional post-Wave-9. Mix into a host User (if you keep one) to add session-aware helpers (`signed_in?`, `sign_out_everywhere!`); the host model needs an `identity_id` column. Most hosts skip this — `Auth::Identity` is the canonical human. |
|
|
28
|
+
| `Auth::Authentication` | Mix into the host's ApplicationController to get `current_identity`, `signed_in?`, `authenticate_identity!` plus a per-request `Auth::Current.identity`. |
|
|
29
|
+
|
|
30
|
+
## Adapters
|
|
31
|
+
|
|
32
|
+
Password hashing is provided by `bcrypt` via Rails' `has_secure_password`.
|
|
33
|
+
To swap in a different hasher, override `Auth::Identity`'s
|
|
34
|
+
`password_digest=` setter in your host application.
|
|
35
|
+
|
|
36
|
+
## Password reset (Rails 8 has_secure_password reset_token)
|
|
37
|
+
|
|
38
|
+
`Auth::Identity` uses Rails 8's built-in reset token machinery — a
|
|
39
|
+
signed_id with a 15-minute default expiry, generated on demand,
|
|
40
|
+
verified via `find_by_password_reset_token`. **No `password_reset_token`
|
|
41
|
+
column, no `password_reset_token_sent_at`, no sweep job.** Token
|
|
42
|
+
expiry is a property of the signature, not a database row.
|
|
43
|
+
|
|
44
|
+
To configure the expiry:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
class Auth::Identity < ApplicationRecord
|
|
48
|
+
has_secure_password reset_token: { expires_in: 30.minutes }
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## OAuth (Sign in with Google / GitHub)
|
|
53
|
+
|
|
54
|
+
Two adapters ship with the engine — `Auth::OAuth::Google` and
|
|
55
|
+
`Auth::OAuth::Github` — both built on Faraday. **No `oauth2` gem
|
|
56
|
+
dependency**, no `Net::HTTP`. Adapter contract lives in
|
|
57
|
+
`lib/auth/oauth/abstract.rb`; subclass it for additional providers
|
|
58
|
+
(Apple, GitLab, Microsoft, etc.).
|
|
59
|
+
|
|
60
|
+
### Configure
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# config/initializers/auth.rb
|
|
64
|
+
Auth.configure do |c|
|
|
65
|
+
c.oauth_providers = {
|
|
66
|
+
google: {
|
|
67
|
+
adapter: "Auth::OAuth::Google",
|
|
68
|
+
client_id: ENV.fetch("GOOGLE_OAUTH_CLIENT_ID"),
|
|
69
|
+
client_secret: ENV.fetch("GOOGLE_OAUTH_CLIENT_SECRET")
|
|
70
|
+
},
|
|
71
|
+
github: {
|
|
72
|
+
adapter: "Auth::OAuth::Github",
|
|
73
|
+
client_id: ENV.fetch("GITHUB_OAUTH_CLIENT_ID"),
|
|
74
|
+
client_secret: ENV.fetch("GITHUB_OAUTH_CLIENT_SECRET")
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The provider's redirect URI must match the URL Rails generates for
|
|
81
|
+
`auth.oauth_callback_url(provider: :google)` (e.g.
|
|
82
|
+
`https://your-app.com/auth/oauth/google/callback`).
|
|
83
|
+
|
|
84
|
+
### Token storage + encryption
|
|
85
|
+
|
|
86
|
+
`Auth::OAuth::Provider` rows store `access_token` + `refresh_token`
|
|
87
|
+
encrypted at the column level via Rails 7+ ActiveRecord::Encryption.
|
|
88
|
+
**One-time host setup:**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bin/rails db:encryption:init
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This prints three keys (primary, deterministic, key derivation salt).
|
|
95
|
+
Store them in Rails credentials (`bin/rails credentials:edit`) under
|
|
96
|
+
`active_record_encryption.*` — see
|
|
97
|
+
https://guides.rubyonrails.org/active_record_encryption.html.
|
|
98
|
+
|
|
99
|
+
### Render the sign-in buttons
|
|
100
|
+
|
|
101
|
+
Drop the partial into your sessions or registrations form:
|
|
102
|
+
|
|
103
|
+
```erb
|
|
104
|
+
<%%= render "auth/sessions/oauth_buttons" %>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
It iterates `Auth.configuration.oauth_providers` and renders one
|
|
108
|
+
`auth.oauth_start_path(provider:)` link per configured provider.
|
|
109
|
+
|
|
110
|
+
### Routes
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
GET /auth/oauth/:provider/start → redirect to provider's authorize URL
|
|
114
|
+
GET /auth/oauth/:provider/callback → exchange code, sign in, set cookie
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Verified against:
|
|
118
|
+
- https://developers.google.com/identity/protocols/oauth2/web-server
|
|
119
|
+
- https://developers.google.com/identity/openid-connect/openid-connect
|
|
120
|
+
- https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
|
121
|
+
- https://docs.github.com/en/rest/users/users
|
|
122
|
+
- https://docs.github.com/en/rest/users/emails
|
|
123
|
+
|
|
124
|
+
## API tokens (Bearer auth)
|
|
125
|
+
|
|
126
|
+
`Auth::ApiToken` ships with the engine for programmatic access. Tokens
|
|
127
|
+
are issued via `Auth::GenerateApiToken.call(identity:, name:, expires_at:)`
|
|
128
|
+
which returns a `Result` carrying both the persisted record and the
|
|
129
|
+
**plaintext token** — the plaintext is shown ONCE and never recoverable
|
|
130
|
+
from the DB (only a SHA-256 digest is stored).
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
result = Auth::GenerateApiToken.call(identity: current_identity, name: "CI key")
|
|
134
|
+
result.plaintext # => "seam_tF9...xQ" — display once, then discard
|
|
135
|
+
result.api_token # => Auth::ApiToken row
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Mix `Auth::ApiAuthenticatable` into API controllers and call
|
|
139
|
+
`authenticate_api_token!` in a before_action:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class Api::WidgetsController < ApplicationController
|
|
143
|
+
include Auth::ApiAuthenticatable
|
|
144
|
+
before_action :authenticate_api_token!
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Clients send `Authorization: Bearer seam_<token>`. On success the
|
|
149
|
+
concern sets `current_identity` + `current_api_token` and bumps the
|
|
150
|
+
token's `last_used_at`. On failure it renders 401 JSON.
|
|
151
|
+
|
|
152
|
+
Events emitted:
|
|
153
|
+
|
|
154
|
+
| Event name | Payload |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `api_token.issued.auth` | `{ identity_id:, api_token_id:, token_prefix: }` |
|
|
157
|
+
| `api_token.revoked.auth` | `{ identity_id:, api_token_id:, token_prefix: }` |
|
|
158
|
+
|
|
159
|
+
## Rate limiting
|
|
160
|
+
|
|
161
|
+
The engine uses Rails 8's built-in `rate_limit` (backed by Solid Cache):
|
|
162
|
+
|
|
163
|
+
| Controller | Action | Limit |
|
|
164
|
+
| --- | --- | --- |
|
|
165
|
+
| `SessionsController` | `create` | 10 / minute |
|
|
166
|
+
| `RegistrationsController` | `create` | 5 / hour |
|
|
167
|
+
| `PasswordResetsController` | `create`, `update` | 5 / hour |
|
|
168
|
+
|
|
169
|
+
Tune by overriding the `rate_limit` declaration in your host app
|
|
170
|
+
controllers. Solid Cache is the default Rails 8 cache store; if your
|
|
171
|
+
host uses a different store, the limit applies to whatever cache backs
|
|
172
|
+
`Rails.cache`.
|
|
173
|
+
|
|
174
|
+
## Cleanup expired sessions
|
|
175
|
+
|
|
176
|
+
`Auth::CleanupExpiredSessionsJob` sweeps expired `Auth::Session` rows
|
|
177
|
+
and emits `session.expired.auth` for each one. Wire it as a Solid Queue
|
|
178
|
+
Recurring entry in `config/recurring.yml`:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
auth_cleanup_expired_sessions:
|
|
182
|
+
class: Auth::CleanupExpiredSessionsJob
|
|
183
|
+
schedule: "every 1 hour"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Platform admin (`staff?`)
|
|
187
|
+
|
|
188
|
+
`Auth::Identity` ships with a `staff` boolean (default false) — an
|
|
189
|
+
Identity-level platform-admin flag that host admin tooling can use to
|
|
190
|
+
bypass account scoping. NOT related to per-account roles (those live
|
|
191
|
+
on `Accounts::Membership`, owned by the accounts engine).
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
identity.staff? # => false (default)
|
|
195
|
+
identity.update!(staff: true)
|
|
196
|
+
identity.staff? # => true
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Promotion is an admin operation only — `Auth::RegisterIdentity` does
|
|
200
|
+
NOT honour `staff:` from sign-up params.
|
|
201
|
+
|
|
202
|
+
## GDPR / data protection
|
|
203
|
+
|
|
204
|
+
Personal data the engine stores and how it's protected at rest:
|
|
205
|
+
|
|
206
|
+
| Column | At rest | Why |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `auth_identities.email` | encrypted (deterministic) | PII (Article 4); deterministic so `find_by(email:)` and the uniqueness index keep working |
|
|
209
|
+
| `auth_identities.password_digest` | bcrypt one-way hash | not PII — never reversible to a password |
|
|
210
|
+
| `auth_oauth_providers.access_token` | encrypted (non-deterministic) | credential |
|
|
211
|
+
| `auth_oauth_providers.refresh_token` | encrypted (non-deterministic) | credential |
|
|
212
|
+
| `auth_oauth_providers.provider_uid` | encrypted (deterministic) | online identifier (Article 4); deterministic so `(provider, provider_uid)` lookup + unique index keep working |
|
|
213
|
+
| `auth_api_tokens.token_digest` | SHA-256 hash | not reversible to the plaintext token |
|
|
214
|
+
| `auth_api_tokens.token_prefix` | plaintext | first 12 chars only — display label, not a secret |
|
|
215
|
+
|
|
216
|
+
### One-time host setup
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
bin/rails db:encryption:init
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Store the printed keys in Rails credentials
|
|
223
|
+
(`bin/rails credentials:edit`) under `active_record_encryption.*`. See
|
|
224
|
+
https://guides.rubyonrails.org/active_record_encryption.html.
|
|
225
|
+
|
|
226
|
+
### Upgrading from Auth Wave ≤10 (existing hosts only)
|
|
227
|
+
|
|
228
|
+
Hosts that deployed Auth before Wave 11 have plaintext `email` +
|
|
229
|
+
`provider_uid` already in the database. Re-encrypt them in place:
|
|
230
|
+
|
|
231
|
+
1. In `config/application.rb`, add the transitional flag so plaintext
|
|
232
|
+
rows are still readable while rotation runs:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
config.active_record.encryption.support_unencrypted_data = true
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
2. Deploy + run:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
bin/rails seams:auth:rotate_pii_encryption
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
3. Once the task reports zero remaining unencrypted rows, remove the
|
|
245
|
+
flag (or set it back to `false`) and redeploy.
|
|
246
|
+
|
|
247
|
+
The task is idempotent — re-running it on already-encrypted rows is a
|
|
248
|
+
no-op. Fresh hosts skip steps 1 and 3 entirely.
|
|
249
|
+
|
|
250
|
+
### Right to erasure (Article 17)
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
Auth::Identity.find_by(email: "user@example.com").destroy
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
cascades to `sessions`, `api_tokens`, and `oauth_providers` via
|
|
257
|
+
`dependent: :destroy`. Hosts that keep their own user-domain records
|
|
258
|
+
linked by `identity_id` must additionally erase those rows — the
|
|
259
|
+
engine does not own that schema.
|
|
260
|
+
|
|
261
|
+
### Logging
|
|
262
|
+
|
|
263
|
+
Don't log `email`. Log `identity_id` instead. Encryption protects the
|
|
264
|
+
DB at rest but is moot if PII leaks into log files. Add the column to
|
|
265
|
+
`config.filter_parameters` in your host:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
config.filter_parameters += %i[email password password_digest]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Right to access / portability (Article 15 / 20)
|
|
272
|
+
|
|
273
|
+
Not yet shipped — `Auth::ExportIdentityData` is on the roadmap. For
|
|
274
|
+
now, hosts can export with a direct query against the identity's rows.
|
|
275
|
+
|
|
276
|
+
## Mounting
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
# config/routes.rb (host application)
|
|
280
|
+
Rails.application.routes.draw do
|
|
281
|
+
mount Auth::Engine, at: "/auth"
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Running the specs
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
bin/rails seams:test[auth]
|
|
289
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
module OAuth
|
|
5
|
+
# Two endpoints per provider — the user-facing flow:
|
|
6
|
+
# GET /auth/oauth/:provider/start → redirect to provider's authorize URL
|
|
7
|
+
# GET /auth/oauth/:provider/callback → exchange code + sign in
|
|
8
|
+
#
|
|
9
|
+
# State token: cryptographically random, stored in the session, and
|
|
10
|
+
# re-verified on callback. Mismatch ⇒ 403. Without this, an attacker
|
|
11
|
+
# can trick a victim into linking the attacker's OAuth identity to
|
|
12
|
+
# the victim's account.
|
|
13
|
+
class CallbacksController < ApplicationController
|
|
14
|
+
skip_before_action :verify_authenticity_token, only: %i[callback]
|
|
15
|
+
|
|
16
|
+
rescue_from Auth::OAuthProviderUnknown do |e|
|
|
17
|
+
redirect_to Auth.configuration.after_sign_out_url, alert: e.message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET /auth/oauth/:provider/start
|
|
21
|
+
def start
|
|
22
|
+
provider = params.require(:provider)
|
|
23
|
+
Auth.oauth(provider) # raises OAuthProviderUnknown if not configured
|
|
24
|
+
|
|
25
|
+
state = SecureRandom.urlsafe_base64(32)
|
|
26
|
+
session[oauth_state_key(provider)] = state
|
|
27
|
+
|
|
28
|
+
redirect_to Auth.oauth(provider).authorize_url(
|
|
29
|
+
state: state,
|
|
30
|
+
redirect_uri: callback_url(provider)
|
|
31
|
+
), allow_other_host: true, status: :see_other
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET /auth/oauth/:provider/callback?code=...&state=...
|
|
35
|
+
def callback
|
|
36
|
+
provider = params.require(:provider)
|
|
37
|
+
returned_state = params[:state]
|
|
38
|
+
expected_state = session.delete(oauth_state_key(provider))
|
|
39
|
+
|
|
40
|
+
if expected_state.nil? || returned_state != expected_state
|
|
41
|
+
return redirect_to Auth.configuration.after_sign_out_url, alert: "OAuth state mismatch"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if (error = params[:error])
|
|
45
|
+
return redirect_to Auth.configuration.after_sign_out_url,
|
|
46
|
+
alert: "OAuth #{provider}: #{error}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result = Auth::OAuth::Authenticator.call(
|
|
50
|
+
provider: provider,
|
|
51
|
+
code: params.require(:code),
|
|
52
|
+
redirect_uri: callback_url(provider)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if result.ok?
|
|
56
|
+
cookies.encrypted[Auth.configuration.cookie_name] = {
|
|
57
|
+
value: result.session.token,
|
|
58
|
+
httponly: true,
|
|
59
|
+
secure: request.ssl?,
|
|
60
|
+
expires: result.session.expires_at
|
|
61
|
+
}
|
|
62
|
+
redirect_to Auth.configuration.after_sign_in_url,
|
|
63
|
+
notice: result.new_identity ? "Welcome!" : "Signed in"
|
|
64
|
+
else
|
|
65
|
+
redirect_to Auth.configuration.after_sign_out_url, alert: result.error
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def oauth_state_key(provider)
|
|
72
|
+
"auth_oauth_state_#{provider}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def callback_url(provider)
|
|
76
|
+
url_for(action: :callback, provider: provider, only_path: false)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
class PasswordResetsController < ApplicationController
|
|
5
|
+
# 5 reset requests per hour per IP. Cheap to send mail but
|
|
6
|
+
# creating reset tokens for every email is a guess-the-user attack
|
|
7
|
+
# vector — rate-limit modestly.
|
|
8
|
+
rate_limit to: 5, within: 1.hour, only: %i[create update],
|
|
9
|
+
with: -> { redirect_to auth.new_session_path, alert: "Too many password-reset requests. Try again later." }
|
|
10
|
+
|
|
11
|
+
# GET /auth/password_reset/new
|
|
12
|
+
def new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# POST /auth/password_reset
|
|
16
|
+
def create
|
|
17
|
+
Auth::ResetPassword.request(email: params[:email])
|
|
18
|
+
# Don't leak whether the email exists — same response either way.
|
|
19
|
+
flash[:notice] = "If that email is registered, a reset link is on its way."
|
|
20
|
+
redirect_to new_session_path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# GET /auth/password_reset/edit?token=...
|
|
24
|
+
def edit
|
|
25
|
+
@token = params[:token]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# PATCH /auth/password_reset
|
|
29
|
+
def update
|
|
30
|
+
result = Auth::ResetPassword.complete(
|
|
31
|
+
token: params[:token],
|
|
32
|
+
new_password: params[:password]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if result.ok?
|
|
36
|
+
redirect_to new_session_path, notice: "Password updated. Sign in with your new password."
|
|
37
|
+
else
|
|
38
|
+
flash.now[:alert] = result.error
|
|
39
|
+
@token = params[:token]
|
|
40
|
+
render :edit, status: :unprocessable_entity
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
class RegistrationsController < ApplicationController
|
|
5
|
+
# 5 sign-ups per hour per IP. Tighter than sign-in because each
|
|
6
|
+
# successful row also sends a welcome email + provisions side
|
|
7
|
+
# effects. Rails 8's built-in rate_limit uses Solid Cache.
|
|
8
|
+
rate_limit to: 5, within: 1.hour, only: %i[create],
|
|
9
|
+
with: -> { redirect_to auth.new_registration_path, alert: "Too many sign-ups from this IP. Try again later." }
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
@identity = Auth::Identity.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
result = Auth::RegisterIdentity.call(
|
|
17
|
+
email: params.dig(:identity, :email),
|
|
18
|
+
password: params.dig(:identity, :password),
|
|
19
|
+
password_confirmation: params.dig(:identity, :password_confirmation)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if result.ok?
|
|
23
|
+
cookies.encrypted[Auth.configuration.cookie_name] = {
|
|
24
|
+
value: result.session.token, httponly: true, expires: result.session.expires_at
|
|
25
|
+
}
|
|
26
|
+
redirect_to Auth.configuration.after_sign_in_url, notice: "Welcome"
|
|
27
|
+
else
|
|
28
|
+
@identity = Auth::Identity.new(email: params.dig(:identity, :email))
|
|
29
|
+
flash.now[:alert] = result.error
|
|
30
|
+
render :new, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
class SessionsController < ApplicationController
|
|
5
|
+
# 10 attempts per minute per IP. Rails 8's built-in rate_limit
|
|
6
|
+
# uses Solid Cache by default; configure cache_store in your host
|
|
7
|
+
# if you need to override.
|
|
8
|
+
rate_limit to: 10, within: 1.minute, only: %i[create],
|
|
9
|
+
with: -> { redirect_to auth.new_session_path, alert: "Too many attempts. Try again in a minute." }
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
# render the sign-in form
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
result = Auth::AuthenticateIdentity.call(
|
|
17
|
+
email: params[:email], password: params[:password]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if result.ok?
|
|
21
|
+
cookies.encrypted[Auth.configuration.cookie_name] = {
|
|
22
|
+
value: result.session.token, httponly: true, expires: result.session.expires_at
|
|
23
|
+
}
|
|
24
|
+
redirect_to Auth.configuration.after_sign_in_url, notice: "Signed in"
|
|
25
|
+
else
|
|
26
|
+
flash.now[:alert] = result.error
|
|
27
|
+
render :new, status: :unprocessable_entity
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def destroy
|
|
32
|
+
token = cookies.encrypted[Auth.configuration.cookie_name]
|
|
33
|
+
if token
|
|
34
|
+
session = Auth::Session.find_by(token: token)
|
|
35
|
+
if session
|
|
36
|
+
identity = session.identity
|
|
37
|
+
Seams::Events::Publisher.publish(
|
|
38
|
+
"identity.signed_out.auth",
|
|
39
|
+
identity_id: identity.id,
|
|
40
|
+
session_id: session.id
|
|
41
|
+
)
|
|
42
|
+
session.destroy
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
cookies.delete(Auth.configuration.cookie_name)
|
|
46
|
+
redirect_to Auth.configuration.after_sign_out_url, notice: "Signed out"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
# Sweeps expired Auth::Session rows. Wire as a Solid Queue Recurring
|
|
5
|
+
# entry in config/recurring.yml:
|
|
6
|
+
#
|
|
7
|
+
# auth_cleanup_expired_sessions:
|
|
8
|
+
# class: Auth::CleanupExpiredSessionsJob
|
|
9
|
+
# schedule: "every 1 hour"
|
|
10
|
+
#
|
|
11
|
+
# Publishes session.expired.auth for each row destroyed so any
|
|
12
|
+
# downstream listener (Notifications, audit log) can react.
|
|
13
|
+
class CleanupExpiredSessionsJob < ApplicationJob
|
|
14
|
+
queue_as :auth
|
|
15
|
+
|
|
16
|
+
BATCH_SIZE = 500
|
|
17
|
+
|
|
18
|
+
def perform
|
|
19
|
+
Auth::Session.where(expires_at: ..Time.current).find_each(batch_size: BATCH_SIZE) do |session|
|
|
20
|
+
identity = session.identity
|
|
21
|
+
Seams::Events::Publisher.publish(
|
|
22
|
+
"session.expired.auth",
|
|
23
|
+
identity_id: identity&.id,
|
|
24
|
+
session_id: session.id
|
|
25
|
+
)
|
|
26
|
+
session.destroy
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
class PasswordsMailer < ::ApplicationMailer
|
|
5
|
+
def reset_email(identity)
|
|
6
|
+
@identity = identity
|
|
7
|
+
# Rails 8 has_secure_password defines `password_reset_token` —
|
|
8
|
+
# a signed_id with a 15-minute default expiry. The mailer is
|
|
9
|
+
# responsible for calling it (so the token is generated as
|
|
10
|
+
# late as possible to maximise validity).
|
|
11
|
+
@token = identity.password_reset_token
|
|
12
|
+
mail(to: identity.email, subject: "Reset your password")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
module Auth
|
|
7
|
+
# Bearer-style API token. Issued to an Identity, presented in the
|
|
8
|
+
# Authorization header by API clients.
|
|
9
|
+
#
|
|
10
|
+
# Storage: only a SHA-256 hash of the token lives in the DB —
|
|
11
|
+
# `token_digest`. The plaintext (`token`) is shown ONCE, at
|
|
12
|
+
# creation time, via the GenerateApiToken service. If the identity
|
|
13
|
+
# loses it they must rotate; we cannot recover it.
|
|
14
|
+
#
|
|
15
|
+
# `token_prefix` is the first few characters of the plaintext, kept
|
|
16
|
+
# in the DB for identification in dashboards / audit logs without
|
|
17
|
+
# exposing the secret. Format: `seam_<8 random chars>` so it's
|
|
18
|
+
# easy to grep for.
|
|
19
|
+
#
|
|
20
|
+
# Optional `expires_at`. Optional `last_used_at` for activity
|
|
21
|
+
# tracking. `name` lets the identity label the token ("CI deploy key",
|
|
22
|
+
# "iPhone shortcut").
|
|
23
|
+
class ApiToken < ApplicationRecord
|
|
24
|
+
self.table_name = "auth_api_tokens"
|
|
25
|
+
|
|
26
|
+
PREFIX = "seam_"
|
|
27
|
+
PLAINTEXT_LENGTH = 32 # bytes → 43 chars urlsafe-base64
|
|
28
|
+
PREFIX_DISPLAY = 12 # chars stored in token_prefix
|
|
29
|
+
|
|
30
|
+
belongs_to :identity, class_name: "Auth::Identity", foreign_key: :identity_id
|
|
31
|
+
|
|
32
|
+
validates :token_digest, presence: true, uniqueness: true
|
|
33
|
+
validates :token_prefix, presence: true
|
|
34
|
+
validates :name, presence: true
|
|
35
|
+
|
|
36
|
+
scope :active, -> { where("expires_at IS NULL OR expires_at > ?", Time.current) }
|
|
37
|
+
scope :expired, -> { where(expires_at: ..Time.current) }
|
|
38
|
+
|
|
39
|
+
def expired?
|
|
40
|
+
expires_at.present? && expires_at <= Time.current
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def touch_last_used!
|
|
44
|
+
update_column(:last_used_at, Time.current) # rubocop:disable Rails/SkipsModelValidations
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Hash a plaintext token the same way #find_by_plaintext does, so
|
|
48
|
+
# callers can verify a candidate token without exposing the digest.
|
|
49
|
+
def self.digest(plaintext)
|
|
50
|
+
Digest::SHA256.hexdigest(plaintext.to_s)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the ApiToken matching the plaintext, or nil. Does NOT
|
|
54
|
+
# touch last_used_at — callers do that explicitly so a passive
|
|
55
|
+
# presence check doesn't bump the timestamp.
|
|
56
|
+
def self.find_by_plaintext(plaintext)
|
|
57
|
+
return nil if plaintext.to_s.empty?
|
|
58
|
+
|
|
59
|
+
find_by(token_digest: digest(plaintext))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auth
|
|
4
|
+
# ActiveSupport::CurrentAttributes namespace for the Auth engine.
|
|
5
|
+
# Set once per request by the Authentication concern; readable from
|
|
6
|
+
# anywhere downstream (services, models, jobs that re-resolve via
|
|
7
|
+
# the same controller stack) without explicit threading.
|
|
8
|
+
#
|
|
9
|
+
# Wave 9 Phase 1a only sets `identity`. Phase 1b will add
|
|
10
|
+
# `Current.account`; Phase 2 will add `Current.user` (the account
|
|
11
|
+
# membership, NOT a host User).
|
|
12
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
13
|
+
attribute :identity
|
|
14
|
+
end
|
|
15
|
+
end
|