Puter.js auth hardening

This commit is contained in:
ProgrammerIn-wonderland
2026-06-04 20:09:25 -04:00
parent 6a6c6fb681
commit 8b1124bf1e
2 changed files with 33 additions and 0 deletions
+19
View File
@@ -56,6 +56,9 @@ class Auth {
let settled = false;
// Interval id for polling whether the user closed the popup.
let checkClosed = null;
// The auth popup window we opened. Pinned as the expected
// `event.source` when validating the token message.
let popupWindow = null;
const cleanup = () => {
if ( checkClosed ) {
@@ -66,6 +69,20 @@ class Auth {
};
function messageHandler (e) {
// Only accept the token from the Puter GUI origin AND from the
// popup window we opened. Origin alone is insufficient (any
// frame on the GUI domain could post), so also pin
// event.source. Mirrors the validated handler in index.js.
// msg_id binds the message to this attempt.
if ( e.origin !== puter.defaultGUIOrigin ) {
return;
}
if ( popupWindow && e.source !== popupWindow ) {
return;
}
if ( e.data?.msg !== 'puter.token' ) {
return;
}
if ( e.data?.msg_id != msg_id ) {
return;
}
@@ -101,6 +118,8 @@ class Auth {
reject({ error: 'popup_blocked', msg: 'The sign-in popup was blocked by the browser.' });
return;
}
// Record the popup so messageHandler can pin event.source.
popupWindow = popup;
checkClosed = setInterval(() => {
if ( ! popup.closed ) {
return;
+14
View File
@@ -440,6 +440,16 @@ class PuterDialog extends (globalThis.HTMLElement || Object) { // It will fall b
// Event listener for the 'message' event
this.messageListener = async (event) => {
// Only accept the token from the Puter GUI origin AND from the
// popup we opened. Origin alone is insufficient (any frame on the
// GUI domain could post), so also pin event.source. Mirrors the
// validated handler in index.js.
if ( event.origin !== puter.defaultGUIOrigin ) {
return;
}
if ( this.authPopup && event.source !== this.authPopup ) {
return;
}
if ( event.data.msg === 'puter.token' ) {
this.close();
// Set the authToken property
@@ -519,6 +529,8 @@ class PuterDialog extends (globalThis.HTMLElement || Object) { // It will fall b
// safe from being popup-blocked because it happens inside a click.
this.shadowRoot.querySelector('#launch-auth-popup')?.addEventListener('click', () => {
const popup = openAuthPopup(this.#popupURL());
// Pinned as the expected event.source in messageListener.
this.authPopup = popup;
// Launcher mode: hand the popup back to the caller and close the
// consent dialog — its only job was to provide the user gesture.
@@ -544,6 +556,8 @@ class PuterDialog extends (globalThis.HTMLElement || Object) { // It will fall b
open () {
if ( hasUserActivation() ) {
const popup = openAuthPopup(this.#popupURL());
// Pinned as the expected event.source in messageListener.
this.authPopup = popup;
if ( this.options.popupURL && typeof this.options.onLaunch === 'function' ) {
this.options.onLaunch(popup);
}