Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 112 additions & 57 deletions src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { ExternalLinkIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useExternalBrowser } from "@/hooks/useExternalBrowser";
import { Analytics } from "@/service/analytics";
import BrowserRedirectPage from "./BrowserRedirectPage";
Expand All @@ -8,78 +16,125 @@ const GOOGLE_LOGIN_URL = `${import.meta.env.VITE_API_URL}/oauth2/authorization/g
const AEGIS_HOMEPAGE_URL = "https://dkuaegis.org/";
const GMAIL_GUIDE_URL = "https://sites.google.com/dankook.ac.kr/help";

// IBK 기업은행 점검 시간 체크 함수
const isMaintenanceTime = (): boolean => {
const now = new Date();
const month = now.getMonth() + 1; // 0-based이므로 +1
const date = now.getDate();
const hours = now.getHours();

// 3월 1일 0시~12시 (0시부터 11시 59분까지)
return month === 3 && date === 1 && hours >= 0 && hours < 12;
};
Comment on lines +19 to +28

@coderabbitai coderabbitai Bot Feb 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine its structure
cd src/pages && ls -la LoginPage.tsx
wc -l LoginPage.tsx

Repository: dkuaegis/aegis-join

Length of output: 143


🏁 Script executed:

# Read the LoginPage.tsx file to see the actual implementation
cat -n src/pages/LoginPage.tsx | head -80

Repository: dkuaegis/aegis-join

Length of output: 3161


🏁 Script executed:

# Search for any maintenance-related server logic or configuration
rg "maintenance|MAINTENANCE|점검|2026-03-01" --type ts --type tsx --type js --type json -i

Repository: dkuaegis/aegis-join

Length of output: 90


🏁 Script executed:

# Search for OAuth or API blocking logic related to maintenance
rg "oauth2.*google|authorization.*google" -A 5 -B 5 --type ts --type tsx --type js

Repository: dkuaegis/aegis-join

Length of output: 90


🏁 Script executed:

# Search for maintenance-related logic without file type specification
rg "maintenance|MAINTENANCE|점검|2026-03-01" -i

Repository: dkuaegis/aegis-join

Length of output: 1059


🏁 Script executed:

# Search for server configuration or environment files that might contain maintenance logic
fd -e json -e config -e env | head -20

Repository: dkuaegis/aegis-join

Length of output: 232


🏁 Script executed:

# Search for any OAuth blocking or server-side validation
rg "oauth|authorization|google" -i | grep -E "(block|maintenance|check|verify)" -i

Repository: dkuaegis/aegis-join

Length of output: 45


🏁 Script executed:

# Check if there's a backend directory or if this is frontend-only
ls -la

Repository: dkuaegis/aegis-join

Length of output: 1459


🏁 Script executed:

# Examine package.json to understand the project structure
cat package.json | head -50

Repository: dkuaegis/aegis-join

Length of output: 1569


🏁 Script executed:

# Search for any service or API files that might handle OAuth
fd -e ts -e js | grep -E "(service|api|oauth)" | head -20

Repository: dkuaegis/aegis-join

Length of output: 262


🏁 Script executed:

# Check the main API file to understand OAuth implementation
cat -n src/api/api.ts | head -100

Repository: dkuaegis/aegis-join

Length of output: 1429


🏁 Script executed:

# Check if there's any maintenance blocking in the OAuth or login flow
cat -n src/api/errorMessage.ts

Repository: dkuaegis/aegis-join

Length of output: 3688


🏁 Script executed:

# Look at the full LoginPage to see where GOOGLE_LOGIN_URL is used
cat -n src/pages/LoginPage.tsx | tail -70

Repository: dkuaegis/aegis-join

Length of output: 2617


로컬 시각 기반 점검 판별로 운영 기준 시각과 불일치 및 매년 반복 차단됩니다.

현재 구현은 브라우저의 로컬 시간대를 따르므로 사용자 위치 시간대에 따라 오판 가능성이 있고, 연도 조건이 없어 매년 3월 1일마다 반복 차단됩니다. 일회성 2026년 3월 1일 점검이라면 절대 시각 기반 비교로 고정해야 합니다.

🔧 제안 수정안
+const MAINTENANCE_START_MS = Date.parse("2026-03-01T00:00:00+09:00");
+const MAINTENANCE_END_MS = Date.parse("2026-03-01T12:00:00+09:00");
+
 // IBK 기업은행 점검 시간 체크 함수
 const isMaintenanceTime = (): boolean => {
-  const now = new Date();
-  const month = now.getMonth() + 1; // 0-based이므로 +1
-  const date = now.getDate();
-  const hours = now.getHours();
-
-  // 3월 1일 0시~12시 (0시부터 11시 59분까지)
-  return month === 3 && date === 1 && hours >= 0 && hours < 12;
+  const nowMs = Date.now();
+  return nowMs >= MAINTENANCE_START_MS && nowMs < MAINTENANCE_END_MS;
 };

추가로 useEffect 의존성 배열이 비어있어 페이지를 열어둔 상태에서 점검 해제 시각 전환이 반영되지 않습니다. 또한 UI 숨김만으로는 서버 측 OAuth 엔드포인트 직접 접근을 차단하지 못하므로, 백엔드에서도 유사한 점검 시간 검증이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/LoginPage.tsx` around lines 19 - 28, Replace the local-time,
year-less check in isMaintenanceTime with an absolute UTC timestamp comparison
for the one-time 2026-03-01 window (e.g., compute start/end via Date.UTC(2026,
2, 1, 0, 0, 0) and Date.UTC(2026, 2, 1, 12, 0, 0) and compare against Date.now()
or new Date().getTime() in UTC), update the React effect that relies on it (the
useEffect that shows/hides the UI) to re-evaluate periodically or set a timer
(setInterval or setTimeout to the end time) so the UI updates when maintenance
ends, and ensure the same absolute-time check is implemented on the backend
OAuth enforcement path so UI hiding cannot be bypassed by direct endpoint calls
(reference isMaintenanceTime and the relevant useEffect).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번만 핫픽스로 하고 이후에 기존 코드로 롤백하고자 하는데도 문제가 있나요?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KwonDeaGeun, 일회성 핫픽스 후 롤백하신다면 연도 조건 누락으로 인한 매년 반복 차단 문제는 해결됩니다.

