- Add a new vacuum setting option on the UI in Admin -> Settings -> Maintenance.
- Also refactor frontend (lock-and-wait-for-restart) login on settings into
the global vue instance so that it can be reused across contexts.
Settings.vue and Maintenance.vue both now use it to wait for the backend
to restart.
This patch introduces the long pending requirement of being able to send messages
to arbitrary recipients via the `/api/tx` endpoint without them having to be
subscribers in the database.
It maintains backwards compatibility and introduces just one new field,
`subscriber_mode` to the `/api/tx` endpoint.
- `default` - Recipients must exist as subscribers in the database.
Pass either `subscriber_emails` or `subscriber_ids`. |
- `fallback` - Only accepts `subscriber_emails` and looks up subscribers in the
database. If not found, sends the message to the e-mail anyway.
In the template, apart from `{{ .Subscriber.Email }}`, other
subscriber fields such as `.Name`. will be empty.
Use `{{ Tx.Data.* }}` instead. |
- `external` - Sends to the given `subscriber_emails` without subscriber lookup
in the database. In the template, apart from
`{{ .Subscriber.Email }}`, other subscriber fields such as
`.Name`. will be empty. Use `{{ Tx.Data.* }}` instead. |
- Like subscribers, select one-or more or 'all' items and delete them
on the lists and campaigns UIs.
- New `DELETE /api/lists` and `DELETE /api/campaigns` endpoints that
take one or more `id` params or a single `query` param.
This patch adds a new `status` field (active, archived) to the lists table and
an 'Archived?' toggle on the UI that allows a list to be marked as archived.
This hides the lists from the lists page, campaigns list selection, list roles,
and public forms. A new "View archived lists" link on the lists UI allows
viewing the list of archived lists.
This is useful to hide/declutter lists by archiving historical, temporary lists
etc. This is largely a UX value addition.
Closes#2613.
- Allow users to enable TOTP 2FA from the profile page by scanning a QR code.
- Create new `internal/tmptokens` in-memory token store for temp tokens for
temporary login -> 2FA flow.
- Refactor reset methods to use this package instead of inline locked map.
* feat: add subscriber activity tracking UI in admin panel
* Apply minor cosmetic fixes to the subscriber activity forum.
- Remove dead icon references
- Remove new i18n language strings and reuse existing ones
- Refresh i18n languages with new strings
- Tweak styles
---------
Co-authored-by: Kailash Nadh <kailash@nadh.in>
The existing hCaptcha implementation as the only CAPTCHA option isn't ideal
as hCaptcha is a proprietary SaaS provider. This commit adds supports for
ALTCHA (altcha.org) a self-contained "proof-of-work" based CAPTCHA option.
Closes#2243.
This patch adds 3 new options to OIDC settings.
Toggle user auto-creation, and select default user/list roles
for auto-created users.
Co-authored-by: Kailash Nadh <kailash@nadh.in>
- Fix merge conflicts.
- Simply logic in campaign preview handler.
- Remove redundant `GetCampaignForPreviewWithTemplate()` and switch to the old
query that optionally takes a template.
- Fix Vue linting issues.
This permission was never checked for and had an unintended consequence of
allowing a non-superadmin user to execute arbitrary queries (expected), but
getting a superadmin session by joining the `sessions` table.
This patch:
- Introduces a table allowlist that uses the Postgres query plan (JSON)
and validate the referenced tables against the allowed ones on arbitrary
queries issued to the various `/subscribers` APIs.
- Explicitly adds the missing `subscribers:sql_query` permission check to all
handlers that accept `query`.
- Introduces a new `search` parameter on all handlers that accept `query`.
This parameter is an interface over the default name/email substring search
instead of relying on `query`.
- Make the beginning of handlers consistent with uniform variable declaration
and grouping.
- Add missing comments.
- Fix staticcheck/vet warnings and idiom issues.
- Move user models from `/models` to `internal/auth`.
- Move and refactor various permission check functions into `User.()`
- Refactor awkward `get, manage bool` function args into `Get|Manage` bitflags.
This patch introduces new `campaigns:get_all` and `campaigns:manage_all`
permissions which alter the behaviour of the the old `campaigns:get` and
`campaigns:manage` permissions. This is a subtle breaking behavioural change.
Old:
- `campaigns:get` -> View all campaigns irrespective of a user's list
permissions.
- `campaigns:manage` -> Manage all campaigns irrespective of a user's list
permissions.
New:
- `campaigns:get_all` -> View all campaigns irrespective of a user's list
permissions.
- `campaigns:manage_all` -> Manage all campaigns irrespective of a user's list
permissions.
- `campaigns:get` -> View only the campaigns that have at least one list to
which which a user has get or manage access.
- `campaigns:manage` -> Manage only the campaigns that have at list one list
to which a user has get or manage access.
In addition, this patch refactors and cleans up certain permission related
logic and functions.
This patch introduces a new `Domain allowlist` input in Settings -> Privacy UI
as a new tab alongside domain `Domain blocklist`. If any domains are entered
here, then only subscriptions/imports/additions of e-mails from those particular
domains are accepted. blocklist is mutually exclusive with allowlist when there
are values in the allowlist.
This patch adds a new optional `name` field to SMTP server config on the UI.
When a name is given to an SMTP server, it's initialized as a standalone messenger
which shows up as a sub-group item under the main "email" messenger
on the campaign page.
Co-authored-by: Kailash Nadh <kailash@nadh.in>
This has been a hair-pulling rabbit hole of an issue. #1931 and others.
When the `next-campaign-subscribers` query that fetches $n subscribers
per batch for a campaign returns no results, the manager assumes
that the campaign is done and marks as finished.
Marathon debugging revealed fundamental flaws in qyery's logic that
would incorrectly return 0 rows under certain conditions.
- Based on the "layout" of subscribers for eg: a series of blocklisted
subscribers between confirmed subscribers.
A series of unconfirmed subscribers in a batch belonging to a double
opt-in list.
- Bulk import blocklisting users, but not marking their subscriptions
as 'unsubscribed'.
- Conditions spread across multiple CTEs resulted in returning an
arbitrary number of rows and $N per batch as the selected $N rows
would get filtered out elsewhere, possibly even becoming 0.
After fixing this and testing it on our prod instance that has
15 million subscribers and ~70 million subscriptions in the
`subscriber_lists` table, ended up discovered significant inefficiences
in Postgres query planning. When `subscriber_lists` and campaign list IDs
are joined dynamically (CTE or ANY() or any kind of JOIN that involves)
a query, the Postgres query planner is unable to use the right indexes.
After testing dozens of approaches, discovered that statically passing
the values to join on (hardcoding or passing via parametrized $1 vars),
the query uses the right indexes. The difference is staggering.
For the particular scenario on our large prod DB to pull a batch,
~15 seconds vs. ~50ms, a whopping 300x improvement!
This patch splits `next-campaign-subscribers` into two separate queries,
one which fetches campaign metadata and list_ids, whose values are then
passed statically to the next query to fetch subscribers by batch.
In addition, it fixes and refactors broken filtering and counting logic
in `create-campaign` and `next-campaign` queries.
Closes#1931, #1993, #1986.
This commit splits roles into two, user roles and list roles, both of which
are attached separately to a user.
List roles are collection of lists each with read|write permissions, while
user roles now have all permissions except for per-list ones.
This allows for easier management of roles, eliminating the need to clone and
create new roles just to adjust specific list permissions.
- Filter lists by permitted list IDs in DB get calls.
- Split getLists() handlers into two (one, all) for clarity.
- Introduce new `subscribers:get_by_list` permission.
- Tweak UI rendering to work with new per-list permssions.