Gacha game centered around vore.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
vore-gacha/src/DiscordWebhookHandler.ts

152 lines
5.6 KiB

import {SavedWebhook} from "./SavedWebhook.js";
import {FastifyReply, FastifyRequest} from "fastify";
import {AccessToken, AuthorizationCode, ModuleOptions} from "simple-oauth2";
import pug from "pug";
import {renderError} from "./PugRenderer.js";
import {APIWebhook} from "discord-api-types";
import {getBaseUrl, getFirstValue} from "./FastifyHelpers.js";
import {join, relative} from "path";
import {RouteGenericInterface} from "fastify/types/route";
import {checkAndClearXSRFCookie, generateXSRFCookie} from "./CookieHelpers.js";
interface DiscordWebhookToken extends AccessToken {
token: { webhook?: APIWebhook }
}
const AuthConfig: ModuleOptions["auth"] = {
tokenHost: "https://discord.com",
authorizePath: "/api/oauth2/authorize",
tokenPath: "/api/oauth2/token",
revokePath: "/api/oauth2/token/revoke",
}
export interface OAuthRoute extends RouteGenericInterface {
Querystring: {
code: string | string[] | undefined,
state: string | string[] | undefined,
error: string | string[] | undefined,
error_description: string | string[] | undefined,
error_uri: string | string[] | undefined,
}
}
export class DiscordWebhookHandler {
readonly webhook: SavedWebhook
readonly templateFilename: string
readonly config: ModuleOptions
readonly destinationFunc: () => string
constructor({
webhook,
templateFilename,
appId,
secret,
destinationFunc
}: { webhook: SavedWebhook, templateFilename: string, appId: string, secret: string, destinationFunc: () => string }) {
this.templateFilename = templateFilename
this.webhook = webhook
this.config = {
client: {
id: appId,
secret,
},
auth: AuthConfig,
}
this.destinationFunc = destinationFunc
}
async handleRequest(req: FastifyRequest<OAuthRoute>, res: FastifyReply): Promise<void> {
const baseUrl = getBaseUrl(req)
const withoutParams = new URL(req.url)
withoutParams.search = ""
const code = getFirstValue(req.query.code) ?? null
const errorCode = getFirstValue(req.query.error) ?? null
const client = new AuthorizationCode(this.config)
if (code === null && errorCode === null) {
const state = generateXSRFCookie(res)
const authUrl = client.authorizeURL({
scope: ["applications.commands", "webhook.incoming"],
redirect_uri: withoutParams.toString(),
state,
})
res.code(200)
res.type("text/html")
res.send(pug.renderFile(join("static/pages", this.templateFilename), {
authUrl,
baseUrl,
isReset: this.webhook.isPresent
}))
return
}
const validXSRF = checkAndClearXSRFCookie(req, res)
if (errorCode !== null || code === null) {
const errorDescription = getFirstValue(req.query.error_description) ?? null
const errorUrl = getFirstValue(req.query.error_uri)
return renderError({
baseUrl,
res,
code: 400,
error: errorDescription ?? errorCode ?? "There was no code or error present.",
context: "authorizing the application",
errorUrl,
buttonText: "Try Again",
buttonUrl: relative("/", new URL(req.url).pathname),
})
}
if (!validXSRF) {
return renderError({
baseUrl,
res,
code: 409,
error: "The state was incorrectly set - try restarting the authentication process.",
context: "processing the new access code",
buttonText: "Try Again",
buttonUrl: relative("/", new URL(req.url).pathname),
})
}
let token: DiscordWebhookToken
try {
token = await client.getToken({
code,
scope: ["applications.commands", "webhook.incoming"],
redirect_uri: getBaseUrl(req) + "setup/gameChannel"
}) as DiscordWebhookToken
} catch (e) {
return renderError({
baseUrl,
res: res,
error: e,
code: 400,
context: "exchanging the code you gave me for a token",
buttonText: "Try Again",
buttonUrl: relative("/", new URL(req.url).pathname),
})
}
if (!token.token.webhook) {
return renderError({
baseUrl,
res: res,
code: 400,
error: "The token did not contain a webhook.",
context: "processing the token I received",
buttonText: "Try Again",
buttonUrl: relative("/", new URL(req.url).pathname),
})
}
const wasSet = this.webhook.isPresent
try {
await this.webhook.replaceHook(token.token.webhook)
} catch (e) {
return renderError({
baseUrl,
res: res,
code: 500,
error: e,
context: `saving the new webhook${wasSet ? " and deleting the old one" : ""}`,
buttonText: "Try Again",
buttonUrl: relative("/", new URL(req.url).pathname),
})
}
res.redirect(this.destinationFunc())
}
}