source_monitor 0.13.1 → 0.14.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/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +1 -0
- data/.claude/skills/sm-configure/SKILL.md +8 -1
- data/.claude/skills/sm-configure/reference/configuration-reference.md +11 -0
- data/.claude/skills/sm-host-setup/SKILL.md +13 -3
- data/.claude/skills/sm-host-setup/reference/initializer-template.md +11 -0
- data/.claude/skills/sm-host-setup/reference/setup-checklist.md +9 -1
- data/.claude/skills/sm-upgrade/reference/version-history.md +12 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/controllers/source_monitor/application_controller.rb +73 -14
- data/app/views/layouts/source_monitor/application.html.erb +6 -0
- data/docs/configuration.md +18 -1
- data/docs/deployment.md +1 -1
- data/docs/goals/engine-hardening/.goalbuddy-board/app.js +543 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/goalbuddy-mark.png +0 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/index.html +111 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/styles.css +991 -0
- data/docs/goals/engine-hardening/goal.md +97 -0
- data/docs/goals/engine-hardening/notes/T001-spec-validation.md +37 -0
- data/docs/goals/engine-hardening/state.yaml +324 -0
- data/docs/setup.md +3 -3
- data/docs/upgrade.md +27 -0
- data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +10 -0
- data/lib/source_monitor/configuration/authentication_settings.rb +5 -1
- data/lib/source_monitor/security/authentication.rb +10 -0
- data/lib/source_monitor/version.rb +1 -1
- data/source_monitor.gemspec +7 -2
- metadata +8 -65
- data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +0 -15
- data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +0 -34
- data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +0 -49
- data/.claude/agents/rails-concern.md +0 -464
- data/.claude/agents/rails-controller.md +0 -424
- data/.claude/agents/rails-hotwire.md +0 -446
- data/.claude/agents/rails-implement.md +0 -374
- data/.claude/agents/rails-job.md +0 -334
- data/.claude/agents/rails-lint.md +0 -294
- data/.claude/agents/rails-mailer.md +0 -371
- data/.claude/agents/rails-migration.md +0 -449
- data/.claude/agents/rails-model.md +0 -420
- data/.claude/agents/rails-policy.md +0 -443
- data/.claude/agents/rails-presenter.md +0 -427
- data/.claude/agents/rails-query.md +0 -412
- data/.claude/agents/rails-review.md +0 -490
- data/.claude/agents/rails-service.md +0 -458
- data/.claude/agents/rails-state-records.md +0 -465
- data/.claude/agents/rails-tdd.md +0 -314
- data/.claude/agents/rails-test.md +0 -441
- data/.claude/agents/rails-view-component.md +0 -418
- data/.claude/commands/rails-audit.md +0 -77
- data/.claude/commands/release.md +0 -366
- data/.claude/hooks/block-secrets.sh +0 -52
- data/.claude/settings.json +0 -85
- data/.claude/skills/action-cable-patterns/SKILL.md +0 -296
- data/.claude/skills/action-mailer-patterns/SKILL.md +0 -295
- data/.claude/skills/active-storage-setup/SKILL.md +0 -311
- data/.claude/skills/api-versioning/SKILL.md +0 -294
- data/.claude/skills/authentication-flow/SKILL.md +0 -335
- data/.claude/skills/authentication-flow/reference/current.md +0 -248
- data/.claude/skills/authentication-flow/reference/passwordless.md +0 -253
- data/.claude/skills/authentication-flow/reference/sessions.md +0 -201
- data/.claude/skills/authorization-pundit/SKILL.md +0 -462
- data/.claude/skills/caching-strategies/SKILL.md +0 -350
- data/.claude/skills/database-migrations/SKILL.md +0 -354
- data/.claude/skills/form-object-patterns/SKILL.md +0 -399
- data/.claude/skills/hotwire-patterns/SKILL.md +0 -247
- data/.claude/skills/hotwire-patterns/reference/stimulus.md +0 -307
- data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +0 -112
- data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +0 -158
- data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +0 -218
- data/.claude/skills/i18n-patterns/SKILL.md +0 -320
- data/.claude/skills/install/SKILL.md +0 -367
- data/.claude/skills/performance-optimization/SKILL.md +0 -311
- data/.claude/skills/rails-architecture/SKILL.md +0 -259
- data/.claude/skills/rails-architecture/reference/error-handling.md +0 -333
- data/.claude/skills/rails-architecture/reference/event-tracking.md +0 -142
- data/.claude/skills/rails-architecture/reference/layer-interactions.md +0 -417
- data/.claude/skills/rails-architecture/reference/multi-tenancy.md +0 -152
- data/.claude/skills/rails-architecture/reference/query-patterns.md +0 -342
- data/.claude/skills/rails-architecture/reference/service-patterns.md +0 -286
- data/.claude/skills/rails-architecture/reference/state-records.md +0 -250
- data/.claude/skills/rails-architecture/reference/testing-strategy.md +0 -326
- data/.claude/skills/rails-concern/SKILL.md +0 -399
- data/.claude/skills/rails-controller/SKILL.md +0 -336
- data/.claude/skills/rails-model-generator/SKILL.md +0 -321
- data/.claude/skills/rails-model-generator/reference/validations.md +0 -298
- data/.claude/skills/rails-presenter/SKILL.md +0 -274
- data/.claude/skills/rails-query-object/SKILL.md +0 -289
- data/.claude/skills/rails-service-object/SKILL.md +0 -349
- data/.claude/skills/solid-queue-setup/SKILL.md +0 -307
- data/.claude/skills/tdd-cycle/SKILL.md +0 -359
- data/.claude/skills/viewcomponent-patterns/SKILL.md +0 -333
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35e62d7d750d8d9fe1cff82806de1846d27fe505fad819551e48474f864b1499
|
|
4
|
+
data.tar.gz: 2f6889306b930aa78caec52cc92c7c6919ba379955a6d0484cc8bf44f52dcf6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ca0f0c0a9d6e1c2557c501a2a51bc9dee52e486b78cb78f1384232f5af704c209026e514f1dea2ea94c6962d502856add17c57b4afc79c462ed8fa39da8d236
|
|
7
|
+
data.tar.gz: ed49ec4ce672c5850b2bc3f3a6d73e31f1d08d6c8f7b1a779258db69ac9cdb48d1aa6e8478784a0f89bf161ec81bad31b79485455373a702240a1580ad31f6dc
|
|
@@ -157,6 +157,7 @@ Has `reset!` method. The `adapter=` setter validates against `VALID_ADAPTERS`.
|
|
|
157
157
|
| `authorize_handler` | Handler/nil | `nil` | Authorization handler |
|
|
158
158
|
| `current_user_method` | Symbol/nil | `nil` | Method name for current user |
|
|
159
159
|
| `user_signed_in_method` | Symbol/nil | `nil` | Method name for signed-in check |
|
|
160
|
+
| `open_access` | Boolean | `false` | Opt out of the fail-closed access guard (demo/non-production only); ignored when a handler is configured |
|
|
160
161
|
|
|
161
162
|
Has `reset!` method.
|
|
162
163
|
|
|
@@ -66,10 +66,17 @@ config.http.retry_max = 3
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### Authentication (Devise)
|
|
69
|
+
SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
|
|
70
|
+
handler configured, every engine route returns `403 Forbidden`. Configure a handler
|
|
71
|
+
to protect the dashboard (the handler decides access and bypasses the fail-closed guard):
|
|
69
72
|
```ruby
|
|
70
73
|
config.authentication.authenticate_with :authenticate_user!
|
|
71
74
|
config.authentication.authorize_with ->(c) { c.current_user&.admin? }
|
|
72
75
|
```
|
|
76
|
+
For local demos/sandboxes only, opt out of the fail-closed guard (non-production):
|
|
77
|
+
```ruby
|
|
78
|
+
config.authentication.open_access = true # default: false -- demo/non-production only
|
|
79
|
+
```
|
|
73
80
|
|
|
74
81
|
### Image Downloads (Active Storage)
|
|
75
82
|
```ruby
|
|
@@ -167,7 +174,7 @@ end
|
|
|
167
174
|
- [ ] Initializer exists at `config/initializers/source_monitor.rb`
|
|
168
175
|
- [ ] Queue names match `config/queue.yml` (or `config/solid_queue.yml`) entries
|
|
169
176
|
- [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by install generator)
|
|
170
|
-
- [ ] Authentication
|
|
177
|
+
- [ ] Authentication decision made: configure `authenticate_with`/`authorize_with` (fail-closed default) OR set `config.authentication.open_access = true` for demos only
|
|
171
178
|
- [ ] HTTP timeouts appropriate for target feeds
|
|
172
179
|
- [ ] Retention policy set for production
|
|
173
180
|
- [ ] Workers restarted after configuration changes
|
|
@@ -283,10 +283,17 @@ config.realtime.solid_cable.connects_to = { database: { writing: :cable } }
|
|
|
283
283
|
|
|
284
284
|
Class: `SourceMonitor::Configuration::AuthenticationSettings`
|
|
285
285
|
|
|
286
|
+
**Fail-closed by default.** When no `authenticate_with`/`authorize_with` handler is
|
|
287
|
+
configured and `open_access` is `false`, the engine denies every route with
|
|
288
|
+
`403 Forbidden` (see `SourceMonitor::Security::Authentication.access_denied_by_default?`).
|
|
289
|
+
Configuring either handler makes the handler authoritative and bypasses the
|
|
290
|
+
fail-closed guard.
|
|
291
|
+
|
|
286
292
|
| Setting | Type | Default | Description |
|
|
287
293
|
|---|---|---|---|
|
|
288
294
|
| `current_user_method` | Symbol/nil | `nil` | Controller method to get current user |
|
|
289
295
|
| `user_signed_in_method` | Symbol/nil | `nil` | Controller method to check sign-in status |
|
|
296
|
+
| `open_access` | Boolean | `false` | Opt out of the fail-closed guard so engine routes are public. **Demo/non-production only.** Ignored when a handler is configured. |
|
|
290
297
|
|
|
291
298
|
### Methods
|
|
292
299
|
|
|
@@ -312,6 +319,10 @@ config.authentication.authorize_with ->(controller) {
|
|
|
312
319
|
config.authentication.authorize_with do
|
|
313
320
|
redirect_to root_path unless current_user&.admin?
|
|
314
321
|
end
|
|
322
|
+
|
|
323
|
+
# Open access (demo/non-production only) -- disables the fail-closed guard.
|
|
324
|
+
# Leave commented/false in production. A configured handler always wins.
|
|
325
|
+
config.authentication.open_access = true # default: false
|
|
315
326
|
```
|
|
316
327
|
|
|
317
328
|
---
|
|
@@ -119,7 +119,7 @@ After installation, review and customize the initializer. Key areas:
|
|
|
119
119
|
| Section | Purpose |
|
|
120
120
|
|---|---|
|
|
121
121
|
| Queue settings | Queue names, concurrency, namespace |
|
|
122
|
-
| Authentication | `authenticate_with
|
|
122
|
+
| Authentication | **Fail-closed by default** -- configure `authenticate_with`/`authorize_with` hooks, or set `open_access = true` for demos only |
|
|
123
123
|
| HTTP client | Timeouts, proxy, retries |
|
|
124
124
|
| Fetching | Adaptive scheduling intervals and factors |
|
|
125
125
|
| Health | Auto-pause/resume thresholds |
|
|
@@ -152,7 +152,17 @@ SOURCE_MONITOR_SETUP_TELEMETRY=true bin/source_monitor verify
|
|
|
152
152
|
# Logs to log/source_monitor_setup.log
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
##
|
|
155
|
+
## Authentication (Fail-Closed by Default)
|
|
156
|
+
|
|
157
|
+
SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
|
|
158
|
+
handler configured, every engine route (including create/update/delete/enqueue actions)
|
|
159
|
+
returns `403 Forbidden`. You must make one of two choices during setup:
|
|
160
|
+
|
|
161
|
+
1. **Configure a handler** (recommended) -- the handler decides access and bypasses the
|
|
162
|
+
fail-closed guard.
|
|
163
|
+
2. **Set `config.authentication.open_access = true`** -- opts out of the guard so routes
|
|
164
|
+
are public. **Demo/non-production only.** A configured handler always takes precedence
|
|
165
|
+
over this flag.
|
|
156
166
|
|
|
157
167
|
When Devise is detected, the guided installer offers to wire authentication hooks:
|
|
158
168
|
|
|
@@ -238,6 +248,6 @@ end
|
|
|
238
248
|
- [x] `Procfile.dev` includes `jobs:` entry for Solid Queue (handled by generator)
|
|
239
249
|
- [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by generator)
|
|
240
250
|
- [ ] Solid Queue workers started
|
|
241
|
-
- [ ] Authentication
|
|
251
|
+
- [ ] Authentication decision made (engine is fail-closed by default): handler configured via `authenticate_with`/`authorize_with`, OR `config.authentication.open_access = true` for demos only
|
|
242
252
|
- [ ] `bin/source_monitor verify` passes
|
|
243
253
|
- [ ] Dashboard accessible at mount path
|
|
@@ -57,7 +57,12 @@ SourceMonitor.configure do |config|
|
|
|
57
57
|
# ===========================================================================
|
|
58
58
|
# Authentication
|
|
59
59
|
# ===========================================================================
|
|
60
|
+
# SECURITY: SourceMonitor is FAIL-CLOSED by default. If you do not configure
|
|
61
|
+
# an authentication or authorization handler below, every engine route
|
|
62
|
+
# (including create/update/delete/enqueue actions) returns 403 Forbidden.
|
|
60
63
|
# Handlers: Symbol (invoked on controller) or callable (receives controller).
|
|
64
|
+
# As soon as a handler is configured it decides access and the fail-closed
|
|
65
|
+
# guard no longer applies.
|
|
61
66
|
|
|
62
67
|
# Authenticate before accessing any SourceMonitor page.
|
|
63
68
|
# config.authentication.authenticate_with :authenticate_user!
|
|
@@ -71,6 +76,12 @@ SourceMonitor.configure do |config|
|
|
|
71
76
|
# config.authentication.current_user_method = :current_user
|
|
72
77
|
# config.authentication.user_signed_in_method = :user_signed_in?
|
|
73
78
|
|
|
79
|
+
# Explicit opt-in for open/unauthenticated access. Leave commented out in
|
|
80
|
+
# production. Only enable for local demos or sandboxes where engine routes
|
|
81
|
+
# are deliberately public.
|
|
82
|
+
# WARNING: non-production / demo only -- this disables the fail-closed guard.
|
|
83
|
+
# config.authentication.open_access = true
|
|
84
|
+
|
|
74
85
|
# ===========================================================================
|
|
75
86
|
# HTTP Client
|
|
76
87
|
# ===========================================================================
|
|
@@ -57,6 +57,10 @@ bin/rails db:migrate
|
|
|
57
57
|
|
|
58
58
|
## Phase 5: Configure Authentication
|
|
59
59
|
|
|
60
|
+
**Fail-closed by default.** With no handler configured, every engine route returns
|
|
61
|
+
`403 Forbidden`. Make one of two choices: configure a handler (recommended) OR opt into
|
|
62
|
+
open access for demos only.
|
|
63
|
+
|
|
60
64
|
Edit `config/initializers/source_monitor.rb`:
|
|
61
65
|
|
|
62
66
|
```ruby
|
|
@@ -68,10 +72,14 @@ SourceMonitor.configure do |config|
|
|
|
68
72
|
}
|
|
69
73
|
config.authentication.current_user_method = :current_user
|
|
70
74
|
config.authentication.user_signed_in_method = :user_signed_in?
|
|
75
|
+
|
|
76
|
+
# OR, for local demos/sandboxes only (non-production) -- opt out of the
|
|
77
|
+
# fail-closed guard so routes are public. A configured handler always wins.
|
|
78
|
+
# config.authentication.open_access = true
|
|
71
79
|
end
|
|
72
80
|
```
|
|
73
81
|
|
|
74
|
-
- [ ] Authentication
|
|
82
|
+
- [ ] Authentication decision made: handler configured (fail-closed default) OR `open_access = true` set for demos only
|
|
75
83
|
- [ ] Authorization hook configured (if needed)
|
|
76
84
|
|
|
77
85
|
## Phase 6: Configure Workers
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Version-specific migration notes for each major/minor version transition. Agents should reference this file when guiding users through multi-version upgrades.
|
|
4
4
|
|
|
5
|
+
## 0.13.1 to 0.14.0
|
|
6
|
+
|
|
7
|
+
**Key changes:**
|
|
8
|
+
- **BREAKING — Fail-closed by default** (#129): engine routes return `403 Forbidden` unless `config.authentication.authenticate_with` or `config.authentication.authorize_with` is configured, OR `config.authentication.open_access = true` is explicitly set (non-production only).
|
|
9
|
+
- **Request flashes response-local** (#130): flash toasts no longer broadcast to the global `source_monitor_notifications` ActionCable stream; rendered inline on HTML loads and appended on turbo_stream responses instead. Background operational toasts stay global but context-free.
|
|
10
|
+
- **Gem package excludes `.claude` internals** (#131): only `.claude/skills/sm-*` shipped; agents, hooks, `settings.json`, commands, and non-`sm-*` skills excluded.
|
|
11
|
+
|
|
12
|
+
**Action items:**
|
|
13
|
+
1. `bundle update source_monitor`
|
|
14
|
+
2. **Action required (auth):** in `config/initializers/source_monitor.rb`, ensure at least one of `authenticate_with`, `authorize_with`, or `open_access = true` (demo/sandbox only). Without one, all engine routes return `403`.
|
|
15
|
+
3. No migrations or other breaking API changes; flash/packaging changes apply transparently.
|
|
16
|
+
|
|
5
17
|
## 0.12.4 to 0.13.0
|
|
6
18
|
|
|
7
19
|
**Key changes:**
|
data/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,21 @@ All notable changes to this project are documented below. The format follows [Ke
|
|
|
13
13
|
|
|
14
14
|
## [Unreleased]
|
|
15
15
|
|
|
16
|
+
- No unreleased changes yet.
|
|
17
|
+
|
|
18
|
+
## [0.14.0] - 2026-05-28
|
|
19
|
+
|
|
20
|
+
### Security (BREAKING)
|
|
21
|
+
- **Fail-closed engine access by default** (#129). When the host app has configured no authentication/authorization handler, every mounted SourceMonitor route now returns `403 Forbidden` instead of being publicly accessible. Previously `authenticate!`/`authorize!` were silent no-ops when unconfigured, so create/update/delete/enqueue routes were public by omission.
|
|
22
|
+
- **Migration:** Configure a handler — `config.authentication.authenticate_with` and/or `config.authentication.authorize_with` — to gate the engine with your host auth stack. Configured handlers are honored exactly as before.
|
|
23
|
+
- **Demo opt-in:** To intentionally keep routes public (local demos/sandboxes only), set `config.authentication.open_access = true` (default `false`). Do not enable this in production.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Request flashes no longer leak across users** (#130). Request flash toasts were broadcast to a global ActionCable stream (`source_monitor_notifications`) that every connected browser tab subscribed to, so one user's flash could appear on every user's screen. Flashes are now delivered response-local — rendered inline on full-page HTML loads and appended to the turbo_stream response otherwise — reaching only the requesting tab. Background operational toasts (fetch/scrape completion) remain global but carry no per-user request context.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **Gem package no longer ships `.claude` internals** (#131). The packaged gem now excludes `.claude` agents, hooks, agent-memory, `settings.json`, commands, and non-`sm-*` skills; only the intended `.claude/skills/sm-*` SourceMonitor skills are shipped. Packaging is also now CWD-independent so the skill files are included deterministically.
|
|
30
|
+
|
|
16
31
|
## [0.13.1] - 2026-05-28
|
|
17
32
|
|
|
18
33
|
### Fixed
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -9,8 +9,8 @@ SourceMonitor is a production-ready Rails 8 mountable engine for ingesting, norm
|
|
|
9
9
|
In your host Rails app:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bundle add source_monitor --version "~> 0.
|
|
13
|
-
# or add `gem "source_monitor", "~> 0.
|
|
12
|
+
bundle add source_monitor --version "~> 0.14.0"
|
|
13
|
+
# or add `gem "source_monitor", "~> 0.14.0"` manually, then run:
|
|
14
14
|
bundle install
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -46,7 +46,7 @@ This exposes `bin/source_monitor` (via Bundler binstubs) so you can run the guid
|
|
|
46
46
|
Before running any SourceMonitor commands inside your host app, add the gem and install dependencies:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
bundle add source_monitor --version "~> 0.
|
|
49
|
+
bundle add source_monitor --version "~> 0.14.0"
|
|
50
50
|
# or edit your Gemfile, then run
|
|
51
51
|
bundle install
|
|
52
52
|
```
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.0
|
|
@@ -4,11 +4,13 @@ module SourceMonitor
|
|
|
4
4
|
class ApplicationController < ActionController::Base
|
|
5
5
|
protect_from_forgery with: :exception, prepend: true
|
|
6
6
|
|
|
7
|
+
before_action :enforce_source_monitor_access_default
|
|
7
8
|
before_action :authenticate_source_monitor_user
|
|
8
9
|
before_action :authorize_source_monitor_access
|
|
9
10
|
|
|
10
|
-
helper_method :source_monitor_current_user, :source_monitor_user_signed_in
|
|
11
|
-
|
|
11
|
+
helper_method :source_monitor_current_user, :source_monitor_user_signed_in?,
|
|
12
|
+
:source_monitor_flash_toasts
|
|
13
|
+
after_action :append_flash_toasts_to_turbo_stream
|
|
12
14
|
|
|
13
15
|
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
|
14
16
|
|
|
@@ -40,6 +42,30 @@ module SourceMonitor
|
|
|
40
42
|
TOAST_DURATION_DEFAULT = 5000
|
|
41
43
|
TOAST_DURATION_ERROR = 6000
|
|
42
44
|
|
|
45
|
+
# Fail-closed guard: when the host app has configured no authentication or
|
|
46
|
+
# authorization handler and has not explicitly opted into open access, deny
|
|
47
|
+
# all engine routes. Configured handlers short-circuit this and decide for
|
|
48
|
+
# themselves (see SourceMonitor::Security::Authentication).
|
|
49
|
+
def enforce_source_monitor_access_default
|
|
50
|
+
return unless SourceMonitor::Security::Authentication.access_denied_by_default?(self)
|
|
51
|
+
|
|
52
|
+
source_monitor_access_forbidden
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def source_monitor_access_forbidden
|
|
56
|
+
message = "SourceMonitor access is not configured"
|
|
57
|
+
respond_to do |format|
|
|
58
|
+
format.html { render plain: message, status: :forbidden }
|
|
59
|
+
format.turbo_stream do
|
|
60
|
+
render turbo_stream: turbo_stream.append("flash",
|
|
61
|
+
partial: "source_monitor/shared/toast",
|
|
62
|
+
locals: { message: message, level: :error }),
|
|
63
|
+
status: :forbidden
|
|
64
|
+
end
|
|
65
|
+
format.json { render json: { error: message }, status: :forbidden }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
43
69
|
def authenticate_source_monitor_user
|
|
44
70
|
SourceMonitor::Security::Authentication.authenticate!(self)
|
|
45
71
|
end
|
|
@@ -60,22 +86,55 @@ module SourceMonitor
|
|
|
60
86
|
level.to_sym == :error ? TOAST_DURATION_ERROR : TOAST_DURATION_DEFAULT
|
|
61
87
|
end
|
|
62
88
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
# Request flashes are delivered response-local, never broadcast over the
|
|
90
|
+
# global ActionCable notification stream. On full-page HTML loads the layout
|
|
91
|
+
# renders the toasts inline (see +source_monitor_flash_toasts+); on
|
|
92
|
+
# turbo_stream responses we append the toasts to the response body so they
|
|
93
|
+
# reach only the requesting tab.
|
|
94
|
+
def source_monitor_flash_toasts
|
|
95
|
+
payloads = flash_toast_payloads
|
|
96
|
+
# Reading via the layout consumes the flash for this request so it does
|
|
97
|
+
# not linger into the next one.
|
|
98
|
+
flash.discard unless payloads.empty?
|
|
99
|
+
payloads
|
|
100
|
+
end
|
|
66
101
|
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
def append_flash_toasts_to_turbo_stream
|
|
103
|
+
return unless request.format.turbo_stream?
|
|
104
|
+
return if response.redirect?
|
|
105
|
+
|
|
106
|
+
payloads = flash_toast_payloads
|
|
107
|
+
return if payloads.empty?
|
|
108
|
+
|
|
109
|
+
streams = payloads.map do |payload|
|
|
110
|
+
view_context.turbo_stream.append(
|
|
111
|
+
"source_monitor_notifications",
|
|
112
|
+
partial: "source_monitor/shared/toast",
|
|
113
|
+
locals: {
|
|
114
|
+
message: payload[:message],
|
|
115
|
+
level: payload[:level],
|
|
116
|
+
delay_ms: toast_delay_for(payload[:level])
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
end
|
|
69
120
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
response.body = "#{response.body}#{streams.join}"
|
|
122
|
+
flash.discard
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Builds the list of toast payloads ({ message:, level: }) for the current
|
|
126
|
+
# request's flash. Used by both the inline layout renderer and the
|
|
127
|
+
# turbo_stream after_action so request flashes stay response-local.
|
|
128
|
+
def flash_toast_payloads
|
|
129
|
+
return [] if flash.empty?
|
|
130
|
+
|
|
131
|
+
flash.flat_map do |key, message|
|
|
132
|
+
Array(message).filter_map do |msg|
|
|
133
|
+
next if msg.blank?
|
|
134
|
+
|
|
135
|
+
{ message: msg, level: FLASH_LEVELS[key.to_sym] || :info }
|
|
75
136
|
end
|
|
76
137
|
end
|
|
77
|
-
ensure
|
|
78
|
-
flash.discard
|
|
79
138
|
end
|
|
80
139
|
end
|
|
81
140
|
end
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
<div id="source_monitor_notifications"
|
|
20
20
|
data-notification-container-target="list"
|
|
21
21
|
class="flex w-full flex-col gap-3">
|
|
22
|
+
<%# Request flashes render response-local here (never broadcast to all tabs). %>
|
|
23
|
+
<% source_monitor_flash_toasts.each do |toast| %>
|
|
24
|
+
<%= render "source_monitor/shared/toast",
|
|
25
|
+
message: toast[:message],
|
|
26
|
+
level: toast[:level] %>
|
|
27
|
+
<% end %>
|
|
22
28
|
</div>
|
|
23
29
|
<div data-notification-container-target="badge"
|
|
24
30
|
class="pointer-events-auto hidden">
|
data/docs/configuration.md
CHANGED
|
@@ -137,6 +137,11 @@ Call `config.realtime.action_cable_config` if you need a full hash for environme
|
|
|
137
137
|
|
|
138
138
|
## Authentication Helpers
|
|
139
139
|
|
|
140
|
+
**Fail-closed by default.** SourceMonitor denies access to every engine route
|
|
141
|
+
(returning `403 Forbidden`) unless you configure an authentication or
|
|
142
|
+
authorization handler. This prevents the engine's create/update/delete/enqueue
|
|
143
|
+
routes from being public by accident.
|
|
144
|
+
|
|
140
145
|
Protect the dashboard with host-specific auth in one place:
|
|
141
146
|
|
|
142
147
|
```ruby
|
|
@@ -148,7 +153,19 @@ config.authentication.current_user_method = :current_user
|
|
|
148
153
|
config.authentication.user_signed_in_method = :user_signed_in?
|
|
149
154
|
```
|
|
150
155
|
|
|
151
|
-
Handlers can be symbols (invoked on the controller) or callables. Return `false` or raise to deny access.
|
|
156
|
+
Handlers can be symbols (invoked on the controller) or callables. Return `false` or raise to deny access. As soon as either handler is configured, the handler decides access and the fail-closed guard no longer applies.
|
|
157
|
+
|
|
158
|
+
### Open access opt-in (non-production)
|
|
159
|
+
|
|
160
|
+
For local demos or sandboxes where engine routes are deliberately public, you
|
|
161
|
+
can explicitly opt out of the fail-closed guard:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
config.authentication.open_access = true # default: false
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This is intended for non-production/demo environments only. Configuring a
|
|
168
|
+
handler always takes precedence over this flag.
|
|
152
169
|
|
|
153
170
|
## Health Model
|
|
154
171
|
|
data/docs/deployment.md
CHANGED
|
@@ -33,7 +33,7 @@ SourceMonitor assumes the standard Rails 8 process split:
|
|
|
33
33
|
|
|
34
34
|
## Security & Authentication
|
|
35
35
|
|
|
36
|
-
- Lock down the
|
|
36
|
+
- SourceMonitor is **fail-closed by default**: without a configured handler every engine route returns `403 Forbidden`. Lock down the routes with authentication hooks (`config.authentication.authenticate_with` / `authorize_with`). Only set `config.authentication.open_access = true` for non-production demos where public access is intentional.
|
|
37
37
|
- Configure HTTPS for Action Cable if you expose Solid Cable over the public internet.
|
|
38
38
|
- Store API keys for authenticated feeds in encrypted credentials and inject them via per-source custom headers.
|
|
39
39
|
|