This commit is contained in:
Maurus Decimus
2026-08-24 15:44:51 +02:00
parent 8cab61a9c5
commit af11f5119c
11 changed files with 110 additions and 24 deletions
+56
View File
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
import { describe, it, expect, afterEach, vi } from 'vitest';
const originalHead = document.head.innerHTML;
async function loadWithMeta(meta: string) {
document.head.innerHTML = meta;
vi.resetModules();
const { getOAuthClientId } = await import('./oauthClientId');
return getOAuthClientId;
}
afterEach(() => {
document.head.innerHTML = originalHead;
vi.resetModules();
});
describe('getOAuthClientId', () => {
it('prefers the client id injected by the server', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content="pocket-id-client" />');
expect(getOAuthClientId()).toBe('pocket-id-client');
});
it('trims surrounding whitespace from the injected client id', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content=" pocket-id-client " />');
expect(getOAuthClientId()).toBe('pocket-id-client');
});
it('falls back to the built-in default when the placeholder is empty', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content="" />');
expect(getOAuthClientId()).toBe('stalwart-webui');
});
it('falls back to the built-in default when the placeholder is only whitespace', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content=" " />');
expect(getOAuthClientId()).toBe('stalwart-webui');
});
it('falls back to the built-in default when the placeholder is absent', async () => {
const getOAuthClientId = await loadWithMeta('');
expect(getOAuthClientId()).toBe('stalwart-webui');
});
it('reads the document only once', async () => {
const getOAuthClientId = await loadWithMeta('<meta name="oauth-client-id" content="pocket-id-client" />');
expect(getOAuthClientId()).toBe('pocket-id-client');
document.head.innerHTML = '<meta name="oauth-client-id" content="changed-later" />';
expect(getOAuthClientId()).toBe('pocket-id-client');
});
});
+17
View File
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
const DEFAULT_CLIENT_ID = 'stalwart-webui';
let cached: string | undefined;
export function getOAuthClientId(): string {
if (cached !== undefined) return cached;
const injected = document.querySelector('meta[name="oauth-client-id"]')?.getAttribute('content')?.trim();
cached = injected ? injected : DEFAULT_CLIENT_ID;
return cached;
}
+2 -1
View File
@@ -6,6 +6,7 @@
import { useAuthStore } from '../stores/authStore';
import { getBasePath } from '@/lib/basePath';
import { getOAuthClientId } from '@/lib/oauthClientId';
export function getApiBaseUrl(): string {
const envUrl = import.meta.env.VITE_API_BASE_URL as string | undefined;
@@ -45,7 +46,7 @@ export async function refreshAccessToken(): Promise<void> {
throw new Error('No refresh token or token endpoint available');
}
const clientId = (import.meta.env.VITE_OAUTH_CLIENT_ID as string) || 'stalwart-webui';
const clientId = getOAuthClientId();
try {
const response = await fetch(tokenEndpoint, {
+4 -4
View File
@@ -6,9 +6,9 @@
import { getApiBaseUrl } from '@/services/api';
import { getBasePath } from '@/lib/basePath';
import { getOAuthClientId } from '@/lib/oauthClientId';
import i18n from '@/i18n';
const CLIENT_ID = (import.meta.env.VITE_OAUTH_CLIENT_ID as string) || 'stalwart-webui';
const SCOPES = import.meta.env.VITE_OAUTH_SCOPES as string | undefined;
const SESSION_PREFIX = 'stalwart-oauth-';
@@ -97,7 +97,7 @@ export async function exchangeCode(
grant_type: 'authorization_code',
code,
code_verifier: codeVerifier,
client_id: CLIENT_ID,
client_id: getOAuthClientId(),
redirect_uri: redirectUri,
});
@@ -153,7 +153,7 @@ export async function startAuthFlow(username: string, returnUrl?: string | null)
const params = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
client_id: getOAuthClientId(),
redirect_uri: getRedirectUri(),
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
@@ -206,7 +206,7 @@ export function getPostLogoutRedirectUri(): string {
export function buildEndSessionUrl(endSessionEndpoint: string, postLogoutRedirectUri: string): string {
const params = new URLSearchParams({
client_id: CLIENT_ID,
client_id: getOAuthClientId(),
post_logout_redirect_uri: postLogoutRedirectUri,
});
const sep = endSessionEndpoint.includes('?') ? '&' : '?';
-1
View File
@@ -8,7 +8,6 @@
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
readonly VITE_OAUTH_CLIENT_ID: string;
readonly VITE_ACCESS_TOKEN: string;
readonly VITE_OAUTH_SCOPES: string;
readonly VITE_DEBUG_JMAP?: string;