Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

827 lines
39 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
2024-05-22 21:48:23 +01:00
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
2025-06-09 00:32:45 +01:00
<th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
<link rel="stylesheet" th:href="@{/css/modern-tables.css}">
</head>
2023-08-13 01:12:29 +01:00
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
2025-06-09 00:32:45 +01:00
<div class="data-container">
<div class="data-panel">
<div class="data-header">
<h1 class="data-title">
<span class="data-icon">
<span class="material-symbols-rounded">settings_account_box</span>
</span>
<span th:text="#{account.accountSettings}">User Settings</span>
</h1>
</div>
<div class="data-body">
<div th:if="${messageType}" class="alert alert-danger data-mb-3">
<span th:text="#{${messageType}}">Default message if not found</span>
</div>
2025-06-09 00:32:45 +01:00
<div th:if="${error}" class="alert alert-danger data-mb-3" role="alert">
<span th:text="${error}">Error Message</span>
</div>
2025-06-09 00:32:45 +01:00
<!-- Admin Settings Banner (for admins only) -->
<div th:if="${role == 'ROLE_ADMIN'}" class="data-panel data-mb-3" style="background-color: var(--md-sys-color-secondary-container);">
<div class="data-body" style="display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; background-color: var(--md-sys-color-secondary-container);">
<div style="display: flex; align-items: center; gap: 1rem;">
<span class="material-symbols-rounded" style="font-size: 2rem; color: var(--md-sys-color-secondary);">
admin_panel_settings
</span>
<div>
<h4 style="margin: 0; color: var(--md-sys-color-secondary);" th:text="#{account.adminTitle}">Administrator Tools</h4>
<p style="margin: 0.25rem 0 0 0; color: var(--md-sys-color-secondary);" th:text="#{account.adminNotif}">You have admin privileges. Access system settings and user management.</p>
</div>
</div>
<a class="data-btn" th:href="@{'/adminSettings'}" role="button" target="_blank"
style="background-color: var(--md-sys-color-secondary); color: var(--md-sys-color-on-secondary); display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1.25rem; border-radius: 0.5rem; font-weight: 500; border: none; cursor: pointer; text-decoration: none;">
<span class="material-symbols-rounded">admin_panel_settings</span>
<span th:text="#{account.adminSettings}">Admin Settings</span>
</a>
</div>
</div>
<!-- Account Management Buttons -->
SSO Refactoring (#2818) # Description of Changes * Refactoring of SSO code around OAuth & SAML 2 * Enabling auto-login with SAML 2 via the new `SSOAutoLogin` property * Correcting typos & general cleanup --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [x] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
2025-02-24 22:18:34 +00:00
<th:block th:if="not ${oAuth2Login} or not ${saml2Login}">
2025-06-09 00:32:45 +01:00
<div class="data-section-title">Account Management</div>
<div class="data-actions data-actions-start data-mb-3">
<button class="data-btn data-btn-primary" data-bs-toggle="modal" data-bs-target="#changeUsernameModal">
<span class="material-symbols-rounded">edit</span>
<span th:text="#{account.changeUsername}">Change Username</span>
</button>
<button class="data-btn data-btn-primary" data-bs-toggle="modal" data-bs-target="#changePasswordModal">
<span class="material-symbols-rounded">key</span>
<span th:text="#{account.changePassword}">Change Password</span>
</button>
</div>
</th:block>
2025-06-09 00:32:45 +01:00
<!-- API Key Section -->
<div class="data-section-title" th:text="#{account.yourApiKey}">API Key</div>
<div class="data-panel data-mb-3">
<div class="data-header">
<h5 class="data-title">
<span class="data-icon">
<span class="material-symbols-rounded">key</span>
</span>
<span th:text="#{account.yourApiKey}">API Key</span>
</h5>
</div>
2025-06-09 00:32:45 +01:00
<div class="data-body">
<div style="display: flex; gap: 0.5rem;">
<input type="password" class="data-form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly style="flex: 1;">
<button class="data-btn data-btn-secondary" id="copyBtn" type="button" onclick="copyToClipboard()" title="Copy to clipboard">
<span class="material-symbols-rounded">content_copy</span>
</button>
<button class="data-btn data-btn-secondary" id="showBtn" type="button" onclick="showApiKey()" title="Show/hide API key">
<span class="material-symbols-rounded" id="eyeIcon">visibility</span>
</button>
<button class="data-btn data-btn-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()" title="Refresh API key">
<span class="material-symbols-rounded">refresh</span>
</button>
</div>
2023-08-13 01:12:29 +01:00
</div>
</div>
2025-06-09 00:32:45 +01:00
<!-- Settings Sync Section -->
<div class="data-section-title" th:text="#{account.syncTitle}">Sync browser settings with Account</div>
<div class="data-panel data-mb-3">
<div class="data-header">
<h5 class="data-title">
<span class="data-icon">
<span class="material-symbols-rounded">sync</span>
</span>
<span th:text="#{account.settingsCompare}">Settings Comparison</span>
</h5>
</div>
<div class="data-body">
<div class="table-responsive">
<table id="settingsTable" class="data-table">
<thead>
<tr>
<th scope="col" th:text="#{account.property}">Property</th>
<th scope="col" th:text="#{account.accountSettings}">Account Setting</th>
<th scope="col" th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
</tr>
</thead>
<tbody>
<!-- This will be dynamically populated by JavaScript -->
</tbody>
</table>
</div>
<div class="data-actions data-mt-3">
<button id="syncToBrowser" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">cloud_download</span>
<span th:text="#{account.syncToBrowser}">Sync Account -> Browser</span>
</button>
<button id="syncToAccount" class="data-btn data-btn-secondary">
<span class="material-symbols-rounded">cloud_upload</span>
<span th:text="#{account.syncToAccount}">Sync Account <- Browser</span>
</button>
</div>
</div>
</div>
2025-06-09 00:32:45 +01:00
</div>
</div>
</div>
</div>
<!-- Change Username Modal -->
<div class="modal fade" id="changeUsernameModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form id="formsavechangeusername" th:action="@{'/api/v1/user/change-username'}" method="post" class="modal-content data-modal">
<div class="data-modal-header">
<h5 class="data-modal-title">
<span class="data-icon">
<span class="material-symbols-rounded">edit</span>
</span>
<span th:text="#{account.changeUsername}">Change Username</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="data-modal-body">
<div class="data-form-group">
<label for="newUsername" class="data-form-label" th:text="#{account.newUsername}">New Username</label>
<input type="text" class="data-form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
<span id="usernameError" style="display: none; color: var(--md-sys-color-error);" th:text="#{invalidUsernameMessage}">Invalid username!</span>
</div>
<div class="data-form-group">
<label for="currentPasswordChangeUsername" class="data-form-label" th:text="#{password}">Password</label>
<input type="password" class="data-form-control" name="currentPasswordChangeUsername" id="currentPasswordChangeUsername" th:placeholder="#{password}">
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{account.changeUsername}">Change Username</span>
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Change Password Modal -->
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<form id="formsavechangepassword" th:action="@{'/api/v1/user/change-password'}" method="post" class="modal-content data-modal">
<div class="data-modal-header">
<h5 class="data-modal-title">
<span class="data-icon">
<span class="material-symbols-rounded">key</span>
</span>
<span th:text="#{account.changePassword}">Change Password</span>
</h5>
<button type="button" class="data-btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="data-modal-body">
<div class="data-form-group">
<label for="currentPassword" class="data-form-label" th:text="#{account.oldPassword}">Old Password</label>
<input type="password" class="data-form-control" name="currentPassword" id="currentPassword" th:placeholder="#{account.oldPassword}">
</div>
<div class="data-form-group">
<label for="newPassword" class="data-form-label" th:text="#{account.newPassword}">New Password</label>
<input type="password" class="data-form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
</div>
<div class="data-form-group">
<label for="confirmNewPassword" class="data-form-label" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
<input type="password" class="data-form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
<span id="confirmPasswordError" style="display: none; color: var(--md-sys-color-error);" th:text="#{confirmPasswordErrorMessage}">New Password and Confirm New Password must match.</span>
</div>
<div class="data-form-actions">
<button type="button" class="data-btn data-btn-secondary" data-bs-dismiss="modal">
<span class="material-symbols-rounded">close</span>
<span th:text="#{cancel}">Cancel</span>
</button>
<button type="submit" class="data-btn data-btn-primary">
<span class="material-symbols-rounded">check</span>
<span th:text="#{account.changePassword}">Change Password</span>
</button>
</div>
</div>
</form>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<!-- JavaScript for validation -->
<script th:inline="javascript">
jQuery.validator.addMethod("usernamePattern", function(value, element) {
// Regular expression for user name: Min. 3 characters, max. 50 characters
const regexUsername = /^[a-zA-Z0-9](?!.*[-@._+]{2,})([a-zA-Z0-9@._+-]{1,48})[a-zA-Z0-9]$/;
2025-06-09 00:32:45 +01:00
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
const regexEmail = /^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?:\.[A-Za-z]{2,})$/;
2025-06-09 00:32:45 +01:00
// Check if the field is optional or meets the requirements
return this.optional(element) || regexUsername.test(value) || regexEmail.test(value);
}, /*[[#{invalidUsernameMessage}]]*/ "Invalid username format");
$(document).ready(function() {
$.validator.addMethod("passwordMatch", function(value, element) {
return $('#newPassword').val() === $('#confirmNewPassword').val();
}, /*[[#{confirmPasswordErrorMessage}]]*/ "New Password and Confirm New Password must match.");
$('#formsavechangepassword').validate({
rules: {
currentPassword: {
required: true
},
newPassword: {
required: true
},
confirmNewPassword: {
required: true,
passwordMatch: true
}
},
errorPlacement: function(error, element) {
if (element.attr("name") === "newPassword" || element.attr("name") === "confirmNewPassword") {
$("#confirmPasswordError").text(error.text()).show();
} else {
error.insertAfter(element);
}
},
success: function(label, element) {
if ($(element).attr("name") === "newPassword" || $(element).attr("name") === "confirmNewPassword") {
$("#confirmPasswordError").hide();
}
}
});
2025-06-09 00:32:45 +01:00
$('#formsavechangeusername').validate({
rules: {
newUsername: {
required: true,
usernamePattern: true
},
currentPasswordChangeUsername: {
required: true
}
},
messages: {
newUsername: {
usernamePattern: /*[[#{invalidUsernameMessage}]]*/ "Invalid username format"
},
},
errorPlacement: function(error, element) {
if (element.attr("name") === "newUsername") {
$("#usernameError").text(error.text()).show();
} else {
error.insertAfter(element);
}
},
success: function(label, element) {
if ($(element).attr("name") === "newUsername") {
$("#usernameError").hide();
}
}
});
});
</script>
2025-06-09 00:32:45 +01:00
<!-- JavaScript for API Key -->
<script th:inline="javascript">
function copyToClipboard() {
const apiKeyElement = document.getElementById("apiKey");
apiKeyElement.select();
document.execCommand("copy");
}
2025-06-09 00:32:45 +01:00
function showApiKey() {
const apiKeyElement = document.getElementById("apiKey");
const copyBtn = document.getElementById("copyBtn");
const eyeIcon = document.getElementById("eyeIcon");
if (apiKeyElement.type === "password") {
apiKeyElement.type = "text";
eyeIcon.textContent = "visibility_off";
copyBtn.disabled = false; // Enable copy button when API key is visible
} else {
apiKeyElement.type = "password";
eyeIcon.textContent = "visibility";
copyBtn.disabled = true; // Disable copy button when API key is hidden
}
}
2025-06-09 00:32:45 +01:00
document.addEventListener("DOMContentLoaded", async function() {
showApiKey();
try {
/*<![CDATA[*/
const urlGetApiKey = /*[[@{/api/v1/user/get-api-key}]]*/ "/api/v1/user/get-api-key";
/*]]>*/
let response = await window.fetchWithCsrf(urlGetApiKey, { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
} else {
manageUIState(null);
}
} catch (error) {
console.error('There was an error:', error);
}
});
2025-06-09 00:32:45 +01:00
async function refreshApiKey() {
try {
/*<![CDATA[*/
const urlUpdateApiKey = /*[[@{/api/v1/user/update-api-key}]]*/ "/api/v1/user/update-api-key";
/*]]>*/
let response = await window.fetchWithCsrf(urlUpdateApiKey, { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
document.getElementById("apiKey").type = 'text';
document.getElementById("copyBtn").disabled = false;
} else {
alert('Error refreshing API key.');
}
} catch (error) {
console.error('There was an error:', error);
}
}
2025-06-09 00:32:45 +01:00
function manageUIState(apiKey) {
const apiKeyElement = document.getElementById("apiKey");
const showBtn = document.getElementById("showBtn");
const copyBtn = document.getElementById("copyBtn");
2025-06-09 00:32:45 +01:00
if (apiKey && apiKey.trim().length > 0) {
apiKeyElement.value = apiKey;
showBtn.disabled = false;
copyBtn.disabled = false;
} else {
apiKeyElement.value = "";
showBtn.disabled = true;
copyBtn.disabled = true;
}
}
</script>
2025-06-09 00:32:45 +01:00
<!-- JavaScript for Settings Sync -->
<script th:inline="javascript">
document.addEventListener("DOMContentLoaded", async function() {
const settingsTableBody = document.querySelector("#settingsTable tbody");
// Helper function to check if a key should be ignored
function shouldIgnoreKey(key) {
return key === 'debug' ||
key === '0' ||
key === '1' ||
key.includes('pdfjs') ||
key.includes('clientSubmissionOrder') ||
key.includes('lastSubmitTime') ||
key.includes('lastClientId') ||
key.includes('posthog') || key.includes('ssoRedirectAttempts') || key.includes('lastRedirectAttempt') || key.includes('surveyVersion') ||
key.includes('pageViews');
}
/*<![CDATA[*/
var accountSettingsString = /*[[${settings}]]*/ {};
/*]]>*/
var accountSettings = JSON.parse(accountSettingsString);
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
allKeys.forEach(key => {
if(shouldIgnoreKey(key)) return; // Using our helper function
const accountValue = accountSettings[key] || '-';
const browserValue = localStorage.getItem(key) || '-';
const row = settingsTableBody.insertRow();
const propertyCell = row.insertCell(0);
const accountCell = row.insertCell(1);
const browserCell = row.insertCell(2);
propertyCell.textContent = key;
accountCell.textContent = accountValue;
browserCell.textContent = browserValue;
});
document.getElementById('syncToBrowser').addEventListener('click', function() {
// First, clear the local storage
localStorage.clear();
// Then, set the account settings to local storage
for (let key in accountSettings) {
if(!shouldIgnoreKey(key)) { // Using our helper function
localStorage.setItem(key, accountSettings[key]);
}
}
location.reload(); // Refresh the page after sync
});
document.getElementById('syncToAccount').addEventListener('click', async function() {
/*<![CDATA[*/
const urlUpdateUserSettings = /*[[@{/api/v1/user/updateUserSettings}]]*/ "/api/v1/user/updateUserSettings";
/*]]>*/
let settings = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if(!shouldIgnoreKey(key)) { // Using our helper function
settings[key] = localStorage.getItem(key);
}
}
try {
const response = await window.fetchWithCsrf(urlUpdateUserSettings, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settings)
});
if (response.ok) {
location.reload();
} else {
alert('Error syncing settings to account');
}
} catch (error) {
console.error('Error:', error);
alert('Error syncing settings to account');
}
});
});
</script>
<th:block th:insert="~{fragments/common :: head(title=#{account.title})}"></th:block>
</head>
2023-08-13 01:12:29 +01:00
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-9 bg-card">
2024-07-04 23:13:03 +02:00
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon organize">settings_account_box</span>
<span class="tool-header-text" th:text="#{account.accountSettings}">User Settings</span>
</div>
<!-- User Settings Title -->
<th:block th:if="${messageType}">
<div class="alert alert-danger">
<span th:text="#{${messageType}}">Default message if not found</span>
</div>
</th:block>
<!-- At the top of the user settings -->
<h3 class="text-center"><span th:text="#{welcome} + ' ' + ${username}">User</span>!</h3>
<th:block th:if="${error}">
<div class="alert alert-danger" role="alert">
<span th:text="${error}">Error Message</span>
</div>
</th:block>
<!-- Change Username Form -->
SSO Refactoring (#2818) # Description of Changes * Refactoring of SSO code around OAuth & SAML 2 * Enabling auto-login with SAML 2 via the new `SSOAutoLogin` property * Correcting typos & general cleanup --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [x] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
2025-02-24 22:18:34 +00:00
<th:block th:if="not ${oAuth2Login} or not ${saml2Login}">
<h4 th:text="#{account.changeUsername}">Change Username?</h4>
<form id="formsavechangeusername" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-username'}" method="post">
<div class="mb-3">
<label for="newUsername" th:text="#{account.newUsername}">Change Username</label>
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
<span id="usernameError" style="display: none;" th:text="#{invalidUsernameMessage}">Invalid username!</span>
</div>
<div class="mb-3">
<label for="currentPasswordChangeUsername" th:text="#{password}">Password</label>
<input type="password" class="form-control" name="currentPasswordChangeUsername" id="currentPasswordChangeUsername" th:placeholder="#{password}">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary" th:text="#{account.changeUsername}">Change Username</button>
</div>
</form>
</th:block>
<!-- Change Password Form -->
SSO Refactoring (#2818) # Description of Changes * Refactoring of SSO code around OAuth & SAML 2 * Enabling auto-login with SAML 2 via the new `SSOAutoLogin` property * Correcting typos & general cleanup --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [x] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
2025-02-24 22:18:34 +00:00
<th:block th:if="not ${oAuth2Login} or not ${saml2Login}">
<h4 th:text="#{account.changePassword}">Change Password?</h4>
<form id="formsavechangepassword" class="bg-card mt-4 mb-4" th:action="@{'/api/v1/user/change-password'}" method="post">
<div class="mb-3">
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
<input type="password" class="form-control" name="currentPassword" id="currentPassword" th:placeholder="#{account.oldPassword}">
</div>
<div class="mb-3">
<label for="newPassword" th:text="#{account.newPassword}">New Password</label>
<input type="password" class="form-control" name="newPassword" id="newPassword" th:placeholder="#{account.newPassword}">
</div>
<div class="mb-3">
<label for="confirmNewPassword" th:text="#{account.confirmNewPassword}">Confirm New Password</label>
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" th:placeholder="#{account.confirmNewPassword}">
</div>
<div class="mb-3">
<span id="confirmPasswordError" style="display: none;" th:text="#{confirmPasswordErrorMessage}">New Password and Confirm New Password must match.</span>
<button type="submit" class="btn btn-primary" th:text="#{account.changePassword}">Change Password</button>
</div>
</form>
</th:block>
<!-- API Key Form -->
<h4 th:text="#{account.yourApiKey}">API Key</h4>
<div class="card mt-4 mb-4">
<div class="card-header" th:text="#{account.yourApiKey}"></div>
<div class="card-body">
<div class="input-group mb-3">
<input type="password" class="form-control" id="apiKey" th:placeholder="#{account.yourApiKey}" readonly>
<div class="input-group-append">
<button class="btn btn-secondary" id="copyBtn" type="button" onclick="copyToClipboard()">
<span class="material-symbols-rounded">
content_copy
</span>
</button>
<button class="btn btn-secondary" id="showBtn" type="button" onclick="showApiKey()">
<span class="material-symbols-rounded" id="eyeIcon">
visibility
</span>
</button>
<button class="btn btn-secondary" id="refreshBtn" type="button" onclick="refreshApiKey()">
<span class="material-symbols-rounded">
refresh
</span>
</button>
</div>
</div>
2023-08-13 01:12:29 +01:00
</div>
</div>
<script th:inline="javascript">
jQuery.validator.addMethod("usernamePattern", function(value, element) {
// Regular expression for user name: Min. 3 characters, max. 50 characters
const regexUsername = /^[a-zA-Z0-9](?!.*[-@._+]{2,})([a-zA-Z0-9@._+-]{1,48})[a-zA-Z0-9]$/;
// Regular expression for email addresses: Max. 320 characters, with RFC-like validation
const regexEmail = /^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?:\.[A-Za-z]{2,})$/;
// Check if the field is optional or meets the requirements
return this.optional(element) || regexUsername.test(value) || regexEmail.test(value);
}, /*[[#{invalidUsernameMessage}]]*/ "Invalid username format");
$(document).ready(function() {
$.validator.addMethod("passwordMatch", function(value, element) {
return $('#newPassword').val() === $('#confirmNewPassword').val();
}, /*[[#{confirmPasswordErrorMessage}]]*/ "New Password and Confirm New Password must match.");
$('#formsavechangepassword').validate({
rules: {
currentPassword: {
required: true
},
newPassword: {
required: true
},
confirmNewPassword: {
required: true,
passwordMatch: true
},
errorPlacement: function(error, element) {
if ($(element).attr("name") === "newPassword" || $(element).attr("name") === "confirmNewPassword") {
$("#confirmPasswordError").text(error.text()).show();
} else {
error.insertAfter(element);
}
},
success: function(label, element) {
if ($(element).attr("name") === "newPassword" || $(element).attr("name") === "confirmNewPassword") {
$("#confirmPasswordError").hide();
}
}
}
});
$('#formsavechangeusername').validate({
rules: {
newUsername: {
required: true,
usernamePattern: true
},
currentPasswordChangeUsername: {
required: true
}
},
messages: {
newUsername: {
usernamePattern: /*[[#{invalidUsernameMessage}]]*/ "Invalid username format"
},
},
errorPlacement: function(error, element) {
if (element.attr("name") === "newUsername") {
$("#usernameError").text(error.text()).show();
} else {
error.insertAfter(element);
}
},
success: function(label, element) {
if ($(element).attr("name") === "newUsername") {
$("#usernameError").hide();
}
}
});
});
</script>
<script th:inline="javascript">
function copyToClipboard() {
const apiKeyElement = document.getElementById("apiKey");
apiKeyElement.select();
document.execCommand("copy");
}
function showApiKey() {
const apiKeyElement = document.getElementById("apiKey");
const copyBtn = document.getElementById("copyBtn");
const eyeIcon = document.getElementById("eyeIcon");
if (apiKeyElement.type === "password") {
apiKeyElement.type = "text";
eyeIcon.textContent = "visibility_off";
copyBtn.disabled = false; // Enable copy button when API key is visible
} else {
apiKeyElement.type = "password";
eyeIcon.textContent = "visibility";
copyBtn.disabled = true; // Disable copy button when API key is hidden
}
}
document.addEventListener("DOMContentLoaded", async function() {
showApiKey();
try {
/*<![CDATA[*/
const urlGetApiKey = /*[[@{/api/v1/user/get-api-key}]]*/ "/api/v1/user/get-api-key";
/*]]>*/
2024-12-14 10:42:07 +00:00
let response = await window.fetchWithCsrf(urlGetApiKey, { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
} else {
manageUIState(null);
}
} catch (error) {
console.error('There was an error:', error);
}
});
async function refreshApiKey() {
try {
/*<![CDATA[*/
const urlUpdateApiKey = /*[[@{/api/v1/user/update-api-key}]]*/ "/api/v1/user/update-api-key";
/*]]>*/
2024-12-14 10:42:07 +00:00
let response = await window.fetchWithCsrf(urlUpdateApiKey, { method: 'POST' });
if (response.status === 200) {
let apiKey = await response.text();
manageUIState(apiKey);
document.getElementById("apiKey").type = 'text';
document.getElementById("copyBtn").disabled = false;
} else {
alert('Error refreshing API key.');
}
} catch (error) {
console.error('There was an error:', error);
}
}
function manageUIState(apiKey) {
const apiKeyElement = document.getElementById("apiKey");
const showBtn = document.getElementById("showBtn");
const copyBtn = document.getElementById("copyBtn");
if (apiKey && apiKey.trim().length > 0) {
apiKeyElement.value = apiKey;
showBtn.disabled = false;
copyBtn.disabled = false;
} else {
apiKeyElement.value = "";
showBtn.disabled = true;
copyBtn.disabled = true;
}
}
</script>
<h4 th:text="#{account.syncTitle}">Sync browser settings with Account</h4>
<div class="bg-card container mt-4">
<h3 th:text="#{account.settingsCompare}">Settings Comparison:</h3>
<table id="settingsTable" class="table table-bordered table-sm table-striped">
<thead>
<tr>
2024-07-04 23:13:03 +02:00
<th scope="col" th:text="#{account.property}">Property</th>
<th scope="col" th:text="#{account.accountSettings}">Account Setting</th>
<th scope="col" th:text="#{account.webBrowserSettings}">Web Browser Setting</th>
</tr>
</thead>
<tbody>
<!-- This will be dynamically populated by JavaScript -->
</tbody>
</table>
<div class="buttons-container mt-3 text-center">
<button id="syncToBrowser" class="btn btn-primary btn-sm" th:text="#{account.syncToBrowser}">Sync Account -> Browser</button>
<button id="syncToAccount" class="btn btn-secondary btn-sm" th:text="#{account.syncToAccount}">Sync Account <- Browser</button>
</div>
</div>
<script th:inline="javascript">
Csrf fix and ssoAutoLogin for enterprise users (#2653) This pull request includes several changes to the `SecurityConfiguration` and other related classes to enhance security and configuration management. The most important changes involve adding new beans, modifying logging levels, and updating dependency injections. Enhancements to security configuration: * [`src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java`](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L3-L36): Added new dependencies and beans for `GrantedAuthoritiesMapper`, `RelyingPartyRegistrationRepository`, and `OpenSaml4AuthenticationRequestResolver`. Removed unused imports and simplified the class by removing the `@Lazy` annotation from `UserService`. [[1]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L3-L36) [[2]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L46-L63) [[3]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L75-R52) [[4]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4R66-L98) [[5]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L109-R85) [[6]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4R96-R98) Logging improvements: * [`src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java`](diffhunk://#diff-742f789731a32cb5aa20f7067ef18049002eec2a4909ef6f240d2a26bdcb53c4L97-R97): Changed the logging level from `info` to `debug` for the license validation response body to reduce log verbosity in production. Configuration updates: * [`src/main/java/stirling/software/SPDF/EE/EEAppConfig.java`](diffhunk://#diff-d842c2a4cf43f37ab5edcd644b19a51d614cb0e39963789e1c7e9fb28ddc1de8R30-R34): Added a new bean `ssoAutoLogin` to manage single sign-on auto-login configuration in the enterprise edition. These changes collectively enhance the security configuration and logging management of the application. Please provide a summary of the changes, including relevant motivation and context. Closes #(issue_number) ## Checklist - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have performed a self-review of my own code - [ ] I have attached images of the change if it is UI based - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) - [ ] My changes generate no new warnings - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
2025-01-09 14:40:51 +00:00
document.addEventListener("DOMContentLoaded", async function() {
const settingsTableBody = document.querySelector("#settingsTable tbody");
/*<![CDATA[*/
var accountSettingsString = /*[[${settings}]]*/ {};
/*]]>*/
var accountSettings = JSON.parse(accountSettingsString);
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
allKeys.forEach(key => {
2024-10-14 22:34:41 +01:00
if(key === 'debug' || key === '0' || key === '1' || key.includes('pdfjs') || key.includes('posthog') || key.includes('pageViews')) return; // Ignoring specific keys
const accountValue = accountSettings[key] || '-';
const browserValue = localStorage.getItem(key) || '-';
const row = settingsTableBody.insertRow();
const propertyCell = row.insertCell(0);
const accountCell = row.insertCell(1);
const browserCell = row.insertCell(2);
propertyCell.textContent = key;
accountCell.textContent = accountValue;
browserCell.textContent = browserValue;
});
document.getElementById('syncToBrowser').addEventListener('click', function() {
// First, clear the local storage
localStorage.clear();
// Then, set the account settings to local storage
for (let key in accountSettings) {
2024-10-14 22:34:41 +01:00
if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) { // Only sync non-ignored keys
localStorage.setItem(key, accountSettings[key]);
}
}
location.reload(); // Refresh the page after sync
});
Csrf fix and ssoAutoLogin for enterprise users (#2653) This pull request includes several changes to the `SecurityConfiguration` and other related classes to enhance security and configuration management. The most important changes involve adding new beans, modifying logging levels, and updating dependency injections. Enhancements to security configuration: * [`src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java`](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L3-L36): Added new dependencies and beans for `GrantedAuthoritiesMapper`, `RelyingPartyRegistrationRepository`, and `OpenSaml4AuthenticationRequestResolver`. Removed unused imports and simplified the class by removing the `@Lazy` annotation from `UserService`. [[1]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L3-L36) [[2]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L46-L63) [[3]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L75-R52) [[4]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4R66-L98) [[5]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4L109-R85) [[6]](diffhunk://#diff-49df1b16b72e9fcaa7d0c58f46c94ffda0033f5f5e3ddab90a88e2f9022b66f4R96-R98) Logging improvements: * [`src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java`](diffhunk://#diff-742f789731a32cb5aa20f7067ef18049002eec2a4909ef6f240d2a26bdcb53c4L97-R97): Changed the logging level from `info` to `debug` for the license validation response body to reduce log verbosity in production. Configuration updates: * [`src/main/java/stirling/software/SPDF/EE/EEAppConfig.java`](diffhunk://#diff-d842c2a4cf43f37ab5edcd644b19a51d614cb0e39963789e1c7e9fb28ddc1de8R30-R34): Added a new bean `ssoAutoLogin` to manage single sign-on auto-login configuration in the enterprise edition. These changes collectively enhance the security configuration and logging management of the application. Please provide a summary of the changes, including relevant motivation and context. Closes #(issue_number) ## Checklist - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have performed a self-review of my own code - [ ] I have attached images of the change if it is UI based - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] If my code has heavily changed functionality I have updated relevant docs on [Stirling-PDFs doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) - [ ] My changes generate no new warnings - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
2025-01-09 14:40:51 +00:00
document.getElementById('syncToAccount').addEventListener('click', async function() {
/*<![CDATA[*/
const urlUpdateUserSettings = /*[[@{/api/v1/user/updateUserSettings}]]*/ "/api/v1/user/updateUserSettings";
/*]]>*/
let settings = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) {
settings[key] = localStorage.getItem(key);
}
}
try {
const response = await window.fetchWithCsrf(urlUpdateUserSettings, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settings)
});
if (response.ok) {
location.reload();
} else {
alert('Error syncing settings to account');
}
} catch (error) {
console.error('Error:', error);
alert('Error syncing settings to account');
}
});
});
</script>
<div class="mb-3 mt-4 text-center">
<a th:href="@{'/logout'}" role="button" class="btn btn-danger" th:text="#{account.signOut}">Sign Out</a>
Security fixes, enterprise stuff and more (#3241) # Description of Changes Please provide a summary of the changes, including: - Enable user to add custom JAVA ops with env JAVA_CUSTOM_OPTS - Added support for prometheus (enabled via JAVA_CUSTOM_OPTS + enterprise license) - Changed settings from enterprise naming to 'Premium' - KeygenLicense Check to support offline licenses - Disable URL-to-PDF due to huge security bug - Remove loud Split PDF logs - addUsers renamed to adminSettings - Added Usage analytics page - Add user button to only be enabled based on total users free - Improve Merge memory usage Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: a <a> Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> Co-authored-by: Connor Yoh <con.yoh13@gmail.com>
2025-03-25 17:57:17 +00:00
<a th:if="${role == 'ROLE_ADMIN'}" class="btn btn-info" th:href="@{'/adminSettings'}" role="button" th:text="#{account.adminSettings}" target="_blank">Admin Settings</a>
</div>
2023-08-13 01:12:29 +01:00
</div>
</div>
2023-08-13 01:12:29 +01:00
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
2023-08-13 01:12:29 +01:00
</div>
</body>
</html>