v0.3.7 highlight: single sign-on only, with the login page as its picture (#248)

The one feature in v0.3.7 (#169) is marked as its highlight and moved to
the top of Unreleased. Its picture is the login page with the provider's
button and no password form, taken by a new sso-only test in
whats-new-screenshots.spec.ts.

The two inline-image entries said the same thing twice; they are one
entry now, linked to #213.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5.5 authored and GitHub committed 2026-10-20 13:22:16 +02:00
1 parent 880f338d53
commit 0591264213
3 files changed
+63 -23

No files matched your search

+18 -23
View File
@@ -8,6 +8,17 @@ The published image tags: `latest` follows the most recent release below,
## Unreleased
<!-- highlight screenshot=sso-only -->
**Single sign-on can be the only way in.** A new setting under Admin → SSO, **Single sign-on only**
(or `OIDC_DISABLE_PASSWORD_LOGIN=true` in the environment), hides the password form and makes the
server refuse a password sign-in, so everybody comes in through the provider. It is in force only
while a provider is set up, so a provider that was removed cannot leave an install with no way in,
and the box can be ticked only by an administrator who has signed in through the provider
themselves. If the provider is down, `php bin/console app:sso:allow-password-login` brings the form
back. App passwords for mail clients and the phone apps, and sessions that are already open, are not
affected. Off by default. Schema change: one boolean column on `oidc_config`, false for every
existing install. [#169](https://github.com/karatektus/pl_mail/issues/169).
**A reply threads for the person who gets it.** A reply sent from plMail, in the web composer or
over JMAP, reached the recipient without `In-Reply-To` or `References`, so Outlook, Gmail and Apple
Mail showed it as a new conversation instead of part of the one it answered. The draft always held
@@ -124,19 +135,14 @@ a sandbox that sends no session cookie, so each image was answered with a redire
page. They now load through a signed link (`/mail/inline/{id}`) that only answers for an image of
the message being read, and nothing that is not a picture. The link stops working after a day or
two, so one that leaks through a forward or a log does not open the image for good; the message
signs a fresh one each time it is shown. Images labelled `application/octet-stream` or
`image/octet-stream` load the same way when their bytes are a picture. Messages already in the
signs a fresh one each time it is shown. Many mailers label an inline image
`application/octet-stream`, or `image/octet-stream`, which is not a registered type at all and is
what Vaultwarden's notification mails put on their logo, and such a part was served as a download.
For those labels only, the file's own bytes now decide: a PNG, JPEG, GIF or WebP is shown, while
SVG, HTML and anything unrecognised are still downloads. A remote image with that label is
identified the same way by the image proxy and served under its real type. Messages already in the
mailbox are fixed too, no backfill needed. No schema change.
**Single sign-on can be the only way in.** A new setting under Admin → SSO, **Single sign-on only**
(or `OIDC_DISABLE_PASSWORD_LOGIN=true` in the environment), hides the password form and makes the
server refuse a password sign-in, so everybody comes in through the provider. It is in force only
while a provider is set up, so a provider that was removed cannot leave an install with no way in,
and the box can be ticked only by an administrator who has signed in through the provider
themselves. If the provider is down, `php bin/console app:sso:allow-password-login` brings the form
back. App passwords for mail clients and the phone apps, and sessions that are already open, are not
affected. Off by default. Schema change: one boolean column on `oidc_config`, false for every
existing install. [#169](https://github.com/karatektus/pl_mail/issues/169).
[#213](https://github.com/karatektus/pl_mail/issues/213).
**A deleted label no longer comes back after the next sync.** On a plain IMAP account, and for a
folder on Outlook, deleting a label left its folder on the mail server, and the next sync made the
@@ -152,17 +158,6 @@ reached Outlook at all, and now does; labels nested under a Gmail label are dele
it. No schema change.
[#40](https://github.com/karatektus/pl_mail/issues/40).
**Inline images labelled `application/octet-stream` show up.** Many mailers do not say what an
inline image is — they label it `application/octet-stream`, or `image/octet-stream`, which is not
a registered type at all and is what Vaultwarden's notification mails put on their logo — and the
attachment route served every such part as a download, so the image never appeared in the message. For those labels only, the file's own
bytes now decide, held to the same allow-list as everything else: a PNG, JPEG, GIF or WebP is
served inline, while SVG, HTML and anything unrecognised are still downloads. The image proxy
already did this for unlabelled remote images and now shares the same check, so a remote image
labelled `image/octet-stream` is now also identified from its bytes and served under its real
type, rather than passed through under the label it came with. No schema change.
Part of [#213](https://github.com/karatektus/pl_mail/issues/213).
**A mail can go to Bcc recipients only.** Sending with nobody in To was refused with "Add at least
one recipient before sending", even with addresses in Cc or Bcc: the window counted all three, but
the server only counted To. It now counts all three as well. A mail with no recipient at all is
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

+45
View File
@@ -188,4 +188,49 @@ test.describe("What's new screenshots, as an administrator", () => {
await capture(page, "sso", panel, 560);
});
/**
* Single sign-on only (#169): the login page with the provider's button and
* no password form, which is the feature as everybody else meets it.
*
* The provider is saved through Admin → SSO, since the client secret is
* stored encrypted and SQL cannot write it. The box itself is set in the
* database: the form only lets an administrator tick it who has signed in
* through that provider, and this stack has no provider to sign in at.
* Undone in `finally`, so the stack is left with password sign-in and no
* provider, as the other pictures and the regression suite expect.
*/
test("sso-only", async ({ page }) => {
await login(page, TEST_DEMO_ADMIN.email, TEST_DEMO_ADMIN.password);
await page.goto("/admin?section=sso");
const panel = page.locator("turbo-frame#admin-sso");
await panel.getByLabel("Issuer").fill("https://auth.example.com/application/o/plmail/");
await panel.getByLabel("Client ID").fill("plmail");
await panel.getByLabel("Client secret").fill("not-a-real-secret");
await panel.getByLabel("Name on the button").fill("Authentik");
await panel.getByRole("button", { name: "Save", exact: true }).click();
await expect(panel.getByLabel("Issuer")).toHaveValue("https://auth.example.com/application/o/plmail/");
try {
consoleCommand(`dbal:run-sql "UPDATE oidc_config SET password_login_disabled = true"`);
await page.context().clearCookies();
await page.goto("/login");
await expect(page.getByLabel("Password")).toHaveCount(0);
await expect(page.getByText("Authentik")).toBeVisible();
// With a margin of the page around it: cropped to the box, the
// logo touches the top edge and the card's bottom border is lost.
const box = await page.locator("div.max-w-sm").first().boundingBox();
expect(box, "sso-only: no login card").not.toBeNull();
await page.screenshot({
path: `${OUT}/sso-only.png`,
clip: { x: box!.x - 32, y: box!.y - 32, width: box!.width + 64, height: box!.height + 64 },
});
} finally {
consoleCommand("app:sso:allow-password-login");
consoleCommand(`dbal:run-sql "DELETE FROM oidc_config"`);
}
});
});