From e7ffccf9d1da8b8b1d8a864026cf9ac2291acde5 Mon Sep 17 00:00:00 2001
From: Lim Chee Aun
Date: Tue, 27 May 2025 16:57:29 +0800
Subject: [PATCH] Allow revoke access token
- Also means storing client credentials
- A bit of cleaning up for vapid key as it's everywhere now
---
src/app.jsx | 11 ++++++++---
src/pages/accounts.jsx | 9 ++++++++-
src/pages/login.jsx | 21 +++++++++++----------
src/pages/settings.jsx | 5 ++++-
src/utils/auth.js | 29 +++++++++++++++++++++++++++++
src/utils/store-utils.js | 19 +++++++++++++++++--
6 files changed, 77 insertions(+), 17 deletions(-)
diff --git a/src/app.jsx b/src/app.jsx
index d3bef332..6a84a391 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -63,7 +63,9 @@ import states, { initStates, statusKey } from './utils/states';
import store from './utils/store';
import {
getAccount,
+ getCredentialApplication,
getCurrentAccount,
+ getVapidKey,
setCurrentAccountID,
} from './utils/store-utils';
@@ -367,9 +369,12 @@ function App() {
window.location.pathname || '/',
);
- const clientID = store.sessionCookie.get('clientID');
- const clientSecret = store.sessionCookie.get('clientSecret');
- const vapidKey = store.sessionCookie.get('vapidKey');
+ const {
+ client_id: clientID,
+ client_secret: clientSecret,
+ vapid_key,
+ } = getCredentialApplication(instanceURL) || {};
+ const vapidKey = getVapidKey(instanceURL) || vapid_key;
const verifier = store.sessionCookie.get('codeVerifier');
(async () => {
diff --git a/src/pages/accounts.jsx b/src/pages/accounts.jsx
index d62ad7af..55f87494 100644
--- a/src/pages/accounts.jsx
+++ b/src/pages/accounts.jsx
@@ -14,6 +14,7 @@ import Menu2 from '../components/menu2';
import NameText from '../components/name-text';
import RelativeTime from '../components/relative-time';
import { api } from '../utils/api';
+import { revokeAccessToken } from '../utils/auth';
import niceDateTime from '../utils/nice-date-time';
import states from '../utils/states';
import store from '../utils/store';
@@ -185,9 +186,15 @@ function Accounts({ onClose }) {
}
disabled={!isCurrent}
menuItemClassName="danger"
- onClick={() => {
+ onClick={async () => {
// const yes = confirm('Log out?');
// if (!yes) return;
+ await revokeAccessToken({
+ instanceURL: account.instanceURL,
+ client_id: account.clientId,
+ client_secret: account.clientSecret,
+ token: account.accessToken,
+ });
accounts.splice(i, 1);
store.local.setJSON('accounts', accounts);
// location.reload();
diff --git a/src/pages/login.jsx b/src/pages/login.jsx
index 7cca2829..8958b8f1 100644
--- a/src/pages/login.jsx
+++ b/src/pages/login.jsx
@@ -18,6 +18,10 @@ import {
} from '../utils/auth';
import { supportsPKCE } from '../utils/oauth-pkce';
import store from '../utils/store';
+import {
+ getCredentialApplication,
+ storeCredentialApplication,
+} from '../utils/store-utils';
import useTitle from '../utils/useTitle';
const { PHANPY_DEFAULT_INSTANCE: DEFAULT_INSTANCE } = import.meta.env;
@@ -87,19 +91,20 @@ function Login() {
setUIState('loading');
try {
- const { client_id, client_secret, vapid_key } =
- await registerApplication({
+ let credentialApplication = getCredentialApplication(instanceURL);
+ if (!credentialApplication) {
+ credentialApplication = await registerApplication({
instanceURL,
});
+ storeCredentialApplication(instanceURL, credentialApplication);
+ }
+
+ const { client_id, client_secret } = credentialApplication;
const authPKCE = await supportsPKCE({ instanceURL });
console.log({ authPKCE });
if (authPKCE) {
if (client_id && client_secret) {
- store.sessionCookie.set('clientID', client_id);
- store.sessionCookie.set('clientSecret', client_secret);
- store.sessionCookie.set('vapidKey', vapid_key);
-
const [url, verifier] = await getPKCEAuthorizationURL({
instanceURL,
client_id,
@@ -111,10 +116,6 @@ function Login() {
}
} else {
if (client_id && client_secret) {
- store.sessionCookie.set('clientID', client_id);
- store.sessionCookie.set('clientSecret', client_secret);
- store.sessionCookie.set('vapidKey', vapid_key);
-
location.href = await getAuthorizationURL({
instanceURL,
client_id,
diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx
index 93e49b30..8cbb32b0 100644
--- a/src/pages/settings.jsx
+++ b/src/pages/settings.jsx
@@ -24,7 +24,7 @@ import {
import showToast from '../utils/show-toast';
import states from '../utils/states';
import store from '../utils/store';
-import { getAPIVersions } from '../utils/store-utils';
+import { getAPIVersions, getVapidKey } from '../utils/store-utils';
import supports from '../utils/supports';
const DEFAULT_TEXT_SIZE = 16;
@@ -860,6 +860,9 @@ function Settings({ onClose }) {
Debugging
+
+ Vapid key: {getVapidKey()}
+
{__BENCH_RESULTS?.size > 0 && (
{Array.from(__BENCH_RESULTS.entries()).map(
diff --git a/src/utils/auth.js b/src/utils/auth.js
index f0aaa8ac..5d4e9553 100644
--- a/src/utils/auth.js
+++ b/src/utils/auth.js
@@ -105,3 +105,32 @@ export async function getAccessToken({
console.log({ tokenJSON });
return tokenJSON;
}
+
+export async function revokeAccessToken({
+ instanceURL,
+ client_id,
+ client_secret,
+ token,
+}) {
+ try {
+ const params = new URLSearchParams({
+ client_id,
+ client_secret,
+ token,
+ });
+
+ const revokeResponse = await fetch(`https://${instanceURL}/oauth/revoke`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: params.toString(),
+ keepalive: true,
+ });
+
+ return revokeResponse.ok;
+ } catch (error) {
+ console.erro('Error revoking token', error);
+ return false;
+ }
+}
diff --git a/src/utils/store-utils.js b/src/utils/store-utils.js
index 59a0b010..5270d1b8 100644
--- a/src/utils/store-utils.js
+++ b/src/utils/store-utils.js
@@ -169,9 +169,11 @@ export function getAPIVersions() {
return instance?.apiVersions || {};
}
-export function getVapidKey() {
+export function getVapidKey(instance) {
// Vapid key has moved from account to instance config
- const config = getCurrentInstanceConfiguration();
+ const config = instance
+ ? getInstanceConfiguration(instance)
+ : getCurrentInstanceConfiguration();
const vapidKey = config?.vapid?.publicKey || config?.vapid?.public_key;
return vapidKey || getCurrentAccount()?.vapidKey;
}
@@ -180,3 +182,16 @@ export function isMediaFirstInstance() {
const instance = getCurrentInstance();
return /pixelfed/i.test(instance?.version);
}
+
+const CREDENTIAL_APPLICATIONS_KEY = 'credentialApplications';
+
+export function storeCredentialApplication(instanceURL, credentialApplication) {
+ const stored = store.local.getJSON(CREDENTIAL_APPLICATIONS_KEY) || {};
+ stored[instanceURL] = credentialApplication;
+ store.local.setJSON(CREDENTIAL_APPLICATIONS_KEY, stored);
+}
+
+export function getCredentialApplication(instanceURL) {
+ const stored = store.local.getJSON(CREDENTIAL_APPLICATIONS_KEY) || {};
+ return stored[instanceURL] || null;
+}