dev(backend): add revoke_access_token endpoint

This commit is contained in:
KernelDeimos
2026-02-03 15:11:33 -05:00
committed by Eric Dubé
parent 0234e34b46
commit 3ffe8eaf30
3 changed files with 95 additions and 0 deletions
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require('../../api/APIError');
const eggspress = require('../../api/eggspress');
const { Context } = require('../../util/context');
/**
* Coerces a read-URL string to the token (JWT) from its query.
* Works for absolute or relative URLs (e.g. .../token-read?uid=...&token=...).
* Returns the given value unchanged if it does not look like a read URL.
*/
function tokenOrUuidFromInput (value) {
if ( typeof value !== 'string' || !value.trim() ) {
return value;
}
const s = value.trim();
console.log('s?', s);
if ( s.includes('/token-read') ) {
try {
const url = new URL(s);
const token = url.searchParams.get('token');
console.log('token?', token);
return token ?? s;
} catch (_) {
return s;
}
}
return s;
}
module.exports = eggspress('/auth/revoke-access-token', {
subdomain: 'api',
auth2: true,
allowedMethods: ['POST'],
}, async (req, res, next) => {
const x = Context.get();
const svc_auth = x.get('services').get('auth');
const raw = req.body.tokenOrUuid;
if ( raw === undefined || raw === null ) {
throw APIError.create('field_missing', null, { key: 'tokenOrUuid' });
}
const tokenOrUuid = tokenOrUuidFromInput(raw);
await svc_auth.revoke_access_token(tokenOrUuid);
res.json({ ok: true });
});
@@ -50,6 +50,7 @@ class PuterAPIService extends BaseService {
app.use(require('../routers/auth/check-app'));
app.use(require('../routers/auth/app-uid-from-origin'));
app.use(require('../routers/auth/create-access-token'));
app.use(require('../routers/auth/revoke-access-token'));
app.use(require('../routers/auth/delete-own-user'));
app.use(require('../routers/auth/configure-2fa'));
app.use(require('../routers/drivers/call'));
@@ -444,9 +444,39 @@ class AuthService extends BaseService {
Object.values(insert_object));
}
console.log('token uuid?', uuid);
return jwt;
}
/**
* Revokes an access token by removing it from the database.
* Accepts either the access token JWT or the token UUID.
*
* @param {string} tokenOrUuid - The access token JWT or the token UUID.
* @returns {Promise<void>}
*/
async revoke_access_token (tokenOrUuid) {
let token_uid;
const isJwt = typeof tokenOrUuid === 'string' &&
/^[\w-]*\.[\w-]*\.[\w-]*$/.test(tokenOrUuid.trim());
if ( isJwt ) {
const decoded = this.modules.jwt.verify(tokenOrUuid, this.global_config.jwt_secret);
if ( decoded.type !== 'access-token' || !decoded.token_uid ) {
throw APIError.create('token_auth_failed');
}
token_uid = decoded.token_uid;
} else {
token_uid = tokenOrUuid;
}
/* eslint-disable */
await this.db.write(
'DELETE FROM `access_token_permissions` WHERE `token_uid` = ?',
[token_uid],
);
/* eslint-enable */
}
/**
* Get the session list for the specified actor.
*