Changeset 2d66d66 in Klonkt for src/services/OAuthService.js


Ignore:
Timestamp:
07/19/2026 04:17:44 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
33e1dbd
Parents:
4407c67
git-author:
Robin <roboburr@…> (07/19/2026 04:17:27 PM)
git-committer:
Robin <roboburr@…> (07/19/2026 04:17:44 PM)
Message:

Feature: revoke connected OAuth apps from the account page

You issue C2S bearer tokens (Shaer, etc.) but had no way to see or revoke them.
The account page now has a 'Connected apps' section listing every authorization
(app name via the client join, site, scope, last used) with a Revoke button.
Already-issued tokens appear because they were always stored (hashed) with the
user/client/site; the bearer is never kept, so revocation is keyed on the safe
token_hash and scoped to the owner (you cannot revoke someone else's).

  • OAuthService.listAuthorizations(userId) / revokeAuthorization(userId, hash).
  • account.js: authorizations passed to the page; POST /account/oauth/revoke.
  • account.ejs: the section + styles; i18n NL/EN/DE. Visible to viewers too (revoking your own app access is a safety action).

83 tests green. Live-verified: two apps listed on /account, revoke one -> it is
gone and the other stays, token count drops in the DB.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/OAuthService.js

    r4407c67 r2d66d66  
    109109}
    110110
    111 export default { registerClient, getClient, createCode, exchangeCode, verifyBearer, revokeToken, validRedirectUri };
     111// The active authorizations (bearer tokens) a user has granted, with the app
     112// name and the site each is scoped to. The bearer itself is never stored, so
     113// revocation is keyed on token_hash: safe to render, you cannot derive the
     114// token from its hash.
     115export function listAuthorizations(userId) {
     116  return db.prepare(`
     117    SELECT t.token_hash, t.site_slug, t.scope, t.created_at, t.last_used_at, c.client_name
     118    FROM oauth_tokens t
     119    LEFT JOIN oauth_clients c ON c.client_id = t.client_id
     120    WHERE t.user_id = ?
     121    ORDER BY t.created_at DESC
     122  `).all(String(userId || ''));
     123}
     124
     125// Revoke one authorization, scoped to the owner so a user can only revoke their
     126// own tokens. Returns true when a row was removed.
     127export function revokeAuthorization(userId, tokenHash) {
     128  try {
     129    const r = db.prepare('DELETE FROM oauth_tokens WHERE token_hash = ? AND user_id = ?')
     130      .run(String(tokenHash || ''), String(userId || ''));
     131    return r.changes > 0;
     132  } catch { return false; }
     133}
     134
     135export default {
     136  registerClient, getClient, createCode, exchangeCode, verifyBearer, revokeToken, validRedirectUri,
     137  listAuthorizations, revokeAuthorization,
     138};
Note: See TracChangeset for help on using the changeset viewer.