From 4133de86cdf08a17719444d37bb64d2ab2ae3381 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 4 Aug 2026 11:07:55 +0200 Subject: [PATCH] Enable generated campaign passwords --- docs/CAMPAIGN_HANDBOOK.md | 6 ++++++ src/govoplan_campaign/backend/manifest.py | 4 ++-- webui/src/features/campaigns/MailSettingsPage.tsx | 2 +- webui/src/features/campaigns/components/FieldValueInput.tsx | 1 + webui/tests/campaign-lifecycle-ui-structure.test.mjs | 4 ++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/CAMPAIGN_HANDBOOK.md b/docs/CAMPAIGN_HANDBOOK.md index 1051415..71ead8d 100644 --- a/docs/CAMPAIGN_HANDBOOK.md +++ b/docs/CAMPAIGN_HANDBOOK.md @@ -124,6 +124,12 @@ action. 5. Open **Mail settings** and select an available Mail profile. The campaign stores only `server.mail_profile_id`; it never accepts SMTP/IMAP settings, usernames, passwords, or credential references. + If the selected profile permits a campaign-scoped Mail credential, that + credential remains Mail-owned even though it is created from this surface. + For that credential and for password-valued campaign fields, the shared + password generator keeps its candidate separate from the form until **Use + password** is explicitly confirmed. Copying a candidate does not save or + submit it. 6. Save the editable version, validate the relevant sections, and resolve every blocking issue. Warnings remain explicit review decisions. 7. Build the exact messages and inspect recipient, addressing, template, diff --git a/src/govoplan_campaign/backend/manifest.py b/src/govoplan_campaign/backend/manifest.py index 150cf3a..7dbd138 100644 --- a/src/govoplan_campaign/backend/manifest.py +++ b/src/govoplan_campaign/backend/manifest.py @@ -844,7 +844,7 @@ manifest = ModuleManifest( id="campaigns.workflow.prepare-validate-and-build", title="Prepare, validate, and build a campaign", summary="Turn governed recipient, template, attachment, and Mail-profile inputs into exact built messages for review.", - body="Prepare each input in its owning surface, resolve every blocking validation issue, and build exact recipient messages before review. Recipient data can activate or deactivate every currently opposite-state row as one explicitly confirmed draft change; saving it creates the normal Campaign version evidence and invalidates stale validation, build, and review state. Campaign freezes recipient and attachment evidence for the selected version; later source changes do not silently alter that build. When the Templates module is installed, its single Templates navigation entry owns the reusable library while campaign-specific composition remains in the campaign workspace.", + body="Prepare each input in its owning surface, resolve every blocking validation issue, and build exact recipient messages before review. Recipient data can activate or deactivate every currently opposite-state row as one explicitly confirmed draft change; saving it creates the normal Campaign version evidence and invalidates stale validation, build, and review state. New campaign credentials and password-valued fields offer the shared secure generator; its candidate remains separate until Use password is confirmed. Campaign freezes recipient and attachment evidence for the selected version; later source changes do not silently alter that build. When the Templates module is installed, its single Templates navigation entry owns the reusable library while campaign-specific composition remains in the campaign workspace.", layer="configured", documentation_types=("user",), audience=("campaign_manager", "campaign_author"), @@ -901,7 +901,7 @@ manifest = ModuleManifest( "steps": [ "Set campaign-wide fields and purpose, then define the recipient fields and templates.", "Import or select recipients, optionally confirm a counted activate-all or deactivate-all draft action, and inspect provenance, exclusions, and review-required rows.", - "Select managed attachment versions and an authorized Mail profile when those capabilities are used.", + "Select managed attachment versions and an authorized Mail profile when those capabilities are used; generate a new password only when creating a campaign credential or password-valued field, then explicitly confirm the candidate.", "Validate the relevant sections and resolve every blocker without hiding warnings.", "Build the selected version and inspect representative and exceptional rendered messages.", ], diff --git a/webui/src/features/campaigns/MailSettingsPage.tsx b/webui/src/features/campaigns/MailSettingsPage.tsx index 25d1951..d8f1fb3 100644 --- a/webui/src/features/campaigns/MailSettingsPage.tsx +++ b/webui/src/features/campaigns/MailSettingsPage.tsx @@ -465,7 +465,7 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting setCredentialDraft({ ...credentialDraft, username: event.target.value })} /> - setCredentialDraft({ ...credentialDraft, password })} disabled={credentialSaving} autoComplete="new-password" /> + setCredentialDraft({ ...credentialDraft, password })} disabled={credentialSaving} generator autoComplete="new-password" />
diff --git a/webui/src/features/campaigns/components/FieldValueInput.tsx b/webui/src/features/campaigns/components/FieldValueInput.tsx index 0170c16..0919404 100644 --- a/webui/src/features/campaigns/components/FieldValueInput.tsx +++ b/webui/src/features/campaigns/components/FieldValueInput.tsx @@ -18,6 +18,7 @@ export default function FieldValueInput({ fieldType = "string", value, disabled inputClassName={className} value={valueToInputText(value, normalizedType)} disabled={disabled} + generator placeholder={placeholder} autoComplete="new-password" onValueChange={(nextValue) => onChange(inputValueToFieldValue(normalizedType, nextValue))} diff --git a/webui/tests/campaign-lifecycle-ui-structure.test.mjs b/webui/tests/campaign-lifecycle-ui-structure.test.mjs index a14fef1..5004ff1 100644 --- a/webui/tests/campaign-lifecycle-ui-structure.test.mjs +++ b/webui/tests/campaign-lifecycle-ui-structure.test.mjs @@ -4,6 +4,8 @@ import { readFileSync } from "node:fs"; const workspace = readFileSync("src/features/campaigns/CampaignWorkspace.tsx", "utf8"); const overview = readFileSync("src/features/campaigns/CampaignOverviewPage.tsx", "utf8"); const recipients = readFileSync("src/features/campaigns/RecipientDataPage.tsx", "utf8"); +const fieldValueInput = readFileSync("src/features/campaigns/components/FieldValueInput.tsx", "utf8"); +const mailSettings = readFileSync("src/features/campaigns/MailSettingsPage.tsx", "utf8"); const api = readFileSync("src/api/campaigns.ts", "utf8"); assert.match(workspace, /