open-loam 0.1.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +148 -8
- data/LICENSE +1 -1
- data/README.md +27 -25
- data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
- data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
- data/app/jobs/open_loam/event_log_prune_job.rb +15 -0
- data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +17 -8
- data/app/models/open_loam/api_token.rb +63 -0
- data/app/models/{loam → open_loam}/audit_record.rb +3 -3
- data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
- data/app/models/{loam → open_loam}/business_rule.rb +10 -10
- data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
- data/app/models/{loam → open_loam}/comment.rb +9 -9
- data/app/models/{loam → open_loam}/config.rb +7 -7
- data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
- data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
- data/app/models/open_loam/dictionary.rb +20 -0
- data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
- data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
- data/app/models/open_loam/event_record.rb +42 -0
- data/app/models/{loam → open_loam}/field_definition.rb +8 -8
- data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
- data/app/models/{loam → open_loam}/membership.rb +6 -6
- data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
- data/app/models/{loam → open_loam}/notification.rb +10 -10
- data/app/models/{loam → open_loam}/pending_action.rb +70 -26
- data/app/models/{loam → open_loam}/perspective.rb +5 -5
- data/app/models/{loam → open_loam}/progress_job.rb +7 -7
- data/app/models/{loam → open_loam}/record_lock.rb +5 -5
- data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
- data/app/models/open_loam/search_token.rb +9 -0
- data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
- data/app/models/open_loam/sso_provider.rb +66 -0
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/open_loam/webhook_endpoint.rb +39 -0
- data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
- data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
- data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +6 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +2 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +32 -22
- data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
- data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
- data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
- data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +8 -5
- data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
- data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/index.html.erb +94 -0
- data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/show.html.erb +38 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +58 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +345 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/api_tokens_controller.rb +48 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +101 -38
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
- data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +12 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
- data/lib/generators/open_loam/install/templates/admin/history_controller.rb +64 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +25 -13
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -4
- data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +11 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +5 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +13 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +23 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +33 -34
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +8 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +7 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
- data/lib/generators/open_loam/install/templates/api_base_controller.rb +127 -0
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/open_loam/install/templates/import_job.rb +38 -0
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +87 -75
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_auth_attempts.rb → open_loam/install/templates/migrations/create_open_loam_auth_attempts.rb} +3 -3
- data/lib/generators/{loam/install/templates/migrations/create_loam_business_rules.rb → open_loam/install/templates/migrations/create_open_loam_business_rules.rb} +10 -10
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_dictionary_entries.rb → open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb} +6 -6
- data/lib/generators/{loam/install/templates/migrations/create_loam_event_deliveries.rb → open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb} +5 -5
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_event_records.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_field_definitions.rb → open_loam/install/templates/migrations/create_open_loam_field_definitions.rb} +4 -4
- data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -10
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_mfa_credentials.rb → open_loam/install/templates/migrations/create_open_loam_mfa_credentials.rb} +4 -4
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_pending_actions.rb → open_loam/install/templates/migrations/create_open_loam_pending_actions.rb} +9 -9
- data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
- data/lib/generators/{loam/install/templates/migrations/create_loam_progress_jobs.rb → open_loam/install/templates/migrations/create_open_loam_progress_jobs.rb} +5 -5
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_scheduled_jobs.rb → open_loam/install/templates/migrations/create_open_loam_scheduled_jobs.rb} +5 -5
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +15 -12
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
- data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
- data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
- data/lib/open-loam.rb +2 -2
- data/lib/{loam → open_loam}/attachable.rb +4 -4
- data/lib/{loam → open_loam}/auditable.rb +23 -23
- data/lib/{loam → open_loam}/auth_throttle.rb +20 -12
- data/lib/{loam → open_loam}/base32.rb +1 -1
- data/lib/{loam → open_loam}/bulk.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
- data/lib/{loam → open_loam}/business_rules.rb +19 -19
- data/lib/{loam → open_loam}/commentable.rb +6 -6
- data/lib/{loam → open_loam}/configs.rb +19 -19
- data/lib/{loam → open_loam}/cron.rb +2 -2
- data/lib/{loam → open_loam}/csv.rb +2 -2
- data/lib/{loam → open_loam}/current.rb +5 -5
- data/lib/{loam → open_loam}/custom_field_index.rb +33 -30
- data/lib/{loam → open_loam}/custom_fields.rb +17 -17
- data/lib/{loam → open_loam}/dashboard.rb +5 -5
- data/lib/{loam → open_loam}/dictionaries.rb +12 -12
- data/lib/{loam → open_loam}/durable_events.rb +19 -19
- data/lib/{loam → open_loam}/encryptable.rb +39 -34
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +19 -3
- data/lib/{loam → open_loam}/encryption.rb +58 -15
- data/lib/open_loam/engine.rb +65 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +20 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- data/lib/open_loam/event_log.rb +77 -0
- data/lib/{loam → open_loam}/event_stream.rb +14 -14
- data/lib/{loam → open_loam}/eventful.rb +12 -12
- data/lib/{loam → open_loam}/events.rb +5 -5
- data/lib/{loam → open_loam}/export.rb +12 -12
- data/lib/{loam → open_loam}/features.rb +15 -15
- data/lib/{loam → open_loam}/generated_key.rb +6 -6
- data/lib/{loam → open_loam}/import.rb +12 -15
- data/lib/{loam → open_loam}/inbound_webhooks.rb +19 -18
- data/lib/{loam → open_loam}/lifecycle.rb +54 -25
- data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
- data/lib/{loam → open_loam}/mcp/server.rb +9 -9
- data/lib/{loam → open_loam}/mcp.rb +31 -22
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- data/lib/open_loam/outbound_url.rb +100 -0
- data/lib/{loam → open_loam}/overrides.rb +10 -10
- data/lib/{loam → open_loam}/pending_actions.rb +9 -9
- data/lib/{loam → open_loam}/permissions.rb +8 -8
- data/lib/{loam → open_loam}/perspectives.rb +9 -9
- data/lib/{loam → open_loam}/policy.rb +21 -8
- data/lib/{loam → open_loam}/progress.rb +6 -6
- data/lib/{loam → open_loam}/record_locks.rb +9 -9
- data/lib/{loam → open_loam}/scheduler.rb +20 -20
- data/lib/{loam → open_loam}/search/driver.rb +3 -3
- data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
- data/lib/{loam → open_loam}/search/token_driver.rb +16 -16
- data/lib/{loam → open_loam}/search.rb +5 -5
- data/lib/{loam → open_loam}/searchable.rb +22 -22
- data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
- data/lib/{loam → open_loam}/sso/claims.rb +1 -1
- data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
- data/lib/open_loam/sso/http_client.rb +51 -0
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +39 -18
- data/lib/{loam → open_loam}/telemetry.rb +6 -6
- data/lib/{loam → open_loam}/tenant_record.rb +10 -10
- data/lib/{loam → open_loam}/test_helpers.rb +4 -4
- data/lib/{loam → open_loam}/totp.rb +3 -3
- data/lib/{loam → open_loam}/translatable.rb +24 -24
- data/lib/{loam → open_loam}/undo.rb +11 -11
- data/lib/open_loam/version.rb +3 -0
- data/lib/{loam → open_loam}/webhooks.rb +4 -4
- data/lib/{loam → open_loam}/widgets.rb +9 -9
- data/lib/{loam → open_loam}/workflow.rb +45 -45
- data/lib/open_loam.rb +124 -0
- data/lib/tasks/open_loam.rake +203 -0
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +247 -242
- data/app/models/loam/api_token.rb +0 -40
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/sso_provider.rb +0 -35
- data/app/models/loam/tenant.rb +0 -20
- data/app/models/loam/webhook_endpoint.rb +0 -26
- data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
- data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
- data/lib/generators/loam/install/templates/AGENTS.md +0 -341
- data/lib/generators/loam/install/templates/admin/api_tokens_controller.rb +0 -30
- data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
- data/lib/generators/loam/install/templates/admin/history_controller.rb +0 -38
- data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
- data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
- data/lib/generators/loam/install/templates/api_base_controller.rb +0 -90
- data/lib/generators/loam/install/templates/import_job.rb +0 -25
- data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
- data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
- data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
- data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
- data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
- data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
- data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
- data/lib/loam/engine.rb +0 -57
- data/lib/loam/sso/http_client.rb +0 -42
- data/lib/loam/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam.rake +0 -171
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Recurring schedules (
|
|
2
|
+
# Recurring schedules (OpenLoam::ScheduledJob) — manager-only. List/create/edit/
|
|
3
3
|
# delete a tenant's schedules, toggle active, and "run now". The runner
|
|
4
|
-
# (`
|
|
4
|
+
# (`open_loam:scheduler:tick`, wired to system cron) is what fires them on schedule.
|
|
5
5
|
class ScheduledJobsController < BaseController
|
|
6
6
|
before_action { require_role!(:manager) }
|
|
7
7
|
before_action :set_job, only: %i[edit update destroy run_now]
|
|
8
8
|
|
|
9
9
|
def index
|
|
10
|
-
@jobs =
|
|
10
|
+
@jobs = OpenLoam::ScheduledJob.order(:key)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def new
|
|
14
|
-
@job =
|
|
14
|
+
@job = OpenLoam::ScheduledJob.new(scope: "tenant", active: true, schedule: "0 3 * * *")
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def create
|
|
18
18
|
# scope is forced to "tenant": a tenant admin must never create a
|
|
19
19
|
# "system" schedule (which would run with no tenant context). System
|
|
20
20
|
# schedules are created only by the app/console.
|
|
21
|
-
@job =
|
|
21
|
+
@job = OpenLoam::ScheduledJob.new(job_params.merge(scope: "tenant"))
|
|
22
22
|
@job.next_run_at ||= safe_next_run(@job)
|
|
23
23
|
if @job.save
|
|
24
24
|
redirect_to admin_scheduled_jobs_path, notice: "Schedule created."
|
|
@@ -45,14 +45,14 @@ module Admin
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def run_now
|
|
48
|
-
|
|
48
|
+
OpenLoam::Scheduler.run_now(@job)
|
|
49
49
|
redirect_to admin_scheduled_jobs_path, notice: "#{@job.name} enqueued."
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
private
|
|
53
53
|
|
|
54
54
|
def set_job
|
|
55
|
-
@job =
|
|
55
|
+
@job = OpenLoam::ScheduledJob.find(params[:id])
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def job_params
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%%#
|
|
1
|
+
<%%# OpenLoam::ScheduledJob is namespaced, so pin the URL + param scope. %>
|
|
2
2
|
<%%= form_with model: @job, scope: :scheduled_job,
|
|
3
3
|
url: (@job.persisted? ? admin_scheduled_job_path(@job) : admin_scheduled_jobs_path) do |f| %>
|
|
4
4
|
<%% if @job.errors.any? %>
|
data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1>Schedules</h1>
|
|
2
2
|
|
|
3
|
-
<p>Recurring jobs for this tenant. The runner (<code>
|
|
3
|
+
<p>Recurring jobs for this tenant. The runner (<code>open_loam:scheduler:tick</code>,
|
|
4
4
|
wired to system cron) fires them on schedule. <%%= link_to "New schedule", new_admin_scheduled_job_path %></p>
|
|
5
5
|
|
|
6
6
|
<table>
|
|
@@ -6,6 +6,8 @@ module Admin
|
|
|
6
6
|
# tenant-scoped relation, so this can only ever find records the current
|
|
7
7
|
# tenant owns.
|
|
8
8
|
class SearchController < BaseController
|
|
9
|
+
skip_authorization! "Each model's `search` returns its own tenant-scoped relation; reads only."
|
|
10
|
+
|
|
9
11
|
PER_ENTITY = 10
|
|
10
12
|
|
|
11
13
|
helper_method :search_result_label
|
|
@@ -16,9 +18,9 @@ module Admin
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
# The first searchable column that has a value, plus the id — enough to
|
|
19
|
-
# recognise a record without
|
|
21
|
+
# recognise a record without OpenLoam having to know what a "title" is.
|
|
20
22
|
def search_result_label(record)
|
|
21
|
-
value = record.class.
|
|
23
|
+
value = record.class.open_loam_searchable_columns.filter_map { |column| record.public_send(column).presence }.first
|
|
22
24
|
|
|
23
25
|
[ value || record.model_name.human, "##{record.id}" ].join(" ")
|
|
24
26
|
end
|
|
@@ -32,7 +34,7 @@ module Admin
|
|
|
32
34
|
end.to_h
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
# Every
|
|
37
|
+
# Every OpenLoam entity that opted into search, in name order.
|
|
36
38
|
#
|
|
37
39
|
# This eager loads, unlike Admin::CommentsController, and for the opposite
|
|
38
40
|
# reason: there is no user-supplied class name to check here, we need the
|
|
@@ -41,8 +43,8 @@ module Admin
|
|
|
41
43
|
def searchable_models
|
|
42
44
|
Rails.application.eager_load!
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
model.name.present? && model.respond_to?(:
|
|
46
|
+
OpenLoam::TenantRecord.descendants.select do |model|
|
|
47
|
+
model.name.present? && model.respond_to?(:open_loam_searchable?) && model.open_loam_searchable?
|
|
46
48
|
end.sort_by(&:name)
|
|
47
49
|
end
|
|
48
50
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# Email + password login for the admin, then (if the user has MFA) a TOTP
|
|
3
|
-
# challenge, then the tenant pick. A
|
|
3
|
+
# challenge, then the tenant pick. A OpenLoam user can belong to several tenants,
|
|
4
4
|
# so: authenticate → second factor → choose a tenant where they hold a
|
|
5
5
|
# membership. The second factor runs BEFORE any tenant is chosen, which is why
|
|
6
|
-
# the MFA secret is keyed to the user, not a tenant (
|
|
6
|
+
# the MFA secret is keyed to the user, not a tenant (OpenLoam::MfaCredential).
|
|
7
7
|
#
|
|
8
8
|
# Not a BaseController subclass: everything there assumes an established
|
|
9
|
-
#
|
|
9
|
+
# OpenLoam::Current, which is exactly what this controller is trying to build.
|
|
10
10
|
class SessionsController < ActionController::Base
|
|
11
11
|
layout "admin"
|
|
12
12
|
|
|
@@ -14,17 +14,17 @@ module Admin
|
|
|
14
14
|
return redirect_to mfa_challenge_admin_session_path if session[:mfa_pending]
|
|
15
15
|
|
|
16
16
|
@user = authenticated_user
|
|
17
|
-
@tenants = @user ?
|
|
17
|
+
@tenants = @user ? OpenLoam::Membership.tenants_for(@user) : OpenLoam::Tenant.none
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def create
|
|
21
21
|
email = params[:email]
|
|
22
22
|
|
|
23
|
-
# Rate-limit BEFORE touching the password (
|
|
23
|
+
# Rate-limit BEFORE touching the password (OpenLoam::AuthThrottle). Throttle by
|
|
24
24
|
# the submitted identifier whether or not the account exists, and give the
|
|
25
25
|
# SAME generic locked response either way — so a lockout can't become an
|
|
26
26
|
# account-existence oracle.
|
|
27
|
-
if
|
|
27
|
+
if OpenLoam::AuthThrottle.locked?(email)
|
|
28
28
|
@error = throttle_message(email)
|
|
29
29
|
return render :new, status: :too_many_requests
|
|
30
30
|
end
|
|
@@ -32,20 +32,21 @@ module Admin
|
|
|
32
32
|
user = User.authenticate_by(email: email, password: params[:password])
|
|
33
33
|
|
|
34
34
|
if user.nil?
|
|
35
|
-
|
|
35
|
+
OpenLoam::AuthThrottle.record_failure(email, kind: "password", ip: request.remote_ip)
|
|
36
36
|
# One message for both cases on purpose: saying which half was wrong
|
|
37
37
|
# tells an attacker which emails exist.
|
|
38
38
|
@error = "Wrong email or password."
|
|
39
39
|
return render :new, status: :unauthorized
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
# PASSWORD only — clearing every kind here would reset the TOTP counter.
|
|
43
|
+
OpenLoam::AuthThrottle.clear(email, kind: "password")
|
|
43
44
|
reset_session # a fresh session id at login: no fixation
|
|
44
45
|
session[:user_id] = user.id
|
|
45
46
|
|
|
46
47
|
# Password is only the first factor when MFA is active — hold the login in
|
|
47
48
|
# a "pending" state (no tenant, no access) until the code checks out.
|
|
48
|
-
if
|
|
49
|
+
if OpenLoam::MfaCredential.active_for(user)
|
|
49
50
|
session[:mfa_pending] = true
|
|
50
51
|
redirect_to mfa_challenge_admin_session_path
|
|
51
52
|
else
|
|
@@ -58,27 +59,25 @@ module Admin
|
|
|
58
59
|
@user = mfa_challenge_user or return redirect_to new_admin_session_path
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
# TODO (follow-up): rate-limit / lock out repeated failed codes here (needs
|
|
62
|
-
# throttling infra) — a 6-digit TOTP is brute-forceable without it.
|
|
63
62
|
def mfa_verify
|
|
64
63
|
user = mfa_challenge_user or return redirect_to new_admin_session_path
|
|
65
64
|
|
|
66
65
|
# A 6-digit TOTP (with drift, several codes valid) is the prime brute-force
|
|
67
66
|
# target — lock it out on the user's identifier.
|
|
68
|
-
if
|
|
67
|
+
if OpenLoam::AuthThrottle.locked?(user.email)
|
|
69
68
|
@user = user
|
|
70
69
|
@error = throttle_message(user.email)
|
|
71
70
|
return render :mfa_challenge, status: :too_many_requests
|
|
72
71
|
end
|
|
73
72
|
|
|
74
|
-
credential =
|
|
73
|
+
credential = OpenLoam::MfaCredential.active_for(user)
|
|
75
74
|
|
|
76
75
|
if credential.verify_totp(params[:code]) || credential.consume_recovery_code(params[:code])
|
|
77
|
-
|
|
76
|
+
OpenLoam::AuthThrottle.clear(user.email, kind: "totp")
|
|
78
77
|
session.delete(:mfa_pending)
|
|
79
78
|
complete_authentication(user)
|
|
80
79
|
else
|
|
81
|
-
|
|
80
|
+
OpenLoam::AuthThrottle.record_failure(user.email, kind: "totp", ip: request.remote_ip)
|
|
82
81
|
# One generic error — never reveal whether a code was close or a
|
|
83
82
|
# recovery code was already spent.
|
|
84
83
|
@user = user
|
|
@@ -92,7 +91,7 @@ module Admin
|
|
|
92
91
|
# back to password login. The `state` is our CSRF token for the round-trip.
|
|
93
92
|
def sso_start
|
|
94
93
|
email = params[:email].to_s
|
|
95
|
-
provider =
|
|
94
|
+
provider = OpenLoam::Sso.provider_for(email: email)
|
|
96
95
|
|
|
97
96
|
if provider.nil?
|
|
98
97
|
return redirect_to new_admin_session_path(email: email),
|
|
@@ -105,8 +104,8 @@ module Admin
|
|
|
105
104
|
session[:sso_provider_id] = provider.id
|
|
106
105
|
session[:sso_tenant_id] = provider.tenant_id
|
|
107
106
|
|
|
108
|
-
url =
|
|
109
|
-
|
|
107
|
+
url = OpenLoam.as_tenant(provider.tenant) do
|
|
108
|
+
OpenLoam::Sso.build(provider, redirect_uri: sso_callback_admin_session_url)
|
|
110
109
|
.authorization_url(state: state, login_hint: email)
|
|
111
110
|
end
|
|
112
111
|
redirect_to url, allow_other_host: true
|
|
@@ -124,15 +123,15 @@ module Admin
|
|
|
124
123
|
return redirect_to new_admin_session_path, alert: "SSO sign-in could not be verified. Please try again."
|
|
125
124
|
end
|
|
126
125
|
|
|
127
|
-
tenant =
|
|
128
|
-
provider = tenant &&
|
|
126
|
+
tenant = OpenLoam::Tenant.find_by(id: tenant_id)
|
|
127
|
+
provider = tenant && OpenLoam.as_tenant(tenant) { OpenLoam::SsoProvider.find_by(id: provider_id) }
|
|
129
128
|
if provider.nil? || !provider.active?
|
|
130
129
|
return redirect_to new_admin_session_path, alert: "That SSO provider is no longer available."
|
|
131
130
|
end
|
|
132
131
|
|
|
133
|
-
user =
|
|
134
|
-
claims =
|
|
135
|
-
|
|
132
|
+
user = OpenLoam.as_tenant(tenant) do
|
|
133
|
+
claims = OpenLoam::Sso.build(provider, redirect_uri: sso_callback_admin_session_url).exchange(code: params[:code])
|
|
134
|
+
OpenLoam::Sso.provision(provider, claims)
|
|
136
135
|
end
|
|
137
136
|
|
|
138
137
|
reset_session
|
|
@@ -141,13 +140,13 @@ module Admin
|
|
|
141
140
|
|
|
142
141
|
# SSO establishes primary auth; if the user also runs app-side MFA, still
|
|
143
142
|
# require the second factor (the safe choice — SSO does not waive it).
|
|
144
|
-
if
|
|
143
|
+
if OpenLoam::MfaCredential.active_for(user)
|
|
145
144
|
session[:mfa_pending] = true
|
|
146
145
|
redirect_to mfa_challenge_admin_session_path
|
|
147
146
|
else
|
|
148
147
|
complete_authentication(user)
|
|
149
148
|
end
|
|
150
|
-
rescue
|
|
149
|
+
rescue OpenLoam::Sso::Error
|
|
151
150
|
# An unverified email, a domain the provider does not own, or any other
|
|
152
151
|
# provisioning refusal: no session, nothing linked, one generic message.
|
|
153
152
|
reset_session
|
|
@@ -162,7 +161,7 @@ module Admin
|
|
|
162
161
|
user = authenticated_user
|
|
163
162
|
return redirect_to new_admin_session_path if user.nil?
|
|
164
163
|
|
|
165
|
-
tenant =
|
|
164
|
+
tenant = OpenLoam::Membership.tenants_for(user).find_by(id: params[:tenant_id])
|
|
166
165
|
|
|
167
166
|
# A tenant the user has no membership in is simply not in that list, so
|
|
168
167
|
# picking one is impossible rather than merely forbidden.
|
|
@@ -173,7 +172,7 @@ module Admin
|
|
|
173
172
|
|
|
174
173
|
def destroy
|
|
175
174
|
reset_session
|
|
176
|
-
|
|
175
|
+
OpenLoam::Current.reset
|
|
177
176
|
redirect_to new_admin_session_path
|
|
178
177
|
end
|
|
179
178
|
|
|
@@ -184,7 +183,7 @@ module Admin
|
|
|
184
183
|
end
|
|
185
184
|
|
|
186
185
|
def throttle_message(identifier)
|
|
187
|
-
minutes = (
|
|
186
|
+
minutes = (OpenLoam::AuthThrottle.remaining_lockout(identifier) / 60.0).ceil
|
|
188
187
|
"Too many attempts. Try again in about #{[ minutes, 1 ].max} minute(s)."
|
|
189
188
|
end
|
|
190
189
|
|
|
@@ -206,11 +205,11 @@ module Admin
|
|
|
206
205
|
# SSO knows exactly which tenant to enter (the IdP's), so honor it even for
|
|
207
206
|
# a multi-tenant user rather than showing the picker.
|
|
208
207
|
if (sso_tenant_id = session.delete(:sso_tenant_id))
|
|
209
|
-
tenant =
|
|
208
|
+
tenant = OpenLoam::Membership.tenants_for(user).find_by(id: sso_tenant_id)
|
|
210
209
|
return enter(user, tenant) if tenant
|
|
211
210
|
end
|
|
212
211
|
|
|
213
|
-
tenants =
|
|
212
|
+
tenants = OpenLoam::Membership.tenants_for(user)
|
|
214
213
|
|
|
215
214
|
if tenants.one?
|
|
216
215
|
enter(user, tenants.first)
|
|
@@ -237,11 +236,11 @@ module Admin
|
|
|
237
236
|
# resolved in that tenant). If the user's role is on the list and they have
|
|
238
237
|
# no active credential, they are sent to enrollment before they can work.
|
|
239
238
|
def mfa_enrollment_required?(user, tenant)
|
|
240
|
-
return false if
|
|
239
|
+
return false if OpenLoam::MfaCredential.active_for(user)
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
role =
|
|
244
|
-
role.present? && Array(
|
|
241
|
+
OpenLoam.as_tenant(tenant) do
|
|
242
|
+
role = OpenLoam::Membership.find_by(user_id: user.id)&.role
|
|
243
|
+
role.present? && Array(OpenLoam::Configs.get("security.mfa_required_roles", default: [])).include?(role)
|
|
245
244
|
end
|
|
246
245
|
end
|
|
247
246
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Admin
|
|
2
2
|
# Configure this tenant's SSO (OIDC) provider — manager-only, since it governs
|
|
3
|
-
# who can sign in and at what role. Tenant-scoped by
|
|
3
|
+
# who can sign in and at what role. Tenant-scoped by OpenLoam::SsoProvider's default
|
|
4
4
|
# scope, so a manager only ever sees and edits their own tenant's connection.
|
|
5
5
|
# The client_secret is WRITE-ONLY: it is encrypted at rest and never rendered
|
|
6
6
|
# back, so editing without retyping it leaves the stored secret untouched.
|
|
@@ -10,20 +10,20 @@ module Admin
|
|
|
10
10
|
|
|
11
11
|
rescue_from JSON::ParserError do |error|
|
|
12
12
|
flash.now[:alert] = "The group→role map must be valid JSON: #{error.message}"
|
|
13
|
-
@provider ||=
|
|
13
|
+
@provider ||= OpenLoam::SsoProvider.new
|
|
14
14
|
render(@provider.persisted? ? :edit : :new, status: :unprocessable_entity)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def index
|
|
18
|
-
@providers =
|
|
18
|
+
@providers = OpenLoam::SsoProvider.order(:domain)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def new
|
|
22
|
-
@provider =
|
|
22
|
+
@provider = OpenLoam::SsoProvider.new(protocol: "oidc", active: true, jit_role: "employee")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def create
|
|
26
|
-
@provider =
|
|
26
|
+
@provider = OpenLoam::SsoProvider.new(provider_params)
|
|
27
27
|
if @provider.save
|
|
28
28
|
redirect_to admin_sso_providers_path, notice: "SSO provider created."
|
|
29
29
|
else
|
|
@@ -49,7 +49,7 @@ module Admin
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
51
|
def set_provider
|
|
52
|
-
@provider =
|
|
52
|
+
@provider = OpenLoam::SsoProvider.find(params[:id])
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def provider_params
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<p style="color:#b00"><%%= @error || flash[:alert] %></p>
|
|
3
3
|
<%% end %>
|
|
4
4
|
|
|
5
|
-
<%# The model is namespaced (
|
|
6
|
-
would be
|
|
5
|
+
<%# The model is namespaced (OpenLoam::SsoProvider), so an inferred URL/param-scope
|
|
6
|
+
would be admin_open_loam_sso_provider_path / open_loam_sso_provider[...]. Pin both to
|
|
7
7
|
the un-namespaced route and the "sso_provider" key the controller permits. %>
|
|
8
8
|
<%%= form_with model: @provider, scope: :sso_provider,
|
|
9
9
|
url: (@provider.persisted? ? admin_sso_provider_path(@provider) : admin_sso_providers_path) do |f| %>
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
below is sent to that identity provider to sign in (OIDC), and provisioned on
|
|
5
5
|
first login. <%%= link_to "New provider", new_admin_sso_provider_path %></p>
|
|
6
6
|
|
|
7
|
+
<p>A provider does nothing until an operator confirms your organisation owns the
|
|
8
|
+
domain — anyone can type a domain here, so the claim has to be checked off the
|
|
9
|
+
admin path. Ask your operator to run
|
|
10
|
+
<code>rake open_loam:sso:verify_domain[<id>]</code>. Editing the domain
|
|
11
|
+
later clears the confirmation.</p>
|
|
12
|
+
|
|
7
13
|
<table>
|
|
8
14
|
<thead>
|
|
9
|
-
<tr><th>Name</th><th>Protocol</th><th>Domain</th><th>JIT role</th><th>Active</th><th></th></tr>
|
|
15
|
+
<tr><th>Name</th><th>Protocol</th><th>Domain</th><th>Ownership</th><th>JIT role</th><th>Active</th><th></th></tr>
|
|
10
16
|
</thead>
|
|
11
17
|
<tbody>
|
|
12
18
|
<%% @providers.each do |provider| %>
|
|
@@ -14,6 +20,7 @@ first login. <%%= link_to "New provider", new_admin_sso_provider_path %></p>
|
|
|
14
20
|
<td><%%= provider.name %></td>
|
|
15
21
|
<td><%%= provider.protocol %></td>
|
|
16
22
|
<td><code><%%= provider.domain %></code></td>
|
|
23
|
+
<td><%%= provider.domain_verified? ? "confirmed" : "not confirmed — SSO is inactive" %></td>
|
|
17
24
|
<td><%%= provider.jit_role %></td>
|
|
18
25
|
<td><%%= provider.active? ? "yes" : "no" %></td>
|
|
19
26
|
<td>
|
|
@@ -4,24 +4,26 @@ module Admin
|
|
|
4
4
|
# (or TOTP code, if the user has MFA) stamps a fresh sudo timestamp and returns
|
|
5
5
|
# to wherever they were headed.
|
|
6
6
|
class SudoController < BaseController
|
|
7
|
+
skip_authorization! "The step-up re-challenge itself — authorizing it would require the sudo it grants."
|
|
8
|
+
|
|
7
9
|
def new
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def create
|
|
11
13
|
identifier = "#{current_actor.email}:sudo" # a distinct throttle bucket from login
|
|
12
14
|
|
|
13
|
-
if
|
|
15
|
+
if OpenLoam::AuthThrottle.locked?(identifier)
|
|
14
16
|
@error = "Too many attempts. Try again later."
|
|
15
17
|
return render :new, status: :too_many_requests
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
if reauthenticated?
|
|
19
|
-
|
|
21
|
+
OpenLoam::AuthThrottle.clear(identifier, kind: "sudo")
|
|
20
22
|
session[:sudo_at] = Time.now.to_i
|
|
21
23
|
redirect_to(session.delete(:sudo_return_to).presence || admin_root_path,
|
|
22
24
|
notice: "Re-authenticated — you can complete the action now.")
|
|
23
25
|
else
|
|
24
|
-
|
|
26
|
+
OpenLoam::AuthThrottle.record_failure(identifier, kind: "sudo", ip: request.remote_ip)
|
|
25
27
|
@error = "That did not verify. Try again."
|
|
26
28
|
render :new, status: :unauthorized
|
|
27
29
|
end
|
|
@@ -32,9 +34,9 @@ module Admin
|
|
|
32
34
|
# If the user has MFA, step-up takes a TOTP code — NOT a recovery code:
|
|
33
35
|
# recovery codes are single-use and reserved for LOGIN, so a routine sudo
|
|
34
36
|
# must never silently burn one. Otherwise fall back to the password. Either
|
|
35
|
-
# way it is the SAME person in
|
|
37
|
+
# way it is the SAME person in OpenLoam::Current re-confirming.
|
|
36
38
|
def reauthenticated?
|
|
37
|
-
credential =
|
|
39
|
+
credential = OpenLoam::MfaCredential.active_for(current_actor)
|
|
38
40
|
if credential
|
|
39
41
|
credential.verify_totp(params[:code])
|
|
40
42
|
else
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p>This action is sensitive, so please re-authenticate.</p>
|
|
8
8
|
|
|
9
9
|
<%%= form_with url: admin_sudo_path, method: :post do |f| %>
|
|
10
|
-
<%% if
|
|
10
|
+
<%% if OpenLoam::MfaCredential.active_for(current_actor) %>
|
|
11
11
|
<p>
|
|
12
12
|
<%%= label_tag :code, "Authenticator code" %><br>
|
|
13
13
|
<%%= text_field_tag :code, nil, autofocus: true, autocomplete: "one-time-code", inputmode: "numeric" %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Admin
|
|
2
|
-
# Per-record content translations (
|
|
2
|
+
# Per-record content translations (OpenLoam::Translatable) — manager-only, since
|
|
3
3
|
# translating content is a privileged edit. Scoped by translatable_type +
|
|
4
|
-
# translatable_id; edits each translatable field across
|
|
4
|
+
# translatable_id; edits each translatable field across OpenLoam.locales. The base
|
|
5
5
|
# (default-locale) value stays on the record itself and is shown read-only for
|
|
6
6
|
# reference — translations are additive.
|
|
7
7
|
class TranslationsController < BaseController
|
|
@@ -12,9 +12,9 @@ module Admin
|
|
|
12
12
|
|
|
13
13
|
def update
|
|
14
14
|
params.require(:translations).to_unsafe_h.each do |field, by_locale|
|
|
15
|
-
next unless @record.class.
|
|
15
|
+
next unless @record.class.open_loam_translatable?(field)
|
|
16
16
|
|
|
17
|
-
by_locale.each { |locale, value| @record.set_translation(field, locale, value) if
|
|
17
|
+
by_locale.each { |locale, value| @record.set_translation(field, locale, value) if OpenLoam.locales.include?(locale) }
|
|
18
18
|
end
|
|
19
19
|
redirect_to translate_path, notice: "Translations saved."
|
|
20
20
|
end
|
|
@@ -22,11 +22,11 @@ module Admin
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def set_record
|
|
25
|
-
model =
|
|
26
|
-
raise ActiveRecord::RecordNotFound unless model.include?(
|
|
25
|
+
model = OpenLoam::Import.allowed_model(params[:translatable_type]) # whitelist: a OpenLoam entity only
|
|
26
|
+
raise ActiveRecord::RecordNotFound unless model.include?(OpenLoam::Translatable)
|
|
27
27
|
|
|
28
28
|
@record = model.find(params[:translatable_id])
|
|
29
|
-
@fields = model.
|
|
29
|
+
@fields = model.open_loam_translatable_attributes
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def translate_path
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<h1>Translations — <%%= @record.class.model_name.human %> #<%%= @record.id %></h1>
|
|
2
2
|
|
|
3
|
-
<p>Translate content per locale. The base (<%%=
|
|
3
|
+
<p>Translate content per locale. The base (<%%= OpenLoam.default_locale.upcase %>) value
|
|
4
4
|
stays on the record; other locales overlay onto it, falling back to the base when
|
|
5
5
|
empty.</p>
|
|
6
6
|
|
|
7
7
|
<%%= form_with url: translate_path, method: :patch do %>
|
|
8
8
|
<%% @fields.each do |field| %>
|
|
9
9
|
<h3><%%= field.humanize %></h3>
|
|
10
|
-
<p><em>Base (<%%=
|
|
11
|
-
<%% (
|
|
10
|
+
<p><em>Base (<%%= OpenLoam.default_locale.upcase %>):</em> <%%= @record.read_attribute(field) %></p>
|
|
11
|
+
<%% (OpenLoam.locales - [OpenLoam.default_locale]).each do |locale| %>
|
|
12
12
|
<p>
|
|
13
13
|
<%%= label_tag "translations[#{field}][#{locale}]", locale.upcase %><br>
|
|
14
14
|
<%%= text_field_tag "translations[#{field}][#{locale}]", @record.translations_for(field)[locale] %>
|
data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb
RENAMED
|
@@ -6,15 +6,15 @@ module Admin
|
|
|
6
6
|
before_action { require_role!(:manager) }
|
|
7
7
|
|
|
8
8
|
def index
|
|
9
|
-
@records =
|
|
9
|
+
@records = OpenLoam::WebhookEndpoint.order(:event_pattern)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def new
|
|
13
|
-
@record =
|
|
13
|
+
@record = OpenLoam::WebhookEndpoint.new(active: true)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def create
|
|
17
|
-
@record =
|
|
17
|
+
@record = OpenLoam::WebhookEndpoint.new(permitted_params)
|
|
18
18
|
|
|
19
19
|
if @record.save
|
|
20
20
|
redirect_to admin_webhook_endpoints_path
|
|
@@ -24,7 +24,7 @@ module Admin
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def destroy
|
|
27
|
-
|
|
27
|
+
OpenLoam::WebhookEndpoint.find(params[:id]).destroy!
|
|
28
28
|
redirect_to admin_webhook_endpoints_path
|
|
29
29
|
end
|
|
30
30
|
|
data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb
RENAMED
|
@@ -28,5 +28,5 @@ domain ("rental."); anything else is one exact event name.</p>
|
|
|
28
28
|
</tbody>
|
|
29
29
|
</table>
|
|
30
30
|
|
|
31
|
-
<p>Each delivery is signed: <code>X-
|
|
31
|
+
<p>Each delivery is signed: <code>X-OpenLoam-Signature: sha256=HMAC-SHA256(secret, body)</code>.
|
|
32
32
|
Recompute it on your side and reject anything that does not match.</p>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* OpenLoam admin console — shared styles (extracted from the layout, L-402). */
|
|
2
2
|
body { font-family: system-ui, -apple-system, sans-serif; margin: 2rem; color: #1c1c1a; line-height: 1.5; }
|
|
3
3
|
h1 { font-size: 1.55rem; margin: 0 0 0.75rem; }
|
|
4
4
|
h2 { font-size: 1.2rem; margin: 1.25rem 0 0.5rem; }
|