commit 2 which is probs further from working than 1

This commit is contained in:
James Brunton
2025-12-16 18:02:13 +00:00
parent bbf33a4f60
commit cbf1b387dd
5 changed files with 146 additions and 29 deletions
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stirling PDF - Authentication</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; color: #111; }
.status { padding: 1rem; border-radius: 8px; border: 1px solid #e5e7eb; background: #f9fafb; }
.error { border-color: #fca5a5; background: #fef2f2; color: #b91c1c; }
</style>
</head>
<body>
<div id="message" class="status">Completing authentication...</div>
<script>
(function () {
const msgEl = document.getElementById('message');
function setStatus(text, isError) {
msgEl.textContent = text;
if (isError) {
msgEl.classList.add('error');
}
}
try {
const hash = window.location.hash.startsWith('#') ? window.location.hash.substring(1) : window.location.hash;
const params = new URLSearchParams(hash);
const token = params.get('access_token');
const error = params.get('error_description') || params.get('error');
// Handle license errors that come via query instead of hash
const urlParams = new URLSearchParams(window.location.search);
const licenseError = urlParams.get('errorOAuth') || urlParams.get('oAuth2RequiresLicense');
if (token) {
try {
localStorage.setItem('stirling_jwt', token);
window.dispatchEvent(new CustomEvent('jwt-available'));
} catch (e) {
console.warn('Failed to persist token in localStorage', e);
}
if (window.opener) {
window.opener.postMessage({ type: 'stirling-sso-success', token }, '*');
}
setStatus('Authentication complete. Returning to app...', false);
setTimeout(() => {
window.location.href = '/';
}, 300);
return;
}
if (error || licenseError) {
const message = error || 'OAuth/SSO requires a paid license. Please contact your administrator.';
if (window.opener) {
window.opener.postMessage({ type: 'stirling-sso-error', error: message }, '*');
}
setStatus(message, true);
return;
}
setStatus('No authentication token received.', true);
} catch (e) {
console.error('Auth callback handling error:', e);
setStatus('Authentication failed. Please try again.', true);
}
})();
</script>
</body>
</html>
@@ -51,7 +51,8 @@ class UserLicenseSettingsServiceTest {
when(applicationProperties.getPremium()).thenReturn(premium);
when(applicationProperties.getAutomaticallyGenerated()).thenReturn(automaticallyGenerated);
when(automaticallyGenerated.getIsNewServer()).thenReturn(false); // Default: not a new server
when(automaticallyGenerated.getIsNewServer())
.thenReturn(false); // Default: not a new server
when(settingsRepository.findSettings()).thenReturn(Optional.of(mockSettings));
when(userService.getTotalUsersCount()).thenReturn(80L);
when(settingsRepository.save(any(UserLicenseSettings.class)))
+19
View File
@@ -0,0 +1,19 @@
{
"identifier": "main",
"description": "Main window capability with remote access for self-hosted SSO.",
"windows": ["main"],
"permissions": [
"core:default",
"http:default",
"http:allow-fetch",
"http:allow-fetch-send",
"http:allow-fetch-read-body",
"opener:default",
"opener:allow-open-url",
"core:webview:allow-internal-toggle-devtools",
"core:webview:allow-create-webview-window"
],
"remote": {
"urls": ["http://localhost:8080/*"]
}
}
+4 -1
View File
@@ -18,7 +18,10 @@
"resizable": true,
"fullscreen": false
}
]
],
"security": {
"capabilities": ["main"]
}
},
"bundle": {
"active": true,
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Text } from '@mantine/core';
import LoginHeader from '@app/routes/login/LoginHeader';
@@ -51,10 +51,54 @@ export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
setValidationError(errorMessage);
};
const waitForSsoCompletion = (popup: Window): Promise<string> => {
const pollForSession = (timeoutMs = 60000): Promise<void> => {
return new Promise((resolve, reject) => {
const start = Date.now();
const interval = window.setInterval(async () => {
if (Date.now() - start > timeoutMs) {
window.clearInterval(interval);
reject(new Error('Timed out waiting for authentication'));
return;
}
try {
const hasToken = localStorage.getItem('stirling_jwt');
if (hasToken) {
window.clearInterval(interval);
resolve();
return;
}
const isAuthed = await authService.isAuthenticated();
if (isAuthed) {
window.clearInterval(interval);
resolve();
}
} catch (e) {
// Ignore transient errors and keep polling
}
}, 1000);
});
};
const waitForSsoCompletion = (popup: Window, expectedOrigin?: string): Promise<string> => {
// Accept messages from the backend origin (e.g., http://localhost:8080) since the wizard runs under the Tauri origin.
const allowedOrigin = (() => {
try {
return expectedOrigin ? new URL(expectedOrigin).origin : null;
} catch (_) {
return null;
}
})();
return new Promise((resolve, reject) => {
const messageHandler = (event: MessageEvent) => {
if (event.origin !== window.location.origin || typeof event.data !== 'object' || event.data === null) {
if (typeof event.data !== 'object' || event.data === null) {
return;
}
// If we can compute the backend origin, ensure the message comes from there.
if (allowedOrigin && event.origin !== allowedOrigin) {
return;
}
@@ -95,30 +139,9 @@ export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
// Mark SSO flow so the callback can short-circuit verification
localStorage.setItem('desktop_sso_in_progress', JSON.stringify({ mode: 'selfhosted' }));
const popup = window.open(provider.url, '_blank', 'width=480,height=720');
if (!popup) {
localStorage.removeItem('desktop_sso_in_progress');
handleOAuthError(t('setup.login.error.oauthFailed', 'OAuth login failed. Please try again.'));
return;
}
try {
const token = await waitForSsoCompletion(popup);
localStorage.removeItem('desktop_sso_in_progress');
await authService.applyExternalToken(token);
await onOAuthSuccess({ username: provider.label || provider.id });
} catch (err) {
localStorage.removeItem('desktop_sso_in_progress');
const message = err instanceof Error ? err.message : t('setup.login.error.oauthFailed', 'OAuth login failed. Please try again.');
handleOAuthError(message);
} finally {
try {
popup.close();
} catch (_) {
// ignore
}
}
console.debug('[Desktop SSO] Launching provider (same-window)', provider);
// Navigate the current webview to the provider URL; the callback page will store the token and reload the app.
window.location.assign(provider.url);
};
const displayError = error || validationError;