Skip to content

Commit

Permalink
Merge branch 'release/3.1.3.14'
Browse files Browse the repository at this point in the history
  • Loading branch information
nilsteampassnet committed Feb 24, 2025
2 parents 966575a + 47194de commit 2629ab8
Show file tree
Hide file tree
Showing 38 changed files with 2,370 additions and 631 deletions.
2 changes: 1 addition & 1 deletion includes/config/include.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

define('TP_VERSION', '3.1.3');
define("UPGRADE_MIN_DATE", "1732981987");
define('TP_VERSION_MINOR', '13');
define('TP_VERSION_MINOR', '14');
define('TP_TOOL_NAME', 'Teampass');
define('TP_ONE_DAY_SECONDS', 86400);
define('TP_ONE_WEEK_SECONDS', 604800);
Expand Down
164 changes: 94 additions & 70 deletions includes/core/load.js.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,47 @@
// Switch light/dark theme button
$('#switch-theme').on('click', function() {
applyTheme(true);
});

// Select all objects with the class .fa-clickable-login
document.querySelectorAll('.clipboard-copy').forEach(element => {
element.addEventListener('click', async function() {
try {
// Retrieve the target defined by clipboard-target
const targetId = this.getAttribute('clipboard-target');
if (!targetId) {
return; // Stop if no target ID is defined
}

// Retrieve the value of the target field
const targetElement = document.getElementById(targetId);
if (!targetElement || !targetElement.textContent) {
return; // Stop if the target element or its value is empty
}

// Copy the value to the clipboard
await navigator.clipboard.writeText(targetElement.textContent);

// Send success message
toastr.info(
'<?php echo $lang->get("copy_to_clipboard"); ?>',
'', {
timeOut: 2000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
} catch (error) {
toastr.error(
'<?php echo $lang->get("clipboard_error"); ?>',
'', {
timeOut: 3000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
}
});
});
});

Expand Down Expand Up @@ -888,25 +929,6 @@ function(data) {
$('#sidebar-footer').addClass('hidden');
}
});


var clipboardCopy = new ClipboardJS(".clipboard-copy", {
text: function(trigger) {
var elementId = $(trigger).data('clipboard-text');
if (debugJavascript === true) console.log($('#' + elementId).val())
return String($('#' + elementId).val());
}
});

clipboardCopy.on('success', function(e) {
toastr.remove();
toastr.info(
'<?php echo $lang->get('copy_to_clipboard'); ?>',
'<?php echo $lang->get('information'); ?>', {
timeOut: 2000
}
);
});

