diff --git a/app/api/auth/github/callback/route.ts b/app/api/auth/github/callback/route.ts index 6a1ef73..435680a 100644 --- a/app/api/auth/github/callback/route.ts +++ b/app/api/auth/github/callback/route.ts @@ -4,6 +4,7 @@ import { cookies } from "next/headers" export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const code = searchParams.get("code") + const state = searchParams.get("state") const error = searchParams.get("error") const origin = request.nextUrl.origin @@ -13,6 +14,16 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(new URL("/?github_error=" + error, origin)) } + const cookieStore = await cookies() + const savedState = cookieStore.get("github_oauth_state")?.value + + if (!state || state !== savedState) { + console.error("[GitHub] State mismatch") + return NextResponse.redirect(new URL("/?github_error=state_mismatch", origin)) + } + + cookieStore.delete("github_oauth_state") + if (!code) { return NextResponse.redirect(new URL("/?github_error=no_code", origin)) } @@ -46,7 +57,6 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(new URL("/?github_error=no_access_token", origin)) } - const cookieStore = await cookies() cookieStore.set("github_token", accessToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", diff --git a/app/api/auth/github/route.ts b/app/api/auth/github/route.ts index ce0175f..4359e8a 100644 --- a/app/api/auth/github/route.ts +++ b/app/api/auth/github/route.ts @@ -1,4 +1,5 @@ import { NextRequest, NextResponse } from "next/server" +import { cookies } from "next/headers" export async function GET(request: NextRequest) { const clientId = process.env.GITHUB_CLIENT_ID @@ -9,11 +10,22 @@ export async function GET(request: NextRequest) { const redirectUri = `${request.nextUrl.origin}/api/auth/github/callback` const scope = "repo read:user" + const state = crypto.randomUUID() + + const cookieStore = await cookies() + cookieStore.set("github_oauth_state", state, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 300, + path: "/", + }) const githubAuthUrl = new URL("https://github.com/login/oauth/authorize") githubAuthUrl.searchParams.set("client_id", clientId) githubAuthUrl.searchParams.set("scope", scope) githubAuthUrl.searchParams.set("redirect_uri", redirectUri) + githubAuthUrl.searchParams.set("state", state) return NextResponse.redirect(githubAuthUrl.toString()) } diff --git a/app/api/auth/slack/callback/route.ts b/app/api/auth/slack/callback/route.ts index dc82a77..7952162 100644 --- a/app/api/auth/slack/callback/route.ts +++ b/app/api/auth/slack/callback/route.ts @@ -4,6 +4,7 @@ import { cookies } from "next/headers" export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const code = searchParams.get("code") + const state = searchParams.get("state") const error = searchParams.get("error") const origin = request.nextUrl.origin @@ -13,6 +14,16 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(new URL("/?slack_error=" + error, origin)) } + const cookieStore = await cookies() + const savedState = cookieStore.get("slack_oauth_state")?.value + + if (!state || state !== savedState) { + console.error("[Slack] State mismatch") + return NextResponse.redirect(new URL("/?slack_error=state_mismatch", origin)) + } + + cookieStore.delete("slack_oauth_state") + if (!code) { return NextResponse.redirect(new URL("/?slack_error=no_code", origin)) } @@ -46,7 +57,6 @@ export async function GET(request: NextRequest) { } // Store token in HTTP-only cookie - const cookieStore = await cookies() cookieStore.set("slack_token", userToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", diff --git a/app/api/auth/slack/route.ts b/app/api/auth/slack/route.ts index 5a62f6a..073aae8 100644 --- a/app/api/auth/slack/route.ts +++ b/app/api/auth/slack/route.ts @@ -1,4 +1,5 @@ import { NextRequest, NextResponse } from "next/server" +import { cookies } from "next/headers" export async function GET(request: NextRequest) { const clientId = process.env.SLACK_CLIENT_ID @@ -9,11 +10,22 @@ export async function GET(request: NextRequest) { const redirectUri = `${request.nextUrl.origin}/api/auth/slack/callback` const scopes = ["search:read", "users:read", "im:read"].join(",") + const state = crypto.randomUUID() + + const cookieStore = await cookies() + cookieStore.set("slack_oauth_state", state, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 300, + path: "/", + }) const slackAuthUrl = new URL("https://slack.com/oauth/v2/authorize") slackAuthUrl.searchParams.set("client_id", clientId) slackAuthUrl.searchParams.set("user_scope", scopes) slackAuthUrl.searchParams.set("redirect_uri", redirectUri) + slackAuthUrl.searchParams.set("state", state) return NextResponse.redirect(slackAuthUrl.toString()) } diff --git a/package.json b/package.json index 8e3cec4..c918776 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "worklog", - "version": "2.0.18", + "version": "2.0.19", "private": true, "scripts": { "dev": "next dev",