Index: src/services/OAuthService.js
===================================================================
--- src/services/OAuthService.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
+++ src/services/OAuthService.js	(revision 2d66d66df4fdd568ae1d3ba78850058c55572590)
@@ -109,3 +109,30 @@
 }
 
-export default { registerClient, getClient, createCode, exchangeCode, verifyBearer, revokeToken, validRedirectUri };
+// The active authorizations (bearer tokens) a user has granted, with the app
+// name and the site each is scoped to. The bearer itself is never stored, so
+// revocation is keyed on token_hash: safe to render, you cannot derive the
+// token from its hash.
+export function listAuthorizations(userId) {
+  return db.prepare(`
+    SELECT t.token_hash, t.site_slug, t.scope, t.created_at, t.last_used_at, c.client_name
+    FROM oauth_tokens t
+    LEFT JOIN oauth_clients c ON c.client_id = t.client_id
+    WHERE t.user_id = ?
+    ORDER BY t.created_at DESC
+  `).all(String(userId || ''));
+}
+
+// Revoke one authorization, scoped to the owner so a user can only revoke their
+// own tokens. Returns true when a row was removed.
+export function revokeAuthorization(userId, tokenHash) {
+  try {
+    const r = db.prepare('DELETE FROM oauth_tokens WHERE token_hash = ? AND user_id = ?')
+      .run(String(tokenHash || ''), String(userId || ''));
+    return r.changes > 0;
+  } catch { return false; }
+}
+
+export default {
+  registerClient, getClient, createCode, exchangeCode, verifyBearer, revokeToken, validRedirectUri,
+  listAuthorizations, revokeAuthorization,
+};
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision 4407c67257b1d061f0376ef1ea63ea597c308e4b)
+++ src/services/i18n.js	(revision 2d66d66df4fdd568ae1d3ba78850058c55572590)
@@ -744,5 +744,5 @@
     'acct.back_home': 'Terug naar home',
     'acct.title': 'Account',
-    'acct.subtitle': 'Profiel en avatar.',
+    'acct.subtitle': 'Profiel en avatar.', 'acct.oauth_apps': 'Verbonden apps', 'acct.oauth_hint': 'Apps die je toegang tot je account hebt gegeven via OAuth. Trek in wat je niet meer vertrouwt of gebruikt.', 'acct.oauth_none': 'Nog geen apps verbonden.', 'acct.oauth_unknown_app': 'Onbekende app', 'acct.oauth_last_used': 'laatst gebruikt', 'acct.oauth_never': 'nooit', 'acct.oauth_revoke': 'Intrekken', 'acct.oauth_revoked': 'App-toegang ingetrokken.', 'acct.oauth_revoke_none': 'Die toegang bestond niet meer.',
     'acct.viewer_mode': 'Kijker-modus',
     'acct.viewer_note': 'Dit is een demo-account. Je kunt alles bekijken, maar niets wijzigen — ook geen foto of bio.',
@@ -1671,5 +1671,5 @@
     'acct.back_home': 'Back to home',
     'acct.title': 'Account',
-    'acct.subtitle': 'Profile and avatar.',
+    'acct.subtitle': 'Profile and avatar.', 'acct.oauth_apps': 'Connected apps', 'acct.oauth_hint': 'Apps you granted access to your account via OAuth. Revoke anything you no longer trust or use.', 'acct.oauth_none': 'No apps connected yet.', 'acct.oauth_unknown_app': 'Unknown app', 'acct.oauth_last_used': 'last used', 'acct.oauth_never': 'never', 'acct.oauth_revoke': 'Revoke', 'acct.oauth_revoked': 'App access revoked.', 'acct.oauth_revoke_none': 'That access no longer existed.',
     'acct.viewer_mode': 'Viewer mode',
     'acct.viewer_note': 'This is a demo account. You can view everything, but change nothing — not even your photo or bio.',
@@ -2598,5 +2598,5 @@
     'acct.back_home': 'Zurück zur Startseite',
     'acct.title': 'Konto',
-    'acct.subtitle': 'Profil und Avatar.',
+    'acct.subtitle': 'Profil und Avatar.', 'acct.oauth_apps': 'Verbundene Apps', 'acct.oauth_hint': 'Apps, denen du \u00fcber OAuth Zugriff auf dein Konto gegeben hast. Widerrufe, was du nicht mehr vertraust oder nutzt.', 'acct.oauth_none': 'Noch keine Apps verbunden.', 'acct.oauth_unknown_app': 'Unbekannte App', 'acct.oauth_last_used': 'zuletzt genutzt', 'acct.oauth_never': 'nie', 'acct.oauth_revoke': 'Widerrufen', 'acct.oauth_revoked': 'App-Zugriff widerrufen.', 'acct.oauth_revoke_none': 'Dieser Zugriff bestand nicht mehr.',
     'acct.viewer_mode': 'Betrachter-Modus',
     'acct.viewer_note': 'Dies ist ein Demo-Konto. Du kannst alles ansehen, aber nichts ändern — auch kein Foto oder keine Bio.',