// Progress bar
setTimeout(
Expand Down Expand Up @@ -2090,61 +2112,63 @@ function hashUserId(userId) {
* @param {string} id_type - 'item_key' or 'item_id'.
* @param {number|string} id_value - The item key or id.
*
* @returns {string} - The item cleartext password if user has access.
* @returns {Promise<string>} - A promise that resolves to the item cleartext password if user has access.
*/
function getItemPassword(action, id_type, id_value) {
let item_password = '';

// Get password from server
$.ajax({
type: "POST",
async: false,
url: 'sources/items.queries.php',
data: 'type=get_item_password&action=' + action + '&' + id_type +
'=' + id_value + '&key=<?php echo $session->get('key'); ?>',
dataType: "",
success: function(data) {
//decrypt data
try {
data = prepareExchangedData(data, "decode", "<?php echo $session->get('key'); ?>");
} catch (e) {
// error
toastr.remove();
toastr.warning(
'<?php echo $lang->get('no_item_to_display'); ?>'
);
return false;
}
async function getItemPassword(action, id_type, id_value) {
const key = "<?php echo $session->get('key'); ?>";
const lang = "<?php echo $lang->get('no_item_to_display'); ?>";

try {
const response = await $.ajax({
type: "POST",
url: 'sources/items.queries.php',
data: `type=get_item_password&action=${action}&${id_type}=${id_value}&key=${key}`
});

// No access
if (data.password_error !== '') {
toastr.remove();
toastr.error(
data.password_error,
'<?php echo $lang->get('caution'); ?>', {
timeOut: 5000,
progressBar: true
}
);
return false;
}
// Decrypt data
let data;
try {
data = prepareExchangedData(response, "decode", key);
} catch (e) {
toastr.remove();
toastr.warning(lang);
return '';
}

const password = simplePurifier(atob(data.password), false, false, false, false).utf8Decode();
if (password === '') {
toastr.info(
'<?php echo $lang->get('password_is_empty'); ?>',
'', {
timeOut: 2000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
}
// No access
if (data.password_error) {
toastr.remove();
toastr.error(data.password_error, '<?php echo $lang->get('caution'); ?>', {
timeOut: 5000,
progressBar: true
});
return '';
}

item_password = password;
const password = simplePurifier(atob(data.password), false, false, false, false).utf8Decode();
if (password === '') {
toastr.info(
'<?php echo $lang->get('password_is_empty'); ?>',
'', {
timeOut: 2000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
return '';
}
});

return item_password;
return password;
} catch (error) {
console.error('Error fetching the password:', error);
toastr.error(
'<?php echo $lang->get("an_error_occurred"); ?>',
'', {
timeOut: 5000,
progressBar: true
}
);
return '';
}
}
</script>
</script>
178 changes: 178 additions & 0 deletions includes/js/secure-clipboard-cleaner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* Teampass - a collaborative passwords manager.
* ---
* This file is part of the TeamPass project.
*
* TeamPass is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* TeamPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Certain components of this file may be under different licenses. For
* details, see the `licenses` directory or individual file headers.
* ---
* @file secure-clipboard-cleaner.js
* @author Nils Laumaillé ([email protected])
* @copyright 2009-2024 Teampass.net
* @license GPL-3.0
* @see https://www.teampass.net
*/

class ClipboardCleaner {
constructor(duration) {
this.duration = duration * 1000; // Convert to milliseconds
this.clearTimeout = null;
this.clearingTime = null;
this.originalText = '';
this.maxRetries = 8;
this.retryDelay = 1000; // 1 second between each attempt
this.retryDuration = this.maxRetries * this.retryDelay;
this.manualClearButton = null;
}

scheduleClearing(onSuccess, onError) {
this.clearingTime = Date.now() + this.duration;

this.clearTimeout = setTimeout(() => {
this.attemptClearing(this.maxRetries, onSuccess, onError);
}, this.duration);
}

async attemptClearing(retriesLeft, onSuccess, onError) {
try {
await navigator.clipboard.writeText('');
if (onSuccess) {
// Remove existing messages
toastr.remove();

// Save clipboard status as safe
localStorage.setItem('clipboardStatus', JSON.stringify({
status: 'safe',
timestamp: Date.now()
}));

// Call success callback
onSuccess();
}
} catch (error) {
if (retriesLeft === this.maxRetries) {
// Show a message indicating the initial failure
toastr.warning(
`${TRANSLATIONS_CLIPBOARD.clipboard_clearing_failed}`,
'', {
timeOut: this.retryDuration,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
}

if (retriesLeft > 0) {
// Retry clearing after a delay
setTimeout(() => {
this.attemptClearing(retriesLeft - 1, onSuccess, onError);
}, this.retryDelay);
} else {
// If all attempts fail, show the button
this.useFallbackApproach(onSuccess, onError);
}
}
}

useFallbackApproach(onSuccess, onError) {
try {
toastr.remove(); // Remove all notifications

// Mark clipboard as unsafe
this.markClipboardAsUnsafe();

// Create an HTML container for the message and button
const container = document.createElement('div');
container.innerHTML = `
<div>
<p>${TRANSLATIONS_CLIPBOARD.clipboard_unsafe}</p>
<button id="manual-clear-btn" class="btn btn-warning">${TRANSLATIONS_CLIPBOARD.clipboard_clear_now}</button>
</div>
`;

// Show the notification with the button
toastr.warning(container.innerHTML, '', {
timeOut: 0, // Keep the toastr active until an action
positionClass: 'toast-bottom-right',
progressBar: false
});

// Add an event listener to the button after inserting it into the DOM
document.getElementById('manual-clear-btn').addEventListener('click', async () => {
toastr.remove(); // Remove all notifications
try {
// Attempt to clear the clipboard
await navigator.clipboard.writeText('');
toastr.success(
`${TRANSLATIONS_CLIPBOARD.clipboard_cleared}`,
'', {
timeOut: 2000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
if (onSuccess) onSuccess();
} catch (error) {
toastr.error(
`${TRANSLATIONS_CLIPBOARD.unable_to_clear_clipboard}`,
'', {
timeOut: 5000,
positionClass: 'toast-bottom-right',
progressBar: true
}
);
if (onError) onError(error);
}
});

} catch (error) {
if (onError) onError(error);
}
}

markClipboardAsUnsafe() {
try {
// Save clipboard status as unsafe
localStorage.setItem('clipboardStatus', JSON.stringify({
status: 'unsafe',
timestamp: Date.now()
}));
} catch (error) {
return;
}
}

cleanup() {
if (this.clearTimeout) {
clearTimeout(this.clearTimeout);
}
}

static isClipboardSafe() {
try {
const clipboardStatus = localStorage.getItem('clipboardStatus');
if (clipboardStatus) {
const status = JSON.parse(clipboardStatus);
// Consider the clipboard safe if it was cleared less than an hour ago
if (Date.now() - status.timestamp < 3600000) {
return status.status === 'safe';
}
}
return true;
} catch {
return false;
}
}
}
Loading

0 comments on commit 2629ab8

Please sign in to comment.