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,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One row per change: who (actor), what (auditable + action + changeset),
|
|
3
3
|
# when (created_at), in which tenant (tenant_id, via TenantRecord).
|
|
4
4
|
# Append-only by convention; not itself audited or evented.
|
|
5
|
-
class AuditRecord <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class AuditRecord < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_audit_records"
|
|
7
7
|
|
|
8
8
|
belongs_to :actor, class_name: "User", optional: true
|
|
9
9
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A single failed authentication attempt, for rate-limiting/lockout
|
|
3
|
-
# (
|
|
3
|
+
# (OpenLoam::AuthThrottle). Deliberately NOT tenant-scoped — authentication happens
|
|
4
4
|
# BEFORE a tenant is chosen (login), so this is a global auth-layer table keyed
|
|
5
|
-
# by the submitted identifier (the same reason
|
|
5
|
+
# by the submitted identifier (the same reason OpenLoam::MfaCredential is global).
|
|
6
6
|
# Allowlisted in the guardrails tenancy lint.
|
|
7
7
|
class AuthAttempt < ApplicationRecord
|
|
8
|
-
include
|
|
9
|
-
self.table_name = "
|
|
8
|
+
include OpenLoam::GeneratedKey
|
|
9
|
+
self.table_name = "open_loam_auth_attempts"
|
|
10
10
|
|
|
11
11
|
validates :identifier, :kind, presence: true
|
|
12
12
|
end
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A per-tenant, admin-configurable rule: WHEN a condition holds THEN run
|
|
3
3
|
# actions. Declared as DATA (a trigger event pattern + a safe condition tree +
|
|
4
|
-
# a typed action list), never as code — see
|
|
4
|
+
# a typed action list), never as code — see OpenLoam::BusinessRules for the engine
|
|
5
5
|
# that evaluates it. Audited like any business record.
|
|
6
|
-
class BusinessRule <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class BusinessRule < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_business_rules"
|
|
8
8
|
|
|
9
|
-
include
|
|
9
|
+
include OpenLoam::Auditable
|
|
10
10
|
|
|
11
|
-
has_many :runs, class_name: "
|
|
11
|
+
has_many :runs, class_name: "OpenLoam::BusinessRuleRun", dependent: :delete_all
|
|
12
12
|
|
|
13
13
|
validates :name, :trigger, presence: true
|
|
14
14
|
# A rule may only target a tenant-scoped model (or none, for an event-only
|
|
15
15
|
# rule). Refusing a global class like `User` at SAVE time means a poisoned
|
|
16
16
|
# rule can't even be persisted — kept in lockstep with
|
|
17
|
-
#
|
|
17
|
+
# OpenLoam::BusinessRules.subject_for, which refuses the same at run time.
|
|
18
18
|
validate :entity_type_targets_a_tenant_record
|
|
19
19
|
|
|
20
20
|
scope :active, -> { where(active: true) }
|
|
@@ -22,7 +22,7 @@ module Loam
|
|
|
22
22
|
scope :for_entity, ->(entity_type) { where(entity_type: entity_type.to_s) }
|
|
23
23
|
|
|
24
24
|
def matches_trigger?(event_name)
|
|
25
|
-
|
|
25
|
+
OpenLoam::Events.pattern_matches?(trigger, event_name)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# The stored json, defensively — a hand-created row could hold a non-hash /
|
|
@@ -41,8 +41,8 @@ module Loam
|
|
|
41
41
|
return if entity_type.blank? # event-only rule, no subject to load
|
|
42
42
|
|
|
43
43
|
klass = entity_type.safe_constantize
|
|
44
|
-
unless klass.is_a?(Class) && klass <
|
|
45
|
-
errors.add(:entity_type, "must name a
|
|
44
|
+
unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
45
|
+
errors.add(:entity_type, "must name a OpenLoam tenant-scoped model (not #{entity_type.inspect})")
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# The log of why a business rule did (or did not) act — so the admin can see
|
|
3
|
-
# WHY something happened. Plumbing, like
|
|
3
|
+
# WHY something happened. Plumbing, like OpenLoam::RecordLock: NOT audited (a log
|
|
4
4
|
# of a log is noise). Capped per rule so the table stays lean.
|
|
5
|
-
class BusinessRuleRun <
|
|
6
|
-
self.table_name = "
|
|
5
|
+
class BusinessRuleRun < OpenLoam::TenantRecord
|
|
6
|
+
self.table_name = "open_loam_business_rule_runs"
|
|
7
7
|
|
|
8
8
|
KEEP_PER_RULE = 50
|
|
9
9
|
|
|
10
|
-
belongs_to :business_rule, class_name: "
|
|
10
|
+
belongs_to :business_rule, class_name: "OpenLoam::BusinessRule"
|
|
11
11
|
|
|
12
12
|
scope :recent, -> { order(created_at: :desc) }
|
|
13
13
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A note by one person, on one record, in one tenant. Polymorphic, so any
|
|
3
|
-
# entity that `include
|
|
3
|
+
# entity that `include OpenLoam::Commentable` gets a discussion for free.
|
|
4
4
|
#
|
|
5
5
|
# Not audited on purpose: the comment IS the activity record. A
|
|
6
|
-
#
|
|
6
|
+
# OpenLoam::AuditRecord saying "someone created a comment" beside a comment
|
|
7
7
|
# saying what they wrote is the same fact stored twice.
|
|
8
8
|
#
|
|
9
|
-
# Evented on purpose, in
|
|
9
|
+
# Evented on purpose, in OpenLoam's own domain ("open_loam.comment.created"), because
|
|
10
10
|
# a new comment is exactly the kind of thing an app wants to react to —
|
|
11
11
|
# notify the watchers, ping a webhook — without every app inventing its own
|
|
12
12
|
# event name for it.
|
|
13
|
-
class Comment <
|
|
14
|
-
include
|
|
13
|
+
class Comment < OpenLoam::TenantRecord
|
|
14
|
+
include OpenLoam::Eventful
|
|
15
15
|
|
|
16
|
-
self.table_name = "
|
|
16
|
+
self.table_name = "open_loam_comments"
|
|
17
17
|
|
|
18
|
-
event_domain :
|
|
19
|
-
event_entity :comment # not "
|
|
18
|
+
event_domain :open_loam
|
|
19
|
+
event_entity :comment # not "open_loam_comment", which the namespace would give
|
|
20
20
|
|
|
21
21
|
belongs_to :commentable, polymorphic: true
|
|
22
22
|
belongs_to :author, class_name: "User"
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A single configuration setting: one namespaced key, one JSON-able value.
|
|
3
3
|
#
|
|
4
|
-
# Deliberately NOT a
|
|
4
|
+
# Deliberately NOT a OpenLoam::TenantRecord. A setting exists at two levels — a
|
|
5
5
|
# GLOBAL default (tenant_id NULL) that every tenant sees, and a per-tenant
|
|
6
6
|
# OVERRIDE (tenant_id set) that wins for that one tenant — so tenancy is a
|
|
7
7
|
# nullable column here, not a default_scope. Resolving a key means reading
|
|
8
8
|
# across both levels at once, which is why the lookup lives in vetted gem code
|
|
9
|
-
# (
|
|
10
|
-
# test/
|
|
9
|
+
# (OpenLoam::Configs), never in app code (see the tenancy allowlist in
|
|
10
|
+
# test/open_loam_guardrails_test.rb).
|
|
11
11
|
#
|
|
12
12
|
# The value goes in a json column, so a bool, number, string, array, or hash
|
|
13
13
|
# all round-trip under the same key. Row existence — not the value — is what
|
|
14
14
|
# marks a key as set, so the value may legitimately be JSON null.
|
|
15
15
|
class Config < ApplicationRecord
|
|
16
|
-
include
|
|
17
|
-
self.table_name = "
|
|
16
|
+
include OpenLoam::GeneratedKey
|
|
17
|
+
self.table_name = "open_loam_configs"
|
|
18
18
|
|
|
19
|
-
belongs_to :tenant, class_name: "
|
|
19
|
+
belongs_to :tenant, class_name: "OpenLoam::Tenant", optional: true
|
|
20
20
|
|
|
21
21
|
validates :key, presence: true
|
|
22
22
|
# One global per key, one override per key per tenant. AR renders the nil
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A read-model projection of ONE custom field of ONE record into typed,
|
|
3
3
|
# INDEXED columns — so filtering/sorting/searching on a custom field is
|
|
4
4
|
# index-backed instead of a per-row JSON extraction over the `custom_fields`
|
|
5
5
|
# column (which can't be indexed and full-scans at scale). One row per
|
|
6
|
-
# (record, custom field); maintained by
|
|
7
|
-
#
|
|
6
|
+
# (record, custom field); maintained by OpenLoam::CustomFieldIndex from the
|
|
7
|
+
# OpenLoam::CustomFields save/destroy hooks. Tenant-scoped.
|
|
8
8
|
#
|
|
9
9
|
# The value is written into the column matching the field's declared type
|
|
10
10
|
# (value_number / value_boolean / value_datetime) AND always into value_text
|
|
11
11
|
# (its string form) for text ops (contains/present) and canonical equality.
|
|
12
|
-
class CustomFieldValue <
|
|
13
|
-
self.table_name = "
|
|
12
|
+
class CustomFieldValue < OpenLoam::TenantRecord
|
|
13
|
+
self.table_name = "open_loam_custom_field_values"
|
|
14
14
|
|
|
15
15
|
validates :indexable_type, :indexable_id, :field_key, presence: true
|
|
16
16
|
validates :field_key, uniqueness: { scope: %i[indexable_type indexable_id] }
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A tenant's dashboard layout: which registered widgets appear, in what order.
|
|
3
3
|
# A manager arranges these on the Dashboard settings screen; the dashboard
|
|
4
|
-
# renders the active ones by position (see
|
|
4
|
+
# renders the active ones by position (see OpenLoam::Dashboard). Tenant-scoped and
|
|
5
5
|
# audited.
|
|
6
|
-
class DashboardWidget <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class DashboardWidget < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_dashboard_widgets"
|
|
8
8
|
|
|
9
|
-
include
|
|
9
|
+
include OpenLoam::Auditable
|
|
10
10
|
|
|
11
11
|
validates :widget_key, presence: true, uniqueness: { scope: :tenant_id }
|
|
12
12
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# A per-tenant managed lookup list — a named set of entries (e.g.
|
|
3
|
+
# "damage_severity" → minor/major/critical). Admins curate the values without a
|
|
4
|
+
# code deploy, and a OpenLoam::FieldDefinition of type "dictionary" can point a
|
|
5
|
+
# custom field at one. Tenant-scoped and audited like every OpenLoam entity; see
|
|
6
|
+
# OpenLoam::Dictionaries for the read API.
|
|
7
|
+
class Dictionary < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_dictionaries"
|
|
9
|
+
|
|
10
|
+
include OpenLoam::Auditable
|
|
11
|
+
|
|
12
|
+
has_many :entries, class_name: "OpenLoam::DictionaryEntry",
|
|
13
|
+
foreign_key: :dictionary_id, dependent: :delete_all
|
|
14
|
+
|
|
15
|
+
validates :key, presence: true, uniqueness: { scope: :tenant_id }
|
|
16
|
+
validates :name, presence: true
|
|
17
|
+
|
|
18
|
+
normalizes :key, with: ->(key) { key.to_s.strip.presence }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
module
|
|
2
|
-
# One option in a
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# One option in a OpenLoam::Dictionary: a stored `value` (the code that lands in a
|
|
3
3
|
# record) plus display metadata (label, color, icon), an ordering `position`,
|
|
4
4
|
# a `is_default` flag, and an `active` switch (a retired option stops being
|
|
5
5
|
# offered without deleting the historical values already stored). Tenant-scoped
|
|
6
6
|
# and audited.
|
|
7
|
-
class DictionaryEntry <
|
|
8
|
-
self.table_name = "
|
|
7
|
+
class DictionaryEntry < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_dictionary_entries"
|
|
9
9
|
|
|
10
|
-
include
|
|
10
|
+
include OpenLoam::Auditable
|
|
11
11
|
|
|
12
|
-
belongs_to :dictionary, class_name: "
|
|
12
|
+
belongs_to :dictionary, class_name: "OpenLoam::Dictionary"
|
|
13
13
|
|
|
14
14
|
validates :value, presence: true, uniqueness: { scope: :dictionary_id }
|
|
15
15
|
validates :label, presence: true
|
|
@@ -25,7 +25,7 @@ module Loam
|
|
|
25
25
|
# A within-request edit must not read a stale cached list (the cache also
|
|
26
26
|
# resets per request, so this only matters when a write and a read share one).
|
|
27
27
|
def clear_dictionary_cache
|
|
28
|
-
|
|
28
|
+
OpenLoam::Dictionaries.clear_cache
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One durable delivery of one event to one persistent subscriber
|
|
3
|
-
# (see
|
|
3
|
+
# (see OpenLoam::DurableEvents). The ROW is the durable record of intent: it is
|
|
4
4
|
# committed in the event's tenant at publish time, and the background job is
|
|
5
5
|
# only an accelerator. If the job is lost, the sweep re-enqueues from this row.
|
|
6
6
|
#
|
|
7
7
|
# Lifecycle: pending -> delivered (handler ran) | dead (handler removed, or
|
|
8
8
|
# MAX_ATTEMPTS exhausted). A pending row with next_attempt_at in the future is
|
|
9
9
|
# simply waiting out its backoff.
|
|
10
|
-
class EventDelivery <
|
|
11
|
-
self.table_name = "
|
|
10
|
+
class EventDelivery < OpenLoam::TenantRecord
|
|
11
|
+
self.table_name = "open_loam_event_deliveries"
|
|
12
12
|
|
|
13
13
|
STATUSES = %w[pending delivered dead].freeze
|
|
14
14
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# One captured domain event (see OpenLoam::EventLog). Where
|
|
3
|
+
# OpenLoam::EventDelivery records that a durable subscriber was told, this
|
|
4
|
+
# records that the thing happened at all.
|
|
5
|
+
#
|
|
6
|
+
# Append-only: a persisted row is readonly, so retention must delete_all
|
|
7
|
+
# rather than destroy.
|
|
8
|
+
class EventRecord < OpenLoam::TenantRecord
|
|
9
|
+
self.table_name = "open_loam_event_records"
|
|
10
|
+
|
|
11
|
+
validates :name, :occurred_at, presence: true
|
|
12
|
+
|
|
13
|
+
scope :chronological, -> { order(:occurred_at, :id) }
|
|
14
|
+
|
|
15
|
+
# ESCAPE is load-bearing: sanitize_sql_like backslash-escapes `_`, and SQLite
|
|
16
|
+
# has no default escape character, so "damage_report." would search for a
|
|
17
|
+
# literal backslash and silently match nothing.
|
|
18
|
+
scope :matching, ->(name_or_prefix) {
|
|
19
|
+
pattern = name_or_prefix.to_s
|
|
20
|
+
if pattern.end_with?(".")
|
|
21
|
+
where("name LIKE ? ESCAPE ?", "#{sanitize_sql_like(pattern)}%", "\\")
|
|
22
|
+
else
|
|
23
|
+
where(name: pattern)
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def readonly?
|
|
28
|
+
persisted?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The stored payload as a Hash regardless of adapter (Postgres jsonb returns
|
|
32
|
+
# a Hash; a text/json column may hand back a String).
|
|
33
|
+
def payload_hash
|
|
34
|
+
value = self[:payload]
|
|
35
|
+
return value if value.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
JSON.parse(value.to_s.presence || "{}")
|
|
38
|
+
rescue JSON::ParserError
|
|
39
|
+
{}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Runtime declaration of a migration-free field on a tenant-scoped entity —
|
|
3
3
|
# "entity_type Equipment gets a field called serial_number of type string,
|
|
4
4
|
# writable by managers only." Tenant-scoped like everything else: a field
|
|
5
5
|
# defined in one tenant is invisible to another's records of the same
|
|
6
6
|
# entity_type, and definitions are the ONLY way values in an entity's
|
|
7
|
-
# `custom_fields` json column get read/written (see
|
|
8
|
-
class FieldDefinition <
|
|
9
|
-
self.table_name = "
|
|
7
|
+
# `custom_fields` json column get read/written (see OpenLoam::CustomFields).
|
|
8
|
+
class FieldDefinition < OpenLoam::TenantRecord
|
|
9
|
+
self.table_name = "open_loam_field_definitions"
|
|
10
10
|
|
|
11
11
|
FIELD_TYPES = %w[string text integer decimal boolean date datetime dictionary].freeze
|
|
12
12
|
|
|
@@ -17,15 +17,15 @@ module Loam
|
|
|
17
17
|
|
|
18
18
|
# Field-level READ gate for the runtime field, mirroring writable_roles: an
|
|
19
19
|
# empty readable_roles means any member may read it; otherwise only the listed
|
|
20
|
-
# roles. Used by
|
|
21
|
-
# restricted field) and
|
|
20
|
+
# roles. Used by OpenLoam::CustomFieldIndex (so a filter can't be an oracle on a
|
|
21
|
+
# restricted field) and OpenLoam::Policy#custom_field_readable?.
|
|
22
22
|
def readable_by?(role)
|
|
23
23
|
roles = readable_roles
|
|
24
24
|
roles.blank? || (role.present? && roles.map(&:to_sym).include?(role.to_sym))
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# The `config` json holds type-specific settings. For a "dictionary" field it
|
|
28
|
-
# carries the key of the
|
|
28
|
+
# carries the key of the OpenLoam::Dictionary whose entries populate the select.
|
|
29
29
|
def dictionary_key
|
|
30
30
|
config.is_a?(Hash) ? config["dictionary"] : nil
|
|
31
31
|
end
|
|
@@ -40,7 +40,7 @@ module Loam
|
|
|
40
40
|
def dictionary_key_resolves
|
|
41
41
|
if dictionary_key.blank?
|
|
42
42
|
errors.add(:dictionary_key, "is required for a dictionary field")
|
|
43
|
-
elsif
|
|
43
|
+
elsif OpenLoam::Dictionaries.get(dictionary_key).nil?
|
|
44
44
|
errors.add(:dictionary_key, "must name an existing dictionary in this tenant")
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One received inbound webhook. Two jobs: an idempotency ledger (a unique
|
|
3
3
|
# (source_id, external_id) index turns a replayed delivery into a no-op) and an
|
|
4
4
|
# audit/payload store — the raw body is kept here so an event subscriber reads
|
|
5
5
|
# it, keeping the published event payload scalar-clean (ids only).
|
|
6
|
-
class InboundWebhookDelivery <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class InboundWebhookDelivery < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_inbound_webhook_deliveries"
|
|
8
8
|
|
|
9
|
-
belongs_to :source, class_name: "
|
|
9
|
+
belongs_to :source, class_name: "OpenLoam::InboundWebhookSource", inverse_of: :deliveries
|
|
10
10
|
|
|
11
11
|
validates :external_id, presence: true
|
|
12
12
|
# (source_id, external_id) uniqueness is enforced by a DB index — the ledger
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A registered external system allowed to POST webhooks INTO this tenant — the
|
|
3
|
-
# inbound sibling of
|
|
3
|
+
# inbound sibling of OpenLoam::WebhookEndpoint (which delivers events OUT). The
|
|
4
4
|
# `token` is the unguessable URL id (`/webhooks/:token`) that identifies the
|
|
5
5
|
# source; the `secret` is the HMAC key that AUTHENTICATES each call. Identity is
|
|
6
6
|
# not authority: rotating either is supported from the admin.
|
|
7
7
|
#
|
|
8
|
-
# On a verified delivery,
|
|
9
|
-
# with a reference to the stored
|
|
10
|
-
# subscribers (
|
|
11
|
-
class InboundWebhookSource <
|
|
12
|
-
self.table_name = "
|
|
8
|
+
# On a verified delivery, OpenLoam publishes `event_name` onto the domain event bus
|
|
9
|
+
# with a reference to the stored OpenLoam::InboundWebhookDelivery, so durable
|
|
10
|
+
# subscribers (OpenLoam::DurableEvents) react — the payload itself lives on the row.
|
|
11
|
+
class InboundWebhookSource < OpenLoam::TenantRecord
|
|
12
|
+
self.table_name = "open_loam_inbound_webhook_sources"
|
|
13
13
|
|
|
14
|
-
DEFAULT_SIGNATURE_HEADER = "X-
|
|
14
|
+
DEFAULT_SIGNATURE_HEADER = "X-OpenLoam-Signature".freeze
|
|
15
15
|
DEFAULT_TOLERANCE = 300 # seconds
|
|
16
16
|
|
|
17
|
-
has_many :deliveries, class_name: "
|
|
17
|
+
has_many :deliveries, class_name: "OpenLoam::InboundWebhookDelivery",
|
|
18
18
|
foreign_key: :source_id, dependent: :destroy, inverse_of: :source
|
|
19
19
|
|
|
20
20
|
validates :name, presence: true
|
|
21
21
|
validates :token, presence: true, uniqueness: true
|
|
22
22
|
validates :secret, presence: true
|
|
23
23
|
validates :event_name, presence: true,
|
|
24
|
-
format: { with:
|
|
24
|
+
format: { with: OpenLoam::Events::NAME_FORMAT, message: "must follow domain.thing.happened" }
|
|
25
25
|
|
|
26
26
|
scope :active, -> { where(active: true) }
|
|
27
27
|
|
|
@@ -32,9 +32,9 @@ module Loam
|
|
|
32
32
|
self.active = true if active.nil?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# THE blessed cross-tenant lookup (see
|
|
35
|
+
# THE blessed cross-tenant lookup (see OpenLoam::ApiToken.authenticate): a public
|
|
36
36
|
# inbound request arrives with no tenant context — the token in the URL is how
|
|
37
|
-
# it discovers its tenant. Establishes
|
|
37
|
+
# it discovers its tenant. Establishes OpenLoam::Current.tenant (never an actor:
|
|
38
38
|
# the sender is a machine, not a user) and returns the ACTIVE source, or nil.
|
|
39
39
|
def self.resolve(raw_token)
|
|
40
40
|
return nil if raw_token.blank?
|
|
@@ -42,7 +42,7 @@ module Loam
|
|
|
42
42
|
source = unscoped.find_by(token: raw_token)
|
|
43
43
|
return nil unless source&.active?
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
OpenLoam::Current.tenant = source.tenant
|
|
46
46
|
source
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Connects an actor (the host app's User) to a tenant with a role. Roles are
|
|
3
3
|
# plain strings ("manager", "employee", ...) — policies interpret them.
|
|
4
4
|
# Tenant-scoped like everything else: asking for someone's role always means
|
|
5
5
|
# "their role in the CURRENT tenant".
|
|
6
|
-
class Membership <
|
|
7
|
-
self.table_name = "
|
|
6
|
+
class Membership < OpenLoam::TenantRecord
|
|
7
|
+
self.table_name = "open_loam_memberships"
|
|
8
8
|
|
|
9
9
|
belongs_to :user
|
|
10
10
|
|
|
11
11
|
validates :role, presence: true
|
|
12
12
|
validates :user_id, uniqueness: { scope: :tenant_id }
|
|
13
13
|
|
|
14
|
-
# The other blessed cross-tenant lookup (see
|
|
14
|
+
# The other blessed cross-tenant lookup (see OpenLoam::ApiToken.authenticate).
|
|
15
15
|
# "Which tenants may this person enter?" is asked at login, before any
|
|
16
16
|
# tenant is chosen, so it cannot be answered from inside one — which is why
|
|
17
17
|
# it lives in the gem rather than in host app code, where reaching across
|
|
18
18
|
# tenants is a guardrail failure.
|
|
19
19
|
#
|
|
20
|
-
# Returns a
|
|
20
|
+
# Returns a OpenLoam::Tenant relation, so callers can order/filter it further.
|
|
21
21
|
def self.tenants_for(user)
|
|
22
22
|
user_id = user.respond_to?(:id) ? user.id : user
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
OpenLoam::Tenant.where(id: unscoped.where(user_id: user_id).select(:tenant_id)).order(:name)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
require "bcrypt"
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module OpenLoam
|
|
4
4
|
# A user's multi-factor credential: a TOTP secret plus single-use recovery
|
|
5
5
|
# codes. Deliberately NOT tenant-scoped — MFA belongs to the PERSON, who spans
|
|
6
6
|
# tenants, and the second-factor challenge runs at login BEFORE any tenant is
|
|
7
|
-
# chosen. So the secret is encrypted under a USER-scoped key (
|
|
7
|
+
# chosen. So the secret is encrypted under a USER-scoped key (OpenLoam::Encryptable
|
|
8
8
|
# `scope:`), which decrypts in any tenant and with no tenant at all — the whole
|
|
9
9
|
# reason a per-tenant key would be a lockout bug here.
|
|
10
10
|
class MfaCredential < ApplicationRecord
|
|
11
|
-
include
|
|
12
|
-
self.table_name = "
|
|
11
|
+
include OpenLoam::GeneratedKey
|
|
12
|
+
self.table_name = "open_loam_mfa_credentials"
|
|
13
13
|
|
|
14
14
|
RECOVERY_CODE_COUNT = 10
|
|
15
15
|
|
|
16
16
|
belongs_to :user
|
|
17
17
|
|
|
18
|
-
include
|
|
18
|
+
include OpenLoam::Encryptable
|
|
19
19
|
encrypts :totp_secret, scope: ->(credential) { "user/#{credential.user_id}" }
|
|
20
20
|
|
|
21
21
|
# Recovery codes are stored HASHED (BCrypt), never in the clear: [{ "digest",
|
|
@@ -46,7 +46,7 @@ module Loam
|
|
|
46
46
|
# if the code is wrong. The old secret stays valid until this succeeds, so a
|
|
47
47
|
# half-finished re-enrollment never downgrades an active credential.
|
|
48
48
|
def activate_with!(candidate_secret, code)
|
|
49
|
-
step =
|
|
49
|
+
step = OpenLoam::Totp.matching_step(candidate_secret, code)
|
|
50
50
|
return nil unless step
|
|
51
51
|
|
|
52
52
|
self.totp_secret = candidate_secret
|
|
@@ -66,7 +66,7 @@ module Loam
|
|
|
66
66
|
return false unless activated?
|
|
67
67
|
|
|
68
68
|
with_lock do
|
|
69
|
-
step =
|
|
69
|
+
step = OpenLoam::Totp.matching_step(totp_secret, code)
|
|
70
70
|
if step && (last_totp_step.nil? || step > last_totp_step)
|
|
71
71
|
update!(last_totp_step: step)
|
|
72
72
|
true
|
|
@@ -100,7 +100,7 @@ module Loam
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def provisioning_uri(issuer:)
|
|
103
|
-
|
|
103
|
+
OpenLoam::Totp.provisioning_uri(totp_secret, account: user.email, issuer: issuer)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
private
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# One in-app message for one recipient (`user`) in one tenant. The same
|
|
3
3
|
# person in two tenants has two separate inboxes — like everything else in
|
|
4
|
-
#
|
|
4
|
+
# OpenLoam, "your notifications" always means "in the current tenant".
|
|
5
5
|
#
|
|
6
|
-
# Plumbing, not business data: not audited, the same way
|
|
7
|
-
# isn't. It IS evented, though — creating one publishes "
|
|
8
|
-
# (carrying the recipient's user_id) so
|
|
6
|
+
# Plumbing, not business data: not audited, the same way OpenLoam::AuditRecord
|
|
7
|
+
# isn't. It IS evented, though — creating one publishes "open_loam.notification.created"
|
|
8
|
+
# (carrying the recipient's user_id) so OpenLoam::EventStream can push it to that
|
|
9
9
|
# user's browser and the bell updates live. Created through
|
|
10
|
-
#
|
|
11
|
-
class Notification <
|
|
12
|
-
self.table_name = "
|
|
10
|
+
# OpenLoam::Notifications.notify, read through the admin bell.
|
|
11
|
+
class Notification < OpenLoam::TenantRecord
|
|
12
|
+
self.table_name = "open_loam_notifications"
|
|
13
13
|
|
|
14
14
|
belongs_to :user
|
|
15
15
|
|
|
@@ -18,9 +18,9 @@ module Loam
|
|
|
18
18
|
scope :unread, -> { where(read_at: nil) }
|
|
19
19
|
|
|
20
20
|
# The one signal the real-time bell listens for. Only id + recipient ride the
|
|
21
|
-
# event (
|
|
21
|
+
# event (OpenLoam::Events also stamps tenant_id); no message content is broadcast.
|
|
22
22
|
after_create_commit do
|
|
23
|
-
|
|
23
|
+
OpenLoam::Events.publish("open_loam.notification.created", id: id, user_id: user_id)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# What the notification is about, if anything (source_type/source_id) —
|