From 9b7d2bba78fb6c9465e08b6196a3ccf4fce19de5 Mon Sep 17 00:00:00 2001 From: Andrei Jiroh Halili Date: Mon, 22 Jul 2024 00:39:34 +0800 Subject: [PATCH] feat(golinks): more Slack slash commands related chaos Also your usual commit snapshot of my work as scrap. Signed-off-by: Andrei Jiroh Halili --- apps/golinks-v2/package.json | 3 +- apps/golinks-v2/src/api/golinks.ts | 11 +- apps/golinks-v2/src/api/slack.ts | 61 ++++++----- apps/golinks-v2/src/api/wikilinks.ts | 0 apps/golinks-v2/src/index.tsx | 4 +- apps/golinks-v2/src/lib/db.ts | 4 +- apps/golinks-v2/src/types.ts | 27 +++-- yarn.lock | 152 ++------------------------- 8 files changed, 68 insertions(+), 194 deletions(-) create mode 100644 apps/golinks-v2/src/api/wikilinks.ts diff --git a/apps/golinks-v2/package.json b/apps/golinks-v2/package.json index eac81ed..2499b8d 100644 --- a/apps/golinks-v2/package.json +++ b/apps/golinks-v2/package.json @@ -22,12 +22,11 @@ "args": "echo $*" }, "dependencies": { - "@octokit/rest": "^21.0.1", "@prisma/adapter-d1": "^5.17.0", "@prisma/client": "^5.17.0", "chanfana": "^2.0.2", "cross-fetch": "^4.0.0", - "hono": "^4.5.0", + "hono": "^4.5.1", "zod": "^3.23.8" }, "devDependencies": { diff --git a/apps/golinks-v2/src/api/golinks.ts b/apps/golinks-v2/src/api/golinks.ts index 9ff43cb..b98994d 100644 --- a/apps/golinks-v2/src/api/golinks.ts +++ b/apps/golinks-v2/src/api/golinks.ts @@ -16,8 +16,7 @@ export class GoLinkCreate extends OpenAPIRoute { "application/json": { schema: z.object({ slug: Str({ required: false }), - targetUrl: Str({ example: "https://example.com" }), - is_active: z.boolean().default(true), + targetUrl: Str({ example: "https://example.com" }) }), }, }, @@ -60,7 +59,7 @@ export class GoLinkCreate extends OpenAPIRoute { console.log(`[golinks-api] received body for link creation ${JSON.stringify(linkToCreate)}`); const slug = linkToCreate.slug !== undefined ? linkToCreate.slug : generateSlug(12); - const result = await addGoLink(c.env.golinks, slug, linkToCreate.targetUrl, linkToCreate.is_active); + const result = await addGoLink(c.env.golinks, slug, linkToCreate.targetUrl); if (result) { return c.newResponse( @@ -123,3 +122,9 @@ export class GoLinkList extends OpenAPIRoute { }; } } + +export class UpdateGoLink extends OpenAPIRoute { + schema = { + summary: "Update a golink" + } +} diff --git a/apps/golinks-v2/src/api/slack.ts b/apps/golinks-v2/src/api/slack.ts index 5e0cbbd..a9edcd4 100644 --- a/apps/golinks-v2/src/api/slack.ts +++ b/apps/golinks-v2/src/api/slack.ts @@ -1,6 +1,7 @@ import fetch from "cross-fetch"; import { Context } from "hono"; import crypto from "node:crypto"; +import { Buffer } from "node:buffer"; /** * Handle requests for OAuth-based app installation @@ -32,12 +33,12 @@ export async function slackOAuth(context: Context) { headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: formBody, }); - const result = await api.json() - console.log(`[slack-oauth] result: ${JSON.stringify(result)} (${api.status})`); + const result = await api.json() + console.log(`[slack-oauth] result: ${JSON.stringify(result)} (${api.status})`); if (result.ok == false) { - return context.json({ ok: false, error: result.error }, api.status); - } - return context.json({ ok: true, result: "Successfully installed." }); + return context.json({ ok: false, error: result.error }, api.status); + } + return context.json({ ok: true, result: "Successfully installed." }); } catch (err) { console.error(err); return context.json({ ok: false, error: "Something gone wrong" }); @@ -55,36 +56,44 @@ export async function handleSlackCommand(context: Context) { // 1. Get Signing Secret and Request Body const signingSecret = context.env.SLACK_SIGNING_SECRET; // Access signing secret from env const body = await context.req.arrayBuffer(); + const start = Date.now() // 2. Get Request Headers const timestamp = context.req.header("X-Slack-Request-Timestamp"); const signature = context.req.header("X-Slack-Signature"); + const { command } = await context.req.param(); + const authToken = await context.req.query("token") + const data = context.req.parseBody(); // Parse body as JSON + + await console.log(`[slack-slash-commands] params: ${JSON.stringify(data)}`); + await console.log(`[slack-slash-commands] headers: ${JSON.stringify({ timestamp, command })}`); + // 3. Validate Request Timestamp (optional) - if (!validateTimestamp(timestamp)) { + /*if (!validateTimestamp(timestamp)) { return new Response("Request is too old.", { status: 403 }); - } + }*/ + + /* temporarily disabled for now // 4. Calculate Base String const baseString = `v0:${timestamp}:${await createHashedBody(body)}`; // 5. Calculate Expected Signature const expectedSignature = `sha256=${crypto.createHmac("sha256", signingSecret).update(baseString).digest("hex")}`; - await console.log(`[slack-slash-commands]: expected: ${expectedSignature}, received: ${signature}`); // 6. Validate Signature if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { - return new Response("Invalid request signature.", { status: 403 }); + await console.log(`[slack-slash-commands]: expected ${expectedSignature} but received ${signature}`); } - - const { command } = await context.req.param(); - const data = JSON.parse(await context.req.text()); // Parse body as JSON - - await console.log(`[slack-slash-commands] params: ${JSON.stringify(data)}`); - await console.log(`[slack-slash-commands] headers: ${JSON.stringify({ timestamp, command })}`); + */ if (command === "go") { return context.newResponse("Still working in it.", 200, { "Content-Type": "text/plain" }); + } else if (command == "ping") { + const end = Date.now() - start + await console.log(`Pong with ${end}ms`) + return context.newResponse(`Pong with ${end}ms response time in backend`) } return context.newResponse("Unsupported command"); } @@ -96,17 +105,19 @@ export async function handleSlackCommand(context: Context) { * @returns */ function validateTimestamp(timestamp: string): boolean { - if (!timestamp) { - return false; - } - const currentTimestamp = Date.now() - const requestTimestamp = new Date(timestamp) - const delta = Math.abs(currentTimestamp - requestTimestamp) / (1000 * 60); - return delta >= 3 && delta <= 5; + if (!timestamp) { + return false; + } + const currentTimestamp = Date.now() + const requestTimestamp = new Date(timestamp).getMilliseconds() + const delta = Math.abs(currentTimestamp - requestTimestamp) / (1000 * 60); + console.log(`[slack-slash-commands] current: ${currentTimestamp}, request: ${requestTimestamp}, delta: ${delta}`) + return delta >= 3 && delta <= 5; } async function createHashedBody(body: ArrayBuffer): Promise { - const hash = crypto.createHash("sha256"); - hash.update(body); - return hash.digest("hex"); + const buffer = Buffer.from(body) + const hash = crypto.createHash("sha256"); + hash.update(buffer); + return hash.digest("hex"); } diff --git a/apps/golinks-v2/src/api/wikilinks.ts b/apps/golinks-v2/src/api/wikilinks.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/golinks-v2/src/index.tsx b/apps/golinks-v2/src/index.tsx index 7d9902a..4aa8438 100644 --- a/apps/golinks-v2/src/index.tsx +++ b/apps/golinks-v2/src/index.tsx @@ -30,7 +30,7 @@ app.use( "/api/*", cors({ origin(origin, c) { - if (origin.endsWith("andreijiroh.xyz") || origin.endsWith("ajhalili2006.workers.dev")) { + if (origin.endsWith("andreijiroh.xyz") || origin.endsWith("ajhalili2006.workers.dev") || origin.endsWith("fawn-cod.ts.net")) { return origin; } }, @@ -76,7 +76,7 @@ openapi.get("/api/commit", CommitHash); app.post("/api/slack/slash-commands/:command", async (c) => handleSlackCommand(c)) // Slack bot and slash commands -app.get("/slack", async(c) => slackOAuth(c)); +app.get("/slack", async (c) => slackOAuth(c)); app.get("/slack/callback", async (c) => slackOAuth(c)); app.get("/", (c) => { diff --git a/apps/golinks-v2/src/lib/db.ts b/apps/golinks-v2/src/lib/db.ts index 2fa48df..b7fde8f 100644 --- a/apps/golinks-v2/src/lib/db.ts +++ b/apps/golinks-v2/src/lib/db.ts @@ -105,15 +105,13 @@ export async function addGoLink( db: EnvBindings["golinks"], slug: string, targetUrl: string, - isActive?: boolean, ): Promise | null { const adapter = new PrismaD1(db); const prisma = new PrismaClient({ adapter }); const result = prisma.goLink.create({ data: { slug, - targetUrl, - is_active: isActive !== undefined ? isActive : true, + targetUrl }, }); return result; diff --git a/apps/golinks-v2/src/types.ts b/apps/golinks-v2/src/types.ts index 1edb234..2ec0015 100644 --- a/apps/golinks-v2/src/types.ts +++ b/apps/golinks-v2/src/types.ts @@ -1,19 +1,12 @@ import { Bool, DateTime, Int, Str } from "chanfana"; import { z } from "zod"; -export const Task = z.object({ - name: Str({ example: "lorem" }), - slug: Str(), - description: Str({ required: false }), - completed: z.boolean().default(false), - due_date: DateTime(), -}); - export const GoLinks = z.object({ id: Int({ required: false }), slug: Str({ required: true }), targetUrl: Str({ example: "https://example.com" }), is_active: z.boolean().default(true), + deactivation_reason: Str({ required: false }), created_on: DateTime({ default: Date.now() }), updated_on: DateTime({ default: Date.now() }), }); @@ -26,19 +19,25 @@ export const DiscordInvites = z.object({ description: Str(), nsfw: z.boolean().default(false), is_active: z.boolean().default(true), + deactivation_reason: Str({ required: false }), created_on: DateTime({ default: Date.now() }), updated_on: DateTime({ default: Date.now() }), }); +/** + * Just export WikiLinks as same schema as golinks +*/ +export const WikiLinks = GoLinks + export interface Env { DEPLOY_ENV: "production" | "staging" | "development"; GIT_DEPLOY_COMMIT: string; - SLACK_OAUTH_ID: string - SLACK_OAUTH_SECRET: string, - SLACK_OAUTH_CALLBACK_URL: string, - SLACK_SIGNING_SECRET: string, - GITHUB_OAUTH_ID: string, - GITHUB_OAUTH_SECRET: string, + SLACK_OAUTH_ID: string + SLACK_OAUTH_SECRET: string, + SLACK_OAUTH_CALLBACK_URL: string, + SLACK_SIGNING_SECRET: string, + GITHUB_OAUTH_ID: string, + GITHUB_OAUTH_SECRET: string, golinks: D1Database; ADMIN_KEY: string; } diff --git a/yarn.lock b/yarn.lock index c1bcc0c..c51d30e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -441,129 +441,6 @@ __metadata: languageName: node linkType: hard -"@octokit/auth-token@npm:^5.0.0": - version: 5.1.1 - resolution: "@octokit/auth-token@npm:5.1.1" - checksum: 10c0/1e6117c5170de9a5532ffb85e0bda153f4dffdd66871c42de952828eddd9029fe5161a2a8bf20b57f0d45c80f8fb9ddc69aa639e0fa6b776829efb1b0881b154 - languageName: node - linkType: hard - -"@octokit/core@npm:^6.1.2": - version: 6.1.2 - resolution: "@octokit/core@npm:6.1.2" - dependencies: - "@octokit/auth-token": "npm:^5.0.0" - "@octokit/graphql": "npm:^8.0.0" - "@octokit/request": "npm:^9.0.0" - "@octokit/request-error": "npm:^6.0.1" - "@octokit/types": "npm:^13.0.0" - before-after-hook: "npm:^3.0.2" - universal-user-agent: "npm:^7.0.0" - checksum: 10c0/f73be16a8013f69197b7744de75537d869f3a2061dda25dcde746d23b87f305bbdc7adbfe044ab0755eec32e6d54d61c73f4ca788d214eba8e88648a3133733e - languageName: node - linkType: hard - -"@octokit/endpoint@npm:^10.0.0": - version: 10.1.1 - resolution: "@octokit/endpoint@npm:10.1.1" - dependencies: - "@octokit/types": "npm:^13.0.0" - universal-user-agent: "npm:^7.0.2" - checksum: 10c0/946517241b33db075e7b3fd8abc6952b9e32be312197d07d415dbefb35b93d26afd508f64315111de7cabc2638d4790a9b0b366cf6cc201de5ec6997c7944c8b - languageName: node - linkType: hard - -"@octokit/graphql@npm:^8.0.0": - version: 8.1.1 - resolution: "@octokit/graphql@npm:8.1.1" - dependencies: - "@octokit/request": "npm:^9.0.0" - "@octokit/types": "npm:^13.0.0" - universal-user-agent: "npm:^7.0.0" - checksum: 10c0/fe68b89b21416f56bc9c0d19bba96a9a8ee567312b6fb764b05ea0649a5e44bec71665a0013e7c34304eb77c20ad7e7a7cf43b87ea27c280350229d71034c131 - languageName: node - linkType: hard - -"@octokit/openapi-types@npm:^22.2.0": - version: 22.2.0 - resolution: "@octokit/openapi-types@npm:22.2.0" - checksum: 10c0/a45bfc735611e836df0729f5922bbd5811d401052b972d1e3bc1278a2d2403e00f4552ce9d1f2793f77f167d212da559c5cb9f1b02c935114ad6d898779546ee - languageName: node - linkType: hard - -"@octokit/plugin-paginate-rest@npm:^11.0.0": - version: 11.3.3 - resolution: "@octokit/plugin-paginate-rest@npm:11.3.3" - dependencies: - "@octokit/types": "npm:^13.5.0" - peerDependencies: - "@octokit/core": ">=6" - checksum: 10c0/f1fd41e0ae47d6813d283e0e4ab89f4a9d55dbc8ec1a44b5bb00355007e5260db124a10f94b34a86b0305a1adda61c35cd57c0914c7c093c05f93199be87459e - languageName: node - linkType: hard - -"@octokit/plugin-request-log@npm:^5.3.1": - version: 5.3.1 - resolution: "@octokit/plugin-request-log@npm:5.3.1" - peerDependencies: - "@octokit/core": ">=6" - checksum: 10c0/2f959934b8285cf39a1d1d0b92ec881b3ae171ae74738225f87b89381afd72a32bc7ea9c04d2dcee74f74ad24c22cce0c5f3e5b4333d531ea67b985e4ee90cb0 - languageName: node - linkType: hard - -"@octokit/plugin-rest-endpoint-methods@npm:^13.0.0": - version: 13.2.4 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.4" - dependencies: - "@octokit/types": "npm:^13.5.0" - peerDependencies: - "@octokit/core": ">=6" - checksum: 10c0/7a63b37bb1eb9972e3cc2cf6e6b53ad5c14152767420a7f9ec4ccf2557b411fd0df442cf98866fa09ebbd39f1e69b688730feaf3cfd4056713e73a33c40ed04f - languageName: node - linkType: hard - -"@octokit/request-error@npm:^6.0.1": - version: 6.1.4 - resolution: "@octokit/request-error@npm:6.1.4" - dependencies: - "@octokit/types": "npm:^13.0.0" - checksum: 10c0/899668b1cd012642fc6a42184cc14b9767de15534b66722a40327fd6ef1c7b050003a7a5ecbc61b0d1f33ceae8dc7b196c941a2397e5f7cc540aa6149ef1f730 - languageName: node - linkType: hard - -"@octokit/request@npm:^9.0.0": - version: 9.1.3 - resolution: "@octokit/request@npm:9.1.3" - dependencies: - "@octokit/endpoint": "npm:^10.0.0" - "@octokit/request-error": "npm:^6.0.1" - "@octokit/types": "npm:^13.1.0" - universal-user-agent: "npm:^7.0.2" - checksum: 10c0/41c26387ca9b5b3081a17eebea0c7d6b0122f6b2cb21c2fd7ef63ca587a828448e40b33973416f615fed139c659598f2ae7a1370cc103738f0f6f3297b5fc4ab - languageName: node - linkType: hard - -"@octokit/rest@npm:^21.0.1": - version: 21.0.1 - resolution: "@octokit/rest@npm:21.0.1" - dependencies: - "@octokit/core": "npm:^6.1.2" - "@octokit/plugin-paginate-rest": "npm:^11.0.0" - "@octokit/plugin-request-log": "npm:^5.3.1" - "@octokit/plugin-rest-endpoint-methods": "npm:^13.0.0" - checksum: 10c0/b1d54fa564ef2a96f9d25f17df6bbc845aa4a10eec7dd810b65ed616755a24c5cc9d98ad20cf9ba61eb0c9d8cf5ce44814e544f4247d0c1e4d2fc74f265188d2 - languageName: node - linkType: hard - -"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.5.0": - version: 13.5.0 - resolution: "@octokit/types@npm:13.5.0" - dependencies: - "@octokit/openapi-types": "npm:^22.2.0" - checksum: 10c0/355ebc6776ce23feace1b1be0927cdda758790fda83068109c4f27b354dcd43d0447d4dc24e5eafdb596465469ea1baed23f3fd63adfec508cc375ccd1dcb0a3 - languageName: node - linkType: hard - "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -1025,13 +902,6 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^3.0.2": - version: 3.0.2 - resolution: "before-after-hook@npm:3.0.2" - checksum: 10c0/dea640f9e88a1085372c9bcc974b7bf379267490693da92ec102a7d8b515dd1e95f00ef575a146b83ca638104c57406c3427d37bdf082f602dde4b56d05bba14 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -1210,14 +1080,13 @@ __metadata: resolution: "cloudflare-workers-openapi@workspace:apps/golinks-v2" dependencies: "@cloudflare/workers-types": "npm:^4.20240712.0" - "@octokit/rest": "npm:^21.0.1" "@prisma/adapter-d1": "npm:^5.17.0" "@prisma/client": "npm:^5.17.0" "@types/node": "npm:^20.14.11" "@types/service-worker-mock": "npm:^2.0.4" chanfana: "npm:^2.0.2" cross-fetch: "npm:^4.0.0" - hono: "npm:^4.5.0" + hono: "npm:^4.5.1" prisma: "npm:^5.17.0" ts-node: "npm:^10.9.2" typescript: "npm:^5.5.3" @@ -1742,10 +1611,10 @@ __metadata: languageName: node linkType: hard -"hono@npm:^4.5.0": - version: 4.5.0 - resolution: "hono@npm:4.5.0" - checksum: 10c0/0c1d10b0e30202a1493ab9db7e8068449a10ad352e3a9794d866eaa08c15b04c0d896499fec8ebe8994035a93ae0f54691c59c712600cd0515bcaee87bfbb1c9 +"hono@npm:^4.5.1": + version: 4.5.1 + resolution: "hono@npm:4.5.1" + checksum: 10c0/71228cefd3808b4bb42c10de23d67f135678eb0ab7fdfd1728d934ec2ee1241be9b1260f279b81acd2a6a57346d06d047a2650ff9bddd48fda6581751a690d80 languageName: node linkType: hard @@ -2904,11 +2773,11 @@ __metadata: "typescript@patch:typescript@npm%3A^5.5.3#optional!builtin": version: 5.5.3 - resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=b45daf" + resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/5a437c416251334deeaf29897157032311f3f126547cfdc4b133768b606cb0e62bcee733bb97cf74c42fe7268801aea1392d8e40988cdef112e9546eba4c03c5 + checksum: 10c0/911c7811d61f57f07df79c4a35f56a0f426a65426a020e5fcd792f66559f399017205f5f10255329ab5a3d8c2d1f1d19530aeceffda70758a521fae1d469432e languageName: node linkType: hard @@ -2967,13 +2836,6 @@ __metadata: languageName: node linkType: hard -"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": - version: 7.0.2 - resolution: "universal-user-agent@npm:7.0.2" - checksum: 10c0/e60517ee929813e6b3ac0ceb3c66deccafadc71341edca160279ff046319c684fd7090a60d63aa61cd34a06c2d2acebeb8c2f8d364244ae7bf8ab788e20cd8c8 - languageName: node - linkType: hard - "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" -- 2.51.2