summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/mastodon/endpoints/apps.ts
blob: 72b520c74a3397c95db0dd73355cee1916c2f356 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
 * SPDX-FileCopyrightText: marie and other Sharkey contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { Injectable } from '@nestjs/common';
import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js';
import { MastodonConverters } from '@/server/api/mastodon/MastodonConverters.js';
import type { FastifyInstance } from 'fastify';

const readScope = [
	'read:account',
	'read:drive',
	'read:blocks',
	'read:favorites',
	'read:following',
	'read:messaging',
	'read:mutes',
	'read:notifications',
	'read:reactions',
	'read:pages',
	'read:page-likes',
	'read:user-groups',
	'read:channels',
	'read:gallery',
	'read:gallery-likes',
];

const writeScope = [
	'write:account',
	'write:drive',
	'write:blocks',
	'write:favorites',
	'write:following',
	'write:messaging',
	'write:mutes',
	'write:notes',
	'write:notifications',
	'write:reactions',
	'write:votes',
	'write:pages',
	'write:page-likes',
	'write:user-groups',
	'write:channels',
	'write:gallery',
	'write:gallery-likes',
];

export interface AuthPayload {
	scopes?: string | string[],
	redirect_uris?: string | string[],
	client_name?: string | string[],
	website?: string | string[],
}

// Not entirely right, but it gets TypeScript to work so *shrug*
type AuthMastodonRoute = { Body?: AuthPayload, Querystring: AuthPayload };

@Injectable()
export class ApiAppsMastodon {
	constructor(
		private readonly clientService: MastodonClientService,
		private readonly mastoConverters: MastodonConverters,
	) {}

	public register(fastify: FastifyInstance): void {
		fastify.post<AuthMastodonRoute>('/v1/apps', async (_request, reply) => {
			const body = _request.body ?? _request.query;
			if (!body.scopes) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "scopes"' });
			if (!body.redirect_uris) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "redirect_uris"' });
			if (Array.isArray(body.redirect_uris)) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Invalid payload "redirect_uris": only one value is allowed' });
			if (!body.client_name) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Missing required payload "client_name"' });
			if (Array.isArray(body.client_name)) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Invalid payload "client_name": only one value is allowed' });
			if (Array.isArray(body.website)) return reply.code(400).send({ error: 'BAD_REQUEST', error_description: 'Invalid payload "website": only one value is allowed' });

			let scope = body.scopes;
			if (typeof scope === 'string') {
				scope = scope.split(/[ +]/g);
			}

			const pushScope = new Set<string>();
			for (const s of scope) {
				if (s.match(/^read/)) {
					for (const r of readScope) {
						pushScope.add(r);
					}
				}
				if (s.match(/^write/)) {
					for (const r of writeScope) {
						pushScope.add(r);
					}
				}
			}

			const client = this.clientService.getClient(_request);
			const appData = await client.registerApp(body.client_name, {
				scopes: Array.from(pushScope),
				redirect_uri: body.redirect_uris,
				website: body.website,
			});

			const response = {
				id: Math.floor(Math.random() * 100).toString(),
				name: appData.name,
				website: body.website,
				redirect_uri: body.redirect_uris,
				client_id: Buffer.from(appData.url || '').toString('base64'),
				client_secret: appData.clientSecret,
			};

			return reply.send(response);
		});

		fastify.get('/v1/apps/verify_credentials', async (_request, reply) => {
			const client = this.clientService.getClient(_request);
			const data = await client.verifyAppCredentials();
			const response = this.mastoConverters.convertApplication(data.data);
			return reply.send(response);
		});
	}
}