Skip to content

Commit

Permalink
feat(golinks): implement JWT-style OAuth states to simplify auth process
Browse files Browse the repository at this point in the history
Related to andreijiroh-dev/personal-launchpad#4

Signed-off-by: Andrei Jiroh Halili <[email protected]>
  • Loading branch information
ajhalili2006 committed Jul 25, 2024
1 parent c156204 commit b5d52a9
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 36 deletions.
5 changes: 3 additions & 2 deletions apps/golinks-v2/.env.staging
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ DOTENV_PUBLIC_KEY_STAGING="034c79ba2fb095ec68ac4eb1e221efd30e59562a0aa0dec6ef59b

# .env.staging
ADMIN_KEY="gostg_hackclub-7yIz7ZgIy7eS9LI2KId1WxdgBisVXTKGBvuLBroIXVBqS9U9"
SLACK_OAUTH_ID="encrypted:BJQ8fQ75jn47+M/zjnB4EUcFNWddn/4CUsKSM2WnAqyfI35Eruy7ok8rC0VbDUnaIlmJci8/Pubw52Q7ay3YdpFf11imbAf0p8QguRa0O1SsI/yS1DmmLoXKDehy0/ryUKHX+ISG8n5G1pwNKBsorzDZDo55wsEfuA=="
SLACK_OAUTH_SECRET="encrypted:BE03v8ulDAGLm3YDUzVfA5nykckcelcONkvVcwbLJf/tzA3KZA8E//0t9l63DnZ+TZRdoUo0zYMfWavc86TKQ0IbxVGa/OFTr/WbgAtQYJmkAbSxXAeAGMeBHb1xHIEXwiCzb6nKyeufwG3GHuMgxAFNwwE8h3SQmXa6aVYxGK/Q"
SLACK_OAUTH_ID="encrypted:BKFzEW1G3qx0HYMABCLTA2hLKeRXH0Ef4bUSdfLjDFMMF6k5noAcgOYWWV5cSX2TO1+1z17Czh4tmuXka3UD4WzVLpFgl5PsDpiHpPyzsnOjaoHRXhyw5p//CZNfGrCfaQPRYGy5DMJIjYgZ4/IOzpVKM5Z0SeUqsw=="
SLACK_OAUTH_SECRET="encrypted:BP/OZimnqrvCWyra4jvCBhdkeK9GgcTYoD3OC4Lg2ji4rZsmcRYvQuOHSsnpa9/wIdt7ZejqCa1m5Fuk5BtxzpwESQKhoI8eM/ll0cdh1CrDyo4tK343Oz1q2v90UjB9kjJjdfYFY9hFYCYHjL9WYYHkMLFnd9Wa2kmI88mweU9M"
SLACK_OAUTH_CALLBACK_URL="https://staging.go-next.andreijiroh.xyz/slack/callback"
SLACK_SIGNING_SECRET="encrypted:BJwkqK6gLGi2KQHmBWTFPLTyqkuVNZGxQKYNgyce6CkXYEJLp8TKl3STEebJN4DOe9fzXAwmrYItjcDOFlL2rJ7GoFT7pcyL+YVDRrNDVabsPifoaPN2gXmPEIer7E1wRRoWezYCmKUIZ/VhC8cixe+j6NJqLjmFfJzOlxsMJZBP"
GITHUB_OAUTH_ID="encrypted:BFgFkTuRYLVQHLjw24RfWeNwBvIsNETscW5IL3Afx31MvWWLhKOSiAX6B11lMoryhd8xg4gCnjf/oxDFUaVCvg1imhn5XHEf7pMK3hnRWsLAcX1gtoZJCSp9CR1t80auv/gu932bgzJkNzOKT8yCiWO/7wzR"
GITHUB_OAUTH_SECRET="encrypted:BL8SpcP5PUf6D63yO4h0zHFZRYsNcBU7D4qOoC7F1Y274XxfvsU4z5iAcJq0c26KN+wE186iEA7v3d9DJvRN+Yj1BO8MpzJLHi+ihSNiKLA1tH4VasFo88SkLtNicqy7jXAXIU5V+kN3Qh5h3ybqMcXTGGWrGmK5hzBacQeZQhJ0pT9l4HV3agI="
JWT_SIGNING_KEY="encrypted:BLCIOnbH+OUgZREsrjTdnplJAR3Uq1hhDswMSbhvPXElSAnUMmrNEWH08W6uGeKLiIzhTJ2qgHKNpJKHscPcA+klY74QOEaOLiLkocl8vUGyqyKLLnbZxRqM9ZXq+u4C6ip+BfBmtxmsQ2yPU/eSFZw4iWuz05cyp3dgQFL+LVpLg9TwdiM4ijiiB8WFncdnVdzM2xlxWSgcYfS6MDyb4KNVmjxhvbFxKB9SvLkT9CHpNHp7sXRohRMVImpI8xHnTAeMv9tg9xKU6jC1LIKjStBBMQiHqz7SzmSyLCzmQ4Td/5ntEOnOT9NJjHtdZxZFrQHawGsTucMEGstBX7aYUT9iBd3ZNNdziRWLQSLF1FWdHJHMrI+QYHsfeSLcnRvYM/drhy2rFQww/l8rG7hJayGgv4D593qYZIx3LsD8G9DkYtPvLO0p6eOdPj9yO/fQXoSRfsX6lFSf0IvX4CTDKiglw1I2OOk9GTVVsNXBnideCFeIMchlKi0SwBggHpac011AG5HHc4JOmBPfM6q4RsqT+jzGL6pHGqKPksZ890NdZ7OE9V6Xd7ink8STF99G/lNo/MZ5SSTShllhWhaKQu2CF9msEg/y7NWMMs1+nh2nWmbqOB/lVSZ5Q79KNitVTzzEbkLV59bE5Qh2wZgIkYRJzu0fGZg075v010zj3FQB2dFZ3Pst6bnj2q5fSypKMeY5rJLU7LVDiykj0n6SoEyFVy2ahM/UC+FlTnsAlrfyM6Kt0KakWo0+9+aC9Ug6fXD55UN7ylFoZPQaiL5Ka7878yvMFk7/Kf5m0ECIb8Xe"
11 changes: 2 additions & 9 deletions apps/golinks-v2/src/api/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,10 @@ export async function githubAuth(context: Context) {
const appId = context.env.GITHUB_OAUTH_ID;
const appSecret = context.env.GITHUB_OAUTH_SECRET;
const redirect_uri = `${context.env.BASE_URL}/auth/github/callback`;
const { slack_id, slack_team, state, client_id } = context.req.query();
const { state, client_id } = context.req.query();
if (client_id == "slack") {
const requestState = encodeURIComponent(
JSON.stringify({
slack_id,
slack_team,
state,
}),
);
return context.redirect(
`https://github.com/login/oauth/authorize?client_id=${appId}&redirect_uri=${redirect_uri}&state=${requestState}&scope=read:user,user:email`,
`https://github.com/login/oauth/authorize?client_id=${appId}&redirect_uri=${redirect_uri}&state=${state}&scope=read:user,user:email`,
);
}
}
Expand Down
106 changes: 86 additions & 20 deletions apps/golinks-v2/src/api/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Buffer } from "node:buffer";
import { generateSlug } from "lib/utils";
import { PrismaD1 } from "@prisma/adapter-d1";
import { Prisma, PrismaClient } from "@prisma/client";
import { addBotToken, generateChallenge, lookupBotToken, slackOAuthExchange, updateBotToken } from "lib/auth";
import { addBotToken, lookupBotToken, slackOAuthExchange, updateBotToken } from "lib/auth";
import jwt from "jsonwebtoken"

