Files
byos_next/migrations/0015_add_plugin_settings.sql
Rémi Bouteiller 515e81bc0f fix(security): enforce tenant ownership on plugin_settings
POST /api/plugin_settings/{id}/data was unauthenticated and looked up rows
by uuid only, allowing any caller who knew or guessed a uuid to overwrite
another tenant's merge_variables. Add the standard auth guard and scope
both the lookup and UPDATE by user_id, mirroring the sibling routes.

Drop the unsafe findPluginSettingByUuid helper now that it has no callers.

Enable ROW LEVEL SECURITY on plugin_settings with the same per-tenant
policies used by devices, playlists, mixups, and recipes (0014_harden_rls).
Without RLS the application-layer where-user_id filters were the only line
of defense, which is how the auth bypass above became exploitable. Also
grant table and sequence privileges to byos_app.
2026-04-29 18:37:17 -07:00

56 lines
2.3 KiB
SQL

-- Title: Add Plugin Settings
-- Description: Stores local TRMNL-compatible plugin settings, data, markup, and archives.
CREATE TABLE IF NOT EXISTS plugin_settings (
id BIGSERIAL PRIMARY KEY,
uuid TEXT NOT NULL UNIQUE,
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
name TEXT NOT NULL,
plugin_id INTEGER NOT NULL,
icon_url TEXT,
icon_content_type TEXT,
read_only BOOLEAN NOT NULL DEFAULT FALSE,
strategy TEXT DEFAULT 'webhook',
merge_variables JSONB NOT NULL DEFAULT '{}'::jsonb,
fields JSONB NOT NULL DEFAULT '{}'::jsonb,
markup JSONB NOT NULL DEFAULT '{}'::jsonb,
settings_yaml TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_plugin_settings_user_id ON plugin_settings(user_id);
CREATE INDEX IF NOT EXISTS idx_plugin_settings_plugin_id ON plugin_settings(plugin_id);
-- =============================================================================
-- RLS: tenant ownership enforced at the DB layer (matches 0014_harden_rls.sql)
-- =============================================================================
ALTER TABLE plugin_settings ENABLE ROW LEVEL SECURITY;
ALTER TABLE plugin_settings FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS plugin_settings_select_policy ON plugin_settings;
DROP POLICY IF EXISTS plugin_settings_insert_policy ON plugin_settings;
DROP POLICY IF EXISTS plugin_settings_update_policy ON plugin_settings;
DROP POLICY IF EXISTS plugin_settings_delete_policy ON plugin_settings;
CREATE POLICY plugin_settings_select_policy ON plugin_settings
FOR SELECT
USING (user_id = current_setting('app.current_user_id', true));
CREATE POLICY plugin_settings_insert_policy ON plugin_settings
FOR INSERT
WITH CHECK (user_id = current_setting('app.current_user_id', true));
CREATE POLICY plugin_settings_update_policy ON plugin_settings
FOR UPDATE
USING (user_id = current_setting('app.current_user_id', true))
WITH CHECK (user_id = current_setting('app.current_user_id', true));
CREATE POLICY plugin_settings_delete_policy ON plugin_settings
FOR DELETE
USING (user_id = current_setting('app.current_user_id', true));
GRANT SELECT, INSERT, UPDATE, DELETE ON plugin_settings TO byos_app;
GRANT USAGE, SELECT ON SEQUENCE plugin_settings_id_seq TO byos_app;