Sign in as inbuxa-admin when hosted apart from the server (inbuxa-server contract C-6)

This commit is contained in:
2026-09-18 13:05:04 -07:00
parent e387e7976f
commit 2399f5ce97
3 changed files with 34 additions and 4 deletions
+4 -1
View File
@@ -22,7 +22,10 @@ server has without hardcoding any of them.
mail server itself. INBUXA Admin is its own deployment, pointed at the server mail server itself. INBUXA Admin is its own deployment, pointed at the server
either at build time (`VITE_API_BASE_URL`) or at deploy time: either at build time (`VITE_API_BASE_URL`) or at deploy time:
`<meta name="api-base-url" content="https://mail.example.com">` in `<meta name="api-base-url" content="https://mail.example.com">` in
`index.html`. `index.html`. Hosted like that, it signs in as the OAuth client
`inbuxa-admin`, which the server registers when it's started with
`INBUXA_ADMIN_URL` set to INBUXA Admin's address (for development,
`http://localhost:5173`).
- **INBUXA's look:** the logo and ihasmail's palette. - **INBUXA's look:** the logo and ihasmail's palette.
- **Two-factor setup** names INBUXA as the issuer, and no longer makes - **Two-factor setup** names INBUXA as the issuer, and no longer makes
authenticator apps fetch a logo from a third-party site. authenticator apps fetch a logo from a third-party site.
+14
View File
@@ -46,6 +46,20 @@ describe('getOAuthClientId', () => {
expect(getOAuthClientId()).toBe('stalwart-webui'); expect(getOAuthClientId()).toBe('stalwart-webui');
}); });
it('is inbuxa-admin when hosted apart from the server', async () => {
const getOAuthClientId = await loadWithMeta(
'<meta name="oauth-client-id" content="" /><meta name="api-base-url" content="https://mail.example.org" />',
);
expect(getOAuthClientId()).toBe('inbuxa-admin');
});
it('still prefers an injected client id when hosted apart from the server', async () => {
const getOAuthClientId = await loadWithMeta(
'<meta name="oauth-client-id" content="custom" /><meta name="api-base-url" content="https://mail.example.org" />',
);
expect(getOAuthClientId()).toBe('custom');
});
it('reads the document only once', async () => { it('reads the document only once', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content="pocket-id-client" />'); const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content="pocket-id-client" />');
expect(getOAuthClientId()).toBe('pocket-id-client'); expect(getOAuthClientId()).toBe('pocket-id-client');
+16 -3
View File
@@ -4,14 +4,27 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
const DEFAULT_CLIENT_ID = 'stalwart-webui'; // INBUXA requires OAuth clients to be registered, and registers these two on
// every start (inbuxa-server contract C-6). Served by the server itself, this
// is the web interface at /admin, registered as `stalwart-webui`. Hosted
// anywhere else, with the server's address in <meta name="api-base-url">, it
// is INBUXA Admin, registered as `inbuxa-admin` from INBUXA_ADMIN_URL.
const SERVED_BY_SERVER_CLIENT_ID = 'stalwart-webui';
const HOSTED_ELSEWHERE_CLIENT_ID = 'inbuxa-admin';
let cached: string | undefined; let cached: string | undefined;
function metaContent(name: string): string | undefined {
return document.querySelector(`meta[name="${name}"]`)?.getAttribute('content')?.trim() || undefined;
}
export function getOAuthClientId(): string { export function getOAuthClientId(): string {
if (cached !== undefined) return cached; if (cached !== undefined) return cached;
const injected = document.querySelector('meta[name="oauth-client-id"]')?.getAttribute('content')?.trim(); const hostedElsewhere = Boolean(
cached = injected ? injected : DEFAULT_CLIENT_ID; (import.meta.env.VITE_API_BASE_URL as string | undefined) || metaContent('api-base-url'),
);
cached =
metaContent('oauth-client-id') ?? (hostedElsewhere ? HOSTED_ELSEWHERE_CLIENT_ID : SERVED_BY_SERVER_CLIENT_ID);
return cached; return cached;
} }