type SlackSlashCommand = {
token?: string;
Expand All @@ -22,9 +23,54 @@ type SlackSlashCommand = {
trigger_id: string;
};

type SlackInteractivityPayload = {
type: string;
user: {
id: string,
username: string,
name: string,
team_id: string
},
api_app_id: string
token: string,
container: {
channel_id: string,
is_epheral: boolean
message_ts: string,
type: string
},
trigger_id: string,
team: {
id: string,
name: string
},
enterprise: null | {
id: string,
name: string
}
is_enterprise_install: boolean,
channel: {
values: object
},
response_url: string,
actions: SlackInteractivityActions[]
}

type SlackInteractivityActions = {
block_id: string,
action_id: string,
type: string,
text: {
type: string,
text: string,
emoji: boolean
},
value: string,
action_ts: string
}

async function helpMessage(context: Context, params: SlackSlashCommand) {
const challenge = generateSlug(24);
const githubAuthUrl = `${context.env.BASE_URL}/auth/github?client_id=slack&slack_team=${params.team_id}&slack_id=${params.user_id}&state=${challenge}`;
const templateJson = {
blocks: [
{
Expand Down Expand Up @@ -74,7 +120,6 @@ async function helpMessage(context: Context, params: SlackSlashCommand) {
},
value: challenge,
action_id: "github-auth-challenge",
url: githubAuthUrl,
},
},
{
Expand Down Expand Up @@ -107,16 +152,28 @@ async function helpMessage(context: Context, params: SlackSlashCommand) {
return context.json(templateJson);
}

function authChallengePrompt(context: Context, params: SlackSlashCommand) {
const newchallenge = `challenge_${generateSlug(24)}`;
const githubAuthUrl = `${context.env.BASE_URL}/auth/github?client_id=slack&slack_team=${params.team_id}&slack_id=${params.user_id}&state=${challenge}`;
async function authChallengePrompt(context: Context, params: SlackInteractivityPayload) {
const challenge = params.actions[0].value;
const jwtState = jwt.sign({
slack_id: params.user.id,
slack_team: params.team.id,
slack_enterprise_id: params.enterprise !== null ? params.enterprise.id : null,
slack_enterprise_install: params.is_enterprise_install,
challenge
},
context.env.JWT_SIGNING_KEY,
{
issuer: context.env.BASE_URL,
expiresIn: '15m',
})
const githubAuthUrl = `${context.env.BASE_URL}/auth/github?client_id=slack&state=${jwtState}`;
const templateCallback = {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: "You should be redirected to the GitHub OAuth flow soon. Once you copied the code, press the button below and paste the code in the popout.",
text: "First, authenicate with GitHub using the button below. Note that we included your Slack ID and the team you are currently in to help us monitor API key requests.",
},
},
{
Expand All @@ -126,20 +183,19 @@ function authChallengePrompt(context: Context, params: SlackSlashCommand) {
type: "button",
text: {
type: "plain_text",
text: "Submit authenication ticket",
text: "Sign in with GitHub",
emoji: true,
},
style: "primary",
value: "ghAuthTicket_tbd",
action_id: "github-auth-challenge",
url: githubAuthUrl,
style: "primary"
},
],
},
{
type: "section",
text: {
type: "mrkdwn",
text: "Having issues? Try authenicating again by using the button below or get a new challenge with `/go login` command.",
text: "Once you copied the code, press the button below and paste the code in the popout.",
},
},
{
Expand All @@ -149,18 +205,21 @@ function authChallengePrompt(context: Context, params: SlackSlashCommand) {
type: "button",
text: {
type: "plain_text",
text: "Retry OAuth flow",
text: "Submit authenication ticket",
emoji: true,
},
url: "https://todo",
style: "primary",
value: "ghAuthTicket_tbd",
action_id: "github-auth-challenge",
},
],
},
],
};
return context.json(templateCallback, 200);
}

