BARON-SSO 로그인 연동

This commit is contained in:
root
2026-07-27 17:23:52 +09:00
parent bf5e694571
commit ac5823234e
44 changed files with 1804 additions and 695 deletions
+11
View File
@@ -5,3 +5,14 @@ G5_MYSQL_USER=egbim
G5_MYSQL_PASSWORD=baron3840!!
MARIADB_ROOT_PASSWORD=root_local_pass
COMPOSE_PROJECT_NAME=egbim
# BARON SSO local test
BARON_SSO_CLIENT_ID=
BARON_SSO_CLIENT_SECRET=
BARON_SSO_AUTHORIZE_ENDPOINT=
BARON_SSO_TOKEN_ENDPOINT=
BARON_SSO_USERINFO_ENDPOINT=
BARON_SSO_ISSUER=
BARON_SSO_REDIRECT_URI=http://localhost:8088/auth/callback
BARON_SSO_SCOPE=openid profile email tenants
BARON_SSO_CLIENT_AUTH_METHOD=basic
+9
View File
@@ -45,6 +45,15 @@ jobs:
G5_MYSQL_PASSWORD=${{ secrets.G5_MYSQL_PASSWORD }}
MARIADB_ROOT_PASSWORD=${{ secrets.MARIADB_ROOT_PASSWORD }}
COMPOSE_PROJECT_NAME=${{ secrets.COMPOSE_PROJECT_NAME }}
BARON_SSO_CLIENT_ID=${{ secrets.BARON_SSO_CLIENT_ID }}
BARON_SSO_CLIENT_SECRET=${{ secrets.BARON_SSO_CLIENT_SECRET }}
BARON_SSO_AUTHORIZE_ENDPOINT=${{ secrets.BARON_SSO_AUTHORIZE_ENDPOINT }}
BARON_SSO_TOKEN_ENDPOINT=${{ secrets.BARON_SSO_TOKEN_ENDPOINT }}
BARON_SSO_USERINFO_ENDPOINT=${{ secrets.BARON_SSO_USERINFO_ENDPOINT }}
BARON_SSO_ISSUER=${{ secrets.BARON_SSO_ISSUER }}
BARON_SSO_REDIRECT_URI=${{ secrets.BARON_SSO_REDIRECT_URI }}
BARON_SSO_SCOPE=${{ secrets.BARON_SSO_SCOPE }}
BARON_SSO_CLIENT_AUTH_METHOD=${{ secrets.BARON_SSO_CLIENT_AUTH_METHOD }}
EOF
- name: Upload deployment script
+5
View File
@@ -1,6 +1,11 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# auth route compatibility
RewriteRule ^auth/baron_login/?$ /auth/baron_login.php [L,QSA]
RewriteRule ^auth/callback/?$ /auth/callback.php [L,QSA]
RewriteRule ^auth/debug_claims/?$ /auth/debug_claims.php [L,QSA]
# local docker/localhost bypass
RewriteCond %{ENV:APP_ENV} =local [OR]
RewriteCond %{HTTP_HOST} ^(localhost|127\.0\.0\.1)(:[0-9]+)?$ [NC]
+335
View File
@@ -0,0 +1,335 @@
# BARON-SSO 연동 및 Q&A 접근 전환 작업 목록
## 목표
- 외부 사용자는 기존 Descope 로그인을 계속 사용한다.
- 내부 사용자는 BARON-SSO 로그인으로 전환한다.
- 내부 사용자도 기존과 동일하게 Q&A 페이지를 조회, 작성, 수정, 댓글 작성할 수 있어야 한다.
- 로그인 공급자가 달라도 Q&A 는 동일한 세션 구조를 사용하도록 정리한다.
## 현재 확인된 전제
- 현재 Q&A 기능은 Descope 전용 세션 파일에 직접 의존한다.
- 국문 `egbim/` 와 영문 `eng/` 에 동일한 구조가 병렬로 존재한다.
- 내부/외부 사용자 판별은 현재 Q&A 작성 로직에서 이메일 도메인 기준으로 처리한다.
- 관리자 권한은 세션 파일 내부의 이메일 화이트리스트 기반이다.
- BARON-SSO 전용 콜백, 토큰 교환, 사용자 정보 매핑 로직은 현재 코드베이스에 별도로 구현되어 있지 않다.
- BARON-SSO 애플리케이션 보안 수준은 `Server-side app` 으로 확정되었다.
## 이번 회차 확정 사항
- [x] BARON-SSO 애플리케이션 유형은 `Server-side app` 으로 설정
- [x] `client_secret` 을 사용하는 Authorization Code 기반 서버 처리 방식 사용
- [x] 토큰 교환과 세션 생성은 브라우저가 아니라 서버에서 처리
- [ ] BARON-SSO claim 샘플 확보
- [ ] callback 엔드포인트 경로 확정
- [ ] 공통 auth bootstrap 파일 구조 확정
## 현재 영향 파일
### 공통 세션/인증 진입점
- `egbim/skin/member/basic/descope_session.php`
- `eng/skin/member/basic/descope_session.php`
- `egbim/skin/member/basic/descope_login.php`
- `eng/skin/member/basic/descope_login.php`
- `egbim/skin/member/basic/descope_logout.php`
- `eng/skin/member/basic/descope_logout.php`
### Q&A 주요 진입점
- `egbim/bbs/descope_qa_list.php`
- `egbim/bbs/descope_qa_detail.php`
- `egbim/bbs/descope_qa_write.php`
- `egbim/bbs/descope_qa_comment.php`
- `egbim/bbs/descope_qa_comment_update.php`
- `egbim/bbs/descope_qa_comment_delete.php`
- `egbim/bbs/descope_qa_delete.php`
- `egbim/bbs/descope_qa_status.php`
- `egbim/bbs/admin_guard.php`
- `eng/bbs/descope_qa_list.php`
- `eng/bbs/descope_qa_detail.php`
- `eng/bbs/descope_qa_write.php`
- `eng/bbs/descope_qa_comment.php`
- `eng/bbs/descope_qa_comment_update.php`
- `eng/bbs/descope_qa_comment_delete.php`
- `eng/bbs/descope_qa_delete.php`
- `eng/bbs/descope_qa_status.php`
- `eng/bbs/admin_guard.php`
## 1. 정책 확정
- [ ] 내부 사용자와 외부 사용자의 로그인 정책을 문서로 확정한다.
- [ ] 내부 사용자 판별 기준을 확정한다.
- [ ] 내부 사용자 판별을 이메일 도메인으로 유지할지, 별도 진입 버튼으로 분기할지 결정한다.
- [ ] 로그인 화면에서 외부용 Descope 와 내부용 BARON-SSO 를 어떻게 노출할지 결정한다.
- [ ] 국문 `egbim/` 와 영문 `eng/` 모두 동일 정책을 적용할지 확인한다.
### 정책 결정 시 확인 항목
- [ ] 내부 사용자 도메인 목록 확정
- [ ] 예외 계정 처리 방식 확정
- [ ] 관리자 계정 판단 기준 유지 여부 확정
- [ ] 로그인 실패 시 안내 문구와 리다이렉트 방식 확정
## 2. BARON-SSO 연동 정보 정리
- [ ] BARON-SSO OIDC 설정값을 확정한다.
- [ ] `client_id`, `client_secret`, `discovery_endpoint`, `issuer`, `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint` 를 운영값 기준으로 문서화한다.
- [ ] Redirect URI 를 실제 서비스 경로와 맞춘다.
- [ ] 운영 도메인이 `eg-bim.com` 인지 `eg-bim.co.kr` 인지 최종 확정한다.
- [ ] 로컬/개발/운영 환경별 Redirect URI 분리 여부를 결정한다.
### 현재 확정된 방식
- [x] 보안 수준: `Server-side app`
- [x] 인증 플로우: 서버에서 Authorization Code 처리
- [x] `client_secret` 필요
- [ ] PKCE 지원 여부 확인
- [ ] `scope` 목록 확정
### 확인 필요 항목
- [ ] 현재 등록된 Redirect URI: `https://eg-bim.com/auth/callback`
- [ ] 실제 애플리케이션에서 사용할 콜백 경로가 존재하는지 확인
- [ ] 사내망 또는 VPN 환경에서만 접근 가능한지 확인
- [ ] BARON-SSO 로그아웃 엔드포인트 제공 여부 확인
- [ ] 로컬 테스트용 Redirect URI 추가: `http://localhost:8088/auth/callback`
## 3. 공통 인증 계층 설계
- [ ] Descope 전용 세션 파일을 공급자 중립적인 공통 인증 bootstrap 구조로 분리한다.
- [ ] 로그인 공급자가 달라도 최종 세션 구조는 동일하게 맞춘다.
- [ ] Q&A 코드는 특정 공급자명을 몰라도 되도록 정리한다.
- [ ] 국문/영문 트리에서 인증 공통화 파일 위치를 통일한다.
### 권장 방향
- [ ] `descope_session.php` 를 바로 확장하지 말고 공통 auth loader 로 분리
- [ ] Descope 와 BARON-SSO 모두 동일한 `$_SESSION['user']` 구조를 채우도록 통합
- [ ] Q&A 는 공통 auth loader 만 include 하도록 변경
- [ ] 공급자별 쿠키 복원, 세션 복원, 권한 매핑은 내부 모듈로 캡슐화
### 세션 표준 구조 초안
- [ ] `$_SESSION['user']['userId']`
- [ ] `$_SESSION['user']['loginIds'][0]`
- [ ] `$_SESSION['user']['name']`
- [ ] `$_SESSION['user']['email']`
- [ ] `$_SESSION['user']['phone']`
- [ ] `$_SESSION['user']['customAttributes']`
- [ ] `$_SESSION['user']['roleNames']`
- [ ] 그누보드 연동용 `$_SESSION['ss_mb_id']`, `$_SESSION['ss_mb_level']`
## 4. BARON-SSO 인증 구현
- [ ] 로그인 시작 엔드포인트 구현
- [ ] state, nonce 저장 및 검증 구현
- [ ] callback 엔드포인트 구현
- [ ] authorization code 를 token 으로 교환하는 로직 구현
- [ ] id token 또는 userinfo 기반 사용자 정보 파싱 구현
- [ ] 실패 시 예외 처리 및 사용자 안내 구현
### 구현 상세 체크리스트
- [x] 브라우저 직접 토큰 저장 방식은 사용하지 않음
- [x] callback 에서 서버가 token endpoint 호출
- [x] callback 성공 후 서버 세션에 사용자 정보 저장
- [ ] OIDC discovery 자동 사용 여부 결정
- [ ] discovery 미사용 시 endpoint 고정값 사용
- [ ] 세션 고정 공격 방지용 session regenerate 적용 여부 확인
- [ ] callback 에서 CSRF 방지용 state 검증
- [ ] 토큰 검증 책임 범위 결정
- [ ] 토큰 만료와 재로그인 정책 정의
### Server-side app 기준 추가 작업
- [ ] callback 에서 `code`, `state`, `error` 파라미터 처리 규칙 정의
- [ ] token endpoint 요청 파라미터 목록 정리
- [ ] `client_secret` 저장 위치 확정
- [ ] 세션 생성 직후 `session_regenerate_id(true)` 적용
- [ ] 로그인 성공 후 이동할 기본 URL 확정
- [ ] 로그인 실패 시 복귀 URL 과 안내 문구 확정
## 5. 사용자 정보 매핑 규칙 정리
- [ ] BARON-SSO claim 샘플을 확보한다.
- [ ] BARON-SSO 의 사용자 식별자 중 무엇을 `loginIds[0]` 으로 쓸지 확정한다.
- [ ] 이름, 이메일, 전화번호, 회사, 계열사, 부서 claim 매핑 규칙을 정한다.
- [ ] BARON claim 에 없는 값은 기본값 또는 빈값 처리 기준을 정한다.
### 현재 Q&A 가 기대하는 값
- [ ] 내부 사용자 저장 시 `family_company`, `department` 사용
- [ ] 외부 사용자 저장 시 `company`, `department` 사용
- [ ] 작성자 식별은 `login_id` 기준
- [ ] 내 글 여부 판단도 `login_id` 기준
### 확인 필요 리스크
- [ ] BARON-SSO 가 이메일을 직접 주지 않는 경우 대응 필요
- [ ] 부서명 claim key 가 `team` 과 다를 가능성 확인 필요
- [ ] 계열사명 claim 이 `familyCompany` 와 다를 가능성 확인 필요
- [ ] 영문 사이트에서 표시명 영문화가 필요한지 검토
## 6. Q&A 접근 제어 변경
- [ ] Q&A 목록에서 로그인 사용자 식별이 공통 세션으로 동작하는지 확인
- [ ] Q&A 상세에서 비밀글 접근 제어가 내부 사용자에게도 동일하게 동작하는지 확인
- [ ] Q&A 작성에서 로그인 강제가 BARON-SSO 사용자에게도 정상 동작하는지 확인
- [ ] 댓글 작성, 수정, 삭제도 동일 세션 구조 기준으로 동작하는지 확인
- [ ] 관리자 상태 변경 기능이 정상 동작하는지 확인
### 변경 대상 포인트
- [ ] `require_once .../descope_session.php` 참조를 공통 auth bootstrap 으로 치환
- [ ] 로그인 여부 조건식이 특정 공급자 쿠키에 의존하지 않는지 점검
- [ ] 작성자 식별 키가 공급자별로 달라지지 않도록 통일
- [ ] 비로그인 사용자 공개글 조회 정책 유지 여부 확인
## 7. 관리자 권한 체계 정리
- [ ] 현재 이메일 화이트리스트 방식을 유지할지 결정한다.
- [ ] BARON-SSO role 또는 group claim 기반으로 바꿀지 결정한다.
- [ ] 관리자 표시 이름 가공 로직과 충돌이 없는지 확인한다.
### 권장 방향
- [ ] 1차 전환은 이메일 화이트리스트 유지
- [ ] 2차에서 BARON role 기반 권한 이관 검토
- [ ] 권한 기준은 국문/영문 공통으로 단일화
## 8. 로그인 화면 및 UX 변경
- [ ] 메인 헤더 또는 로그인 팝업에 내부 사용자용 BARON-SSO 진입 버튼 추가
- [ ] 외부 사용자용 기존 Descope 로그인 버튼 유지
- [ ] 로그인 실패 안내 문구를 공급자별로 구분할지 결정
- [ ] 내부 사용자가 Descope 로그인으로 잘못 진입했을 때 안내 문구 제공 여부 결정
### 권장 UX
- [ ] 버튼 2개 분리: `외부 사용자 로그인`, `사내 사용자 로그인`
- [ ] 최초 단계에서는 자동 도메인 판별보다 명시적 분기 우선
- [ ] 내부 사용자 안내 문구에 사내 계정 사용 정책 명시
## 9. 로그아웃 및 세션 종료 처리
- [ ] Descope 로그아웃은 기존 방식 유지
- [ ] BARON-SSO 로그아웃 처리 추가
- [ ] 로컬 세션 종료와 IdP 세션 종료를 분리할지 결정
- [ ] 로그아웃 후 재진입 시 자동 재로그인 여부 확인
### 점검 항목
- [ ] PHP 세션 제거
- [ ] Descope 관련 쿠키 제거
- [ ] BARON 관련 쿠키 또는 state 쿠키 제거
- [ ] 브라우저 백버튼 진입 시 인증 상태 오동작 여부 확인
## 10. 설정값 및 비밀정보 관리
- [ ] BARON-SSO 설정값을 코드 하드코딩 없이 분리한다.
- [ ] 운영/개발/로컬 설정 파일 구조를 정리한다.
- [ ] 민감정보가 저장소에 올라가지 않도록 관리 방식을 정한다.
### 체크리스트
- [ ] `client_secret` 는 환경변수 또는 비공개 설정 파일로 관리
- [ ] callback URL 도 환경별 분리
- [ ] issuer, endpoint 값도 환경별 override 가능하도록 구성
- [ ] 배포 문서에 설정 절차 추가
## 11. 테스트 시나리오
- [ ] 외부 사용자 Descope 로그인 후 Q&A 목록 조회
- [ ] 외부 사용자 Descope 로그인 후 Q&A 작성
- [ ] 내부 사용자 BARON-SSO 로그인 후 Q&A 목록 조회
- [ ] 내부 사용자 BARON-SSO 로그인 후 Q&A 작성
- [ ] 내부 사용자 BARON-SSO 로그인 후 댓글 작성
- [ ] 내부 관리자 BARON-SSO 로그인 후 상태 변경
- [ ] 비로그인 사용자 공개글 조회
- [ ] 비밀글 작성자 본인 접근
- [ ] 비밀글 타 사용자 접근 차단
- [ ] 로그아웃 후 접근 차단
- [ ] 국문 `egbim/` 검증
- [ ] 영문 `eng/` 검증
### 추가 확인 항목
- [ ] 모바일 브라우저 로그인 진입 확인
- [ ] 로그인 팝업 차단 이슈 여부 확인
- [ ] 세션 만료 후 재시도 흐름 확인
- [ ] 첨부파일 다운로드 권한 흐름 확인
## 12. 배포 및 롤백 계획
- [ ] BARON-SSO 기능 배포 순서를 정한다.
- [ ] 운영 반영 전 내부 사용자 테스트 계정으로 사전 검증한다.
- [ ] 장애 시 내부 사용자를 임시 우회시킬 방법을 준비한다.
### 권장 배포 순서
- [ ] 1차: 공통 auth bootstrap 도입
- [ ] 2차: BARON-SSO callback 및 세션 매핑 구현
- [ ] 3차: 로그인 UI 에 사내 사용자 버튼 노출
- [ ] 4차: 내부 사용자 실사용 테스트
- [ ] 5차: 운영 전환
### 롤백 포인트
- [ ] BARON-SSO 버튼 숨김만으로 즉시 차단 가능한지 확인
- [ ] 내부 사용자 임시 Descope 허용 스위치 필요 여부 검토
- [ ] 공통 auth bootstrap 에 공급자 fallback 허용 여부 검토
## 바로 실행할 우선순위
### P0
- [ ] 내부/외부 로그인 정책 확정
- [ ] BARON-SSO claim 샘플 확보
- [ ] Redirect URI 와 실제 콜백 경로 확정
- [ ] 공통 세션 구조 정의
## 다음 진행 항목
### 지금 바로 할 일
- [ ] BARON-SSO callback URL 을 실제 구현 경로로 확정한다.
- [ ] BARON-SSO 에서 내려주는 id token 또는 userinfo claim 샘플 1건을 확보한다.
- [ ] 공통 auth bootstrap 파일명을 확정한다.
- [ ] Descope 세션 구조와 BARON 세션 구조 매핑표를 작성한다.
### 다음 구현 순서
- [ ] 1단계: BARON 로그인 시작 엔드포인트 추가
- [ ] 2단계: BARON callback 엔드포인트 추가
- [ ] 3단계: callback 에서 공통 `$_SESSION['user']` 생성
- [ ] 4단계: Q&A 진입 파일들의 `descope_session.php` include 를 공통 auth bootstrap 으로 교체
- [ ] 5단계: 로그인 UI 에 사내 사용자 로그인 버튼 추가
### 구현 전에 확정되면 좋은 값
- [ ] 내부 사용자 기본 이동 페이지
- [ ] 로그인 성공 후 이동 페이지가 메인인지 Q&A 인지 결정
- [ ] 로그아웃 후 이동 페이지 결정
- [ ] BARON-SSO 에서 제공하는 부서, 계열사, 이메일 claim key 명칭 확정
### P1
- [ ] 공통 auth bootstrap 구현
- [ ] BARON-SSO callback 구현
- [ ] Q&A 진입점 공통 auth 로 치환
- [ ] 로그아웃 처리 추가
### P2
- [ ] 로그인 UI 개선
- [ ] 관리자 권한 체계 정리
- [ ] 영문 사이트 동기화
- [ ] 운영 문서와 배포 절차 정리
## 메모
- 현재 구조에서는 Q&A 비즈니스 로직보다 인증 세션 통합이 핵심 작업이다.
- 가장 안전한 방향은 BARON-SSO 사용자도 최종적으로 기존과 동일한 `$_SESSION['user']` 구조를 갖게 만드는 것이다.
- 이렇게 하면 Q&A 자체의 수정량을 줄이고, 외부 사용자용 Descope 흐름도 그대로 유지할 수 있다.
+18
View File
@@ -138,6 +138,15 @@ Gitea 저장소 화면에서 아래 순서로 실행한다.
- `G5_MYSQL_PASSWORD`: 운영 DB 비밀번호
- `MARIADB_ROOT_PASSWORD`: 운영 MariaDB root 비밀번호
- `COMPOSE_PROJECT_NAME`: 예) `egbim`
- `BARON_SSO_CLIENT_ID`: BARON 서버사이드 앱 Client ID
- `BARON_SSO_CLIENT_SECRET`: BARON 서버사이드 앱 Client Secret
- `BARON_SSO_AUTHORIZE_ENDPOINT`: `https://app.brsw.kr/oidc/oauth2/auth`
- `BARON_SSO_TOKEN_ENDPOINT`: `https://app.brsw.kr/oidc/oauth2/token`
- `BARON_SSO_USERINFO_ENDPOINT`: `https://app.brsw.kr/oidc/userinfo`
- `BARON_SSO_ISSUER`: `https://app.brsw.kr/oidc`
- `BARON_SSO_REDIRECT_URI`: 운영 콜백 URL 예) `https://<운영도메인>/auth/callback`
- `BARON_SSO_SCOPE`: 예) `openid profile email tenants`
- `BARON_SSO_CLIENT_AUTH_METHOD`: 보통 `basic`, 필요 시 `post`
이 워크플로는 위 시크릿으로 서버의 `.env` 파일을 생성한 뒤 배포를 수행한다.
@@ -169,6 +178,15 @@ G5_MYSQL_USER=egbim
G5_MYSQL_PASSWORD=<set-on-server>
MARIADB_ROOT_PASSWORD=<set-on-server>
COMPOSE_PROJECT_NAME=egbim
BARON_SSO_CLIENT_ID=<set-on-server>
BARON_SSO_CLIENT_SECRET=<set-on-server>
BARON_SSO_AUTHORIZE_ENDPOINT=https://app.brsw.kr/oidc/oauth2/auth
BARON_SSO_TOKEN_ENDPOINT=https://app.brsw.kr/oidc/oauth2/token
BARON_SSO_USERINFO_ENDPOINT=https://app.brsw.kr/oidc/userinfo
BARON_SSO_ISSUER=https://app.brsw.kr/oidc
BARON_SSO_REDIRECT_URI=https://<운영도메인>/auth/callback
BARON_SSO_SCOPE=openid profile email tenants
BARON_SSO_CLIENT_AUTH_METHOD=basic
```
## 5-1. SQL 덤프 포함 주의
+52
View File
@@ -13,6 +13,7 @@
2. 필요하면 `.env.docker.example` 내용을 참고해 `.env` 파일을 만든다.
3. DB 덤프가 있으면 `docker/mysql/init/` 아래에 `.sql` 파일로 넣는다.
4. 기본 로컬 DB명은 운영과 맞춘 `egbim` 이다.
5. BARON-SSO 를 로컬에서 테스트하려면 BARON 앱 설정에 로컬 Redirect URI 를 추가해야 한다.
## 실행
@@ -20,6 +21,57 @@
docker compose up --build
```
## BARON-SSO 로컬 테스트 설정
`.env` 에 아래 값을 채운다.
```dotenv
BARON_SSO_CLIENT_ID=<client id>
BARON_SSO_CLIENT_SECRET=<client secret>
BARON_SSO_AUTHORIZE_ENDPOINT=https://app.brsw.kr/oidc/oauth2/auth
BARON_SSO_TOKEN_ENDPOINT=https://app.brsw.kr/oidc/oauth2/token
BARON_SSO_USERINFO_ENDPOINT=https://app.brsw.kr/oidc/userinfo
BARON_SSO_ISSUER=https://app.brsw.kr/oidc
BARON_SSO_REDIRECT_URI=http://localhost:8088/auth/callback
BARON_SSO_SCOPE=openid profile email tenants
BARON_SSO_CLIENT_AUTH_METHOD=basic
```
`BARON_SSO_CLIENT_AUTH_METHOD` 는 우선 `basic` 으로 두고, 토큰 엔드포인트가 `invalid_client` 를 반환하면 `post` 로 바꿔 다시 확인한다.
BARON 관리자 화면의 Redirect URI 허용 목록에도 아래 중 하나를 추가한다.
```text
http://localhost:8088/auth/callback
http://eg-bim.com:8088/auth/callback
```
운영에 등록된 `https://eg-bim.com/auth/callback` 만 허용되어 있으면 로컬 callback 은 실패한다.
## 로컬 확인 순서
1. `docker compose up --build -d`
2. `docker compose ps``app`, `db` 상태 확인
3. 브라우저에서 `http://localhost:8088/egbim/` 접속
4. 로그인 팝업에서 `사내 사용자 로그인` 버튼 클릭
5. BARON 로그인 후 `http://localhost:8088/auth/callback` 로 복귀하는지 확인
6. 복귀 후 `http://localhost:8088/egbim/bbs/descope_qa_list.php` 로 이동하는지 확인
## 빠른 점검 URL
- BARON 시작점: `http://localhost:8088/auth/baron_login?site=egbim&return_url=/egbim/bbs/descope_qa_list.php`
- BARON 콜백: `http://localhost:8088/auth/callback`
- 국문 Q&A: `http://localhost:8088/egbim/bbs/descope_qa_list.php`
- 영문 Q&A: `http://localhost:8088/eng/bbs/descope_qa_list.php`
## 문제 발생 시 먼저 볼 항목
- Redirect URI 가 BARON 허용 목록과 정확히 일치하는지 확인
- `.env` 값이 비어 있지 않은지 확인
- 컨테이너 재기동 전에 `.env` 변경분이 반영되었는지 확인
- callback 경로가 `/auth/callback` 으로 등록되어 있는지 확인
- 브라우저 개발자도구 Network 에서 token endpoint 호출 전 redirect 단계가 정상인지 확인
## 접속
- 기본: `http://localhost:8080/egbim/`
+28
View File
@@ -0,0 +1,28 @@
<?php
require_once __DIR__ . '/common.php';
auth_session_start_if_needed();
$site = ($_GET['site'] ?? 'egbim') === 'eng' ? 'eng' : 'egbim';
$returnUrl = trim((string) ($_GET['return_url'] ?? auth_site_qna_url($site)));
if (!auth_baron_is_configured()) {
auth_script_redirect('BARON-SSO 설정이 아직 완료되지 않았습니다.', auth_site_base_url($site) . '/index.php');
}
$config = auth_baron_config();
$statePayload = auth_baron_state_payload($site, $returnUrl);
$state = auth_store_baron_state($statePayload);
$query = http_build_query([
'client_id' => $config['client_id'],
'response_type' => 'code',
'redirect_uri' => auth_baron_redirect_uri(),
'scope' => $config['scope'],
'state' => $state,
'nonce' => $statePayload['nonce'],
]);
header('Location: ' . $config['authorize_endpoint'] . '?' . $query, true, 302);
exit;
?>
+170
View File
@@ -0,0 +1,170 @@
<?php
require_once __DIR__ . '/common.php';
auth_session_start_if_needed();
$error = trim((string) ($_GET['error'] ?? ''));
$state = trim((string) ($_GET['state'] ?? ''));
$code = trim((string) ($_GET['code'] ?? ''));
$fallbackSite = 'egbim';
if ($error !== '') {
auth_script_redirect('BARON-SSO 로그인에 실패했습니다: ' . $error, auth_site_base_url($fallbackSite) . '/index.php');
}
if ($state === '' || $code === '') {
auth_script_redirect('BARON-SSO 응답값이 올바르지 않습니다.', auth_site_base_url($fallbackSite) . '/index.php');
}
$statePayload = auth_consume_baron_state($state);
if ($statePayload === null) {
auth_script_redirect('BARON-SSO state 검증에 실패했습니다.', auth_site_base_url($fallbackSite) . '/index.php');
}
$site = ($statePayload['site'] ?? 'egbim') === 'eng' ? 'eng' : 'egbim';
$redirectUrl = $statePayload['return_url'] ?? auth_site_qna_url($site);
$config = auth_baron_config();
if (!auth_baron_is_configured()) {
auth_script_redirect('BARON-SSO 설정이 아직 완료되지 않았습니다.', auth_site_base_url($site) . '/index.php');
}
$tokenRequestData = [
'grant_type' => 'authorization_code',
'redirect_uri' => auth_baron_redirect_uri(),
'code' => $code,
];
$basicAuthUser = null;
$basicAuthPassword = null;
if ($config['client_auth_method'] === 'post') {
$tokenRequestData['client_id'] = $config['client_id'];
$tokenRequestData['client_secret'] = $config['client_secret'];
} else {
$tokenRequestData['client_id'] = $config['client_id'];
$basicAuthUser = $config['client_id'];
$basicAuthPassword = $config['client_secret'];
}
$tokenResponse = auth_http_post_form(
$config['token_endpoint'],
$tokenRequestData,
[],
$basicAuthUser,
$basicAuthPassword
);
if ($tokenResponse['error'] !== '') {
auth_script_redirect('BARON-SSO 토큰 요청에 실패했습니다.', auth_site_base_url($site) . '/index.php');
}
$tokenData = json_decode($tokenResponse['body'], true);
if ($tokenResponse['http_code'] < 200 || $tokenResponse['http_code'] >= 300 || !is_array($tokenData)) {
if (auth_is_local_debug()) {
$debugMessage = sprintf(
'BARON token error HTTP %d | body=%s',
$tokenResponse['http_code'],
auth_compact_debug_value((string) $tokenResponse['body'])
);
auth_script_redirect($debugMessage, auth_site_base_url($site) . '/index.php');
}
auth_script_redirect('BARON-SSO 토큰 응답을 처리할 수 없습니다.', auth_site_base_url($site) . '/index.php');
}
$claims = [];
$idToken = trim((string) ($tokenData['id_token'] ?? ''));
if ($idToken !== '') {
$claims = auth_jwt_payload($idToken);
}
if ($config['userinfo_endpoint'] !== '' && !empty($tokenData['access_token'])) {
$userInfoResponse = auth_http_get_json($config['userinfo_endpoint'], (string) $tokenData['access_token']);
$userInfoData = json_decode($userInfoResponse['body'], true);
if ($userInfoResponse['http_code'] >= 200 && $userInfoResponse['http_code'] < 300 && is_array($userInfoData)) {
$claims = array_merge($claims, $userInfoData);
}
}
$user = auth_baron_user_from_claims($claims);
if (empty($user['userId']) || empty($user['loginIds'])) {
auth_script_redirect('BARON-SSO 사용자 정보를 확인할 수 없습니다.', auth_site_base_url($site) . '/index.php');
}
$normalizedUser = auth_apply_user_session($user, 'baron', [
'baron_access_token' => (string) ($tokenData['access_token'] ?? ''),
'baron_id_token' => $idToken,
]);
setcookie('descope_login_id', '', auth_cookie_options(time() - 42000));
setcookie('descope_user_id', '', auth_cookie_options(time() - 42000));
setcookie('descope_user_name', '', auth_cookie_options(time() - 42000));
setcookie('descope_user_email', '', auth_cookie_options(time() - 42000));
setcookie('descope_user_phone', '', auth_cookie_options(time() - 42000));
setcookie('descope_custom_attributes', '', auth_cookie_options(time() - 42000));
setcookie('descope_role_names', '', auth_cookie_options(time() - 42000));
auth_set_descope_session_cookies($normalizedUser);
auth_set_baron_session_cookies($normalizedUser, $claims);
$clientSessionPayload = [
'loginId' => (string) ($normalizedUser['loginIds'][0] ?? ''),
'descopeUserId' => (string) ($normalizedUser['userId'] ?? ''),
'userName' => (string) ($normalizedUser['name'] ?? ''),
'phone' => (string) ($normalizedUser['phone'] ?? ''),
'company' => (string) ($normalizedUser['customAttributes']['company'] ?? ''),
'familyCompany' => (string) ($normalizedUser['customAttributes']['familyCompany'] ?? ''),
'team' => (string) ($normalizedUser['customAttributes']['team'] ?? ''),
'position' => (string) ($normalizedUser['customAttributes']['position'] ?? ''),
'familyUniqueKey' => (string) ($normalizedUser['customAttributes']['familyUniqueKey'] ?? ''),
'userRole' => (string) (($normalizedUser['roleNames'][0] ?? '')),
'authProvider' => 'baron',
'sessionJwt' => $idToken !== '' ? $idToken : (string) ($tokenData['access_token'] ?? ''),
'baronClaims' => json_encode($claims, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
];
$jsonPayload = json_encode($clientSessionPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$jsonRedirectUrl = json_encode($redirectUrl, JSON_UNESCAPED_SLASHES);
header('Content-Type: text/html; charset=UTF-8');
echo <<<HTML
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Signing in...</title>
</head>
<body>
<script>
(function () {
var payload = {$jsonPayload} || {};
var redirectUrl = {$jsonRedirectUrl};
Object.keys(payload).forEach(function (key) {
var value = payload[key];
if (typeof value === 'string' && value !== '') {
sessionStorage.setItem(key, value);
localStorage.setItem(key, value);
} else {
sessionStorage.removeItem(key);
}
});
try {
window.dispatchEvent(new CustomEvent('descope-auth-changed', {
detail: { loginId: payload.loginId || '' }
}));
} catch (e) {}
window.location.replace(redirectUrl);
})();
</script>
</body>
</html>
HTML;
exit;
?>
+790
View File
@@ -0,0 +1,790 @@
<?php
function auth_session_start_if_needed(): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
function auth_cookie_options(int $expires): array
{
return [
'expires' => $expires,
'path' => '/',
'samesite' => 'Lax',
];
}
function auth_json_cookie_encode($value): string
{
return base64_encode(json_encode($value, JSON_UNESCAPED_UNICODE));
}
function auth_json_cookie_decode(string $value, $default)
{
if ($value === '') {
return $default;
}
$decoded = base64_decode($value, true);
if ($decoded === false) {
return $default;
}
$json = json_decode($decoded, true);
return $json === null ? $default : $json;
}
function auth_current_host(): string
{
return $_SERVER['HTTP_HOST'] ?? 'eg-bim.com';
}
function auth_current_scheme(): string
{
$https = $_SERVER['HTTPS'] ?? '';
$forwardedProto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
if ($https === 'on' || $https === '1' || strtolower((string) $forwardedProto) === 'https') {
return 'https';
}
return 'http';
}
function auth_absolute_url(string $path): string
{
return auth_current_scheme() . '://' . auth_current_host() . $path;
}
function auth_site_base_url(string $site): string
{
return $site === 'eng' ? '/eng' : '/egbim';
}
function auth_site_qna_url(string $site): string
{
return auth_site_base_url($site) . '/bbs/descope_qa_list.php';
}
function auth_baron_config(): array
{
$scopes = trim((string) (getenv('BARON_SSO_SCOPE') ?: 'openid profile email'));
return [
'client_id' => trim((string) getenv('BARON_SSO_CLIENT_ID')),
'client_secret' => trim((string) getenv('BARON_SSO_CLIENT_SECRET')),
'authorize_endpoint' => trim((string) getenv('BARON_SSO_AUTHORIZE_ENDPOINT')),
'token_endpoint' => trim((string) getenv('BARON_SSO_TOKEN_ENDPOINT')),
'userinfo_endpoint' => trim((string) getenv('BARON_SSO_USERINFO_ENDPOINT')),
'issuer' => trim((string) getenv('BARON_SSO_ISSUER')),
'redirect_uri' => trim((string) getenv('BARON_SSO_REDIRECT_URI')),
'scope' => $scopes,
'client_auth_method' => trim((string) (getenv('BARON_SSO_CLIENT_AUTH_METHOD') ?: 'basic')),
];
}
function auth_baron_redirect_uri(): string
{
$config = auth_baron_config();
if ($config['redirect_uri'] !== '') {
return $config['redirect_uri'];
}
return auth_absolute_url('/auth/callback');
}
function auth_baron_is_configured(): bool
{
$config = auth_baron_config();
return $config['client_id'] !== ''
&& $config['client_secret'] !== ''
&& $config['authorize_endpoint'] !== ''
&& $config['token_endpoint'] !== '';
}
function auth_normalize_user(array $user): array
{
$loginIds = $user['loginIds'] ?? [];
if (!is_array($loginIds)) {
$loginIds = [];
}
$loginId = trim((string) ($loginIds[0] ?? $user['loginId'] ?? $user['email'] ?? ''));
if ($loginId !== '' && empty($loginIds)) {
$loginIds = [$loginId];
}
$customAttributes = $user['customAttributes'] ?? [];
$roleNames = $user['roleNames'] ?? [];
return [
'userId' => trim((string) ($user['userId'] ?? $loginId)),
'loginIds' => $loginIds,
'name' => trim((string) ($user['name'] ?? '')),
'email' => trim((string) ($user['email'] ?? $loginId)),
'phone' => trim((string) ($user['phone'] ?? '')),
'customAttributes' => is_array($customAttributes) ? $customAttributes : [],
'roleNames' => is_array($roleNames) ? $roleNames : [],
];
}
function auth_descope_user_from_cookies(): ?array
{
$loginId = trim((string) ($_COOKIE['descope_login_id'] ?? ''));
if ($loginId === '') {
return null;
}
return auth_normalize_user([
'userId' => trim((string) ($_COOKIE['descope_user_id'] ?? $loginId)),
'loginIds' => [$loginId],
'name' => trim((string) ($_COOKIE['descope_user_name'] ?? '')),
'email' => trim((string) ($_COOKIE['descope_user_email'] ?? $loginId)),
'phone' => trim((string) ($_COOKIE['descope_user_phone'] ?? '')),
'customAttributes' => auth_json_cookie_decode((string) ($_COOKIE['descope_custom_attributes'] ?? ''), []),
'roleNames' => auth_json_cookie_decode((string) ($_COOKIE['descope_role_names'] ?? ''), []),
]);
}
function auth_baron_user_from_cookies(): ?array
{
$encodedUser = (string) ($_COOKIE['baron_user'] ?? '');
if ($encodedUser === '') {
return null;
}
$user = auth_json_cookie_decode($encodedUser, []);
if (!is_array($user) || empty($user)) {
return null;
}
return auth_normalize_user($user);
}
function auth_restore_user_session(): void
{
auth_session_start_if_needed();
if (!empty($_SESSION['user']['userId'])) {
$_SESSION['user'] = auth_normalize_user($_SESSION['user']);
return;
}
$user = auth_baron_user_from_cookies();
if ($user === null) {
$user = auth_descope_user_from_cookies();
}
if ($user !== null) {
$_SESSION['user'] = $user;
}
}
function auth_apply_user_session(array $user, string $provider, array $tokens = []): array
{
auth_session_start_if_needed();
session_regenerate_id(true);
$normalizedUser = auth_normalize_user($user);
$_SESSION['user'] = $normalizedUser;
$_SESSION['auth_provider'] = $provider;
foreach ($tokens as $key => $value) {
$_SESSION[$key] = $value;
}
return $normalizedUser;
}
function auth_set_descope_session_cookies(array $user): void
{
$normalizedUser = auth_normalize_user($user);
$expire = time() + 86400;
$cookieOptions = auth_cookie_options($expire);
setcookie('descope_login_id', (string) ($normalizedUser['loginIds'][0] ?? ''), $cookieOptions);
setcookie('descope_user_id', (string) $normalizedUser['userId'], $cookieOptions);
setcookie('descope_user_name', (string) $normalizedUser['name'], $cookieOptions);
setcookie('descope_user_email', (string) $normalizedUser['email'], $cookieOptions);
setcookie('descope_user_phone', (string) $normalizedUser['phone'], $cookieOptions);
setcookie('descope_custom_attributes', auth_json_cookie_encode($normalizedUser['customAttributes']), $cookieOptions);
setcookie('descope_role_names', auth_json_cookie_encode($normalizedUser['roleNames']), $cookieOptions);
}
function auth_set_baron_session_cookies(array $user, array $claims = []): void
{
$normalizedUser = auth_normalize_user($user);
$expire = time() + 86400;
$cookieOptions = auth_cookie_options($expire);
setcookie('baron_user', auth_json_cookie_encode($normalizedUser), $cookieOptions);
setcookie('baron_claims', auth_json_cookie_encode($claims), $cookieOptions);
setcookie('baron_provider', 'baron', $cookieOptions);
}
function auth_clear_all_cookies(): void
{
$expired = auth_cookie_options(time() - 42000);
foreach ([
'PHPSESSID',
'G5sessphp',
'descope_login_id',
'descope_user_id',
'descope_user_name',
'descope_user_email',
'descope_user_phone',
'descope_custom_attributes',
'descope_role_names',
'baron_user',
'baron_claims',
'baron_provider',
] as $cookieName) {
setcookie($cookieName, '', $expired);
}
}
function auth_admin_emails(): array
{
return [
'b23008@baroncs.co.kr',
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'shyeom1@samaneng.com',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'cozyjin@hanmaceng.co.kr',
'm24031@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b21367@hanmaceng.co.kr',
'b25023@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com',
];
}
function auth_normalize_identity(string $value): string
{
return strtolower(trim($value));
}
function auth_is_admin_login(string $loginId): bool
{
$normalizedLoginId = auth_normalize_identity($loginId);
if ($normalizedLoginId === '') {
return false;
}
foreach (auth_admin_emails() as $adminLoginId) {
if ($normalizedLoginId === auth_normalize_identity($adminLoginId)) {
return true;
}
}
return false;
}
function auth_is_admin_user(array $user, ?string $loginId = null): bool
{
$candidates = [];
if ($loginId !== null) {
$candidates[] = $loginId;
}
$loginIds = $user['loginIds'] ?? [];
if (is_array($loginIds)) {
foreach ($loginIds as $candidate) {
if (is_string($candidate)) {
$candidates[] = $candidate;
}
}
}
$candidates[] = (string) ($user['email'] ?? '');
$customAttributes = $user['customAttributes'] ?? [];
if (is_array($customAttributes)) {
$candidates[] = (string) ($customAttributes['employeeId'] ?? '');
$candidates[] = (string) ($customAttributes['familyUniqueKey'] ?? '');
}
foreach ($candidates as $candidate) {
if (auth_is_admin_login((string) $candidate)) {
return true;
}
}
$roleNames = $user['roleNames'] ?? [];
if (is_array($roleNames)) {
foreach ($roleNames as $roleName) {
$normalizedRole = auth_normalize_identity((string) $roleName);
if ($normalizedRole === 'super' || $normalizedRole === 'admin' || $normalizedRole === 'administrator') {
return true;
}
}
}
return false;
}
function auth_sync_gnuboard_session(): array
{
auth_restore_user_session();
$loginIds = $_SESSION['user']['loginIds'] ?? [];
$loginId = (is_array($loginIds) && !empty($loginIds)) ? trim((string) $loginIds[0]) : '';
$userName = trim((string) ($_SESSION['user']['name'] ?? ''));
$userEmail = trim((string) ($_SESSION['user']['email'] ?? ''));
$isAdmin = !empty($_SESSION['user']) && is_array($_SESSION['user'])
? auth_is_admin_user($_SESSION['user'], $loginId)
: false;
if ($loginId !== '') {
$_SESSION['ss_mb_id'] = $loginId;
$_SESSION['ss_mb_level'] = $isAdmin ? 10 : 2;
return [
'loginId' => $loginId,
'isAdmin' => $isAdmin,
'is_member' => true,
'is_admin' => $isAdmin ? 'super' : '',
'member' => [
'mb_id' => $loginId,
'mb_name' => $userName !== '' ? $userName : $loginId,
'mb_email' => $userEmail,
'mb_level' => $_SESSION['ss_mb_level'],
'mb_nick' => $userName !== '' ? $userName : $loginId,
],
];
}
unset($_SESSION['ss_mb_id'], $_SESSION['ss_mb_level']);
return [
'loginId' => '',
'isAdmin' => false,
'is_member' => false,
'is_admin' => '',
'member' => [],
];
}
function auth_url_is_safe(string $url, string $site): bool
{
if ($url === '') {
return false;
}
if (strpos($url, '://') !== false) {
return false;
}
return strpos($url, auth_site_base_url($site) . '/') === 0;
}
function auth_baron_state_payload(string $site, string $returnUrl): array
{
return [
'site' => $site,
'return_url' => auth_url_is_safe($returnUrl, $site) ? $returnUrl : auth_site_qna_url($site),
'nonce' => bin2hex(random_bytes(16)),
'created_at' => time(),
];
}
function auth_store_baron_state(array $payload): string
{
auth_session_start_if_needed();
$state = bin2hex(random_bytes(16));
$_SESSION['baron_oauth_state'] = [
'value' => $state,
'payload' => $payload,
];
return $state;
}
function auth_consume_baron_state(string $state): ?array
{
auth_session_start_if_needed();
$stored = $_SESSION['baron_oauth_state'] ?? null;
unset($_SESSION['baron_oauth_state']);
if (!is_array($stored) || ($stored['value'] ?? '') !== $state) {
return null;
}
$payload = $stored['payload'] ?? null;
return is_array($payload) ? $payload : null;
}
function auth_http_post_form(string $url, array $data, array $headers = [], ?string $username = null, ?string $password = null): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
$mergedHeaders = array_merge([
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
], $headers);
curl_setopt($ch, CURLOPT_HTTPHEADER, $mergedHeaders);
if ($username !== null) {
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $username . ':' . ($password ?? ''));
}
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'body' => $response === false ? '' : $response,
'http_code' => $httpCode,
'error' => $error,
];
}
function auth_http_get_json(string $url, string $accessToken): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Authorization: Bearer ' . $accessToken,
]);
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'body' => $response === false ? '' : $response,
'http_code' => $httpCode,
'error' => $error,
];
}
function auth_jwt_payload(string $jwt): array
{
$parts = explode('.', $jwt);
if (count($parts) < 2) {
return [];
}
$payload = strtr($parts[1], '-_', '+/');
$padding = strlen($payload) % 4;
if ($padding > 0) {
$payload .= str_repeat('=', 4 - $padding);
}
$decoded = base64_decode($payload, true);
if ($decoded === false) {
return [];
}
$json = json_decode($decoded, true);
return is_array($json) ? $json : [];
}
function auth_first_non_empty_string(...$values): string
{
foreach ($values as $value) {
if (is_string($value)) {
$trimmed = trim($value);
if ($trimmed !== '') {
return $trimmed;
}
}
}
return '';
}
function auth_claim_list($value): array
{
if (is_array($value)) {
return $value;
}
$stringValue = trim((string) $value);
if ($stringValue === '') {
return [];
}
return [$stringValue];
}
function auth_extract_phone_from_claims(array $claims): string
{
$profile = isset($claims['profile']) && is_array($claims['profile']) ? $claims['profile'] : [];
$phones = auth_claim_list($claims['phones'] ?? ($profile['phones'] ?? []));
foreach ($phones as $phoneEntry) {
if (is_array($phoneEntry)) {
$candidate = auth_first_non_empty_string(
$phoneEntry['value'] ?? '',
$phoneEntry['phone_number'] ?? '',
$phoneEntry['number'] ?? ''
);
if ($candidate !== '') {
return $candidate;
}
continue;
}
$candidate = trim((string) $phoneEntry);
if ($candidate !== '') {
return $candidate;
}
}
return auth_first_non_empty_string(
$claims['phone_number'] ?? '',
$claims['phone'] ?? '',
$profile['phone_number'] ?? '',
$profile['phone'] ?? ''
);
}
function auth_extract_company_fields(array $claims): array
{
$company = auth_first_non_empty_string(
$claims['company'] ?? '',
$claims['company_name'] ?? '',
$claims['organization'] ?? '',
$claims['organization_name'] ?? '',
$claims['tenant_name'] ?? ''
);
$familyCompany = auth_first_non_empty_string(
$claims['familyCompany'] ?? '',
$claims['family_company'] ?? '',
$claims['group_name'] ?? '',
$claims['affiliate'] ?? ''
);
$team = auth_first_non_empty_string(
$claims['team'] ?? '',
$claims['department'] ?? '',
$claims['dept'] ?? '',
$claims['division'] ?? '',
$claims['org_unit'] ?? ''
);
$tenants = auth_claim_list($claims['tenants'] ?? []);
$primaryTenant = null;
$tenantId = trim((string) ($claims['tenant_id'] ?? ''));
foreach ($tenants as $tenantKey => $tenant) {
if (!is_array($tenant)) {
continue;
}
$candidateId = trim((string) ($tenant['id'] ?? (is_string($tenantKey) ? $tenantKey : '')));
if (($tenant['isPrimary'] ?? false) === true || ($tenant['representative'] ?? false) === true || ($tenantId !== '' && $candidateId === $tenantId)) {
$primaryTenant = $tenant;
break;
}
}
if ($primaryTenant === null) {
foreach ($tenants as $tenant) {
if (is_array($tenant)) {
$primaryTenant = $tenant;
break;
}
}
}
if (is_array($primaryTenant)) {
$companyAncestor = '';
$familyCompanyAncestor = '';
$departmentAncestor = '';
$ancestors = $primaryTenant['ancestors'] ?? [];
if (is_array($ancestors)) {
foreach ($ancestors as $ancestor) {
if (!is_array($ancestor)) {
continue;
}
$ancestorType = strtoupper(trim((string) ($ancestor['type'] ?? '')));
$ancestorName = trim((string) ($ancestor['name'] ?? ''));
if ($ancestorName === '') {
continue;
}
if ($companyAncestor === '' && $ancestorType === 'COMPANY') {
$companyAncestor = $ancestorName;
}
if ($familyCompanyAncestor === '' && $ancestorType === 'COMPANY_GROUP') {
$familyCompanyAncestor = $ancestorName;
}
if ($ancestorType === 'ORGANIZATION') {
$departmentAncestor = $ancestorName;
}
}
}
$company = auth_first_non_empty_string(
$company,
$companyAncestor,
trim((string) ($primaryTenant['name'] ?? ''))
);
$familyCompany = auth_first_non_empty_string(
$familyCompany,
$companyAncestor,
$familyCompanyAncestor,
$company
);
$team = auth_first_non_empty_string(
$team,
$departmentAncestor,
trim((string) ($primaryTenant['name'] ?? ''))
);
}
foreach ($tenants as $tenant) {
if (!is_array($tenant)) {
if ($company === '') {
$company = trim((string) $tenant);
}
continue;
}
if ($company === '') {
$company = auth_first_non_empty_string(
$tenant['name'] ?? '',
$tenant['display_name'] ?? '',
$tenant['company'] ?? '',
$tenant['company_name'] ?? '',
$tenant['organization'] ?? ''
);
}
if ($familyCompany === '') {
$familyCompany = auth_first_non_empty_string(
$tenant['familyCompany'] ?? '',
$tenant['family_company'] ?? '',
$tenant['group_name'] ?? '',
$tenant['affiliate'] ?? ''
);
}
if ($team === '') {
$team = auth_first_non_empty_string(
$tenant['team'] ?? '',
$tenant['department'] ?? '',
$tenant['dept'] ?? '',
$tenant['division'] ?? '',
$tenant['org_unit'] ?? ''
);
}
}
return [
'company' => $company,
'familyCompany' => $familyCompany,
'team' => $team,
];
}
function auth_baron_user_from_claims(array $claims): array
{
$profile = isset($claims['profile']) && is_array($claims['profile']) ? $claims['profile'] : [];
$email = trim((string) ($claims['email'] ?? $profile['email'] ?? $claims['upn'] ?? $claims['preferred_username'] ?? $claims['sub'] ?? ''));
$name = trim((string) ($claims['name'] ?? $profile['name'] ?? $claims['display_name'] ?? $claims['preferred_username'] ?? $email));
$phone = auth_extract_phone_from_claims($claims);
$companyFields = auth_extract_company_fields($claims);
$company = $companyFields['company'];
$familyCompany = $companyFields['familyCompany'];
$team = $companyFields['team'];
$employeeId = trim((string) ($claims['employee_id'] ?? $profile['employee_id'] ?? $claims['employeeId'] ?? $claims['familyUniqueKey'] ?? ''));
$status = trim((string) ($claims['status'] ?? $profile['status'] ?? ''));
$tenants = auth_claim_list($claims['tenants'] ?? []);
$secondaryEmails = auth_claim_list($claims['secondary_emails'] ?? $claims['secondaryEmails'] ?? ($profile['secondary_emails'] ?? []));
$roles = auth_claim_list($claims['roles'] ?? $claims['roleNames'] ?? []);
$position = auth_first_non_empty_string(
$claims['position'] ?? '',
$profile['position'] ?? ''
);
foreach ($tenants as $tenant) {
if (!is_array($tenant)) {
continue;
}
if ($position === '') {
$position = auth_first_non_empty_string(
$tenant['grade'] ?? '',
$tenant['position'] ?? ''
);
}
}
return auth_normalize_user([
'userId' => trim((string) ($claims['sub'] ?? $email)),
'loginIds' => $email !== '' ? [$email] : [],
'name' => $name,
'email' => $email,
'phone' => $phone,
'customAttributes' => [
'company' => $company,
'familyCompany' => $familyCompany,
'team' => $team,
'position' => $position,
'employeeId' => $employeeId,
'familyUniqueKey' => $employeeId,
'status' => $status,
'tenants' => $tenants,
'secondaryEmails' => $secondaryEmails,
],
'roleNames' => $roles,
]);
}
function auth_script_redirect(string $message, string $redirectUrl): void
{
$safeMessage = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
$safeUrl = htmlspecialchars($redirectUrl, ENT_QUOTES, 'UTF-8');
echo "<script>alert('{$safeMessage}'); window.location.href = '{$safeUrl}';</script>";
exit;
}
function auth_is_local_debug(): bool
{
return (getenv('APP_ENV') ?: '') === 'local';
}
function auth_compact_debug_value(string $value, int $maxLength = 300): string
{
$value = preg_replace('/\s+/', ' ', trim($value)) ?? '';
if (strlen($value) <= $maxLength) {
return $value;
}
return substr($value, 0, $maxLength) . '...';
}
+23
View File
@@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/common.php';
auth_session_start_if_needed();
if (!auth_is_local_debug()) {
http_response_code(404);
exit;
}
header('Content-Type: application/json; charset=UTF-8');
$baronClaims = auth_json_cookie_decode((string) ($_COOKIE['baron_claims'] ?? ''), []);
$baronUser = auth_baron_user_from_cookies();
$sessionUser = $_SESSION['user'] ?? [];
echo json_encode([
'auth_provider' => $_SESSION['auth_provider'] ?? '',
'session_user' => is_array($sessionUser) ? $sessionUser : [],
'baron_user_cookie' => $baronUser,
'baron_claims_cookie' => is_array($baronClaims) ? $baronClaims : [],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
+9
View File
@@ -13,6 +13,15 @@ services:
G5_MYSQL_USER: ${G5_MYSQL_USER:-egbim}
G5_MYSQL_PASSWORD: ${G5_MYSQL_PASSWORD:-baron3840!!}
G5_MYSQL_DB: ${G5_MYSQL_DB:-egbim}
BARON_SSO_CLIENT_ID: ${BARON_SSO_CLIENT_ID:-}
BARON_SSO_CLIENT_SECRET: ${BARON_SSO_CLIENT_SECRET:-}
BARON_SSO_AUTHORIZE_ENDPOINT: ${BARON_SSO_AUTHORIZE_ENDPOINT:-}
BARON_SSO_TOKEN_ENDPOINT: ${BARON_SSO_TOKEN_ENDPOINT:-}
BARON_SSO_USERINFO_ENDPOINT: ${BARON_SSO_USERINFO_ENDPOINT:-}
BARON_SSO_ISSUER: ${BARON_SSO_ISSUER:-}
BARON_SSO_REDIRECT_URI: ${BARON_SSO_REDIRECT_URI:-}
BARON_SSO_SCOPE: ${BARON_SSO_SCOPE:-openid profile email}
BARON_SSO_CLIENT_AUTH_METHOD: ${BARON_SSO_CLIENT_AUTH_METHOD:-basic}
ports:
- "${APP_PORT:-8088}:80"
volumes:
+4 -23
View File
@@ -1,31 +1,12 @@
<?php
// /egbim/bbs/admin_guard.php
if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
require_once dirname(__DIR__, 2) . '/auth/common.php';
if (!function_exists('is_qna_admin')) {
function is_qna_admin(): bool {
$loginId = $_SESSION['user']['loginIds'][0] ?? '';
$allow = [
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'b23008@baroncs.co.kr',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b21367@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com'
];
$norm = static function($s){ return strtolower(trim((string)$s)); };
$loginId = $norm($loginId);
foreach ($allow as $id) {
if ($loginId === $norm($id)) return true;
}
return false;
function is_qna_admin(?string $loginId = null): bool {
$user = $_SESSION['user'] ?? [];
return is_array($user) && auth_is_admin_user($user, $loginId);
}
}
+1 -1
View File
@@ -5885,7 +5885,7 @@ footer .address em.tel {
padding: 0;
}
.login form {
gap: 24px;
gap: 10px;
}
.login .input_wrap {
width: 100%;
+15 -25
View File
@@ -1,25 +1,7 @@
<?php
session_start();
require_once dirname(__DIR__, 4) . '/auth/common.php';
function set_descope_session_cookies(array $user): void {
$expire = time() + 86400;
$cookieOptions = [
'expires' => $expire,
'path' => '/',
'samesite' => 'Lax',
];
$customAttributes = $user['customAttributes'] ?? [];
$roleNames = $user['roleNames'] ?? [];
setcookie('descope_login_id', (string)($user['loginIds'][0] ?? ''), $cookieOptions);
setcookie('descope_user_id', (string)($user['userId'] ?? ''), $cookieOptions);
setcookie('descope_user_name', (string)($user['name'] ?? ''), $cookieOptions);
setcookie('descope_user_email', (string)($user['email'] ?? ''), $cookieOptions);
setcookie('descope_user_phone', (string)($user['phone'] ?? ''), $cookieOptions);
setcookie('descope_custom_attributes', base64_encode(json_encode($customAttributes, JSON_UNESCAPED_UNICODE)), $cookieOptions);
setcookie('descope_role_names', base64_encode(json_encode($roleNames, JSON_UNESCAPED_UNICODE)), $cookieOptions);
}
auth_session_start_if_needed();
$email = trim($_POST['mb_id'] ?? '');
$password = trim($_POST['mb_password'] ?? '');
@@ -60,16 +42,24 @@ if ($http_code === 200 && isset($res_data['sessionJwt'])) {
// 로그인 성공 → 실패횟수 초기화
$_SESSION['login_fail_count'] = 0;
//session으로도 저장
$_SESSION['user'] = $res_data['user']; // userId, email 등
$_SESSION['sessionJwt'] = $res_data['sessionJwt'];
$_SESSION['refreshJwt'] = $res_data['refreshJwt'];
set_descope_session_cookies($res_data['user'] ?? []);
$normalizedUser = auth_apply_user_session(
$res_data['user'] ?? [],
'descope',
[
'sessionJwt' => $res_data['sessionJwt'] ?? '',
'refreshJwt' => $res_data['refreshJwt'] ?? '',
]
);
setcookie('baron_user', '', auth_cookie_options(time() - 42000));
setcookie('baron_claims', '', auth_cookie_options(time() - 42000));
setcookie('baron_provider', '', auth_cookie_options(time() - 42000));
auth_set_descope_session_cookies($normalizedUser);
echo json_encode([
'status' => 'ok',
'sessionJwt' => $res_data['sessionJwt'] ?? '',
// 'refreshJwt' => $res_data['refreshJwt'] ?? '',
'user' => $res_data['user'] ?? [],
'user' => $normalizedUser,
'session' => $_SESSION,
]);
// } else {
+3 -10
View File
@@ -1,4 +1,5 @@
<?php
require_once dirname(__DIR__, 4) . '/auth/common.php';
// descope_logout.php
// session_start();
// $_SESSION = [];
@@ -16,17 +17,9 @@
// http_response_code(200);
// echo json_encode(['status'=>'ok']);
// exit;
session_start();
auth_session_start_if_needed();
$_SESSION = [];
foreach (['PHPSESSID', 'G5sessphp', 'descope_login_id', 'descope_user_id', 'descope_user_name', 'descope_user_email', 'descope_user_phone', 'descope_custom_attributes', 'descope_role_names'] as $cookieName) {
setcookie($cookieName, '', [
'expires' => time() - 42000,
'path' => '/',
'samesite' => 'Lax',
]);
}
auth_clear_all_cookies();
session_destroy();
http_response_code(200);
+7 -144
View File
@@ -1,148 +1,11 @@
<?php
// // descope_session.php
require_once dirname(__DIR__, 4) . '/auth/common.php';
// // 1) 에러 출력 (개발용)
// // ini_set('display_errors', 1);
// // error_reporting(E_ALL);
$authState = auth_sync_gnuboard_session();
// // 2) 세션 시작 (중복 호출 방지)
// if (session_status() === PHP_SESSION_NONE) {
// session_start();
// }
// // 3) 로그인 검사
// // 로그인 시 $_SESSION['user']['userId'] 와 $_SESSION['user']['loginIds'][0] 를 설정해 두었다면
// // if (empty($_SESSION['user']['userId'])) {
// // // 로그인 필요 알림 후 로그인 페이지로 이동
// // echo <<<HTML
// // <script>
// // alert('Q&A를 이용하시려면 로그인이 필요합니다.');
// // window.location.href = '/egbim/index.php';
// // </script>
// // HTML;
// // exit;
// // }
// // ✅ Descope 로그인 성공 시 g5_member 세션 동기화
// $loginId = $_SESSION['user']['loginIds'][0] ?? '';
// if ($loginId === 'b24014@hanmaceng.co.kr') {
// // g5_member 계정이 이미 존재한다고 가정
// $_SESSION['ss_mb_id'] = $loginId;
// $_SESSION['ss_mb_level'] = 10;
// $is_member = true;
// $is_admin = 'super';
// $member = [
// 'mb_id' => $loginId,
// 'mb_name' => $_SESSION['user']['name'] ?? '관리자',
// 'mb_email' => $_SESSION['user']['email'] ?? '',
// 'mb_level' => 10,
// 'mb_nick' => '관리자',
// ];
// }
// // Descope 로그인 성공 시 (예: descope_session.php 내부)
// if (!empty($_SESSION['user']['loginIds'][0])) {
// $loginId = $_SESSION['user']['loginIds'][0];
// // g5_member 테이블에 계정이 존재한다고 가정
// $_SESSION['ss_mb_id'] = $loginId;
// $_SESSION['ss_mb_level'] = 10; // 관리자 레벨
// // 그누보드 전역 변수 보정
// $is_member = true;
// $is_admin = 'super';
// $member = [
// 'mb_id' => $loginId,
// 'mb_name' => $_SESSION['user']['name'] ?? '관리자',
// 'mb_email' => $_SESSION['user']['email'] ?? '',
// 'mb_level' => 10,
// 'mb_nick' => '관리자',
// ];
// }
// descope_session.php
// 1) 세션 시작
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($_SESSION['user']['userId']) && !empty($_COOKIE['descope_login_id'])) {
$customAttributes = json_decode(base64_decode($_COOKIE['descope_custom_attributes'] ?? ''), true);
$roleNames = json_decode(base64_decode($_COOKIE['descope_role_names'] ?? ''), true);
$loginIdFromCookie = trim((string)($_COOKIE['descope_login_id'] ?? ''));
$_SESSION['user'] = [
'userId' => trim((string)($_COOKIE['descope_user_id'] ?? $loginIdFromCookie)),
'loginIds' => $loginIdFromCookie !== '' ? [$loginIdFromCookie] : [],
'name' => trim((string)($_COOKIE['descope_user_name'] ?? '')),
'email' => trim((string)($_COOKIE['descope_user_email'] ?? '')),
'phone' => trim((string)($_COOKIE['descope_user_phone'] ?? '')),
'customAttributes' => is_array($customAttributes) ? $customAttributes : [],
'roleNames' => is_array($roleNames) ? $roleNames : [],
];
}
// 2) 로그인 검사 (비로그인 차단)
// if (empty($_SESSION['user']['userId'])) {
// // 세션 없으면 바로 차단
// session_destroy();
// echo "<script>
// alert('로그인이 필요합니다.');
// // 로그인 팝업 자동 오픈 플래그 전달
// window.location.href = '/egbim/index.php?popup=login';
// </script>";
// exit;
// }
// 3) 로그인 정보 추출
$loginIds = $_SESSION['user']['loginIds'] ?? [];
$loginId = (is_array($loginIds) && !empty($loginIds)) ? $loginIds[0] : '';
$userName = $_SESSION['user']['name'] ?? '';
$userEmail = $_SESSION['user']['email'] ?? '';
// 4) 관리자/일반 사용자 구분
$ADMIN_EMAILS = [
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'shyeom1@samaneng.com',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'cozyjin@hanmaceng.co.kr',
'm24031@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b25023@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com'
];
// $isAdmin = in_array($loginId, $ADMIN_EMAILS, true);
$isAdmin = $loginId && in_array($loginId, $ADMIN_EMAILS, true);
// ✅ 그누보드 전역 세션 동기화
if ($loginId) {
$_SESSION['ss_mb_id'] = $loginId;
$_SESSION['ss_mb_level'] = $isAdmin ? 10 : 2;
$is_member = true;
$is_admin = $isAdmin ? 'super' : '';
$member = [
'mb_id' => $loginId,
'mb_name' => $userName ?: $loginId,
'mb_email' => $userEmail,
'mb_level' => $_SESSION['ss_mb_level'],
'mb_nick' => $userName ?: $loginId,
];
} else {
// ✅ 비회원: 로그인 세션 없음
$is_member = false;
$is_admin = '';
$member = [];
}
$loginId = $authState['loginId'];
$isAdmin = $authState['isAdmin'];
$is_member = $authState['is_member'];
$is_admin = $authState['is_admin'];
$member = $authState['member'];
?>
+27 -56
View File
@@ -1,66 +1,38 @@
<?php
// header('Content-Type: application/json');
require_once dirname(__DIR__, 4) . '/auth/common.php';
// $management_key = 'P2wON5fy1K6kyia269VpeIzYP8oP:K2ycqpjeh1voPBdxXxzB3ScZOQ6v9aiLmU2cIj70X1H8Kcoz0KWfCWmofUwAsAkJroXA8QC';
// $loginId = $_POST['loginId'] ?? '';
auth_session_start_if_needed();
header('Content-Type: application/json; charset=UTF-8');
// $url = "https://api.descope.com/v2/mgmt/user/search";
// $payload = json_encode([
// "loginId" => $loginId,
// "limit" => 1
// ]);
$loginId = trim((string) ($_POST['loginId'] ?? ''));
$sessionUser = $_SESSION['user'] ?? [];
$sessionLoginId = trim((string) ($sessionUser['loginIds'][0] ?? ''));
// $headers = [
// "Authorization: Bearer $management_key",
// "Content-Type: application/json"
// ];
if ($loginId !== '' && $sessionLoginId !== '' && strcasecmp($loginId, $sessionLoginId) === 0) {
echo json_encode(['status' => 'ok', 'user' => auth_normalize_user($sessionUser)]);
exit;
}
// $ch = curl_init($url);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
// curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// $response = curl_exec($ch);
// $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// curl_close($ch);
// if ($http_code === 200) {
// $res = json_decode($response, true);
// if (!empty($res['users'][0])) {
// // ✅ 먼저 $user에 복사
// $user = $res['users'][0];
// // ✅ 값이 없으면 테스트용 기본값 삽입
// $user['name'] = $user['name'] ?: '홍길동';
// $user['phone'] = $user['phone'] ?: '01012345678';
// $user['customAttributes']['company'] = $user['customAttributes']['company'] ?? '한맥ENG';
// $user['customAttributes']['team'] = $user['customAttributes']['team'] ?? '설계팀';
// $user['customAttributes']['employeeId'] = $user['customAttributes']['employeeId'] ?? 'HM2024';
// // ✅ 수정된 $user 사용
// echo json_encode(['status' => 'ok', 'user' => $user]);
// } else {
// echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음']);
// }
// } else {
// echo json_encode(['status' => 'fail', 'message' => 'Descope API 오류', 'code' => $http_code]);
// }
?>
<?php
header('Content-Type: application/json');
$cookieUser = auth_baron_user_from_cookies();
if ($loginId !== '' && $cookieUser !== null) {
$cookieLoginId = trim((string) ($cookieUser['loginIds'][0] ?? ''));
if ($cookieLoginId !== '' && strcasecmp($loginId, $cookieLoginId) === 0) {
echo json_encode(['status' => 'ok', 'user' => $cookieUser]);
exit;
}
}
$management_key = 'P2wON5fy1K6kyia269VpeIzYP8oP:K32l5ORmzy32OvaaPvpdZsMY3JmKQb7a3vvrl10PgjlJUGk3K7EssMH3uW5VGQSbrgtEdPj';
$loginId = $_POST['loginId'] ?? '';
$url = "https://api.descope.com/v2/mgmt/user/search";
$url = 'https://api.descope.com/v2/mgmt/user/search';
$payload = json_encode([
"loginId" => $loginId,
"limit" => 1
'loginId' => $loginId,
'limit' => 1,
]);
$headers = [
"Authorization: Bearer $management_key",
"Content-Type: application/json"
'Content-Type: application/json',
];
$ch = curl_init($url);
@@ -76,18 +48,17 @@ if ($http_code === 200) {
if (!empty($res['users'][0])) {
$user = $res['users'][0];
// ✅ 실제 값이 있으면 그대로 사용, 없으면 빈 문자열
$user['name'] = $user['name'] ?? '';
$user['phone'] = $user['phone'] ?? '';
$user['customAttributes']['company'] = $user['customAttributes']['company'] ?? '';
$user['customAttributes']['familyCompany'] = $user['customAttributes']['familyCompany'] ?? '';
$user['customAttributes']['team'] = $user['customAttributes']['team'] ?? '';
$user['customAttributes']['employeeId'] = $user['customAttributes']['employeeId'] ?? '';
$user['customAttributes']['familyUniqueKey'] = $user['customAttributes']['familyUniqueKey'] ?? '';
echo json_encode(['status' => 'ok', 'user' => $user]);
} else {
echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음']);
exit;
}
} else {
echo json_encode(['status' => 'fail', 'message' => 'Descope API 오류', 'code' => $http_code]);
}
echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음', 'code' => $http_code]);
+9 -1
View File
@@ -52,8 +52,12 @@
<div class="join_btn_wrap">
<button type="submit" id="btn_login">로그인</button>
</div>
<div class="join_btn_wrap" style="margin-top:10px;">
<a href="/auth/baron_login?site=egbim&amp;return_url=/egbim/bbs/descope_qa_list.php" style="display:block; width:100%; text-align:center; padding:14px 16px; border:1px solid #0f3a2f; border-radius:10px; color:#0f3a2f; font-weight:700;">BARON-SSO 로그인</a>
</div>
<p style="margin-bottom:20px; font-size:16px; font-weight:700; color:#c62828; line-height:1.6; text-align:center;">eg-bim 1.5.1 버전 이상 사용자는 baron-sso로 로그인해야 합니다.</p>
<div class="btn_go">
<div class="go_signup"><a href="javascript:agreement();"><span>회원가입</span><i class="arrow_r"></i></a></div>
<div class="go_signup"><a href="#" class="signup-disabled-link" aria-disabled="true"><span>회원가입</span><i class="arrow_r"></i></a></div>
<div class="go_find"><a href="javascript:search();"><span>비밀번호 재설정</span><i class="arrow_r"></i></a></div>
</div>
</form>
@@ -127,6 +131,10 @@ $(document).ready(function () {
e.preventDefault(); // 기본 제출 막기
$('#btn_login').click(); // 버튼 클릭 이벤트 재사용
});
$('.signup-disabled-link').on('click', function (e) {
e.preventDefault();
});
});
document.addEventListener("DOMContentLoaded", () => {
+82 -3
View File
@@ -55,7 +55,7 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
<a href="javascript:descopeLogout();">로그아웃</a>
</li>
<li id="my_join" class="my_join">
<a href="javascript:agreement();">회원가입</a>
<a href="#" class="signup-disabled-link" aria-disabled="true">회원가입</a>
</li>
<li id="my_login" class="my_login">
<a href="javascript:login();">로그인</a>
@@ -94,9 +94,84 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
// e.preventDefault(); // 링크 이동 막기
// alert('다운로드 기능은 준비중입니다.');
// });
</script>
<script>
(function () {
function getCookieValue(name) {
const prefix = name + '=';
const parts = document.cookie.split(';');
for (let i = 0; i < parts.length; i += 1) {
const part = parts[i].trim();
if (part.indexOf(prefix) === 0) {
return part.substring(prefix.length);
}
}
return '';
}
function decodeBase64Json(value) {
if (!value) return null;
try {
const binary = window.atob(value);
const bytes = Array.prototype.map.call(binary, function (char) {
return '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2);
}).join('');
return JSON.parse(decodeURIComponent(bytes));
} catch (error) {
return null;
}
}
function setStoredValue(key, value) {
if (typeof value === 'string' && value !== '') {
sessionStorage.setItem(key, value);
}
}
function restoreClientAuthSessionFromCookies() {
const baronUser = decodeBase64Json(getCookieValue('baron_user'));
if (baronUser && Array.isArray(baronUser.loginIds) && baronUser.loginIds[0]) {
const customAttributes = baronUser.customAttributes || {};
const roleNames = Array.isArray(baronUser.roleNames) ? baronUser.roleNames : [];
setStoredValue('authProvider', 'baron');
setStoredValue('loginId', baronUser.loginIds[0] || '');
setStoredValue('descopeUserId', baronUser.userId || '');
setStoredValue('userName', baronUser.name || '');
setStoredValue('phone', baronUser.phone || '');
setStoredValue('company', customAttributes.company || '');
setStoredValue('familyCompany', customAttributes.familyCompany || '');
setStoredValue('team', customAttributes.team || '');
setStoredValue('position', customAttributes.position || '');
setStoredValue('familyUniqueKey', customAttributes.familyUniqueKey || '');
setStoredValue('userRole', roleNames[0] || '');
setStoredValue('sessionJwt', sessionStorage.getItem('sessionJwt') || 'baron.session.marker');
return;
}
const descopeLoginId = getCookieValue('descope_login_id');
if (descopeLoginId && !sessionStorage.getItem('loginId')) {
const customAttributes = decodeBase64Json(getCookieValue('descope_custom_attributes')) || {};
const roleNames = decodeBase64Json(getCookieValue('descope_role_names')) || [];
setStoredValue('authProvider', sessionStorage.getItem('authProvider') || 'descope');
setStoredValue('loginId', descopeLoginId);
setStoredValue('descopeUserId', getCookieValue('descope_user_id'));
setStoredValue('userName', getCookieValue('descope_user_name'));
setStoredValue('phone', getCookieValue('descope_user_phone'));
setStoredValue('company', customAttributes.company || '');
setStoredValue('familyCompany', customAttributes.familyCompany || '');
setStoredValue('team', customAttributes.team || '');
setStoredValue('position', customAttributes.position || '');
setStoredValue('familyUniqueKey', customAttributes.familyUniqueKey || '');
setStoredValue('userRole', Array.isArray(roleNames) ? (roleNames[0] || '') : '');
}
}
restoreClientAuthSessionFromCookies();
})();
(function () {
const ADMIN_ICON_IDS = [
'b24014@hanmaceng.co.kr',
@@ -181,6 +256,10 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
}, 50);
});
$(document).on('click', '.signup-disabled-link', function (e) {
e.preventDefault();
});
// 혹시 로그인 팝업을 다른 곳에서 열어도 배너가 필요하면, 팝업 열릴 때 한 번 더 시도
$(document).on('show-login-popup', function () {
injectBanner();
+1 -1
View File
@@ -438,7 +438,7 @@ function updateHeaderMenu(){
$('#my_join2').html(
isLoggedIn && isAdmin
? "<a href='/egbim/adm/index.php' target='_blank'>관리페이지</a>"
: "<a href='/egbim/index.php'>메인으로</a>"
: "<a href='javascript:mypage03();'>마이페이지</a>"
);
} catch(e){ console.error(e); }
}
@@ -430,7 +430,7 @@ function updateHeaderMenu(){
$('#my_join2').html(
isLoggedIn && isAdmin
? "<a href='/egbim/adm/index.php' target='_blank'>관리페이지</a>"
: "<a href='/egbim/index.php'>메인으로</a>"
: "<a href='javascript:mypage03();'>마이페이지</a>"
);
} catch(e){ console.error(e); }
}
+4 -16
View File
@@ -1,24 +1,12 @@
<?php
// /egbim/bbs/admin_guard.php
if (session_status() !== PHP_SESSION_ACTIVE) @session_start();
require_once dirname(__DIR__, 2) . '/auth/common.php';
if (!function_exists('is_qna_admin')) {
function is_qna_admin(): bool {
$loginId = $_SESSION['user']['loginIds'][0] ?? '';
$allow = [
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'b23008@baroncs.co.kr',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
];
$norm = static function($s){ return strtolower(trim((string)$s)); };
$loginId = $norm($loginId);
foreach ($allow as $id) {
if ($loginId === $norm($id)) return true;
}
return false;
function is_qna_admin(?string $loginId = null): bool {
$user = $_SESSION['user'] ?? [];
return is_array($user) && auth_is_admin_user($user, $loginId);
}
}
+34 -19
View File
@@ -1185,27 +1185,42 @@ $(function () {
$(".menu_my").mouseout(function () {
$(".menu_my_list").hide();
});
//footer family site toggle
$(".family_btn").click(function (event) {
event.stopPropagation(); // family_btn 클릭 시 이벤트 전파를 막음
$(".family_list").toggleClass("family_on");
$(".family_btn").toggleClass("family_on");
});
// 화면 아무 곳이나 클릭했을 때 family_list를 제외한 영역 클릭 시 리스트 닫기
$(document).click(function (event) {
if (
!$(event.target).closest(".family_list").length &&
!$(event.target).closest(".family_btn").length
) {
// family_list와 family_btn 외의 영역을 클릭한 경우
$(".family_list").removeClass("family_on");
$(".family_btn").removeClass("family_on");
}
});
});
if (!window.__egbimFamilySiteToggleBound) {
window.__egbimFamilySiteToggleBound = true;
document.addEventListener(
"click",
function (event) {
const familyButton = event.target.closest(".family_btn");
if (familyButton) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
const shouldOpen = !familyButton.classList.contains("family_on");
document
.querySelectorAll(".family_btn, .family_list")
.forEach((element) => {
element.classList.toggle("family_on", shouldOpen);
});
return;
}
if (!event.target.closest(".family_wrap")) {
document
.querySelectorAll(".family_btn, .family_list")
.forEach((element) => {
element.classList.remove("family_on");
});
}
},
true
);
}
// 마우스 스크롤 마크 표시하기
// 사용 클래스 : js__mouse_mark , js__mouse_area
// + TODO 진슬 추가_ addEventListener error debugging
+12 -1
View File
@@ -660,8 +660,19 @@ function html_purifier($html)
include_once(G5_PLUGIN_PATH . '/htmlpurifier/extend.video.php');
$config = HTMLPurifier_Config::createDefault();
// data/cache 가 없거나 쓰기 불가한 환경에서도 FAQ 출력이 깨지지 않도록 캐시 경로를 보장한다.
$serializer_path = G5_DATA_PATH . '/cache';
if (!is_dir($serializer_path)) {
@mkdir($serializer_path, 0755, true);
}
if (!is_dir($serializer_path) || !is_writable($serializer_path)) {
$serializer_path = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'egbim_htmlpurifier_cache';
if (!is_dir($serializer_path)) {
@mkdir($serializer_path, 0755, true);
}
}
// data/cache 디렉토리에 CSS, HTML, URI 디렉토리 등을 만든다.
$config->set('Cache.SerializerPath', G5_DATA_PATH . '/cache');
$config->set('Cache.SerializerPath', $serializer_path);
$config->set('HTML.SafeEmbed', false);
$config->set('HTML.SafeObject', false);
$config->set('Output.FlashCompat', false);
-13
View File
@@ -268,19 +268,6 @@ echo '<div id="faq_hhtml">'.conv_content($fm['fm_head_html'], 1).'</div>';
<!-- 상단(헤더) 스크립트 구문 -->
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
-13
View File
@@ -112,19 +112,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
+15 -25
View File
@@ -1,25 +1,7 @@
<?php
session_start();
require_once dirname(__DIR__, 4) . '/auth/common.php';
function set_descope_session_cookies(array $user): void {
$expire = time() + 86400;
$cookieOptions = [
'expires' => $expire,
'path' => '/',
'samesite' => 'Lax',
];
$customAttributes = $user['customAttributes'] ?? [];
$roleNames = $user['roleNames'] ?? [];
setcookie('descope_login_id', (string)($user['loginIds'][0] ?? ''), $cookieOptions);
setcookie('descope_user_id', (string)($user['userId'] ?? ''), $cookieOptions);
setcookie('descope_user_name', (string)($user['name'] ?? ''), $cookieOptions);
setcookie('descope_user_email', (string)($user['email'] ?? ''), $cookieOptions);
setcookie('descope_user_phone', (string)($user['phone'] ?? ''), $cookieOptions);
setcookie('descope_custom_attributes', base64_encode(json_encode($customAttributes, JSON_UNESCAPED_UNICODE)), $cookieOptions);
setcookie('descope_role_names', base64_encode(json_encode($roleNames, JSON_UNESCAPED_UNICODE)), $cookieOptions);
}
auth_session_start_if_needed();
$email = trim($_POST['mb_id'] ?? '');
$password = trim($_POST['mb_password'] ?? '');
@@ -60,16 +42,24 @@ if ($http_code === 200 && isset($res_data['sessionJwt'])) {
// 로그인 성공 → 실패횟수 초기화
$_SESSION['login_fail_count'] = 0;
//session으로도 저장
$_SESSION['user'] = $res_data['user']; // userId, email 등
$_SESSION['sessionJwt'] = $res_data['sessionJwt'];
$_SESSION['refreshJwt'] = $res_data['refreshJwt'];
set_descope_session_cookies($res_data['user'] ?? []);
$normalizedUser = auth_apply_user_session(
$res_data['user'] ?? [],
'descope',
[
'sessionJwt' => $res_data['sessionJwt'] ?? '',
'refreshJwt' => $res_data['refreshJwt'] ?? '',
]
);
setcookie('baron_user', '', auth_cookie_options(time() - 42000));
setcookie('baron_claims', '', auth_cookie_options(time() - 42000));
setcookie('baron_provider', '', auth_cookie_options(time() - 42000));
auth_set_descope_session_cookies($normalizedUser);
echo json_encode([
'status' => 'ok',
'sessionJwt' => $res_data['sessionJwt'] ?? '',
// 'refreshJwt' => $res_data['refreshJwt'] ?? '',
'user' => $res_data['user'] ?? [],
'user' => $normalizedUser,
'session' => $_SESSION,
]);
// } else {
+3 -10
View File
@@ -1,4 +1,5 @@
<?php
require_once dirname(__DIR__, 4) . '/auth/common.php';
// descope_logout.php
// session_start();
// $_SESSION = [];
@@ -16,17 +17,9 @@
// http_response_code(200);
// echo json_encode(['status'=>'ok']);
// exit;
session_start();
auth_session_start_if_needed();
$_SESSION = [];
foreach (['PHPSESSID', 'G5sessphp', 'descope_login_id', 'descope_user_id', 'descope_user_name', 'descope_user_email', 'descope_user_phone', 'descope_custom_attributes', 'descope_role_names'] as $cookieName) {
setcookie($cookieName, '', [
'expires' => time() - 42000,
'path' => '/',
'samesite' => 'Lax',
]);
}
auth_clear_all_cookies();
session_destroy();
http_response_code(200);
+7 -144
View File
@@ -1,148 +1,11 @@
<?php
// // descope_session.php
require_once dirname(__DIR__, 4) . '/auth/common.php';
// // 1) 에러 출력 (개발용)
// // ini_set('display_errors', 1);
// // error_reporting(E_ALL);
$authState = auth_sync_gnuboard_session();
// // 2) 세션 시작 (중복 호출 방지)
// if (session_status() === PHP_SESSION_NONE) {
// session_start();
// }
// // 3) 로그인 검사
// // 로그인 시 $_SESSION['user']['userId'] 와 $_SESSION['user']['loginIds'][0] 를 설정해 두었다면
// // if (empty($_SESSION['user']['userId'])) {
// // // 로그인 필요 알림 후 로그인 페이지로 이동
// // echo <<<HTML
// // <script>
// // alert('Q&A를 이용하시려면 로그인이 필요합니다.');
// // window.location.href = '/egbim/index.php';
// // </script>
// // HTML;
// // exit;
// // }
// // ✅ Descope 로그인 성공 시 g5_member 세션 동기화
// $loginId = $_SESSION['user']['loginIds'][0] ?? '';
// if ($loginId === 'b24014@hanmaceng.co.kr') {
// // g5_member 계정이 이미 존재한다고 가정
// $_SESSION['ss_mb_id'] = $loginId;
// $_SESSION['ss_mb_level'] = 10;
// $is_member = true;
// $is_admin = 'super';
// $member = [
// 'mb_id' => $loginId,
// 'mb_name' => $_SESSION['user']['name'] ?? '관리자',
// 'mb_email' => $_SESSION['user']['email'] ?? '',
// 'mb_level' => 10,
// 'mb_nick' => '관리자',
// ];
// }
// // Descope 로그인 성공 시 (예: descope_session.php 내부)
// if (!empty($_SESSION['user']['loginIds'][0])) {
// $loginId = $_SESSION['user']['loginIds'][0];
// // g5_member 테이블에 계정이 존재한다고 가정
// $_SESSION['ss_mb_id'] = $loginId;
// $_SESSION['ss_mb_level'] = 10; // 관리자 레벨
// // 그누보드 전역 변수 보정
// $is_member = true;
// $is_admin = 'super';
// $member = [
// 'mb_id' => $loginId,
// 'mb_name' => $_SESSION['user']['name'] ?? '관리자',
// 'mb_email' => $_SESSION['user']['email'] ?? '',
// 'mb_level' => 10,
// 'mb_nick' => '관리자',
// ];
// }
// descope_session.php
// 1) 세션 시작
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($_SESSION['user']['userId']) && !empty($_COOKIE['descope_login_id'])) {
$customAttributes = json_decode(base64_decode($_COOKIE['descope_custom_attributes'] ?? ''), true);
$roleNames = json_decode(base64_decode($_COOKIE['descope_role_names'] ?? ''), true);
$loginIdFromCookie = trim((string)($_COOKIE['descope_login_id'] ?? ''));
$_SESSION['user'] = [
'userId' => trim((string)($_COOKIE['descope_user_id'] ?? $loginIdFromCookie)),
'loginIds' => $loginIdFromCookie !== '' ? [$loginIdFromCookie] : [],
'name' => trim((string)($_COOKIE['descope_user_name'] ?? '')),
'email' => trim((string)($_COOKIE['descope_user_email'] ?? '')),
'phone' => trim((string)($_COOKIE['descope_user_phone'] ?? '')),
'customAttributes' => is_array($customAttributes) ? $customAttributes : [],
'roleNames' => is_array($roleNames) ? $roleNames : [],
];
}
// 2) 로그인 검사 (비로그인 차단)
// if (empty($_SESSION['user']['userId'])) {
// // 세션 없으면 바로 차단
// session_destroy();
// echo "<script>
// alert('로그인이 필요합니다.');
// // 로그인 팝업 자동 오픈 플래그 전달
// window.location.href = '/egbim/index.php?popup=login';
// </script>";
// exit;
// }
// 3) 로그인 정보 추출
$loginIds = $_SESSION['user']['loginIds'] ?? [];
$loginId = (is_array($loginIds) && !empty($loginIds)) ? $loginIds[0] : '';
$userName = $_SESSION['user']['name'] ?? '';
$userEmail = $_SESSION['user']['email'] ?? '';
// 4) 관리자/일반 사용자 구분
$ADMIN_EMAILS = [
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'shyeom1@samaneng.com',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'cozyjin@hanmaceng.co.kr',
'm24031@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b25023@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com'
];
// $isAdmin = in_array($loginId, $ADMIN_EMAILS, true);
$isAdmin = $loginId && in_array($loginId, $ADMIN_EMAILS, true);
// ✅ 그누보드 전역 세션 동기화
if ($loginId) {
$_SESSION['ss_mb_id'] = $loginId;
$_SESSION['ss_mb_level'] = $isAdmin ? 10 : 2;
$is_member = true;
$is_admin = $isAdmin ? 'super' : '';
$member = [
'mb_id' => $loginId,
'mb_name' => $userName ?: $loginId,
'mb_email' => $userEmail,
'mb_level' => $_SESSION['ss_mb_level'],
'mb_nick' => $userName ?: $loginId,
];
} else {
// ✅ 비회원: 로그인 세션 없음
$is_member = false;
$is_admin = '';
$member = [];
}
$loginId = $authState['loginId'];
$isAdmin = $authState['isAdmin'];
$is_member = $authState['is_member'];
$is_admin = $authState['is_admin'];
$member = $authState['member'];
?>
+27 -56
View File
@@ -1,66 +1,38 @@
<?php
// header('Content-Type: application/json');
require_once dirname(__DIR__, 4) . '/auth/common.php';
// $management_key = 'P2wON5fy1K6kyia269VpeIzYP8oP:K2ycqpjeh1voPBdxXxzB3ScZOQ6v9aiLmU2cIj70X1H8Kcoz0KWfCWmofUwAsAkJroXA8QC';
// $loginId = $_POST['loginId'] ?? '';
auth_session_start_if_needed();
header('Content-Type: application/json; charset=UTF-8');
// $url = "https://api.descope.com/v2/mgmt/user/search";
// $payload = json_encode([
// "loginId" => $loginId,
// "limit" => 1
// ]);
$loginId = trim((string) ($_POST['loginId'] ?? ''));
$sessionUser = $_SESSION['user'] ?? [];
$sessionLoginId = trim((string) ($sessionUser['loginIds'][0] ?? ''));
// $headers = [
// "Authorization: Bearer $management_key",
// "Content-Type: application/json"
// ];
if ($loginId !== '' && $sessionLoginId !== '' && strcasecmp($loginId, $sessionLoginId) === 0) {
echo json_encode(['status' => 'ok', 'user' => auth_normalize_user($sessionUser)]);
exit;
}
// $ch = curl_init($url);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
// curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// $response = curl_exec($ch);
// $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// curl_close($ch);
// if ($http_code === 200) {
// $res = json_decode($response, true);
// if (!empty($res['users'][0])) {
// // ✅ 먼저 $user에 복사
// $user = $res['users'][0];
// // ✅ 값이 없으면 테스트용 기본값 삽입
// $user['name'] = $user['name'] ?: '홍길동';
// $user['phone'] = $user['phone'] ?: '01012345678';
// $user['customAttributes']['company'] = $user['customAttributes']['company'] ?? '한맥ENG';
// $user['customAttributes']['team'] = $user['customAttributes']['team'] ?? '설계팀';
// $user['customAttributes']['employeeId'] = $user['customAttributes']['employeeId'] ?? 'HM2024';
// // ✅ 수정된 $user 사용
// echo json_encode(['status' => 'ok', 'user' => $user]);
// } else {
// echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음']);
// }
// } else {
// echo json_encode(['status' => 'fail', 'message' => 'Descope API 오류', 'code' => $http_code]);
// }
?>
<?php
header('Content-Type: application/json');
$cookieUser = auth_baron_user_from_cookies();
if ($loginId !== '' && $cookieUser !== null) {
$cookieLoginId = trim((string) ($cookieUser['loginIds'][0] ?? ''));
if ($cookieLoginId !== '' && strcasecmp($loginId, $cookieLoginId) === 0) {
echo json_encode(['status' => 'ok', 'user' => $cookieUser]);
exit;
}
}
$management_key = 'P2wON5fy1K6kyia269VpeIzYP8oP:K32l5ORmzy32OvaaPvpdZsMY3JmKQb7a3vvrl10PgjlJUGk3K7EssMH3uW5VGQSbrgtEdPj';
$loginId = $_POST['loginId'] ?? '';
$url = "https://api.descope.com/v2/mgmt/user/search";
$url = 'https://api.descope.com/v2/mgmt/user/search';
$payload = json_encode([
"loginId" => $loginId,
"limit" => 1
'loginId' => $loginId,
'limit' => 1,
]);
$headers = [
"Authorization: Bearer $management_key",
"Content-Type: application/json"
'Content-Type: application/json',
];
$ch = curl_init($url);
@@ -76,18 +48,17 @@ if ($http_code === 200) {
if (!empty($res['users'][0])) {
$user = $res['users'][0];
// ✅ 실제 값이 있으면 그대로 사용, 없으면 빈 문자열
$user['name'] = $user['name'] ?? '';
$user['phone'] = $user['phone'] ?? '';
$user['customAttributes']['company'] = $user['customAttributes']['company'] ?? '';
$user['customAttributes']['familyCompany'] = $user['customAttributes']['familyCompany'] ?? '';
$user['customAttributes']['team'] = $user['customAttributes']['team'] ?? '';
$user['customAttributes']['employeeId'] = $user['customAttributes']['employeeId'] ?? '';
$user['customAttributes']['familyUniqueKey'] = $user['customAttributes']['familyUniqueKey'] ?? '';
echo json_encode(['status' => 'ok', 'user' => $user]);
} else {
echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음']);
exit;
}
} else {
echo json_encode(['status' => 'fail', 'message' => 'Descope API 오류', 'code' => $http_code]);
}
echo json_encode(['status' => 'fail', 'message' => '사용자를 찾을 수 없음', 'code' => $http_code]);
-13
View File
@@ -355,19 +355,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
-13
View File
@@ -198,19 +198,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
-13
View File
@@ -219,19 +219,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
+9 -7
View File
@@ -1,9 +1,3 @@
<script type="text/javascript" src="<?php echo G5_URL ?>/js/jquery-3.6.1.min.js"></script>
<script type="text/javascript" src="<?php echo G5_URL ?>/js/jquery.mousewheel.min.js"></script>
<script type="text/javascript" src="<?php echo G5_URL ?>/js/common.js"></script>
<script type="text/javascript" src="<?php echo G5_URL ?>/js/include.js"></script>
<script type="text/javascript" src="<?php echo G5_URL ?>/js/popup.js?v=20260723"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<!--popup_wrap-->
@@ -57,8 +51,12 @@
<div class="join_btn_wrap">
<button type="submit" id="btn_login">Login</button>
</div>
<div class="join_btn_wrap" style="margin-top:10px;">
<a href="/auth/baron_login?site=eng&amp;return_url=/eng/bbs/descope_qa_list.php" style="display:block; width:100%; text-align:center; padding:14px 16px; border:1px solid #0f3a2f; border-radius:10px; color:#0f3a2f; font-weight:700;">BARON-SSO Login</a>
</div>
<p style="margin-top:10px; font-size:16px; font-weight:700; color:#c62828; line-height:1.6; text-align:center;">eg-bim 1.5.1 버전 이상 사용자는 baron-sso로 로그인해야 합니다.</p>
<div class="btn_go">
<div class="go_signup"><a href="javascript:agreement();"><span>Sign up</span><i class="arrow_r"></i></a></div>
<div class="go_signup"><a href="#" class="signup-disabled-link" aria-disabled="true"><span>Sign up</span><i class="arrow_r"></i></a></div>
<div class="go_find"><a href="javascript:search();"><span>Reset Password</span><i class="arrow_r"></i></a></div>
</div>
</form>
@@ -132,6 +130,10 @@ $(document).ready(function () {
e.preventDefault(); // 기본 제출 막기
$('#btn_login').click(); // 버튼 클릭 이벤트 재사용
});
$('.signup-disabled-link').on('click', function (e) {
e.preventDefault();
});
});
document.addEventListener("DOMContentLoaded", () => {
-13
View File
@@ -151,19 +151,6 @@ include_once __DIR__ . '/asset_urls.php';
</script>
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
+82 -3
View File
@@ -55,7 +55,7 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
<a href="javascript:descopeLogout();">Logout</a>
</li>
<li id="my_join" class="my_join">
<a href="javascript:agreement();">Sign up</a>
<a href="#" class="signup-disabled-link" aria-disabled="true">Sign up</a>
</li>
<li id="my_login" class="my_login">
<a href="javascript:login();">Login</a>
@@ -94,9 +94,84 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
// e.preventDefault(); // 링크 이동 막기
// alert('다운로드 기능은 준비중입니다.');
// });
</script>
<script>
(function () {
function getCookieValue(name) {
const prefix = name + '=';
const parts = document.cookie.split(';');
for (let i = 0; i < parts.length; i += 1) {
const part = parts[i].trim();
if (part.indexOf(prefix) === 0) {
return part.substring(prefix.length);
}
}
return '';
}
function decodeBase64Json(value) {
if (!value) return null;
try {
const binary = window.atob(value);
const bytes = Array.prototype.map.call(binary, function (char) {
return '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2);
}).join('');
return JSON.parse(decodeURIComponent(bytes));
} catch (error) {
return null;
}
}
function setStoredValue(key, value) {
if (typeof value === 'string' && value !== '') {
sessionStorage.setItem(key, value);
}
}
function restoreClientAuthSessionFromCookies() {
const baronUser = decodeBase64Json(getCookieValue('baron_user'));
if (baronUser && Array.isArray(baronUser.loginIds) && baronUser.loginIds[0]) {
const customAttributes = baronUser.customAttributes || {};
const roleNames = Array.isArray(baronUser.roleNames) ? baronUser.roleNames : [];
setStoredValue('authProvider', 'baron');
setStoredValue('loginId', baronUser.loginIds[0] || '');
setStoredValue('descopeUserId', baronUser.userId || '');
setStoredValue('userName', baronUser.name || '');
setStoredValue('phone', baronUser.phone || '');
setStoredValue('company', customAttributes.company || '');
setStoredValue('familyCompany', customAttributes.familyCompany || '');
setStoredValue('team', customAttributes.team || '');
setStoredValue('position', customAttributes.position || '');
setStoredValue('familyUniqueKey', customAttributes.familyUniqueKey || '');
setStoredValue('userRole', roleNames[0] || '');
setStoredValue('sessionJwt', sessionStorage.getItem('sessionJwt') || 'baron.session.marker');
return;
}
const descopeLoginId = getCookieValue('descope_login_id');
if (descopeLoginId && !sessionStorage.getItem('loginId')) {
const customAttributes = decodeBase64Json(getCookieValue('descope_custom_attributes')) || {};
const roleNames = decodeBase64Json(getCookieValue('descope_role_names')) || [];
setStoredValue('authProvider', sessionStorage.getItem('authProvider') || 'descope');
setStoredValue('loginId', descopeLoginId);
setStoredValue('descopeUserId', getCookieValue('descope_user_id'));
setStoredValue('userName', getCookieValue('descope_user_name'));
setStoredValue('phone', getCookieValue('descope_user_phone'));
setStoredValue('company', customAttributes.company || '');
setStoredValue('familyCompany', customAttributes.familyCompany || '');
setStoredValue('team', customAttributes.team || '');
setStoredValue('position', customAttributes.position || '');
setStoredValue('familyUniqueKey', customAttributes.familyUniqueKey || '');
setStoredValue('userRole', Array.isArray(roleNames) ? (roleNames[0] || '') : '');
}
}
restoreClientAuthSessionFromCookies();
})();
(function () {
const ADMIN_ICON_IDS = [
'b24014@hanmaceng.co.kr',
@@ -179,6 +254,10 @@ $download_url = "https://release-download.hmac.kr/eg-bim/{$version}/setup_eg-bim
}, 50);
});
$(document).on('click', '.signup-disabled-link', function (e) {
e.preventDefault();
});
// 혹시 로그인 팝업을 다른 곳에서 열어도 배너가 필요하면, 팝업 열릴 때 한 번 더 시도
$(document).on('show-login-popup', function () {
injectBanner();
-13
View File
@@ -312,19 +312,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
-3
View File
@@ -17,9 +17,6 @@ if (!isset($register_action_url)) {
<link rel="stylesheet" href="../css/style.css" />
<script src="../js/jquery-3.6.1.min.js"></script> -->
<!-- </head> -->
<script src="<?php echo G5_URL ?>/js/common.js"></script>
<script src="<?php echo G5_URL ?>/js/popup.js?v=20260723"></script>
<!-- 회원가입약관 동의 시작 { -->
<!-- <body>
<div class="register"> -->
-13
View File
@@ -256,19 +256,6 @@
</body>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
@@ -556,19 +556,6 @@ foreach ($partials as $p) {
</html>
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
+1 -14
View File
@@ -307,19 +307,6 @@ function getQueryString(name){
if (writer) writer.addEventListener('change', submitForm);
})();
// Footer family site toggle
$(function(){
$('.family_btn').on('click', function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).on('click', function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// Cookie helpers (기존 로직 유지)
function deleteCookie(name){
document.cookie = name+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/eng;";
@@ -437,7 +424,7 @@ function updateHeaderMenu(){
$('#my_join2').html(
isLoggedIn && isAdmin
? "<a href='/eng/adm/index.php' target='_blank'>관리페이지</a>"
: "<a href='/eng/index.php'>Home</a>"
: "<a href='javascript:mypage03();'>My Page</a>"
);
} catch(e){ console.error(e); }
}
+1 -1
View File
@@ -430,7 +430,7 @@ function updateHeaderMenu(){
$('#my_join2').html(
isLoggedIn && isAdmin
? "<a href='/eng/adm/index.php' target='_blank'>관리페이지</a>"
: "<a href='/eng/index.php'>Home</a>"
: "<a href='javascript:mypage03();'>My Page</a>"
);
} catch(e){ console.error(e); }
}
-13
View File
@@ -363,19 +363,6 @@
<!-- 상단(헤더) 스크립트 구문 -->
<script>
$(function(){
// footer family site toggle
$('.family_btn').click(function(e){
e.stopPropagation();
$('.family_list, .family_btn').toggleClass('family_on');
});
$(document).click(function(e){
if(!$(e.target).closest('.family_wrap').length){
$('.family_list, .family_btn').removeClass('family_on');
}
});
});
// 회원 탈퇴
function member_leave() {
if (confirm("회원에서 탈퇴 하시겠습니까?"))
+8
View File
@@ -38,6 +38,14 @@ if [[ -f .env ]]; then
set +a
fi
mkdir -p \
egbim/data/cache \
egbim/data/session \
egbim/data/log \
eng/data/cache \
eng/data/session \
eng/data/log
mkdir -p docker/mysql/init
docker compose up -d --build