Add endpoint to GET a push subscription

This commit is contained in:
Alex Gleason 2024-10-14 15:48:55 -05:00
parent b3928bac45
commit 4019099c57
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
2 changed files with 67 additions and 19 deletions

View file

@ -73,7 +73,7 @@ import {
updateConfigController,
} from '@/controllers/api/pleroma.ts';
import { preferencesController } from '@/controllers/api/preferences.ts';
import { pushSubscribeController } from '@/controllers/api/push.ts';
import { getSubscriptionController, pushSubscribeController } from '@/controllers/api/push.ts';
import { deleteReactionController, reactionController, reactionsController } from '@/controllers/api/reactions.ts';
import { relayController } from '@/controllers/nostr/relay.ts';
import {
@ -281,6 +281,7 @@ app.get('/api/v1/mutes', requireSigner, mutesController);
app.get('/api/v1/markers', requireProof(), markersController);
app.post('/api/v1/markers', requireProof(), updateMarkersController);
app.get('/api/v1/push/subscription', requireSigner, getSubscriptionController);
app.post('/api/v1/push/subscription', requireProof(), pushSubscribeController);
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions', reactionsController);

View file

@ -7,6 +7,15 @@ import { Storages } from '@/storages.ts';
import { parseBody } from '@/utils/api.ts';
import { getTokenHash } from '@/utils/auth.ts';
/** https://docs.joinmastodon.org/entities/WebPushSubscription/ */
interface MastodonPushSubscription {
id: string;
endpoint: string;
server_key: string;
alerts: Record<string, boolean>;
policy: 'all' | 'followed' | 'follower' | 'none';
}
const pushSubscribeSchema = z.object({
subscription: z.object({
endpoint: z.string().url(),
@ -33,20 +42,13 @@ const pushSubscribeSchema = z.object({
});
export const pushSubscribeController: AppController = async (c) => {
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
const vapidPublicKey = await Conf.vapidPublicKey;
const header = c.req.header('authorization');
const match = header?.match(BEARER_REGEX);
if (!match) {
return c.json({ error: 'Unauthorized' }, 401);
if (!vapidPublicKey) {
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
}
const [_, accessToken] = match;
if (!accessToken.startsWith('token1')) {
return c.json({ error: 'Unauthorized' }, 401);
}
const accessToken = getAccessToken(c.req.raw);
const kysely = await Storages.kysely();
const signer = c.get('signer')!;
@ -82,11 +84,56 @@ export const pushSubscribeController: AppController = async (c) => {
.executeTakeFirstOrThrow();
});
return c.json({
id,
return c.json(
{
id: id.toString(),
endpoint: subscription.endpoint,
alerts: data?.alerts ?? {},
policy: data?.policy ?? 'all',
server_key: await Conf.vapidPublicKey,
});
server_key: vapidPublicKey,
} satisfies MastodonPushSubscription,
);
};
export const getSubscriptionController: AppController = async (c) => {
const vapidPublicKey = await Conf.vapidPublicKey;
if (!vapidPublicKey) {
return c.json({ error: 'The administrator of this server has not enabled Web Push notifications.' }, 404);
}
const accessToken = getAccessToken(c.req.raw);
const kysely = await Storages.kysely();
const tokenHash = await getTokenHash(accessToken as `token1${string}`);
const row = await kysely
.selectFrom('push_subscriptions')
.selectAll()
.where('token_hash', '=', tokenHash)
.executeTakeFirstOrThrow();
return c.json(
{
id: row.id.toString(),
endpoint: row.endpoint,
alerts: row.data?.alerts ?? {},
policy: row.data?.policy ?? 'all',
server_key: vapidPublicKey,
} satisfies MastodonPushSubscription,
);
};
/** Get access token from HTTP headers, but only if it's a `token1`. Otherwise return undefined. */
function getAccessToken(request: Request): `token1${string}` | undefined {
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
const authorization = request.headers.get('authorization');
const match = authorization?.match(BEARER_REGEX);
const [_, accessToken] = match ?? [];
if (accessToken?.startsWith('token1')) {
return accessToken as `token1${string}`;
}
}