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
1 change: 0 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ VITE_APP_EGOV_CONTEXT_URL=localhost:8080
#SNS 간편로그인 변수
VITE_APP_NAVER_CLIENTID=YOUR_CLIENT_ID
VITE_APP_NAVER_CALLBACKURL=http://localhost:3000/login/naver/callback
VITE_APP_STATE=egovframe
VITE_APP_KAKAO_CLIENTID=YOUR_CLIENT_ID
VITE_APP_KAKAO_CALLBACKURL=http://localhost:3000/login/kakao/callback
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
settings: { react: { version: "18.2" } },
plugins: ["react-refresh"],
rules: {
"react/jsx-no-target-blank": "off",
"react/jsx-no-target-blank": "error",
"react/prop-types": ["off"],
"react-refresh/only-export-components": [
"warn",
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@ yarn-debug.log*
yarn-error.log*

# code
.history
.history

# Secrets
secrets/
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

![sh1](https://github.com/user-attachments/assets/41757fa0-b976-435a-81ac-e163e2846998)

1. 최초 관리자 계정 설정은 **[ 로그인계정 : admin , 로그인암호 : 1 ]** 로 설정되어 있다.
1. 최초 관리자 계정 설정은 **[ 로그인계정 : admin , 로그인암호 : Admin@1234 ]** 로 설정되어 있다.
2. 메인 화면 우측 상단의 **회원가입** 버튼을 통해 사용자 계정을 생성 가능하다.
3. 기본 기능이나 예시 메뉴를 실무적으로 추가 커스터마이징하여 활용할 수 있다.

Expand All @@ -37,11 +37,14 @@
1. 최초 관리자 계정을 통한 로그인이 가능하다.
2. **회원가입** 버튼을 통해 생성한 사용자 계정을 통한 로그인이 가능하다. (사용자 계정은 일부 메뉴 접근이 제한된다)
3. 로그인 창 하단의 소셜 로그인 버튼으로 네이버 및 카카오 계정으로 로그인이 가능하다. 이 경우의 권한은 사용자 계정과 동일하다.
4. 소셜 로그인 기능의 사용을 위해서는 사전에 **[네이버 개발자 센터](https://developers.naver.com/main/)** 및 **[카카오 개발자 센터](https://developers.kakao.com/)** 에서 Client ID와 Client Secret을 발급 받은 후 Callback URL을 프론트엔드와 백엔드 환경 설정 파일에 등록해야 한다.
5. 프론트엔드 환경 설정 파일은 본 애플리케이션의 `.env.development` (개발 환경) 및 `.env.production` (빌드 시 사용) 을 참고한다.
6. 백엔드 환경 설정 파일은 [심플 홈페이지 Backend](https://github.com/eGovFramework/egovframe-template-simple-backend.git) 의 `application.properties` 를 참고한다.
7. 네이버 소셜 로그인에 대한 상세 사항은 **[네이버 로그인 API 문서](https://developers.naver.com/docs/login/api/api.md)** 를 참조 가능하다.
8. 카카오 소셜 로그인에 대한 상세 사항은 **[카카오 로그인 API 문서](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaologin)** 를 참조 가능하다.
4. 소셜 로그인 기능의 사용을 위해서는 사전에 **[네이버 개발자 센터](https://developers.naver.com/main/)** 및 **[카카오 개발자 센터](https://developers.kakao.com/)** 에서 Client ID·Client Secret 발급 및 Callback URL 등록을 진행한 뒤, 받은 값을 아래와 같이 입력한다.
- **네이버** — 백엔드 `application.properties` 의 `Sns.naver.clientId`, `Sns.naver.clientSecret`, `Sns.naver.callbackUrl` / 프론트엔드 `.env.development` 의 `VITE_APP_NAVER_CLIENTID`, `VITE_APP_NAVER_CALLBACKURL`
- **카카오** — 백엔드 `application.properties` 의 `Sns.kakao.clientId`, `Sns.kakao.callbackUrl` / 프론트엔드 `.env.development` 의 `VITE_APP_KAKAO_CLIENTID`, `VITE_APP_KAKAO_CALLBACKURL`
- ※ Client Secret 은 백엔드에만 등록 (프론트엔드 번들에 포함되면 안 됨). 카카오는 본 프로젝트에서 Secret 미사용.
- ※ OAuth CSRF 방어용 `state` 값은 로그인 시도 시 자동으로 무작위 생성되므로 별도 환경변수 설정 불필요.
5. 프론트엔드 환경 설정 파일은 개발 환경은 `.env.development`, 빌드 산출물 환경은 `.env.production` 을 사용한다. 백엔드 환경 설정 파일은 [심플 홈페이지 Backend](https://github.com/eGovFramework/egovframe-template-simple-backend.git) 의 `application.properties` 를 참고한다.
6. 네이버 소셜 로그인에 대한 상세 사항은 **[네이버 로그인 API 문서](https://developers.naver.com/docs/login/api/api.md)** 를 참조 가능하다.
7. 카카오 소셜 로그인에 대한 상세 사항은 **[카카오 로그인 API 문서](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaologin)** 를 참조 가능하다.

### 사이트 소개 화면

Expand Down Expand Up @@ -91,12 +94,12 @@

## 환경 설정

프로젝트에서 사용된 환경 프로그램 정보는 다음과 같다.
프로젝트 구동에 필요한 환경 프로그램 정보는 다음과 같다.

| 프로그램 명 | 버전 명 |
| 프로그램 명 | 버전 명 |
| :---------- | :------- |
| Node.js | v18.12.0 |
| NPM | v8.19.2 |
| Node.js | v20.19 ~ v22 LTS |
| NPM | v10 이상 |

## BackEnd 구동

Expand Down Expand Up @@ -153,7 +156,7 @@ npm run preview
```

```bash
# 테스트 대상 파일 경로는 vite.config.js에 명시되어 있으며 디폴트로 EgovMain.jsx의 테스트를 실행한다.
# 테스트 대상은 vite.config.js 의 test.include 패턴에 따라 수집된다.
# watch 모드로 테스트를 실행할 경우에는 아래 명령어를 사용한다.
npm run test

Expand Down
5 changes: 4 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import RootRoutes from "@/routes";
import { BrowserRouter as Router } from "react-router-dom";
import { AuthProvider } from "@/contexts/AuthContext";

import "@/css/base.css";
import "@/css/layout.css";
Expand All @@ -11,7 +12,9 @@ function App() {
return (
<div className="wrap">
<Router>
<RootRoutes />
<AuthProvider>
<RootRoutes />
</AuthProvider>
</Router>
</div>
);
Expand Down
39 changes: 5 additions & 34 deletions src/api/egovFetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SERVER_URL } from "../config";
import URL from "@/constants/url";
import CODE from "@/constants/code";
import { getSessionItem, setSessionItem } from "@/utils/storage";
import { logger } from "@/utils/logger";

export function getQueryString(params) {
return `?${Object.entries(params)
Expand All @@ -11,40 +12,18 @@ export function getQueryString(params) {
}

export function requestFetch(url, requestOptions, handler, errorHandler) {
console.groupCollapsed("requestFetch");
console.log("requestFetch [URL] : ", SERVER_URL + url);
console.log("requestFetch [requestOption] : ", requestOptions);

// Login 했을경우 JWT 설정
const sessionUser = getSessionItem("loginUser");
const sessionUserId = sessionUser?.id || null;
const jToken = getSessionItem("jToken");
if (sessionUserId != null && sessionUserId !== undefined) {
if (!requestOptions["headers"]) requestOptions["headers"] = {};
if (!requestOptions["headers"]["Authorization"])
requestOptions["headers"]["Authorization"] = null;
requestOptions["headers"]["Authorization"] = jToken;
}

//CORS ISSUE 로 인한 조치 - origin 및 credentials 추가
// origin 추가
if (!requestOptions["origin"]) {
requestOptions = { ...requestOptions, origin: SERVER_URL };
}
// credentials 추가
// JWT는 httpOnly 쿠키로 전달 — credentials: "include" 로 브라우저가 자동 첨부
if (!requestOptions["credentials"]) {
requestOptions = { ...requestOptions, credentials: "include" };
}

fetch(SERVER_URL + url, requestOptions)
.then((response) => {
// response Stream. Not completion object
//console.log("requestFetch [Response Stream] ", response);
return response.json();
})
.then((resp) => {
if (Number(resp.resultCode) === Number(CODE.RCV_ERROR_AUTH)) {
alert("Login Alert"); //index.jsx라우터파일에 jwtAuthentication 함수로 공통 인증을 사용하는 코드 추가로 alert 원상복구
alert("Login Alert");
setSessionItem("loginUser", { id: "" });
window.location.href = URL.LOGIN;
return false;
Expand All @@ -53,30 +32,22 @@ export function requestFetch(url, requestOptions, handler, errorHandler) {
}
})
.then((resp) => {
console.groupCollapsed("requestFetch.then()");
console.log("requestFetch [response] ", resp);
if (typeof handler === "function") {
handler(resp);
} else {
console.log("egov fetch handler not assigned!");
logger.warn("egov fetch handler not assigned");
}
console.groupEnd("requestFetch.then()");
})
.catch((error) => {
console.error("There was an error!"); // 26.03.04 KISA 보안취약점 조치 : error 객체 제거
logger.error("requestFetch error"); // 26.03.04 KISA 보안취약점 조치 : error 객체 미노출
if (error === "TypeError: Failed to fetch") {
alert("서버와의 연결이 원활하지 않습니다. 서버를 확인하세요.");
}

if (typeof errorHandler === "function") {
errorHandler(error);
} else {
console.error("egov error handler not assigned!");
alert("요청 처리 중 오류가 발생했습니다."); // 26.03.04 KISA 보안취약점 조치 : 메시지 대체
}
})
.finally(() => {
console.log("requestFetch finally end");
console.groupEnd("requestFetch");
});
}
82 changes: 0 additions & 82 deletions src/api/egovFetch.jsx

This file was deleted.

20 changes: 7 additions & 13 deletions src/components/EgovAttachFile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function EgovAttachFile({
fnDeleteFile,
posblAtchFileNumber,
}) {
console.groupCollapsed("EgovAttachFile");

// posblAtchFileNumber는 수정일 경우에만 값이 넘어오므로 방어 로직
// 해당 컴포넌트는 스케줄 화면과 공유하며, 스케줄에서는 첨부파일을 1개 넣을 수 있으므로 디폴트 값을 1로 설정
Expand All @@ -27,14 +26,16 @@ function EgovAttachFile({
const navigate = useNavigate();

function onClickDownFile(atchFileId, fileSn) {
window.open(
SERVER_URL + "/file?atchFileId=" + atchFileId + "&fileSn=" + fileSn + ""
);
// 화이트리스트 검증 — atchFileId 는 Base64+URL-safe 문자만 허용, fileSn 은 숫자만
if (!/^[A-Za-z0-9+/=_-]+$/.test(String(atchFileId))) return;
if (!/^\d+$/.test(String(fileSn))) return;
const url =
`${SERVER_URL}/file?atchFileId=${encodeURIComponent(atchFileId)}` +
`&fileSn=${encodeURIComponent(fileSn)}`;
window.open(url, "_blank", "noopener,noreferrer");
}

function onClickDeleteFile(atchFileId, fileSn, fileIndex) {
console.log("onClickDeleteFile Params : ", atchFileId, fileSn, fileIndex);

const requestOptions = {
method: "POST",
headers: {
Expand All @@ -46,10 +47,7 @@ function EgovAttachFile({
}),
};
EgovNet.requestFetch(`/file`, requestOptions, function (resp) {
console.log("===>>> board file delete= ", resp);
if (Number(resp.resultCode) === Number(CODE.RCV_SUCCESS)) {
// 성공
console.log("Deleted fileIndex = ", fileIndex);
// eslint-disable-next-line no-unused-vars
const _deleteFile = boardFiles.splice(fileIndex, 1);
const _boardFiles = Object.assign([], boardFiles);
Expand All @@ -66,7 +64,6 @@ function EgovAttachFile({
}

function onChangeFileInput(e) {
console.log("===>>> e = " + e.target.files[0]);
if (
e.target.files.length + (boardFiles?.length || 0) >
posblAtchFileNumber
Expand Down Expand Up @@ -116,9 +113,6 @@ function EgovAttachFile({
filesTag.push(<br key={["br", `${index}`].join(" ")} />);
});
}
console.log("filesTag : ", filesTag);
console.groupEnd("EgovAttachFile");

return (
<dl>
<dt>첨부파일</dt>
Expand Down
Loading
Loading