diff --git a/bun.lockb b/bun.lockb index 4eb5e31..8645e16 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/index.ts b/index.ts index 9f900ec..f8ddb2a 100644 --- a/index.ts +++ b/index.ts @@ -1,36 +1,132 @@ -Bun.serve({ - port: 41691, - async fetch(req: Request) { - if (req.method == "OPTIONS") { - return new Response(null) - } - const url = new URL(req.url, "https://loc.al/"); +const { App, ExpressReceiver } = (await import("@slack/bolt")); +import postgres from "postgres"; +import "dotenv/config"; +import bcrypt from "bcrypt"; - if (url.pathname == "/callback") { - const code = url.searchParams.get("code"); +const sql = postgres({ + host: '/var/run/postgresql', + database: 'haroon_osu', + username: 'haroon' +}) - const data = await fetch("https://osu.ppy.sh/oauth/token", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: `client_id=33126&client_secret=${encodeURIComponent(Bun.env.CLIENT_SECRET!)}&code=${code}&grant_type=authorization_code&redirect_uri=${encodeURIComponent("https://osu.haroon.hackclub.app/callback")}` - }).then(res => res.json()); +const receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET! }) - if (data.error) { - console.log(data) - return new Response(`Something went wrong: \n\n${data.message} (${data.error})\n\nThis has been reported.`) - } else { - const user = await fetch("https://osu.ppy.sh/api/v2/me", { - headers: { - "Authorization": `Bearer ${data.access_token}` - } - }).then(res => res.json()); - - return new Response(`Hello, ${user.username}!`) - } - } - - return new Response(null, { status: 404 }); +const app = new App({ + token: process.env.SLACK_BOT_TOKEN, + signingSecret: process.env.SLACK_SIGNING_SECRET, + receiver, + installerOptions: { + port: 41691 } -}) \ No newline at end of file +}); + +const states = new Map(); + +app.command("/osu-link", async (ctx) => { + await ctx.ack(); + + const [ exists = null ] = await sql`SELECT osu_id FROM links WHERE slack_id = ${ctx.context.userId}`; + + if (exists) { + return ctx.respond({ + text: "This slack account is already linked to an osu! account.", + unfurl_links: true, + blocks: [ + { + type: 'section', + text: { + type: "mrkdwn", + text: `This slack account is already linked to an .` + } + } + ] + + }) + + return; + } + + const verifCode = `OSULEADERBOARD-${ctx.context.userId}-${Date.now()}`; + + states.set(ctx.context.userId, verifCode); + + const encodedCode = await bcrypt.hash(verifCode, 10); + + ctx.respond({ + replace_original: true, + text: "View this message in your client to verify!", + blocks: [ + { + type: 'section', + text: { + type: "mrkdwn", + text: `Hey <@${ctx.context.userId}>! To link your osu! account to your Slack account, click this button:` + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "Link account", + "emoji": true + }, + "value": "link", + "url": `https://osu.ppy.sh/oauth/authorize?client_id=33126&redirect_uri=https://osu.haroon.hackclub.app/osu/callback&response_type=code&state=${encodeURIComponent(ctx.context.userId + ":" + encodedCode)}`, + "action_id": "link" + } + } + ] + }) +}) + +receiver.router.get("/osu/callback", async (req, res) => { + res.contentType("text/html") + + const code = req.query.code as string; + const state = req.query.state as string; + + const [userId, hash] = state.split(':'); + + try { + const isValid = await bcrypt.compare(states.get(userId), hash); + + if (!isValid) { + throw new Error(); + } + } catch (err) { + return res.send(`Something went wrong:

Your state was invalid. Please re-authenticate. (invalid_state)

This has been reported.`) + } + + states.delete(userId); + + const data = await fetch("https://osu.ppy.sh/oauth/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: `client_id=33126&client_secret=${encodeURIComponent(process.env.CLIENT_SECRET!)}&code=${code}&grant_type=authorization_code&redirect_uri=${encodeURIComponent("https://osu.haroon.hackclub.app/osu/callback")}` + }).then(res => res.json()); + + if (data.error) { + console.log(data) + return res.send(`Something went wrong:

${data.message} (${data.error})

This has been reported.`) + } else { + const user = await fetch("https://osu.ppy.sh/api/v2/me", { + headers: { + "Authorization": `Bearer ${data.access_token}` + } + }).then(res => res.json()); + + // {user.id} - osu! user ID + // userId - slack user ID + + sql`INSERT INTO links VALUES (${user.id}, ${userId})` + + return res.send(`Your osu! account (${user.id}) has been successfully linked to your Slack account (${userId})!`) + } +}) + +;(async () => { + await app.start(41691); + + console.log('⚡️ Bolt app is running!'); +})(); \ No newline at end of file diff --git a/package.json b/package.json index d092803..b37c796 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,12 @@ }, "peerDependencies": { "typescript": "^5.0.0" + }, + "dependencies": { + "@slack/bolt": "^3.19.0", + "@types/bcrypt": "^5.0.2", + "bcrypt": "^5.1.1", + "dotenv": "^16.4.5", + "postgres": "^3.4.4" } } \ No newline at end of file