feat: add /pickable-roles and /pick-role basis

This commit is contained in:
41666 2021-08-01 18:57:57 -04:00
parent c2ee4f380a
commit a4207d5713
22 changed files with 343 additions and 23 deletions

View file

@ -0,0 +1,23 @@
import { interactionsEndpoint } from '../utils/api-tools';
import { getGuildData } from '../utils/guild';
import { invalid, notFound, ok } from '../utils/responses';
export const InteractionsPickRole = interactionsEndpoint(
async (request: Request): Promise<Response> => {
const reqURL = new URL(request.url);
const [, , serverID, userID, roleID] = reqURL.pathname.split('/');
if (!serverID || !userID || !roleID) {
return invalid();
}
const guildData = await getGuildData(serverID);
if (!guildData) {
return notFound();
}
// We get exactly one role, but we have to interact with it the same way as UI does.
// So check for safety, disable any "single" mode roles
return ok();
}
);

View file

@ -0,0 +1,33 @@
import { Category, CategorySlug } from '@roleypoly/types';
import { respond } from '@roleypoly/worker-utils';
import { interactionsEndpoint } from '../utils/api-tools';
import { getGuildData } from '../utils/guild';
import { notFound } from '../utils/responses';
export const InteractionsPickableRoles = interactionsEndpoint(
async (request: Request): Promise<Response> => {
const reqURL = new URL(request.url);
const [, , serverID] = reqURL.pathname.split('/');
const guildData = await getGuildData(serverID);
if (!guildData) {
return notFound();
}
const roleMap: Record<Category['name'], CategorySlug> = {};
for (let category of guildData.categories) {
if (category.hidden) {
continue;
}
// TODO: role safety?
roleMap[category.name] = {
roles: category.roles,
type: category.type,
};
}
return respond(roleMap);
}
);

View file

@ -1,3 +1,5 @@
import { InteractionsPickRole } from '@roleypoly/api/handlers/interactions-pick-role';
import { InteractionsPickableRoles } from '@roleypoly/api/handlers/interactions-pickable-roles';
import { Router } from '@roleypoly/worker-utils/router';
import { BotJoin } from './handlers/bot-join';
import { ClearGuildCache } from './handlers/clear-guild-cache';
@ -32,6 +34,10 @@ router.add('PATCH', 'update-guild', UpdateGuild);
router.add('POST', 'sync-from-legacy', SyncFromLegacy);
router.add('POST', 'clear-guild-cache', ClearGuildCache);
// Interactions endpoints
router.add('GET', 'interactions-pickable-roles', InteractionsPickableRoles);
router.add('POST', 'interactions-pick-role', InteractionsPickRole);
// Tester Routes
router.add('GET', 'x-headers', (request) => {
const headers: { [x: string]: string } = {};

View file

@ -1,3 +1,4 @@
import { notAuthenticated } from '@roleypoly/api/utils/responses';
import {
evaluatePermission,
permissions as Permissions,
@ -5,7 +6,12 @@ import {
import { SessionData, UserGuildPermissions } from '@roleypoly/types';
import { Handler, WrappedKVNamespace } from '@roleypoly/worker-utils';
import KSUID from 'ksuid';
import { allowedCallbackHosts, apiPublicURI, rootUsers } from './config';
import {
allowedCallbackHosts,
apiPublicURI,
interactionsSharedKey,
rootUsers,
} from './config';
import { Sessions } from './kv';
export const formData = (obj: Record<string, any>): string => {
@ -157,3 +163,14 @@ export const isAllowedCallbackHost = (host: string): boolean => {
null
);
};
export const interactionsEndpoint =
(handler: Handler): Handler =>
async (request: Request): Promise<Response> => {
const authHeader = request.headers.get('authorization') || '';
if (authHeader !== `Shared ${interactionsSharedKey}`) {
return notAuthenticated();
}
return handler(request);
};

View file

@ -2,6 +2,7 @@ import { uiPublicURI } from '@roleypoly/api/utils/config';
import {
Category,
DiscordUser,
Embed,
GuildData,
GuildDataUpdate,
GuildSlug,
@ -11,25 +12,10 @@ import { userAgent } from '@roleypoly/worker-utils';
import deepEqual from 'deep-equal';
import { sortBy, uniq } from 'lodash';
type WebhookEmbed = {
fields: {
name: string;
value: string;
inline: boolean;
}[];
timestamp: string;
title: string;
color: number;
author?: {
name: string;
icon_url: string;
};
};
type WebhookPayload = {
username: string;
avatar_url: string;
embeds: WebhookEmbed[];
embeds: Embed[];
provider: {
name: string;
url: string;
@ -39,7 +25,7 @@ type WebhookPayload = {
type ChangeHandler = (
oldValue: GuildDataUpdate[keyof GuildDataUpdate],
newValue: GuildData[keyof GuildDataUpdate]
) => WebhookEmbed[];
) => Embed[];
const changeHandlers: Record<keyof GuildDataUpdate, ChangeHandler> = {
message: (oldValue, newValue) => [

View file

@ -13,3 +13,4 @@ export const apiPublicURI = safeURI(env('API_PUBLIC_URI'));
export const rootUsers = list(env('ROOT_USERS'));
export const allowedCallbackHosts = list(env('ALLOWED_CALLBACK_HOSTS'));
export const importSharedKey = env('BOT_IMPORT_TOKEN');
export const interactionsSharedKey = env('INTERACTIONS_SHARED_KEY');

View file

@ -20,3 +20,6 @@ export const rateLimited = () =>
export const invalid = (obj: any = {}) =>
respond({ err: 'client sent something invalid', data: obj }, { status: 400 });
export const notAuthenticated = () =>
respond({ err: 'not authenticated' }, { status: 403 });

View file

@ -12,6 +12,7 @@ module.exports = {
'API_PUBLIC_URI',
'ROOT_USERS',
'ALLOWED_CALLBACK_HOSTS',
'INTERACTIONS_SHARED_KEY',
]),
kv: ['KV_SESSIONS', 'KV_GUILDS', 'KV_GUILD_DATA'],
};