mirror of
https://github.com/THU-MAIC/OpenMAIC.git
synced 2026-10-17 05:29:46 +08:00
* feat(access-code): add status API endpoint Returns whether ACCESS_CODE is enabled and whether the current user is authenticated via cookie, to support the frontend access gate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(access-code): add verify API endpoint Creates POST /api/access-code/verify that validates ACCESS_CODE env var and sets an HttpOnly 7-day cookie on success. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(access-code): add Next.js middleware to gate API routes Intercepts all requests when ACCESS_CODE env var is set. API routes return 401 without the openmaic_access cookie; page requests pass through so the frontend can render the access-code modal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(i18n): add accessCode translations for all 4 locales Add accessCode top-level key to en-US, zh-CN, ja-JP, and ru-RU locale files with title, placeholder, submit, and error strings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: add ACCESS_CODE documentation to .env.example Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add AccessCodeGuard component and integrate into root layout Wraps page children with an access-code gate that checks /api/access-code/status on mount and renders AccessCodeModal when authentication is required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(access-code): add AccessCodeModal component Adds the modal dialog component that prompts users for an access code, using existing Dialog/Button/Input UI components and i18n translation keys. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(access-code): fix lint error and format files - Fix useEffect setState pattern in access-code-guard.tsx - Format middleware.ts and layout.tsx with Prettier Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style(access-code): redesign modal as full-screen gate Replace generic Radix Dialog with a custom full-screen gate: - Mesh gradient background with noise texture - Frosted glass card with backdrop-blur - Staggered motion animations on mount - Inline submit button inside input field - Success state animation before dismissal - Proper dark mode support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(access-code): address code review findings Critical: - Middleware now validates HMAC-signed cookie tokens, not just existence - Verify endpoint uses timingSafeEqual for constant-time comparison - Cookie value is now `timestamp.HMAC(ACCESS_CODE, timestamp)` instead of UUID Important: - Middleware 401 response includes `errorCode` matching apiError shape - Status endpoint uses apiSuccess wrapper for consistency - Remove unused `accessCode.submit` i18n key from all 4 locales Suggestions: - Guard renders children during loading (overlay on top, not blocking) - Network error defaults to enabled=true (safer than silently disabling) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(access-code): use Web Crypto API in middleware for Edge Runtime Node.js crypto module is not available in Next.js Edge Runtime. Replace createHmac/timingSafeEqual with crypto.subtle HMAC and manual constant-length XOR comparison. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: 杨慎 <117187635+cosarah@users.noreply.github.com>
51 lines
1.3 KiB
TypeScript
51 lines
1.3 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState, ReactNode } from 'react';
|
|
import { AccessCodeModal } from '@/components/access-code-modal';
|
|
|
|
export function AccessCodeGuard({ children }: { children: ReactNode }) {
|
|
const [status, setStatus] = useState<{
|
|
enabled: boolean;
|
|
authenticated: boolean;
|
|
loading: boolean;
|
|
}>({ enabled: false, authenticated: false, loading: true });
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
fetch('/api/access-code/status')
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
if (!cancelled) {
|
|
setStatus({
|
|
enabled: data.enabled,
|
|
authenticated: data.authenticated,
|
|
loading: false,
|
|
});
|
|
}
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) {
|
|
// Default to requiring auth on error — safer than silently disabling
|
|
setStatus({ enabled: true, authenticated: false, loading: false });
|
|
}
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
const needsAuth = !status.loading && status.enabled && !status.authenticated;
|
|
|
|
return (
|
|
<>
|
|
{needsAuth && (
|
|
<AccessCodeModal
|
|
open={true}
|
|
onSuccess={() => setStatus((s) => ({ ...s, authenticated: true }))}
|
|
/>
|
|
)}
|
|
{children}
|
|
</>
|
|
);
|
|
}
|