Remove mastodon posts on delete and OTP

This commit is contained in:
2026-08-26 14:16:29 +02:00
parent 80a3a3d541
commit f503dbaef2
24 changed files with 388 additions and 11 deletions
+2 -1
View File
@@ -16,7 +16,8 @@ form.addEventListener('submit', async (event) => {
});
if (!response.ok) {
status.textContent = 'Sign-in failed.';
const result = await response.json().catch(() => ({}));
status.textContent = result.detail || 'Sign-in failed.';
status.style.color = '#b91c1c';
return;
}
+68 -1
View File
@@ -9,6 +9,15 @@ const profileLogoutButton = document.querySelector('#logout-button');
const accessToken = localStorage.getItem('linklogAccessToken');
const defaultPostPrefix = 'From my #LinkLog: ';
const mastodonConnectButton = document.querySelector('#mastodon-connect');
const otpSetupButton = document.querySelector('#otp-setup');
const otpEnableButton = document.querySelector('#otp-enable');
const otpDisableButton = document.querySelector('#otp-disable');
const otpProvisioning = document.querySelector('#otp-provisioning');
const otpDisabled = document.querySelector('#otp-disabled');
const otpEnabled = document.querySelector('#otp-enabled');
const otpSecret = document.querySelector('#otp-secret');
const otpUri = document.querySelector('#otp-uri');
const otpStatus = document.querySelector('#otp-status');
function authHeaders(includeJson = false) {
return {
@@ -23,6 +32,64 @@ function setStatus(selector, message, isError = false) {
status.style.color = isError ? '#b91c1c' : '#166534';
}
function setOtpStatus(message, isError = false) {
otpStatus.textContent = message;
otpStatus.style.color = isError ? '#b91c1c' : '#166534';
}
async function loadOtp() {
const response = await fetch('/api/user/otp', {headers: authHeaders()});
if (!response.ok) throw new Error('Could not load one-time password settings');
const result = await response.json();
otpDisabled.classList.toggle('hidden', result.enabled);
otpEnabled.classList.toggle('hidden', !result.enabled);
}
otpSetupButton.addEventListener('click', async () => {
const response = await fetch('/api/user/otp/setup', {method: 'POST', headers: authHeaders()});
const result = await response.json();
if (!response.ok) {
setOtpStatus(result.detail || 'Could not start one-time password setup.', true);
return;
}
otpSecret.textContent = result.secret;
otpUri.href = result.otpauth_url;
otpProvisioning.classList.remove('hidden');
setOtpStatus('Enter a code from your authenticator app to confirm setup.');
});
otpEnableButton.addEventListener('click', async () => {
const code = document.querySelector('#otp-setup-code').value.trim();
const response = await fetch('/api/user/otp', {
method: 'POST', headers: authHeaders(true), body: JSON.stringify({action: 'enable', code}),
});
const result = await response.json();
if (!response.ok) {
setOtpStatus(result.detail || 'Could not enable one-time password.', true);
return;
}
otpDisabled.classList.add('hidden');
otpEnabled.classList.remove('hidden');
otpProvisioning.classList.add('hidden');
setOtpStatus('One-time password enabled.');
});
otpDisableButton.addEventListener('click', async () => {
const code = document.querySelector('#otp-disable-code').value.trim();
const response = await fetch('/api/user/otp', {
method: 'POST', headers: authHeaders(true), body: JSON.stringify({action: 'disable', code}),
});
const result = await response.json();
if (!response.ok) {
setOtpStatus(result.detail || 'Could not disable one-time password.', true);
return;
}
otpDisabled.classList.remove('hidden');
otpEnabled.classList.add('hidden');
document.querySelector('#otp-disable-code').value = '';
setOtpStatus('One-time password disabled.');
});
async function loadProfile() {
const response = await fetch('/api/user/me', {headers: authHeaders()});
if (!response.ok) throw new Error('Could not load profile');
@@ -136,7 +203,7 @@ passwordForm.addEventListener('submit', async (event) => {
if (response.ok) passwordForm.reset();
});
Promise.all([loadProfile(), loadMastodonConfig()]).catch((error) => {
Promise.all([loadProfile(), loadMastodonConfig(), loadOtp()]).catch((error) => {
setStatus('#profile-status', accessToken ? error.message : 'Please sign in first.', true);
});
})();
+21
View File
@@ -77,6 +77,27 @@
</form>
</section>
<section class="link-item settings-panel">
<h2>One-time password</h2>
<p>Use an authenticator app to add a second sign-in step.</p>
<div id="otp-disabled">
<button id="otp-setup" type="button">Set up one-time password</button>
<div id="otp-provisioning" class="hidden">
<p>Scan this QR code or enter the secret in your authenticator app:</p>
<code id="otp-secret"></code>
<p><a id="otp-uri" href="" target="_blank" rel="noopener noreferrer">Open authenticator link</a></p>
<label>Verification code <input id="otp-setup-code" inputmode="numeric" autocomplete="one-time-code" /></label>
<button id="otp-enable" type="button">Enable one-time password</button>
</div>
</div>
<div id="otp-enabled" class="hidden">
<p>One-time password is enabled.</p>
<label>Verification code <input id="otp-disable-code" inputmode="numeric" autocomplete="one-time-code" /></label>
<button id="otp-disable" type="button">Disable one-time password</button>
</div>
<p id="otp-status" class="status" role="status"></p>
</section>
<section class="link-item settings-panel">
<h2>Mastodon</h2>
<form id="mastodon-form">