Merge branch 'revoke' into 'main'

Implement OAuth revoke endpoint

See merge request soapbox-pub/ditto!551
This commit is contained in:
Alex Gleason 2024-10-17 19:24:53 +00:00
commit 96416747c2
3 changed files with 42 additions and 7 deletions

View file

@ -51,7 +51,7 @@ import {
statusZapSplitsController, statusZapSplitsController,
updateZapSplitsController, updateZapSplitsController,
} from '@/controllers/api/ditto.ts'; } from '@/controllers/api/ditto.ts';
import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.ts'; import { emptyArrayController, notImplementedController } from '@/controllers/api/fallback.ts';
import { import {
instanceDescriptionController, instanceDescriptionController,
instanceV1Controller, instanceV1Controller,
@ -61,7 +61,12 @@ import { markersController, updateMarkersController } from '@/controllers/api/ma
import { mediaController, updateMediaController } from '@/controllers/api/media.ts'; import { mediaController, updateMediaController } from '@/controllers/api/media.ts';
import { mutesController } from '@/controllers/api/mutes.ts'; import { mutesController } from '@/controllers/api/mutes.ts';
import { notificationController, notificationsController } from '@/controllers/api/notifications.ts'; import { notificationController, notificationsController } from '@/controllers/api/notifications.ts';
import { createTokenController, oauthAuthorizeController, oauthController } from '@/controllers/api/oauth.ts'; import {
createTokenController,
oauthAuthorizeController,
oauthController,
revokeTokenController,
} from '@/controllers/api/oauth.ts';
import { import {
configController, configController,
frontendConfigController, frontendConfigController,
@ -201,7 +206,7 @@ app.get('/api/v1/apps/verify_credentials', appCredentialsController);
app.post('/api/v1/apps', createAppController); app.post('/api/v1/apps', createAppController);
app.post('/oauth/token', createTokenController); app.post('/oauth/token', createTokenController);
app.post('/oauth/revoke', emptyObjectController); app.post('/oauth/revoke', revokeTokenController);
app.post('/oauth/authorize', oauthAuthorizeController); app.post('/oauth/authorize', oauthAuthorizeController);
app.get('/oauth/authorize', oauthController); app.get('/oauth/authorize', oauthController);

View file

@ -1,7 +1,6 @@
import { Context } from '@hono/hono'; import { Context } from '@hono/hono';
const emptyArrayController = (c: Context) => c.json([]); const emptyArrayController = (c: Context) => c.json([]);
const emptyObjectController = (c: Context) => c.json({});
const notImplementedController = (c: Context) => Promise.resolve(c.json({ error: 'Not implemented' }, 404)); const notImplementedController = (c: Context) => Promise.resolve(c.json({ error: 'Not implemented' }, 404));
export { emptyArrayController, emptyObjectController, notImplementedController }; export { emptyArrayController, notImplementedController };

View file

@ -9,7 +9,7 @@ import { Storages } from '@/storages.ts';
import { nostrNow } from '@/utils.ts'; import { nostrNow } from '@/utils.ts';
import { parseBody } from '@/utils/api.ts'; import { parseBody } from '@/utils/api.ts';
import { aesEncrypt } from '@/utils/aes.ts'; import { aesEncrypt } from '@/utils/aes.ts';
import { generateToken } from '@/utils/auth.ts'; import { generateToken, getTokenHash } from '@/utils/auth.ts';
const passwordGrantSchema = z.object({ const passwordGrantSchema = z.object({
grant_type: z.literal('password'), grant_type: z.literal('password'),
@ -79,6 +79,37 @@ const createTokenController: AppController = async (c) => {
} }
}; };
// This endpoint only requires the token.
// I don't think having the app credentials solves anything.
const revokeTokenSchema = z.object({
token: z.string(),
});
/**
* Mastodon OAuth token revocation.
* https://docs.joinmastodon.org/methods/oauth/#revoke
*/
const revokeTokenController: AppController = async (c) => {
const body = await parseBody(c.req.raw);
const result = revokeTokenSchema.safeParse(body);
if (!result.success) {
return c.json({ error: 'Bad request', schema: result.error }, 400);
}
const { token } = result.data;
const kysely = await Storages.kysely();
const tokenHash = await getTokenHash(token as `token1${string}`);
await kysely
.deleteFrom('auth_tokens')
.where('token_hash', '=', tokenHash)
.execute();
return c.json({});
};
async function getToken( async function getToken(
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] }, { pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
): Promise<`token1${string}`> { ): Promise<`token1${string}`> {
@ -229,4 +260,4 @@ function addCodeToRedirectUri(redirectUri: string, code: string, state?: string)
return url.toString(); return url.toString();
} }
export { createTokenController, oauthAuthorizeController, oauthController }; export { createTokenController, oauthAuthorizeController, oauthController, revokeTokenController };