다만 다음 두 가지는 일회성 핫픽스라도 여전히 고려가 필요합니다:

  1. 타임존 문제: 현재 코드는 new Date()로 로컬 시각을 사용하므로, 해외에 있는 사용자나 시스템 시간대 설정이 다른 사용자는 의도하지 않은 시간에 차단되거나 차단이 풀릴 수 있습니다. 한국 시각 기준으로 정확히 제어하려면 KST(UTC+9) 고정이 필요합니다.

  2. 백엔드 검증 부재: UI에서만 Google 로그인 버튼을 숨겨도, 사용자가 GOOGLE_LOGIN_URL을 직접 브라우저 주소창에 입력하거나 개발자 도구로 우회하면 OAuth 인증이 진행됩니다. 점검 기간 중 실제로 차단하려면 백엔드에서도 동일한 시간 체크가 필요합니다.

핫픽스로 급하게 적용하시는 상황이라면 현재 코드로도 대부분의 한국 사용자에게는 정상 작동할 것으로 보입니다. 하지만 가능하다면 위 두 가지를 추가로 보완하시는 것을 권장드립니다.


const LoginPage = () => {
const { isKakaoInApp } = useExternalBrowser();
const [showMaintenanceModal, setShowMaintenanceModal] = useState(false);
const [isMaintenance, setIsMaintenance] = useState(false);

useEffect(() => {
const maintenance = isMaintenanceTime();
setIsMaintenance(maintenance);
if (maintenance) {
setShowMaintenanceModal(true);
}
}, []);

if (isKakaoInApp) {
return <BrowserRedirectPage />;
}

return (
<div className="line-breaks flex min-h-screen flex-col items-center justify-center bg-background">
<div className="w-full max-w-[400px] space-y-6 p-4">
<div className="flex flex-col space-y-2 text-center">
<img
src="/aegis-logo.webp"
alt="Aegis Logo"
className="mx-auto mb-4 h-32 w-32"
/>
<h1 className="font-semibold text-2xl tracking-tight">
Aegis 회원 가입
</h1>
<p className="text-muted-foreground text-sm">
단국대학교 구글 계정으로 로그인해주세요
</p>
<>
<Dialog
open={showMaintenanceModal}
onOpenChange={setShowMaintenanceModal}
>
<DialogContent>
<DialogHeader>
<DialogTitle>시스템 점검 안내</DialogTitle>
<DialogDescription className="pt-4 text-base">
IBK 기업은행 시스템 점검으로 인해
<br />
<strong className="text-foreground">3월 1일 0시 ~ 12시</strong>
까지
<br />
회원 가입이 불가능합니다.
<br />
<br />
점검 종료 후 이용해주시기 바랍니다.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>

<div className="line-breaks flex min-h-screen flex-col items-center justify-center bg-background">
<div className="w-full max-w-[400px] space-y-6 p-4">
<div className="flex flex-col space-y-2 text-center">
<img
src="/aegis-logo.webp"
alt="Aegis Logo"
className="mx-auto mb-4 h-32 w-32"
/>
<h1 className="font-semibold text-2xl tracking-tight">
Aegis 회원 가입
</h1>
<p className="text-muted-foreground text-sm">
단국대학교 구글 계정으로 로그인해주세요
</p>
</div>
{!isMaintenance && (
<Button
onClick={() => {
Analytics.safeTrack("Google_Login_Click", {
category: "Auth",
method: "Google",
});
}}
className="w-full"
asChild
>
<a href={GOOGLE_LOGIN_URL}>Google로 로그인</a>
</Button>
)}
Comment on lines +86 to +99

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 점검/로그인 차단 관련 로직이 저장소 내에 존재하는지 확인
rg -n -C2 'oauth2/authorization/google|isMaintenanceTime|isMaintenance' --type typescript --type tsx --type ts

Repository: dkuaegis/aegis-join

Length of output: 97


🏁 Script executed:

#!/bin/bash
# 백엔드 OAuth 엔드포인트 관련 로직 검색
rg -n -C3 'oauth2|authorization|google' --type java --type kotlin --type python --type js -i

Repository: dkuaegis/aegis-join

Length of output: 93


🏁 Script executed:

#!/bin/bash
# 점검 모드 관련 서버 사이드 로직 확인
rg -n 'maintenance|점검|isMaintenanceTime' -A3 -B3 | head -100

Repository: dkuaegis/aegis-join

Length of output: 2449


🏁 Script executed:

#!/bin/bash
# 백엔드 OAuth 구현 및 인증 엔드포인트 찾기
fd -e java -e kt -e py | head -20

Repository: dkuaegis/aegis-join

Length of output: 45


🏁 Script executed:

#!/bin/bash
# GOOGLE_LOGIN_URL 정의 및 OAuth 엔드포인트 확인
rg -n "GOOGLE_LOGIN_URL|oauth2/authorization"

Repository: dkuaegis/aegis-join

Length of output: 261


🏁 Script executed:

#!/bin/bash
# 인증/OAuth 관련 서버 사이드 로직 검색
rg -n "authorize|oauth|authentication|Authorization" -C2 | head -150

Repository: dkuaegis/aegis-join

Length of output: 802


🏁 Script executed:

#!/bin/bash
# 저장소 전체 구조 확인 - 백엔드 코드 유무
find . -name "*.java" -o -name "*.kt" -o -name "*.py" -o -name "application.yml" -o -name "application.yaml" 2>/dev/null | head -20

Repository: dkuaegis/aegis-join

Length of output: 45


🏁 Script executed:

#!/bin/bash
# package.json 및 프로젝트 구조 확인
cat package.json | head -30

Repository: dkuaegis/aegis-join

Length of output: 997


🏁 Script executed:

#!/bin/bash
# 환경 변수 및 API 설정 확인
rg -n "VITE_API_URL|API_BASE_URL|backends|proxy"

Repository: dkuaegis/aegis-join

Length of output: 389


OAuth 엔드포인트 접근 차단이 백엔드에서도 강제되어야 합니다.

현재 구현은 프론트엔드에서만 버튼을 숨기고 있어, /oauth2/authorization/google에 직접 접근할 경우 점검 시간 차단을 우회할 수 있습니다. 이 저장소는 프론트엔드만 포함하고 있으므로, 백엔드 OAuth 엔드포인트에서도 isMaintenanceTime() 로직과 동일한 조건으로 요청을 거부하도록 구현되었는지 확인이 필요합니다. 프론트 가드는 UX 안내용으로 유지하고, 실제 차단은 백엔드 인증 엔드포인트에서 시행되어야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/LoginPage.tsx` around lines 86 - 99, Frontend currently hides the
Google login Button (GOOGLE_LOGIN_URL) when isMaintenance is true, but the
backend OAuth endpoint (/oauth2/authorization/google) must also enforce the same
maintenance logic; update the authentication server's OAuth entry (the handler
that processes requests to /oauth2/authorization/google) to call the same
maintenance check (isMaintenanceTime() or equivalent) and return an HTTP 503 (or
an appropriate error status) with a maintenance message when true, while keeping
the frontend guard for UX; ensure the backend log includes context and the same
maintenance condition is centralized/shared if possible so both front and back
use the same predicate.

<Button
onClick={() => {
Analytics.safeTrack("Go_Homepage_Click", {
category: "Auth",
method: "Email",
});
}}
className="w-full"
asChild
>
<a
href={AEGIS_HOMEPAGE_URL}
target="_blank"
rel="noopener noreferrer"
>
Aegis 홈페이지
</a>
</Button>
</div>
<Button
onClick={() => {
Analytics.safeTrack("Google_Login_Click", {
category: "Auth",
method: "Google",
});
}}
className="w-full"
asChild
>
<a href={GOOGLE_LOGIN_URL}>Google로 로그인</a>
</Button>
<Button
onClick={() => {
Analytics.safeTrack("Go_Homepage_Click", {
category: "Auth",
method: "Email",
});
}}
className="w-full"
asChild
>
<div className="flex flex-col text-center">
<a
href={AEGIS_HOMEPAGE_URL}
href={GMAIL_GUIDE_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-1 font-extrabold text-muted-foreground text-sm underline"
onClick={() => {
Analytics.safeTrack("Gmail_Guide_Click", {
category: "Link",
method: "Guide",
});
}}
>
Aegis 홈페이지
단국대 Gmail 생성 가이드
<ExternalLinkIcon className="h-4 w-4" />
</a>
</Button>
</div>
<div className="flex flex-col text-center">
<a
href={GMAIL_GUIDE_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-1 font-extrabold text-muted-foreground text-sm underline"
onClick={() => {
Analytics.safeTrack("Gmail_Guide_Click", {
category: "Link",
method: "Guide",
});
}}
>
단국대 Gmail 생성 가이드
<ExternalLinkIcon className="h-4 w-4" />
</a>
</div>
</div>
</div>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Payment/Payment.Information.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const Information: React.FC = () => {
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(
import.meta.env.VITE_ADMIN_ACCOUNT_NUMBER,
import.meta.env.VITE_ADMIN_ACCOUNT_NUMBER
);
toast.success("복사되었습니다.");
} catch (error) {
Expand Down