oauth optimizing
Build LinkLog Development Image / development-image (push) Successful in 22s

This commit is contained in:
Olaf
2026-08-26 08:20:42 +02:00
parent b35249cb86
commit 333aab8e8a
4 changed files with 31 additions and 7 deletions
+2
View File
@@ -233,6 +233,8 @@ After logging in, open <http://localhost:8000/profile>, enter the Mastodon insta
`LINKLOG_PUBLIC_URL` must be the URL users can reach for the OAuth callback. For local development it can remain `http://localhost:8000`; for a deployment it must be the public LinkLog URL. Existing manually entered access tokens remain compatible with the plugin configuration API.
LinkLog caches the OAuth application credentials per Mastodon server in the persistent SQLite `app_settings` table, so subsequent connections do not register a new application on every attempt. If the server rate-limits application registration, the profile page reports the upstream `429` response and the user can retry after the server's cooldown.
Enable the plugin from the admin API or the admin page. New links are saved first and then posted to the configured instance at `/api/v1/statuses`. A Mastodon network failure does not undo the saved link.
## Useful API Calls
+9
View File
@@ -2,6 +2,7 @@
## SPDX-License-Identifier: GPL-3.0-or-later
from urllib.parse import quote
from urllib.error import HTTPError
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import RedirectResponse
@@ -17,6 +18,14 @@ router = APIRouter()
def oauth_start(instance: str = 'mastodon.social', user: dict = Depends(get_current_user)):
try:
authorization_url = start_authorization(user['id'], instance)
except HTTPError as error:
retry_after = error.headers.get('Retry-After') if error.headers else None
headers = {'Retry-After': retry_after} if retry_after else None
raise HTTPException(
status_code=error.code,
detail=f'Mastodon returned HTTP {error.code} while registering LinkLog. Try again later.',
headers=headers,
) from error
except Exception as error:
raise HTTPException(status_code=502, detail=f'Could not register with Mastodon: {error}') from error
return {'authorization_url': authorization_url}
+13
View File
@@ -6,6 +6,7 @@ from hashlib import sha256
import json
from secrets import token_urlsafe
from urllib.parse import urlencode
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from uuid import uuid4
@@ -34,12 +35,24 @@ def post_form(url: str, values: dict) -> dict:
def start_authorization(user_id: str, instance: str) -> str:
instance = normalize_instance(instance)
redirect_uri = f'{settings.public_url}/api/mastodon/oauth/callback'
setting_name = f'mastodon_app:{instance}'
with get_connection() as conn:
row = conn.execute('SELECT value FROM app_settings WHERE name = ?', (setting_name,)).fetchone()
app = json.loads(row['value']) if row else None
if not app:
app = post_form(f'{instance}/api/v1/apps', {
'client_name': settings.mastodon_client_name,
'redirect_uris': redirect_uri,
'scopes': 'read:accounts write:statuses',
'website': settings.public_url,
})
with get_connection() as conn:
conn.execute(
'''INSERT INTO app_settings (name, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(name) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP''',
(setting_name, json.dumps(app)),
)
conn.commit()
state = token_urlsafe(32)
expires_at = datetime.now(timezone.utc) + timedelta(minutes=settings.mastodon_oauth_expiry_minutes)
with get_connection() as conn:
+1 -1
View File
@@ -111,7 +111,7 @@ mastodonConnectButton.addEventListener('click', async () => {
setStatus('#mastodon-status', `Authorizing with ${instance}...`);
const response = await fetch(`/api/mastodon/oauth/start?instance=${encodeURIComponent(instance)}`, {headers: authHeaders()});
const result = await response.json();
if (!response.ok || !result.authorization_url) throw new Error(result.error || 'Could not start Mastodon authorization.');
if (!response.ok || !result.authorization_url) throw new Error(result.detail || result.error || 'Could not start Mastodon authorization.');
window.location.assign(result.authorization_url);
} catch (error) {
setStatus('#mastodon-status', error.message, true);