sanctum/avia_core/LoginWithToken.js
AvaLilac b22130645a
adds the ability to login to stoat desktop with your session token
Signed-off-by: AvaLilac <257690424+AvaLilac@users.noreply.github.com>
2026-04-01 18:08:46 -04:00

139 lines
5.4 KiB
JavaScript

(function () {
if (window.__LOGIN_WITH_TOKEN__) return;
window.__LOGIN_WITH_TOKEN__ = true;
async function loginWithToken(token) {
const res = await fetch('https://stoat.chat/api/users/@me', {
headers: { 'x-session-token': token }
});
if (!res.ok) throw new Error('Invalid token');
const user = await res.json();
const db = await new Promise((resolve, reject) => {
const r = indexedDB.open('localforage');
r.onsuccess = () => resolve(r.result);
r.onerror = () => reject(r.error);
});
const tx = db.transaction('keyvaluepairs', 'readwrite');
await new Promise((resolve, reject) => {
const r = tx.objectStore('keyvaluepairs').put({
session: {
_id: user._id,
token: token,
userId: user._id,
valid: true
}
}, 'auth');
r.onsuccess = () => resolve();
r.onerror = () => reject(r.error);
});
location.reload();
}
function openTokenDialog() {
const backdrop = document.createElement('div');
backdrop.className = 'top_0 left_0 right_0 bottom_0 pos_fixed z_100 max-h_100% d_grid us_none place-items_center pointer-events_all anim-n_scrimFadeIn anim-dur_0.1s anim-fm_forwards trs_var(--transitions-medium)_all p_80px ov-y_auto';
backdrop.style.cssText = '--background: rgba(0, 0, 0, 0.6);';
backdrop.innerHTML = `
<div style="opacity: 1; --motion-translateY: 0px; transform: translateY(var(--motion-translateY));">
<div class="p_24px min-w_280px max-w_560px bdr_28px d_flex flex-d_column c_var(--md-sys-color-on-surface) bg_var(--md-sys-color-surface-container-high)">
<span class="lh_2rem fs_1.5rem ls_0 fw_400 mbe_16px">Login With Token</span>
<div class="c_var(--md-sys-color-on-surface-variant) lh_1.25rem fs_0.875rem ls_0.015625rem fw_400">
<div class="d_flex flex-d_column flex-g_initial m_0 ai_initial jc_initial gap_var(--gap-md)">
<mdui-text-field id="lwt-token-input" variant="filled" type="password" name="token" required label="Session Token"></mdui-text-field>
</div>
</div>
<div class="gap_8px d_flex jc_end mbs_24px">
<button id="lwt-close-btn" type="button" class="lh_1.25rem fs_0.875rem ls_0.015625rem fw_400 pos_relative px_16px flex-sh_0 d_flex ai_center jc_center ff_inherit cursor_pointer bd_none trs_var(--transitions-medium)_all c_var(--color) fill_var(--color) h_40px bdr_var(--borderRadius-full) --color_var(--md-sys-color-primary)">
<md-ripple aria-hidden="true"></md-ripple>Close
</button>
<button id="lwt-login-btn" type="button" class="lh_1.25rem fs_0.875rem ls_0.015625rem fw_400 pos_relative px_16px flex-sh_0 d_flex ai_center jc_center ff_inherit cursor_pointer bd_none trs_var(--transitions-medium)_all c_var(--color) fill_var(--color) h_40px bdr_var(--borderRadius-full) --color_var(--md-sys-color-on-primary) bg_var(--md-sys-color-primary)">
<md-ripple aria-hidden="true"></md-ripple>Login
</button>
</div>
</div>
</div>
`;
document.body.appendChild(backdrop);
const closeBtn = backdrop.querySelector('#lwt-close-btn');
const loginBtn = backdrop.querySelector('#lwt-login-btn');
const tokenInput = backdrop.querySelector('#lwt-token-input');
function close() { backdrop.remove(); }
function setLoading(loading) {
loginBtn.disabled = loading;
loginBtn.style.cursor = loading ? 'not-allowed' : 'pointer';
const ripple = loginBtn.querySelector('md-ripple');
loginBtn.textContent = loading ? 'Logging in…' : 'Login';
if (ripple) loginBtn.prepend(ripple);
}
function setError(msg) {
loginBtn.disabled = false;
loginBtn.style.cursor = 'pointer';
const ripple = loginBtn.querySelector('md-ripple');
loginBtn.textContent = msg;
if (ripple) loginBtn.prepend(ripple);
setTimeout(() => {
loginBtn.textContent = 'Login';
if (ripple) loginBtn.prepend(ripple);
}, 2000);
}
backdrop.addEventListener('click', (e) => { if (e.target === backdrop) close(); });
closeBtn.addEventListener('click', close);
loginBtn.addEventListener('click', async () => {
const token = tokenInput.value?.trim();
if (!token) {
setError('Enter a token!');
return;
}
setLoading(true);
try {
await loginWithToken(token);
} catch (err) {
setError('Invalid token!');
}
});
}
function injectLoginButton() {
const signUpBtn = [...document.querySelectorAll('button')]
.find(b => b.textContent.trim() === 'Sign Up');
if (!signUpBtn) return;
const parent = signUpBtn.parentElement;
if (parent.querySelector('[data-lwt-btn]')) return;
const clone = signUpBtn.cloneNode(false);
clone.dataset.lwtBtn = 'true';
clone.textContent = 'Login With Token';
const ripple = document.createElement('md-ripple');
ripple.setAttribute('aria-hidden', 'true');
clone.prepend(ripple);
clone.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openTokenDialog();
});
signUpBtn.insertAdjacentElement('afterend', clone);
}
let debounceTimer = null;
new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(injectLoginButton, 150);
}).observe(document.body, { childList: true, subtree: true });
injectLoginButton();
})();