`;
@@ -74,6 +81,15 @@ async function UIWindowChangeEmail (options) {
show_in_taskbar: false,
onAppend: function (this_window) {
$(this_window).find('.new-email').get(0)?.focus({ preventScroll: true });
+ const oidc_only = !!(window.user && window.user.oidc_only);
+ const authRow = $(this_window).find('.change-email-auth-row');
+ if ( oidc_only ) {
+ authRow.find('.change-email-password-wrap').hide();
+ const oidcWrap = authRow.find('.change-email-oidc-wrap').show();
+ oidcWrap.find('.change-email-revalidate-btn').text(i18n('revalidate_with_google') || 'Re-validate with Google');
+ } else {
+ authRow.find('.change-email-oidc-wrap').hide();
+ }
},
window_class: 'window-publishWebsite',
body_css: {
@@ -87,12 +103,16 @@ async function UIWindowChangeEmail (options) {
password_entry.attach(place_password_entry);
- $(el_window).find('.change-email-btn').on('click', function (e) {
- // hide previous error/success msg
- $(el_window).find('.form-success-msg, .form-success-msg').hide();
+ const origin = window.gui_origin || window.api_origin || '';
+ const apiUrl = `${origin}/user-protected/change-email`;
+ let revalidated = false;
+
+ $(el_window).find('.change-email-btn').on('click', async function (e) {
+ $(el_window).find('.form-success-msg, .form-error-msg').hide();
const new_email = $(el_window).find('.new-email').val();
- const password = $(el_window).find('.password').val();
+ const password = password_entry.get('value');
+ const oidc_only = !!(window.user && window.user.oidc_only);
if ( ! new_email ) {
$(el_window).find('.form-error-msg').html(i18n('all_fields_required'));
@@ -101,45 +121,118 @@ async function UIWindowChangeEmail (options) {
}
$(el_window).find('.form-error-msg').hide();
-
- // disable button
$(el_window).find('.change-email-btn').addClass('disabled');
- // disable input
$(el_window).find('.new-email').attr('disabled', true);
- $.ajax({
- url: `${window.gui_origin}/user-protected/change-email`,
- type: 'POST',
- async: true,
- // headers: {
- // 'Authorization': `Bearer ${window.auth_token}`,
- // },
- contentType: 'application/json',
- data: JSON.stringify({
- new_email: new_email,
- password: password_entry.get('value'),
+ const doSubmit = () => fetch(apiUrl, {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ new_email,
+ password: password !== undefined && password !== '' ? password : undefined,
}),
- success: function (data) {
- $(el_window).find('.form-success-msg').html(i18n('email_change_confirmation_sent'));
- $(el_window).find('.form-success-msg').fadeIn();
- $(el_window).find('input').val('');
- // update email
- window.user.email = new_email;
- // enable button
- $(el_window).find('.change-email-btn').removeClass('disabled');
- // enable input
- $(el_window).find('.new-email').attr('disabled', false);
- },
- error: function (err) {
- $(el_window).find('.form-error-msg').html(html_encode(err.responseJSON?.message));
+ });
+
+ if ( oidc_only && !revalidated && !password ) {
+ openRevalidatePopup(null, async (err) => {
+ if ( err ) {
+ onError(err.message || 'Re-validation required.');
+ return;
+ }
+ const res = await doSubmit();
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+ if ( res.ok ) onSuccess();
+ else onError(data.message || 'Request failed');
+ });
+ return;
+ }
+
+ let res = await doSubmit();
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+
+ if ( res.ok ) {
+ onSuccess();
+ return;
+ }
+ if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
+ openRevalidatePopup(data.revalidate_url, async (err) => {
+ if ( err ) {
+ onError(err.message || 'Re-validation required.');
+ return;
+ }
+ const r2 = await doSubmit();
+ const d2 = r2.ok ? await r2.json().catch(() => ({})) : await r2.json().catch(() => ({}));
+ if ( r2.ok ) onSuccess();
+ else onError(d2.message || 'Request failed');
+ });
+ return;
+ }
+ onError(data.message || 'Request failed');
+ });
+
+ function openRevalidatePopup (revalidateUrl, onDone) {
+ const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ if ( ! url ) {
+ onDone && onDone(new Error('No revalidate URL'));
+ return null;
+ }
+ let doneCalled = false;
+ const hint = $(el_window).find('.change-email-oidc-hint');
+ hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
+ const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
+ const onMessage = (ev) => {
+ if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
+ if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
+ if ( doneCalled ) return;
+ doneCalled = true;
+ window.removeEventListener('message', onMessage);
+ revalidated = true;
+ hint.hide();
+ $(el_window).find('.change-email-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ $(el_window).find('.change-email-revalidate-btn').hide();
+ onDone && onDone();
+ };
+ window.addEventListener('message', onMessage);
+ const checkClosed = setInterval(() => {
+ if ( popup && popup.closed ) {
+ clearInterval(checkClosed);
+ window.removeEventListener('message', onMessage);
+ hint.hide();
+ if ( ! doneCalled ) {
+ doneCalled = true;
+ onDone && onDone(new Error('Popup closed'));
+ }
+ }
+ }, 300);
+ return popup;
+ }
+
+ $(el_window).find('.change-email-revalidate-btn').on('click', function () {
+ openRevalidatePopup(null, (err) => {
+ if ( err ) {
+ $(el_window).find('.form-error-msg').html(html_encode(err.message || 'Re-validation required.'));
$(el_window).find('.form-error-msg').fadeIn();
- // enable button
- $(el_window).find('.change-email-btn').removeClass('disabled');
- // enable input
- $(el_window).find('.new-email').attr('disabled', false);
- },
+ }
});
});
+
+ function onError (message) {
+ $(el_window).find('.form-error-msg').html(html_encode(message));
+ $(el_window).find('.form-error-msg').fadeIn();
+ $(el_window).find('.change-email-btn').removeClass('disabled');
+ $(el_window).find('.new-email').attr('disabled', false);
+ }
+
+ function onSuccess () {
+ const new_email = $(el_window).find('.new-email').val();
+ $(el_window).find('.form-success-msg').html(i18n('email_change_confirmation_sent'));
+ $(el_window).find('.form-success-msg').fadeIn();
+ $(el_window).find('input').val('');
+ window.user.email = new_email;
+ $(el_window).find('.change-email-btn').removeClass('disabled');
+ $(el_window).find('.new-email').attr('disabled', false);
+ }
}
export default UIWindowChangeEmail;
\ No newline at end of file
diff --git a/src/gui/src/UI/UIWindowChangePassword.js b/src/gui/src/UI/UIWindowChangePassword.js
index db210ea09..27d99e090 100644
--- a/src/gui/src/UI/UIWindowChangePassword.js
+++ b/src/gui/src/UI/UIWindowChangePassword.js
@@ -17,8 +17,8 @@
* along with this program. If not, see
.
*/
-import UIWindow from './UIWindow.js';
import check_password_strength from '../helpers/check_password_strength.js';
+import UIWindow from './UIWindow.js';
async function UIWindowChangePassword (options) {
options = options ?? {};
@@ -30,11 +30,17 @@ async function UIWindowChangePassword (options) {
h += '
';
+ // current password / OIDC revalidate
+ h += '
';
+ h += '
';
h += ``;
h += ``;
h += '
';
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ h += '
';
// new password
h += '
';
h += ``;
@@ -45,6 +51,7 @@ async function UIWindowChangePassword (options) {
h += ``;
h += ``;
h += '
';
+ h += '
';
// Change Password
h += `
`;
@@ -72,7 +79,16 @@ async function UIWindowChangePassword (options) {
dominant: true,
show_in_taskbar: false,
onAppend: function (this_window) {
- $(this_window).find('.current-password').get(0).focus({ preventScroll: true });
+ $(this_window).find('.current-password').get(0)?.focus({ preventScroll: true });
+ const oidc_only = !!(window.user && window.user.oidc_only);
+ const authRow = $(this_window).find('.change-password-auth-row');
+ if ( oidc_only ) {
+ authRow.find('.change-password-current-wrap').hide();
+ const oidcWrap = authRow.find('.change-password-oidc-wrap').show();
+ oidcWrap.find('.change-password-revalidate-btn').text(i18n('revalidate_with_google') || 'Re-validate with Google');
+ } else {
+ authRow.find('.change-password-oidc-wrap').hide();
+ }
},
window_class: 'window-publishWebsite',
body_css: {
@@ -84,27 +100,47 @@ async function UIWindowChangePassword (options) {
...options.window_options,
});
- $(el_window).find('.change-password-btn').on('click', function (e) {
+ const origin = window.gui_origin || window.api_origin || '';
+ const apiUrl = `${origin}/user-protected/change-password`;
+
+ $(el_window).find('.change-password-btn').on('click', async function (e) {
const current_password = $(el_window).find('.current-password').val();
const new_password = $(el_window).find('.new-password').val();
const confirm_new_password = $(el_window).find('.confirm-new-password').val();
+ const oidc_only = !!(window.user && window.user.oidc_only);
- // hide success message
- $(el_window).find('.form-success-msg').hide();
+ $(el_window).find('.form-success-msg, .form-error-msg').hide();
- // check if all fields are filled
- if ( !current_password || !new_password || !confirm_new_password ) {
+ if ( !new_password || !confirm_new_password ) {
$(el_window).find('.form-error-msg').html('All fields are required.');
$(el_window).find('.form-error-msg').fadeIn();
return;
}
- // check if new password and confirm new password are the same
- else if ( new_password !== confirm_new_password ) {
+ // For password users, current password is required; for OIDC, we need revalidated or will open popup
+ if ( !oidc_only && !current_password ) {
+ $(el_window).find('.form-error-msg').html('All fields are required.');
+ $(el_window).find('.form-error-msg').fadeIn();
+ return;
+ }
+ if ( oidc_only && !revalidated && !current_password ) {
+ $(el_window).find('.change-password-btn').addClass('disabled');
+ openRevalidatePopup(null, async (err) => {
+ if ( err ) {
+ onError(err.message || 'Re-validation required.');
+ return;
+ }
+ const res = await doSubmit('');
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+ if ( res.ok ) onSuccess();
+ else onError(data.message || 'Request failed');
+ });
+ return;
+ }
+ if ( new_password !== confirm_new_password ) {
$(el_window).find('.form-error-msg').html(i18n('passwords_do_not_match'));
$(el_window).find('.form-error-msg').fadeIn();
return;
}
- // check password strength
const pass_strength = check_password_strength(new_password);
if ( ! pass_strength.overallPass ) {
$(el_window).find('.form-error-msg').html(i18n('password_strength_error'));
@@ -113,29 +149,102 @@ async function UIWindowChangePassword (options) {
}
$(el_window).find('.form-error-msg').hide();
+ $(el_window).find('.change-password-btn').addClass('disabled');
+ $(el_window).find('.current-password, .new-password, .confirm-new-password').attr('disabled', true);
- // Do not send Authorization: user-protected endpoints use session cookie (hasHttpPowers)
- $.ajax({
- url: `${window.api_origin }/user-protected/change-password`,
- type: 'POST',
- async: true,
- xhrFields: { withCredentials: true },
- contentType: 'application/json',
- data: JSON.stringify({
- password: current_password,
+ const doSubmit = (currentPass) => fetch(apiUrl, {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ password: currentPass !== undefined ? currentPass : current_password,
new_pass: new_password,
}),
- success: function (data) {
- $(el_window).find('.form-success-msg').html(i18n('password_changed'));
- $(el_window).find('.form-success-msg').fadeIn();
- $(el_window).find('input').val('');
- },
- error: function (err) {
- $(el_window).find('.form-error-msg').html(html_encode(err.responseText));
- $(el_window).find('.form-error-msg').fadeIn();
- },
+ });
+
+ let res = await doSubmit(current_password);
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+
+ if ( res.ok ) {
+ onSuccess();
+ return;
+ }
+ if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
+ openRevalidatePopup(data.revalidate_url, async (err) => {
+ if ( err ) {
+ onError(err.message || 'Re-validation required.');
+ return;
+ }
+ const r2 = await doSubmit('');
+ const d2 = r2.ok ? await r2.json().catch(() => ({})) : await r2.json().catch(() => ({}));
+ if ( r2.ok ) onSuccess();
+ else onError(d2.message || 'Request failed');
+ });
+ return;
+ }
+ onError(data.message || res.statusText || 'Request failed');
+ });
+ let revalidated = false;
+
+ function openRevalidatePopup (revalidateUrl, onDone) {
+ const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ if ( ! url ) {
+ onDone && onDone(new Error('No revalidate URL'));
+ return null;
+ }
+ let doneCalled = false;
+ const hint = $(el_window).find('.change-password-oidc-hint');
+ hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
+ const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
+ const onMessage = (ev) => {
+ if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
+ if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
+ if ( doneCalled ) return;
+ doneCalled = true;
+ window.removeEventListener('message', onMessage);
+ revalidated = true;
+ hint.hide();
+ $(el_window).find('.change-password-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ $(el_window).find('.change-password-revalidate-btn').hide();
+ onDone && onDone();
+ };
+ window.addEventListener('message', onMessage);
+ const checkClosed = setInterval(() => {
+ if ( popup && popup.closed ) {
+ clearInterval(checkClosed);
+ window.removeEventListener('message', onMessage);
+ hint.hide();
+ if ( ! doneCalled ) {
+ doneCalled = true;
+ onDone && onDone(new Error('Popup closed'));
+ }
+ }
+ }, 300);
+ return popup;
+ }
+
+ $(el_window).find('.change-password-revalidate-btn').on('click', function () {
+ openRevalidatePopup(null, (err) => {
+ if ( err ) {
+ onError(err.message || 'Re-validation required.');
+ }
});
});
+
+ function onError (message) {
+ $(el_window).find('.form-error-msg').html(html_encode(message));
+ $(el_window).find('.form-error-msg').fadeIn();
+ $(el_window).find('.change-password-btn').removeClass('disabled');
+ $(el_window).find('.current-password, .new-password, .confirm-new-password').attr('disabled', false);
+ }
+
+ function onSuccess () {
+ $(el_window).find('.form-success-msg').html(i18n('password_changed'));
+ $(el_window).find('.form-success-msg').fadeIn();
+ $(el_window).find('input').val('');
+ $(el_window).find('.change-password-btn').removeClass('disabled');
+ $(el_window).find('.current-password, .new-password, .confirm-new-password').attr('disabled', false);
+ }
}
export default UIWindowChangePassword;
\ No newline at end of file
From df1f5c44cc2910a4728ad1cbde6b0d2255d437e5 Mon Sep 17 00:00:00 2001
From: KernelDeimos <7225168+KernelDeimos@users.noreply.github.com>
Date: Thu, 12 Feb 2026 18:27:01 -0500
Subject: [PATCH 09/39] refactor(oidc): extract common (email + username)
There is common functionality between all of the GUI code for actions on
protected endpoints. Update UIWindowChangeEmail and
UIWindowChangeUsername to both use a new utility function called
openRevalidatePopup in util/openid.js.
This file is called `openid.js` instead of `oidc.js` so that it's more
easily recognized by contributors who might be more familiar with the
name of the organization than the name of the standard itself.
After these changes, UIWindowChangePassword and the "disable 2FA" button
in UITabSecurity still need to be updated to use `util/openid.js`
instead of duplicating this functionality.
The justification for following DRY here instead of leaving the
implementation as-is is because these flows are particularly error
prone and will be difficult to maintain without this consistency. Some
subtle bugs I previously wasn't aware of got fixed in the process.
---
.../src/UI/Settings/UIWindowChangeEmail.js | 129 +++++++-----------
src/gui/src/UI/UIWindowChangeUsername.js | 84 +++++-------
src/gui/src/util/openid.js | 61 +++++++++
3 files changed, 141 insertions(+), 133 deletions(-)
create mode 100644 src/gui/src/util/openid.js
diff --git a/src/gui/src/UI/Settings/UIWindowChangeEmail.js b/src/gui/src/UI/Settings/UIWindowChangeEmail.js
index 3ba7398bc..abbbe6aa0 100644
--- a/src/gui/src/UI/Settings/UIWindowChangeEmail.js
+++ b/src/gui/src/UI/Settings/UIWindowChangeEmail.js
@@ -17,6 +17,7 @@
* along with this program. If not, see
.
*/
+import { openRevalidatePopup } from '../../util/openid.js';
import Placeholder from '../../util/Placeholder.js';
import PasswordEntry from '../Components/PasswordEntry.js';
import UIWindow from '../UIWindow.js';
@@ -107,6 +108,25 @@ async function UIWindowChangeEmail (options) {
const apiUrl = `${origin}/user-protected/change-email`;
let revalidated = false;
+ const hint = $(el_window).find('.change-email-oidc-hint');
+ const REVALIDATE_POPUP_TEXT = i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.';
+
+ const myOpenRevalidatePopup = async (revalidateUrl) => {
+ revalidateUrl = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ $(el_window).find('.change-email-btn').addClass('disabled');
+ hint.text(REVALIDATE_POPUP_TEXT).show();
+ try {
+ await openRevalidatePopup(revalidateUrl);
+ } catch (e) {
+ onError(e.message || 'Authentication failed');
+ return;
+ } finally {
+ hint.hide();
+ }
+ $(el_window).find('.change-email-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ $(el_window).find('.change-email-revalidate-btn').hide();
+ };
+
$(el_window).find('.change-email-btn').on('click', async function (e) {
$(el_window).find('.form-success-msg, .form-error-msg').hide();
@@ -120,11 +140,38 @@ async function UIWindowChangeEmail (options) {
return;
}
+ if ( oidc_only && !revalidated && !password ) {
+ await myOpenRevalidatePopup();
+
+ const res = await doSubmit({ new_email });
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+ if ( res.ok ) onSuccess();
+ else onError(data.message || 'Request failed');
+ return;
+ }
$(el_window).find('.form-error-msg').hide();
$(el_window).find('.change-email-btn').addClass('disabled');
$(el_window).find('.new-email').attr('disabled', true);
- const doSubmit = () => fetch(apiUrl, {
+ let res = await doSubmit({ new_email, password });
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+
+ if ( res.ok ) {
+ onSuccess();
+ return;
+ }
+ if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
+ await myOpenRevalidatePopup(data.revalidate_url);
+ const r = await doSubmit();
+ if ( r.ok ) onSuccess();
+ else r.json().then((d) => onError(d.message || 'Request failed')).catch(() => onError('Request failed'));
+ return;
+ }
+ onError(data.message || 'Request failed');
+ });
+
+ function doSubmit ({ new_email, password }) {
+ return fetch(apiUrl, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
@@ -133,88 +180,10 @@ async function UIWindowChangeEmail (options) {
password: password !== undefined && password !== '' ? password : undefined,
}),
});
-
- if ( oidc_only && !revalidated && !password ) {
- openRevalidatePopup(null, async (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- return;
- }
- const res = await doSubmit();
- const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
- if ( res.ok ) onSuccess();
- else onError(data.message || 'Request failed');
- });
- return;
- }
-
- let res = await doSubmit();
- const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
-
- if ( res.ok ) {
- onSuccess();
- return;
- }
- if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
- openRevalidatePopup(data.revalidate_url, async (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- return;
- }
- const r2 = await doSubmit();
- const d2 = r2.ok ? await r2.json().catch(() => ({})) : await r2.json().catch(() => ({}));
- if ( r2.ok ) onSuccess();
- else onError(d2.message || 'Request failed');
- });
- return;
- }
- onError(data.message || 'Request failed');
- });
-
- function openRevalidatePopup (revalidateUrl, onDone) {
- const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
- if ( ! url ) {
- onDone && onDone(new Error('No revalidate URL'));
- return null;
- }
- let doneCalled = false;
- const hint = $(el_window).find('.change-email-oidc-hint');
- hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
- const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
- const onMessage = (ev) => {
- if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
- if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
- if ( doneCalled ) return;
- doneCalled = true;
- window.removeEventListener('message', onMessage);
- revalidated = true;
- hint.hide();
- $(el_window).find('.change-email-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
- $(el_window).find('.change-email-revalidate-btn').hide();
- onDone && onDone();
- };
- window.addEventListener('message', onMessage);
- const checkClosed = setInterval(() => {
- if ( popup && popup.closed ) {
- clearInterval(checkClosed);
- window.removeEventListener('message', onMessage);
- hint.hide();
- if ( ! doneCalled ) {
- doneCalled = true;
- onDone && onDone(new Error('Popup closed'));
- }
- }
- }, 300);
- return popup;
}
$(el_window).find('.change-email-revalidate-btn').on('click', function () {
- openRevalidatePopup(null, (err) => {
- if ( err ) {
- $(el_window).find('.form-error-msg').html(html_encode(err.message || 'Re-validation required.'));
- $(el_window).find('.form-error-msg').fadeIn();
- }
- });
+ myOpenRevalidatePopup();
});
function onError (message) {
diff --git a/src/gui/src/UI/UIWindowChangeUsername.js b/src/gui/src/UI/UIWindowChangeUsername.js
index 4f77ac0a8..370f5c2b3 100644
--- a/src/gui/src/UI/UIWindowChangeUsername.js
+++ b/src/gui/src/UI/UIWindowChangeUsername.js
@@ -18,6 +18,7 @@
*/
import update_username_in_gui from '../helpers/update_username_in_gui.js';
+import { openRevalidatePopup } from '../util/openid.js';
import UIWindow from './UIWindow.js';
async function UIWindowChangeUsername (options) {
@@ -93,6 +94,25 @@ async function UIWindowChangeUsername (options) {
const apiUrl = `${origin}/user-protected/change-username`;
let revalidated = false;
+ const hint = $(el_window).find('.change-username-oidc-hint');
+ const REVALIDATE_POPUP_TEXT = i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.';
+
+ const myOpenRevalidatePopup = async (revalidateUrl) => {
+ revalidateUrl = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ $(el_window).find('.change-username-btn').addClass('disabled');
+ hint.text(REVALIDATE_POPUP_TEXT).show();
+ try {
+ await openRevalidatePopup(revalidateUrl);
+ } catch (e) {
+ onError(e.message || 'Authentication failed');
+ return;
+ } finally {
+ hint.hide();
+ }
+ $(el_window).find('.change-username-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ $(el_window).find('.change-username-revalidate-btn').hide();
+ };
+
$(el_window).find('.change-username-btn').on('click', async function (e) {
$(el_window).find('.form-success-msg, .form-error-msg').hide();
const new_username = $(el_window).find('.new-username').val();
@@ -106,16 +126,12 @@ async function UIWindowChangeUsername (options) {
}
if ( oidc_only && !revalidated && !password ) {
$(el_window).find('.change-username-btn').addClass('disabled');
- openRevalidatePopup(null, async (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- return;
- }
- const res = await doSubmit();
- const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
- if ( res.ok ) onSuccess();
- else onError(data.message || 'Request failed');
- });
+ myOpenRevalidatePopup();
+
+ const res = await doSubmit();
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+ if ( res.ok ) onSuccess();
+ else onError(data.message || 'Request failed');
return;
}
$(el_window).find('.form-error-msg').hide();
@@ -130,55 +146,17 @@ async function UIWindowChangeUsername (options) {
return;
}
if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
- openRevalidatePopup(data.revalidate_url, async () => {
- const r = await doSubmit();
- if ( r.ok ) onSuccess();
- else r.json().then((d) => onError(d.message || 'Request failed')).catch(() => onError('Request failed'));
- });
+ myOpenRevalidatePopup(data.revalidate_url);
+ const r = await doSubmit();
+ if ( r.ok ) onSuccess();
+ else r.json().then((d) => onError(d.message || 'Request failed')).catch(() => onError('Request failed'));
return;
}
onError(data.message || 'Request failed');
});
- function openRevalidatePopup (revalidateUrl, onDone) {
- const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
- if ( ! url ) {
- onDone && onDone(new Error('No revalidate URL'));
- return null;
- }
- let doneCalled = false;
- const hint = $(el_window).find('.change-username-oidc-hint');
- hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
- const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
- const onMessage = (ev) => {
- if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
- if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
- if ( doneCalled ) return;
- doneCalled = true;
- window.removeEventListener('message', onMessage);
- revalidated = true;
- hint.hide();
- $(el_window).find('.change-username-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
- $(el_window).find('.change-username-revalidate-btn').hide();
- onDone && onDone();
- };
- window.addEventListener('message', onMessage);
- const checkClosed = setInterval(() => {
- if ( popup && popup.closed ) {
- clearInterval(checkClosed);
- window.removeEventListener('message', onMessage);
- hint.hide();
- if ( ! doneCalled ) {
- doneCalled = true;
- onDone && onDone(new Error('Popup closed'));
- }
- }
- }, 300);
- return popup;
- }
-
$(el_window).find('.change-username-revalidate-btn').on('click', function () {
- openRevalidatePopup();
+ myOpenRevalidatePopup();
});
function doSubmit (password) {
diff --git a/src/gui/src/util/openid.js b/src/gui/src/util/openid.js
new file mode 100644
index 000000000..ab73ef6ec
--- /dev/null
+++ b/src/gui/src/util/openid.js
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2026-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see
.
+ */
+
+import TeePromise from './TeePromise.js';
+
+/**
+ * This file contains common functions that are used to re-authenticate an
+ * OIDC-authenticated user when performing actions on protected endpoints.
+ *
+ * No design patterns, no abstractions; only simple functions.
+ * (this is not merely a description; it is a guideline for future changes)
+ */
+
+const POPUP_FEATURES = 'width=500,height=600';
+
+export const openRevalidatePopup = async (revalidateUrl) => {
+ const donePromise = new TeePromise();
+
+ const url = revalidateUrl;
+ if ( ! url ) {
+ throw new Error('No revalidate URL');
+ }
+ let doneCalled = false;
+ const popup = window.open(url, 'puter-revalidate', POPUP_FEATURES);
+ const onMessage = ev => {
+ if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
+ if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
+ if ( doneCalled ) return;
+ doneCalled = true;
+ window.removeEventListener('message', onMessage);
+ donePromise.resolve();
+ };
+ window.addEventListener('message', onMessage);
+ const checkClosed = setInterval(() => {
+ if ( popup && popup.closed ) {
+ clearInterval(checkClosed);
+ window.removeEventListener('message', onMessage);
+ if ( ! doneCalled ) {
+ doneCalled = true;
+ donePromise.reject(new Error('Popup closed'));
+ }
+ }
+ }, 300);
+ await donePromise;
+};
From 8923bdac956a1cf8ba322949d8bd71d928a44696 Mon Sep 17 00:00:00 2001
From: KernelDeimos <7225168+KernelDeimos@users.noreply.github.com>
Date: Thu, 12 Feb 2026 19:26:25 -0500
Subject: [PATCH 10/39] refactor(oidc): update UIWindowChangePassword
Use the openRevalidatePopup function in util/openid.js within
UIWindowChangePassword instead of re-implementing that functionality.
Additionally, normalize some of the code so it is more similar to
UIWindowChangeUsername and UIWindowChangePassword.
---
src/gui/src/UI/UIWindowChangePassword.js | 151 ++++++++++++-----------
1 file changed, 81 insertions(+), 70 deletions(-)
diff --git a/src/gui/src/UI/UIWindowChangePassword.js b/src/gui/src/UI/UIWindowChangePassword.js
index 27d99e090..aa0feef35 100644
--- a/src/gui/src/UI/UIWindowChangePassword.js
+++ b/src/gui/src/UI/UIWindowChangePassword.js
@@ -18,6 +18,7 @@
*/
import check_password_strength from '../helpers/check_password_strength.js';
+import { openRevalidatePopup } from '../util/openid.js';
import UIWindow from './UIWindow.js';
async function UIWindowChangePassword (options) {
@@ -102,6 +103,26 @@ async function UIWindowChangePassword (options) {
const origin = window.gui_origin || window.api_origin || '';
const apiUrl = `${origin}/user-protected/change-password`;
+ let revalidated = false;
+
+ const hint = $(el_window).find('.change-password-oidc-hint');
+ const REVALIDATE_POPUP_TEXT = i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.';
+
+ const myOpenRevalidatePopup = async (revalidateUrl) => {
+ revalidateUrl = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ $(el_window).find('.change-password-btn').addClass('disabled');
+ hint.text(REVALIDATE_POPUP_TEXT).show();
+ try {
+ await openRevalidatePopup(revalidateUrl);
+ } catch (e) {
+ onError(e.message || 'Authentication failed');
+ return;
+ } finally {
+ hint.hide();
+ }
+ $(el_window).find('.change-password-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ $(el_window).find('.change-password-revalidate-btn').hide();
+ };
$(el_window).find('.change-password-btn').on('click', async function (e) {
const current_password = $(el_window).find('.current-password').val();
@@ -122,20 +143,6 @@ async function UIWindowChangePassword (options) {
$(el_window).find('.form-error-msg').fadeIn();
return;
}
- if ( oidc_only && !revalidated && !current_password ) {
- $(el_window).find('.change-password-btn').addClass('disabled');
- openRevalidatePopup(null, async (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- return;
- }
- const res = await doSubmit('');
- const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
- if ( res.ok ) onSuccess();
- else onError(data.message || 'Request failed');
- });
- return;
- }
if ( new_password !== confirm_new_password ) {
$(el_window).find('.form-error-msg').html(i18n('passwords_do_not_match'));
$(el_window).find('.form-error-msg').fadeIn();
@@ -148,20 +155,20 @@ async function UIWindowChangePassword (options) {
return;
}
+ if ( oidc_only && !revalidated && !current_password ) {
+ await myOpenRevalidatePopup();
+
+ const res = await doSubmit({ new_password });
+ const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
+ if ( res.ok ) onSuccess();
+ else onError(data.message || 'Request failed');
+ return;
+ }
+
$(el_window).find('.form-error-msg').hide();
$(el_window).find('.change-password-btn').addClass('disabled');
$(el_window).find('.current-password, .new-password, .confirm-new-password').attr('disabled', true);
- const doSubmit = (currentPass) => fetch(apiUrl, {
- method: 'POST',
- credentials: 'include',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- password: currentPass !== undefined ? currentPass : current_password,
- new_pass: new_password,
- }),
- });
-
let res = await doSubmit(current_password);
const data = res.ok ? await res.json().catch(() => ({})) : await res.json().catch(() => ({}));
@@ -170,57 +177,61 @@ async function UIWindowChangePassword (options) {
return;
}
if ( data.code === 'oidc_revalidation_required' && data.revalidate_url ) {
- openRevalidatePopup(data.revalidate_url, async (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- return;
- }
- const r2 = await doSubmit('');
- const d2 = r2.ok ? await r2.json().catch(() => ({})) : await r2.json().catch(() => ({}));
- if ( r2.ok ) onSuccess();
- else onError(d2.message || 'Request failed');
- });
+ await myOpenRevalidatePopup(data.revalidate_url);
+ const r = await doSubmit();
+ if ( r.ok ) onSuccess();
+ else r.json().then((d) => onError(d.message || 'Request failed')).catch(() => onError('Request failed'));
return;
}
onError(data.message || res.statusText || 'Request failed');
});
- let revalidated = false;
- function openRevalidatePopup (revalidateUrl, onDone) {
- const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
- if ( ! url ) {
- onDone && onDone(new Error('No revalidate URL'));
- return null;
- }
- let doneCalled = false;
- const hint = $(el_window).find('.change-password-oidc-hint');
- hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
- const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
- const onMessage = (ev) => {
- if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
- if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
- if ( doneCalled ) return;
- doneCalled = true;
- window.removeEventListener('message', onMessage);
- revalidated = true;
- hint.hide();
- $(el_window).find('.change-password-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
- $(el_window).find('.change-password-revalidate-btn').hide();
- onDone && onDone();
- };
- window.addEventListener('message', onMessage);
- const checkClosed = setInterval(() => {
- if ( popup && popup.closed ) {
- clearInterval(checkClosed);
- window.removeEventListener('message', onMessage);
- hint.hide();
- if ( ! doneCalled ) {
- doneCalled = true;
- onDone && onDone(new Error('Popup closed'));
- }
- }
- }, 300);
- return popup;
+ // function openRevalidatePopup (revalidateUrl, onDone) {
+ // const url = revalidateUrl || (window.user && window.user.oidc_revalidate_url);
+ // if ( ! url ) {
+ // onDone && onDone(new Error('No revalidate URL'));
+ // return null;
+ // }
+ // let doneCalled = false;
+ // const hint = $(el_window).find('.change-password-oidc-hint');
+ // hint.text(i18n('revalidate_sign_in_popup') || 'Sign in with your linked account in the popup.').show();
+ // const popup = window.open(url, 'puter-revalidate', 'width=500,height=600');
+ // const onMessage = (ev) => {
+ // if ( (ev.origin !== window.gui_origin) && (ev.origin !== window.location.origin) ) return;
+ // if ( !ev.data || ev.data.type !== 'puter-revalidate-done' ) return;
+ // if ( doneCalled ) return;
+ // doneCalled = true;
+ // window.removeEventListener('message', onMessage);
+ // revalidated = true;
+ // hint.hide();
+ // $(el_window).find('.change-password-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
+ // $(el_window).find('.change-password-revalidate-btn').hide();
+ // onDone && onDone();
+ // };
+ // window.addEventListener('message', onMessage);
+ // const checkClosed = setInterval(() => {
+ // if ( popup && popup.closed ) {
+ // clearInterval(checkClosed);
+ // window.removeEventListener('message', onMessage);
+ // hint.hide();
+ // if ( ! doneCalled ) {
+ // doneCalled = true;
+ // onDone && onDone(new Error('Popup closed'));
+ // }
+ // }
+ // }, 300);
+ // return popup;
+ // }
+ function doSubmit ({ new_password, current_password }) {
+ return fetch(apiUrl, {
+ method: 'POST',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ password: current_password,
+ new_pass: new_password,
+ }),
+ });
}
$(el_window).find('.change-password-revalidate-btn').on('click', function () {
From e2068e7b9c2f381653bb2794a3c52f7279ee34aa Mon Sep 17 00:00:00 2001
From: KernelDeimos <7225168+KernelDeimos@users.noreply.github.com>
Date: Fri, 13 Feb 2026 13:46:58 -0500
Subject: [PATCH 11/39] fix(oidc): fix QR code login issues caused by OIDC
In implementing OIDC it became necessary to introduce the separation of
"GUI Tokens" and "Session Tokens". This breaks QR login because Puter
does not set the HTTP-only session cookie when logging in with this
flow.
Add a middelware to WebServerService to detect QR Code logins and set
the appropriate HTTP-only session cookie.
---
.../src/modules/web/WebServerService.js | 28 +++++++++++++++++++
.../routers/user-protected/change-username.js | 2 +-
src/backend/src/services/auth/AuthService.js | 19 +++++++++++++
3 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/src/backend/src/modules/web/WebServerService.js b/src/backend/src/modules/web/WebServerService.js
index 1814b4412..7db1ad6a4 100644
--- a/src/backend/src/modules/web/WebServerService.js
+++ b/src/backend/src/modules/web/WebServerService.js
@@ -337,6 +337,34 @@ class WebServerService extends BaseService {
next();
});
+ // When the user visits the main origin (not api/dav subdomain) with ?auth_token=
+ // (e.g. QR login), set the HTTP-only session cookie so user-protected endpoints work.
+ app.use(async (req, res, next) => {
+ const has_subdomain = req.hostname.slice(0, -1 * (config.domain.length + 1)) !== '';
+ if ( has_subdomain ) return next();
+
+ const token = req.query?.auth_token;
+ if ( !token || typeof token !== 'string' ) return next();
+
+ try {
+ const svc_auth = req.services.get('auth');
+ const cleanToken = token.replace('Bearer ', '').trim();
+ const actor = await svc_auth.authenticate_from_token(cleanToken);
+ const session_token = svc_auth.create_session_token_for_session(
+ actor.type.user,
+ actor.type.session,
+ );
+ res.cookie(config.cookie_name, session_token, {
+ sameSite: 'none',
+ secure: true,
+ httpOnly: true,
+ });
+ } catch ( _e ) {
+ // Invalid or expired token; do not set cookie
+ }
+ next();
+ });
+
// Measure data transfer amounts
app.use(measure());
diff --git a/src/backend/src/routers/user-protected/change-username.js b/src/backend/src/routers/user-protected/change-username.js
index 822b39ba4..ece0c5fc7 100644
--- a/src/backend/src/routers/user-protected/change-username.js
+++ b/src/backend/src/routers/user-protected/change-username.js
@@ -25,7 +25,7 @@ const { Context } = require('../../util/context');
module.exports = {
route: '/change-username',
methods: ['POST'],
- handler: async (req, res, next) => {
+ handler: async (req, res, _next) => {
const user = req.user;
const new_username = req.body.new_username;
diff --git a/src/backend/src/services/auth/AuthService.js b/src/backend/src/services/auth/AuthService.js
index 91b6ad0a4..1881c3ed1 100644
--- a/src/backend/src/services/auth/AuthService.js
+++ b/src/backend/src/services/auth/AuthService.js
@@ -355,6 +355,25 @@ class AuthService extends BaseService {
}, this.global_config.jwt_secret);
}
+ /**
+ * Creates a session token (hasHttpPowers) for an existing session.
+ * Used when the client authenticated with a GUI token (e.g. QR login via
+ * ?auth_token=) so we can set the HTTP-only cookie and allow user-protected
+ * endpoints (change password, email, username, etc.) to work.
+ *
+ * @param {*} user - User object (must have .uuid).
+ * @param {string} session_uuid - Existing session UUID.
+ * @returns {string} JWT session token.
+ */
+ create_session_token_for_session (user, session_uuid) {
+ return this.modules.jwt.sign({
+ type: 'session',
+ version: '0.0.0',
+ uuid: session_uuid,
+ user_uid: user.uuid,
+ }, this.global_config.jwt_secret);
+ }
+
/**
* This method checks if the provided session token is valid and returns the associated user and token.
* If the token is not a valid session token or it does not exist in the database, it returns an empty object.
From 21e959bbaa1d9050d9b0caa2570943526f2a0f7b Mon Sep 17 00:00:00 2001
From: KernelDeimos <7225168+KernelDeimos@users.noreply.github.com>
Date: Fri, 13 Feb 2026 17:32:09 -0500
Subject: [PATCH 12/39] dev(oidc): remove button to manually invoke re-auth
This button was useful during manual testing, but the re-authentication
flow for protected endpoints with OIDC users reliably invokes the popup,
so this is no longer necessary. Removing this button reduces clutter on
these screens and might make the flow easier for users to understand.
---
src/gui/src/UI/Settings/UITabSecurity.js | 4 ++++
src/gui/src/UI/Settings/UIWindowChangeEmail.js | 12 +++++-------
src/gui/src/UI/UIWindowChangePassword.js | 16 +++++-----------
src/gui/src/UI/UIWindowChangeUsername.js | 12 +++++-------
src/gui/src/i18n/translations/en.js | 1 +
5 files changed, 20 insertions(+), 25 deletions(-)
diff --git a/src/gui/src/UI/Settings/UITabSecurity.js b/src/gui/src/UI/Settings/UITabSecurity.js
index d4518d0fe..4e5711bbd 100644
--- a/src/gui/src/UI/Settings/UITabSecurity.js
+++ b/src/gui/src/UI/Settings/UITabSecurity.js
@@ -161,11 +161,15 @@ export default {
$win.find('.error-message').text(data.message || i18n('error_unknown_cause')).show();
};
+ const oidc_only = !!(window.user && window.user.oidc_only);
let h = '';
h += '';
h += '
';
h += `
${i18n('disable_2fa_confirm')}
`;
h += `
${i18n('disable_2fa_instructions')}
`;
+ if ( oidc_only ) {
+ h += `
${i18n('revalidate_flow_notice')}
`;
+ }
h += '
';
h += '
';
h += '';
diff --git a/src/gui/src/UI/Settings/UIWindowChangeEmail.js b/src/gui/src/UI/Settings/UIWindowChangeEmail.js
index abbbe6aa0..a09e66cb1 100644
--- a/src/gui/src/UI/Settings/UIWindowChangeEmail.js
+++ b/src/gui/src/UI/Settings/UIWindowChangeEmail.js
@@ -49,7 +49,7 @@ async function UIWindowChangeEmail (options) {
h += `${place_password_entry.html}`;
h += '
';
h += '
';
- h += '
';
+ h += '
';
h += '
';
h += '
';
h += '
';
@@ -87,7 +87,10 @@ async function UIWindowChangeEmail (options) {
if ( oidc_only ) {
authRow.find('.change-email-password-wrap').hide();
const oidcWrap = authRow.find('.change-email-oidc-wrap').show();
- oidcWrap.find('.change-email-revalidate-btn').text(i18n('revalidate_with_google') || 'Re-validate with Google');
+ oidcWrap.find('.change-email-oidc-flow-notice').text(
+ i18n('revalidate_flow_notice') ||
+ 'You will be asked to sign in with your linked account when you continue.',
+ );
} else {
authRow.find('.change-email-oidc-wrap').hide();
}
@@ -124,7 +127,6 @@ async function UIWindowChangeEmail (options) {
hint.hide();
}
$(el_window).find('.change-email-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
- $(el_window).find('.change-email-revalidate-btn').hide();
};
$(el_window).find('.change-email-btn').on('click', async function (e) {
@@ -182,10 +184,6 @@ async function UIWindowChangeEmail (options) {
});
}
- $(el_window).find('.change-email-revalidate-btn').on('click', function () {
- myOpenRevalidatePopup();
- });
-
function onError (message) {
$(el_window).find('.form-error-msg').html(html_encode(message));
$(el_window).find('.form-error-msg').fadeIn();
diff --git a/src/gui/src/UI/UIWindowChangePassword.js b/src/gui/src/UI/UIWindowChangePassword.js
index aa0feef35..528c01e4f 100644
--- a/src/gui/src/UI/UIWindowChangePassword.js
+++ b/src/gui/src/UI/UIWindowChangePassword.js
@@ -38,7 +38,7 @@ async function UIWindowChangePassword (options) {
h += `
`;
h += '
';
h += '';
- h += '
';
+ h += '
';
h += '
';
h += '
';
h += '';
@@ -86,7 +86,10 @@ async function UIWindowChangePassword (options) {
if ( oidc_only ) {
authRow.find('.change-password-current-wrap').hide();
const oidcWrap = authRow.find('.change-password-oidc-wrap').show();
- oidcWrap.find('.change-password-revalidate-btn').text(i18n('revalidate_with_google') || 'Re-validate with Google');
+ oidcWrap.find('.change-password-oidc-flow-notice').text(
+ i18n('revalidate_flow_notice') ||
+ 'You will be asked to sign in with your linked account when you continue.',
+ );
} else {
authRow.find('.change-password-oidc-wrap').hide();
}
@@ -121,7 +124,6 @@ async function UIWindowChangePassword (options) {
hint.hide();
}
$(el_window).find('.change-password-revalidated-msg').text(i18n('revalidated') || 'Re-validated.').show();
- $(el_window).find('.change-password-revalidate-btn').hide();
};
$(el_window).find('.change-password-btn').on('click', async function (e) {
@@ -234,14 +236,6 @@ async function UIWindowChangePassword (options) {
});
}
- $(el_window).find('.change-password-revalidate-btn').on('click', function () {
- openRevalidatePopup(null, (err) => {
- if ( err ) {
- onError(err.message || 'Re-validation required.');
- }
- });
- });
-
function onError (message) {
$(el_window).find('.form-error-msg').html(html_encode(message));
$(el_window).find('.form-error-msg').fadeIn();
diff --git a/src/gui/src/UI/UIWindowChangeUsername.js b/src/gui/src/UI/UIWindowChangeUsername.js
index 370f5c2b3..c54babfd9 100644
--- a/src/gui/src/UI/UIWindowChangeUsername.js
+++ b/src/gui/src/UI/UIWindowChangeUsername.js
@@ -39,7 +39,7 @@ async function UIWindowChangeUsername (options) {
h += `