mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 05:51:55 +08:00
feat: Replace localStorage with secure cookies for OAuth state management
This commit is contained in:
parent
a40234e6c8
commit
67b434e692
@ -29,10 +29,12 @@
|
|||||||
"@radix-ui/react-switch": "^1.1.0",
|
"@radix-ui/react-switch": "^1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.3",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-toast": "^1.2.1",
|
"@radix-ui/react-toast": "^1.2.1",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"framer-motion": "^11.5.4",
|
"framer-motion": "^11.5.4",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.437.0",
|
"lucide-react": "^0.437.0",
|
||||||
"next": "14.2.7",
|
"next": "14.2.7",
|
||||||
"next-auth": "5.0.0-beta.20",
|
"next-auth": "5.0.0-beta.20",
|
||||||
|
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -44,6 +44,9 @@ dependencies:
|
|||||||
'@radix-ui/react-toast':
|
'@radix-ui/react-toast':
|
||||||
specifier: ^1.2.1
|
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)
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@ -56,6 +59,9 @@ dependencies:
|
|||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^11.5.4
|
specifier: ^11.5.4
|
||||||
version: 11.18.2(react-dom@18.3.1)(react@18.3.1)
|
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:
|
lucide-react:
|
||||||
specifier: ^0.437.0
|
specifier: ^0.437.0
|
||||||
version: 0.437.0(react@18.3.1)
|
version: 0.437.0(react@18.3.1)
|
||||||
@ -1295,6 +1301,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/js-cookie@3.0.6:
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/json5@0.0.29:
|
/@types/json5@0.0.29:
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2962,6 +2972,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
|
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/js-cookie@3.0.5:
|
||||||
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/js-tokens@4.0.0:
|
/js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
import { MessageCircleCode } from "lucide-react";
|
import { MessageCircleCode } from "lucide-react";
|
||||||
|
|
||||||
import { UserAuthorize } from "@/components/auth/user-authorize";
|
import { UserAuthorize } from "@/components/auth/user-authorize";
|
||||||
@ -15,18 +16,19 @@ export default function AuthPage({ searchParams }: Props) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 检查是否有待处理的 OAuth 请求
|
// 检查是否有 OAuth 状态参数
|
||||||
const pendingOAuth = localStorage.getItem("oauth_pending");
|
const oauthState = Cookies.get("oauth_state");
|
||||||
if (pendingOAuth) {
|
if (oauthState) {
|
||||||
try {
|
try {
|
||||||
const params = JSON.parse(pendingOAuth);
|
// 解码 OAuth 参数
|
||||||
// 清除存储的 OAuth 参数
|
const params = JSON.parse(atob(oauthState));
|
||||||
localStorage.removeItem("oauth_pending");
|
// 删除 cookie
|
||||||
// 重定向到 OAuth 授权页面
|
Cookies.remove("oauth_state", { path: "/" });
|
||||||
|
// 构建重定向 URL
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
router.push(`/oauth/authorize?${searchParams.toString()}`);
|
router.push(`/oauth/authorize?${searchParams.toString()}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to process OAuth params:", error);
|
console.error("Failed to process OAuth state:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getClientByClientId } from "@/lib/dto/client";
|
import { getClientByClientId } from "@/lib/dto/client";
|
||||||
import { getCurrentUser } from "@/lib/session";
|
import { getCurrentUser } from "@/lib/session";
|
||||||
import { Authorizing } from "@/components/auth/authorizing";
|
import { Authorizing } from "@/components/auth/authorizing";
|
||||||
import { ErrorCard } from "@/components/auth/error-card";
|
import { ErrorCard } from "@/components/auth/error-card";
|
||||||
import { SaveOAuthParams } from "@/components/auth/save-oauth-params";
|
|
||||||
|
|
||||||
export interface AuthorizeParams {
|
export interface AuthorizeParams {
|
||||||
scope?: string;
|
scope?: string;
|
||||||
@ -22,9 +22,15 @@ export default async function OAuthAuthorization({
|
|||||||
// 检查用户是否已登录
|
// 检查用户是否已登录
|
||||||
const user = await getCurrentUser();
|
const user = await getCurrentUser();
|
||||||
if (!user?.id) {
|
if (!user?.id) {
|
||||||
return (
|
// 将 OAuth 参数保存到 cookie
|
||||||
<SaveOAuthParams searchParams={searchParams} redirectTo="/sign-in" />
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证必要的参数
|
// 验证必要的参数
|
||||||
|
@ -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;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user