Compare commits

...
Author SHA1 Message Date
Anthony Stirling 714da48eec lint 2025-11-25 16:15:55 +00:00
Anthony Stirling 34e9e85e37 extra translations 2025-11-25 16:11:11 +00:00
Anthony Stirling c7301f5205 Merge remote-tracking branch 'origin/V2' into translationsandlogin 2025-11-25 15:58:16 +00:00
Anthony Stirling 7fdc01867b translations and login page 2025-11-25 15:56:40 +00:00
43 changed files with 32299 additions and 2979 deletions
@@ -11,7 +11,7 @@
#############################################################################################################
security:
enableLogin: false # set to 'true' to enable login
enableLogin: true # set to 'true' to enable login
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
@@ -120,6 +120,37 @@ public class ProprietaryUIDataController {
// Add enableLogin flag so frontend doesn't need to call /app-config
data.setEnableLogin(securityProps.getEnableLogin());
// Check if this is first-time setup with default credentials
// The isFirstLogin flag captures: default username/password usage and unchanged state
boolean isFirstTimeSetup = false;
boolean showDefaultCredentials = false;
List<User> allUsers = userRepository.findAll();
List<User> realUsers =
allUsers.stream()
.filter(
user ->
!Role.INTERNAL_API_USER
.getRoleId()
.equals(user.getUsername()))
.toList();
long userCount = realUsers.size();
if (userCount == 0) {
isFirstTimeSetup = true;
showDefaultCredentials = true;
} else if (userCount == 1) {
Optional<User> adminUser = userRepository.findByUsernameIgnoreCase("admin");
if (adminUser.isPresent() && Boolean.TRUE.equals(adminUser.get().getIsFirstLogin())) {
isFirstTimeSetup = true;
showDefaultCredentials = true;
}
}
data.setFirstTimeSetup(isFirstTimeSetup);
data.setShowDefaultCredentials(showDefaultCredentials);
OAUTH2 oauth = securityProps.getOauth2();
if (oauth != null && oauth.getEnabled()) {
@@ -456,6 +487,8 @@ public class ProprietaryUIDataController {
private Map<String, String> providerList;
private String loginMethod;
private boolean altLogin;
private boolean firstTimeSetup;
private boolean showDefaultCredentials;
}
@Data
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -3517,6 +3517,8 @@
"accountCreatedSuccess": "Account created successfully! You can now sign in.",
"passwordChangedSuccess": "Password changed successfully! Please sign in with your new password.",
"credentialsUpdated": "Your credentials have been updated. Please sign in again.",
"defaultCredentials": "Default Login Credentials",
"changePasswordWarning": "Please change your password after logging in for the first time",
"slides": {
"overview": {
"alt": "Stirling PDF overview",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+32
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Text, Stack, Alert } from '@mantine/core';
import { springAuth } from '@app/auth/springAuthClient';
import { useAuth } from '@app/auth/UseSession';
import { useTranslation } from 'react-i18next';
@@ -29,6 +30,8 @@ export default function Login() {
const [enabledProviders, setEnabledProviders] = useState<string[]>([]);
const [hasSSOProviders, setHasSSOProviders] = useState(false);
const [_enableLogin, setEnableLogin] = useState<boolean | null>(null);
const [isFirstTimeSetup, setIsFirstTimeSetup] = useState(false);
const [showDefaultCredentials, setShowDefaultCredentials] = useState(false);
// Redirect immediately if user has valid session (JWT already validated by AuthProvider)
useEffect(() => {
@@ -55,6 +58,10 @@ export default function Login() {
setEnableLogin(data.enableLogin ?? true);
// Set first-time setup flags
setIsFirstTimeSetup(data.firstTimeSetup ?? false);
setShowDefaultCredentials(data.showDefaultCredentials ?? false);
// Extract provider IDs from the providerList map
// The keys are like "/oauth2/authorization/google" - extract the last part
const providerIds = Object.keys(data.providerList || {})
@@ -259,6 +266,31 @@ export default function Login() {
</div>
)}
{/* Help section - only show on first-time setup with default credentials */}
{isFirstTimeSetup && showDefaultCredentials && (
<Alert
color="blue"
variant="light"
radius="md"
mt="xl"
>
<Stack gap="xs" align="center">
<Text size="sm" fw={600} ta="center">
{t('login.defaultCredentials', 'Default Login Credentials')}
</Text>
<Text size="sm" ta="center">
<Text component="span" fw={600}>{t('login.username', 'Username')}:</Text> admin
</Text>
<Text size="sm" ta="center">
<Text component="span" fw={600}>{t('login.password', 'Password')}:</Text> stirling
</Text>
<Text size="xs" c="dimmed" ta="center" mt="xs">
{t('login.changePasswordWarning', 'Please change your password after logging in for the first time')}
</Text>
</Stack>
</Alert>
)}
</AuthLayout>
);
}