Add a scoped newsletter API user for the scheduled campaign task

Token is generated once and kept in Secret listmonk-newsletter-api.
Tested: create, test-send and schedule work; subscribers/settings 403.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 20:32:03 -05:00
co-authored by Claude Opus 5.5
parent 3dd9cee930
commit e4a3d13870
5 changed files with 91 additions and 13 deletions
+31 -13
View File
@@ -103,41 +103,59 @@ WITH upd AS (
SELECT count(*) FROM upd;
{% endfor %}
{% if listmonk_api_token | length > 0 %}
{#- API user for the membership app: subscribers on listmonk_api_lists only. #}
{#- Scoped API users: the membership app (subscribers) and the newsletter
automation (campaigns). Each gets a user role and a list role limited to
its lists. listmonk caches API users at startup; the play restarts it. #}
{%- set api_users = [] %}
{%- if listmonk_api_token | length > 0 %}
{%- set _ = api_users.append({'username': listmonk_api_user, 'token': listmonk_api_token, 'role': listmonk_api_role,
'permissions': ['subscribers:get', 'subscribers:manage'], 'lists': listmonk_api_lists}) %}
{%- endif %}
{%- if listmonk_newsletter_api_token | default('') | length > 0 %}
{%- set _ = api_users.append({'username': listmonk_newsletter_api_user, 'token': listmonk_newsletter_api_token, 'role': listmonk_newsletter_api_role,
'permissions': ['campaigns:get', 'campaigns:manage', 'campaigns:send', 'templates:get'], 'lists': listmonk_newsletter_api_lists}) %}
{%- endif %}
{% for api in api_users %}
WITH ins AS (
INSERT INTO roles (type, name, permissions)
SELECT 'user', {{ q(listmonk_api_role) }}, ARRAY['subscribers:get', 'subscribers:manage']
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'user' AND name = {{ q(listmonk_api_role) }})
SELECT 'user', {{ q(api.role) }}, ARRAY[{% for p in api.permissions %}{{ q(p) }}{{ ',' if not loop.last }}{% endfor %}]
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'user' AND name = {{ q(api.role) }})
RETURNING id
)
SELECT count(*) FROM ins;
WITH upd AS (
UPDATE roles SET permissions = ARRAY[{% for p in api.permissions %}{{ q(p) }}{{ ',' if not loop.last }}{% endfor %}], updated_at = NOW()
WHERE type = 'user' AND name = {{ q(api.role) }}
AND permissions IS DISTINCT FROM ARRAY[{% for p in api.permissions %}{{ q(p) }}{{ ',' if not loop.last }}{% endfor %}]
RETURNING id
)
SELECT count(*) FROM upd;
WITH ins AS (
INSERT INTO roles (type, name)
SELECT 'list', {{ q(listmonk_api_role) }}
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL)
SELECT 'list', {{ q(api.role) }}
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'list' AND name = {{ q(api.role) }} AND parent_id IS NULL)
RETURNING id
)
SELECT count(*) FROM ins;
WITH parent AS (
SELECT id FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL
SELECT id FROM roles WHERE type = 'list' AND name = {{ q(api.role) }} AND parent_id IS NULL
), ins AS (
INSERT INTO roles (type, parent_id, list_id, permissions)
SELECT 'list', parent.id, l.id, ARRAY['list:get', 'list:manage']
FROM parent, lists l
WHERE l.name IN ({% for n in listmonk_api_lists %}{{ q(n) }}{{ ',' if not loop.last }}{% endfor %})
WHERE l.name IN ({% for n in api.lists %}{{ q(n) }}{{ ',' if not loop.last }}{% endfor %})
ON CONFLICT (parent_id, list_id) DO NOTHING
RETURNING id
)
SELECT count(*) FROM ins;
WITH ur AS (
SELECT id FROM roles WHERE type = 'user' AND name = {{ q(listmonk_api_role) }}
SELECT id FROM roles WHERE type = 'user' AND name = {{ q(api.role) }}
), lr AS (
SELECT id FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL
SELECT id FROM roles WHERE type = 'list' AND name = {{ q(api.role) }} AND parent_id IS NULL
), up AS (
INSERT INTO users (username, password_login, password, email, name, type, user_role_id, list_role_id, status)
SELECT {{ q(listmonk_api_user) }}, false, {{ q(listmonk_api_token | hash('sha256')) }},
{{ q(listmonk_api_user ~ '@api') }}, {{ q(listmonk_api_user) }}, 'api', ur.id, lr.id, 'enabled'
SELECT {{ q(api.username) }}, false, {{ q(api.token | hash('sha256')) }},
{{ q(api.username ~ '@api') }}, {{ q(api.username) }}, 'api', ur.id, lr.id, 'enabled'
FROM ur, lr
ON CONFLICT (username) DO UPDATE
SET password = EXCLUDED.password, user_role_id = EXCLUDED.user_role_id,
@@ -147,7 +165,7 @@ WITH ur AS (
RETURNING id
)
SELECT count(*) FROM up;
{% endif %}
{% endfor %}
{% if listmonk_remove_sample_data | bool %}
WITH del AS (