Sign in on the mail server's own page (OAuth with PKCE), sessions hold tokens; tenants on every edition

Contract C-8 and C-10: with OAUTH_CLIENT_SECRET set, sign-in goes through the
server's page and the session keeps sealed tokens, renewed before they expire,
instead of a password. Push keeps a credential that renews itself. A password
change signs the session out, since the server revokes its tokens. The mock
answers OAuth for tests and development. Eleven new strings, in all nine
catalogues.
This commit is contained in:
2026-09-18 15:30:37 -07:00
parent c118184975
commit 8857bdac30
24 changed files with 1058 additions and 74 deletions
+18
View File
@@ -262,6 +262,20 @@ function httpUrl(raw: string, where: string): string {
const stalwartServers = readStalwartServers();
/*
* Signing in through the mail server's own page. On when OAUTH_CLIENT_SECRET
* is set: the secret of the confidential client the server registers for this
* webmail (INBUXA registers `ihasmail-inbuxa` from INBUXA_WEBMAIL_URL and
* INBUXA_WEBMAIL_CLIENT_SECRET). PUBLIC_URL is where browsers reach ihasmail,
* without BASE_PATH; the redirect URI is built from it and must match the one
* registered exactly.
*/
const oauthClientSecret = process.env.OAUTH_CLIENT_SECRET ?? "";
const publicUrl = process.env.PUBLIC_URL ? httpUrl(process.env.PUBLIC_URL, "PUBLIC_URL") : "";
if (oauthClientSecret && !publicUrl) {
throw new Error("OAUTH_CLIENT_SECRET is set but PUBLIC_URL is not: the sign-in redirect needs ihasmail's public address");
}
export const config = {
isProd,
appName: env("APP_NAME", "ihasmail"),
@@ -314,6 +328,10 @@ export const config = {
* should not suggest they come without the license.
*/
showEnterpriseNotices: bool("SHOW_ENTERPRISE_NOTICES", false),
/** See the note above `config`. Empty keeps the password form. */
oauthClientSecret,
oauthClientId: env("OAUTH_CLIENT_ID", "ihasmail-inbuxa"),
publicUrl,
appSecret,
trustProxy: bool("TRUST_PROXY", true),
/**