async function sneakyWIPScreen(context: Context, params: SlackSlashCommand) {
async function sneakyWIPScreen(context: Context, params: SlackInteractivityPayload) {
const blocks = {
type: "modal",
close: {
Expand All @@ -178,7 +237,7 @@ async function sneakyWIPScreen(context: Context, params: SlackSlashCommand) {
type: "section",
text: {
type: "mrkdwn",
text: "This feature is currently under development and working on stablizing this for you to use them soon. ",
text: "This feature is currently under development and working on stablizing this for you to use them soon.",
},
},
{
Expand All @@ -199,7 +258,7 @@ async function sneakyWIPScreen(context: Context, params: SlackSlashCommand) {
},
],
};
return context.json(blocks);
return context.json(blocks, 200);
}

/**
Expand Down Expand Up @@ -321,11 +380,18 @@ export async function handleSlackCommand(context: Context) {

export async function handleSlackInteractivity(context: Context) {
const authToken = await context.req.query("token");
const data = await context.req.parseBody();
const { payload } = await context.req.parseBody();
const parsedPayload: SlackInteractivityPayload = JSON.parse(payload)
const headers = await context.req.header()
await console.log(`[slack-interactivity] data:`, data);
await console.log(`[slack-interactivity] data:`, parsedPayload);
await console.log(`[slack-interactivity] actions:`, parsedPayload.actions)
await console.log(`[slack-interactivity] headers:`, headers);
return await sneakyWIPScreen(context, data);

if (parsedPayload.actions[0].action_id == "github-auth-challenge") {
return await authChallengePrompt(context, parsedPayload)
} else {
return await sneakyWIPScreen(context, parsedPayload);
}
}

/**
Expand Down
4 changes: 2 additions & 2 deletions apps/golinks-v2/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GoLinkCreate, GoLinkList, GoLinkUpdate } from "api/golinks";
import { fromHono } from "chanfana";
import { Context, Hono } from "hono";
import { cors } from "hono/cors";
import { EnvBindings, Env } from "./types";
import { EnvBindings } from "./types";
import { getDiscordInvite, getLink } from "lib/db";
import {
adminApiKey,
Expand All @@ -25,7 +25,7 @@ import { handleSlackCommand, handleSlackInteractivity, slackOAuth, slackOAuthCal
import { githubAuth } from "api/github";

// Start a Hono app
const app = new Hono<{ Bindings: EnvBindings<Env> }>();
const app = new Hono<{ Bindings: EnvBindings }>();
app.use(prettyJSON());
app.use("/openapi.*", cors());
app.use(
Expand Down
8 changes: 5 additions & 3 deletions apps/golinks-v2/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const DiscordInvites = z.object({
*/
export const WikiLinks = GoLinks;

export interface Env {
export interface EnvBindings {
DEPLOY_ENV: "production" | "staging" | "development";
GIT_DEPLOY_COMMIT: string;
SLACK_OAUTH_ID: string;
Expand All @@ -41,9 +41,10 @@ export interface Env {
golinks: D1Database;
ADMIN_KEY: string;
BASE_URL: string;
JWT_SIGNING_KEY: string
}

export type EnvBindings<Env> = {
/*type EnvBindings = {
golinks: D1Database;
BASE_URL: string;
DEPLOY_ENV: "production" | "staging" | "development";
Expand All @@ -55,4 +56,5 @@ export type EnvBindings<Env> = {
SLACK_SIGNING_SECRET: string;
GITHUB_OAUTH_ID: string;
GITHUB_OAUTH_SECRET: string;
};
JWT_SIGNING_KEY: string
};*/

0 comments on commit b5d52a9

Please sign in to comment.