From 67b434e692a03ab73532f75d686e017256fd352f Mon Sep 17 00:00:00 2001 From: wood chen Date: Fri, 21 Feb 2025 20:04:26 +0800 Subject: [PATCH] feat: Replace localStorage with secure cookies for OAuth state management --- package.json | 2 ++ pnpm-lock.yaml | 15 +++++++++++++++ src/app/(auth)/authorize/page.tsx | 18 ++++++++++-------- src/app/(oauth)/oauth/authorize/page.tsx | 14 ++++++++++---- src/components/auth/save-oauth-params.tsx | 21 --------------------- 5 files changed, 37 insertions(+), 33 deletions(-) delete mode 100644 src/components/auth/save-oauth-params.tsx diff --git a/package.json b/package.json index f71dea4..0da164c 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,12 @@ "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-toast": "^1.2.1", + "@types/js-cookie": "^3.0.6", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "crypto-js": "^4.2.0", "framer-motion": "^11.5.4", + "js-cookie": "^3.0.5", "lucide-react": "^0.437.0", "next": "14.2.7", "next-auth": "5.0.0-beta.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bc90c4..7e1de82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ dependencies: '@radix-ui/react-toast': specifier: ^1.2.1 version: 1.2.6(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 class-variance-authority: specifier: ^0.7.0 version: 0.7.1 @@ -56,6 +59,9 @@ dependencies: framer-motion: specifier: ^11.5.4 version: 11.18.2(react-dom@18.3.1)(react@18.3.1) + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 lucide-react: specifier: ^0.437.0 version: 0.437.0(react@18.3.1) @@ -1295,6 +1301,10 @@ packages: resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} dev: true + /@types/js-cookie@3.0.6: + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + dev: false + /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true @@ -2962,6 +2972,11 @@ packages: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} dev: false + /js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} diff --git a/src/app/(auth)/authorize/page.tsx b/src/app/(auth)/authorize/page.tsx index 685921c..8dc4ce8 100644 --- a/src/app/(auth)/authorize/page.tsx +++ b/src/app/(auth)/authorize/page.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; +import Cookies from "js-cookie"; import { MessageCircleCode } from "lucide-react"; import { UserAuthorize } from "@/components/auth/user-authorize"; @@ -15,18 +16,19 @@ export default function AuthPage({ searchParams }: Props) { const router = useRouter(); useEffect(() => { - // 检查是否有待处理的 OAuth 请求 - const pendingOAuth = localStorage.getItem("oauth_pending"); - if (pendingOAuth) { + // 检查是否有 OAuth 状态参数 + const oauthState = Cookies.get("oauth_state"); + if (oauthState) { try { - const params = JSON.parse(pendingOAuth); - // 清除存储的 OAuth 参数 - localStorage.removeItem("oauth_pending"); - // 重定向到 OAuth 授权页面 + // 解码 OAuth 参数 + const params = JSON.parse(atob(oauthState)); + // 删除 cookie + Cookies.remove("oauth_state", { path: "/" }); + // 构建重定向 URL const searchParams = new URLSearchParams(params); router.push(`/oauth/authorize?${searchParams.toString()}`); } catch (error) { - console.error("Failed to process OAuth params:", error); + console.error("Failed to process OAuth state:", error); } } }, [router]); diff --git a/src/app/(oauth)/oauth/authorize/page.tsx b/src/app/(oauth)/oauth/authorize/page.tsx index a79ed3d..ab4eaf1 100644 --- a/src/app/(oauth)/oauth/authorize/page.tsx +++ b/src/app/(oauth)/oauth/authorize/page.tsx @@ -1,10 +1,10 @@ +import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { getClientByClientId } from "@/lib/dto/client"; import { getCurrentUser } from "@/lib/session"; import { Authorizing } from "@/components/auth/authorizing"; import { ErrorCard } from "@/components/auth/error-card"; -import { SaveOAuthParams } from "@/components/auth/save-oauth-params"; export interface AuthorizeParams { scope?: string; @@ -22,9 +22,15 @@ export default async function OAuthAuthorization({ // 检查用户是否已登录 const user = await getCurrentUser(); if (!user?.id) { - return ( - - ); + // 将 OAuth 参数保存到 cookie + const encodedParams = btoa(JSON.stringify(searchParams)); + cookies().set("oauth_state", encodedParams, { + maxAge: 60 * 10, // 10分钟过期 + path: "/", + httpOnly: true, + secure: process.env.NODE_ENV === "production", + }); + redirect("/sign-in"); } // 验证必要的参数 diff --git a/src/components/auth/save-oauth-params.tsx b/src/components/auth/save-oauth-params.tsx deleted file mode 100644 index f966dde..0000000 --- a/src/components/auth/save-oauth-params.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { redirect } from "next/navigation"; - -import { AuthorizeParams } from "@/app/(oauth)/oauth/authorize/page"; - -export function SaveOAuthParams({ - searchParams, - redirectTo, -}: { - searchParams: AuthorizeParams; - redirectTo: string; -}) { - useEffect(() => { - localStorage.setItem("oauth_pending", JSON.stringify(searchParams)); - window.location.href = redirectTo; - }, [searchParams, redirectTo]); - - return null; -}