AYS 관리자 콘솔 재정비 — 크라우니 통합회원 SSO + 3탭 콘솔
개요
ays.crowny.org(:9920) 관리자 페이지
public/admin.html을 패스코드 단일 인증 + 사례게시 단일화면에서,
크라우니 통합회원(SSO) + AYS그룹 인가 기반 로그인 +
3탭 콘솔(웹페이지 관리 / 공사진행보고·채팅 / 고객 후속관리)로 재구축.
인증 흐름도
[브라우저] admin.html 로그인 폼
│ POST /api/admin/sso-login {username,password}
▼
[서버 9920] → 서버간 POST 9401 /api/login → {ok, token}
│ (프론트로 token 반환, localStorage 저장)
▼
[브라우저] 이후 모든 관리자 API 호출에 Authorization: Bearer <token>
│
▼
[서버 9920] isAdminAuthed(req, body)
├─ isAdmin(req, body) ── 레거시 패스코드(x-admin-pass) 통과? ──▶ 통과(via: passcode)
└─ checkSsoAuth(req)
│ (5분 캐시 히트? → 캐시 결과 즉시 반환)
▼ (미스)
GET 9401 /api/verify (Authorization: Bearer) → 유효? accountId 확보
GET 9401 /api/me/orgs (Authorization: Bearer) → org 목록에 name="AYS" 존재?
▼
그룹인가(토큰유효, 조직소속) 4상 판정 (그룹인가.한선)
토큰무효 → 타(거부)
토큰유효 × 조직소속 → 티(통과)
토큰유효 × 비소속 → 타(거부, reason="AYS 그룹 권한 필요")
▼
결과 5분 캐시(ssoCache), 통과 시 (via: sso)
라우트 스펙 (신규/변경)
| 라우트 | 메서드 | 인증 | 설명 |
|---|
/api/admin/sso-login | POST | 없음(로그인 자체) | body {username,password} → 9401 /api/login 프록시, {ok,token,username,displayName} |
/api/admin/session | GET | Bearer | checkSsoAuth 실행, {ok:true,user} / 401(토큰없음) / 403(비소속) |
/api/admin/auth | POST | 패스코드(레거시) | 변경 없음(폴백 유지) |
/api/admin/case GET /cases POST /case/delete | - | isAdminAuthed(패스코드 OR SSO) | 기존 로직 그대로, 인증 게이트만 교체 |
/api/admin/imagegen /imagegen/list /imagegen/attach | - | isAdminAuthed | 상동 |
/api/admin/followup | POST | isAdminAuthed | body {type,ref_id,memo} → data/후속관리.psv append |
/api/admin/followup | GET | isAdminAuthed | ?ref=<ref_id> → 해당 후속 메모 목록(최신순) |
탭별 기능표
| 탭 | 기능 | 데이터/API |
|---|
| ① 웹페이지 관리 | 공사실적 게시(사업부/제목/위치/면적/기간/설명+사진)·목록·삭제, 이미지생성 도구 링크 | 기존 /api/admin/case* 재사용 |
| ② 공사진행보고·채팅 | job_id 입력 → 진행현황 조회(/work/admin/inbox), 관리자뷰·리포트 딥링크(iframe+새창), 간이 채팅(조회/전송) | /work/*(9929 same-origin 프록시) 직접 fetch — 신규 서버 라우트 불필요 |
| ③ 고객 후속관리 | 상담문의·후기·거래 목록(마스킹 유지) + 항목별 후속 메모 기록/이력조회 | /api/inquiries /api/reviews /api/deals + 신규 /api/admin/followup |
동반 한선씨 파일
/Users/ef/crowny-ays/그룹인가.한선 (+.toau): 토큰유효×조직소속 4상 판정 진리표. hanseonc_high 컴파일 성공, 자가시험 ALL_PASS(4/4).
/Users/ef/crowny-ays/후속관리.한선 (+.toau): 후속 메모 PSV 라인 생성/참조매치/파이프 이스케이프. 컴파일 성공, 자가시험 ALL_PASS(3/3, 문자열.한선 가져오기).
실측 결과 (2026-07-30)
curl 9401/api/health → {"ok":true,...} 정상.
- sso-login(ays/ays1234) → 토큰 발급 200.
/api/admin/session with token(AYS 멤버) → 200.
/api/admin/session no token → 401.
- 임시 테스트계정
ays-console-test(비밀번호는 코드/로그/문서 어디에도 미기록) 등록 → AYS 비소속 → /api/admin/session → 403 {"ok":false,"phase":"타","reason":"AYS 그룹 권한 필요"} (계정은 재검증용으로 그대로 둠).
- 레거시 패스코드 폴백:
x-admin-pass: Ays!7838(운영 실제값, plist EnvironmentVariables) → /api/admin/cases 200. (코드 기본값 ays4466으로는 401 — 운영 plist 값이 실제 적용됨을 재확인.)
- 탭1 종단: case POST(1x1 png) → 201 →
GET /api/admin/cases에 즉시 반영 확인 → POST /api/admin/case/delete → 200 → 재조회 시 사라짐 확인(테스트 사례 검증테스트사례-삭제예정, 삭제 완료).
- 탭3 종단:
POST /api/admin/followup(type=상담, ref_id=test_ref_001, memo에 파이프 포함) → 201 → GET /api/admin/followup?ref=test_ref_001 → 파이프가 ¦로 안전 이스케이프된 상태로 조회됨.
- 헤드리스(크라우니AI검증.sh shot): 로그인 화면 렌더 확인(스크린샷: 스크래치패드
ays-admin/login.png), 로그인 후 3탭 콘솔 렌더 확인(탭1 실데이터 12건 목록 포함, 스크린샷 ays-admin/console2.png, DOM에 탭2/탭3 패널·jobSummary·inquiryList·reviewList·dealList 요소 존재 확인).
- 회귀:
/ 200, /work 200, /api/stats 200, imagegen.html 200, 게이트웨이 https://ays.crowny.org:8443/admin.html 200, https://ays.crowny.org:8443/ 200.
관련 파일
/Users/ef/crowny-ays/server.js — SSO 미들웨어(authRequest/extractToken/groupPhase/checkSsoAuth/isAdminAuthed), followup 로직(followupAppend/followupList), 신규 라우트 3종, 기존 게이트 7곳 전환.
/Users/ef/crowny-ays/public/admin.html — 전면 재작성(3탭 콘솔).
/Users/ef/crowny-ays/public/admin.html.bak-재정비전 — 구버전 백업.
/Users/ef/crowny-ays/그룹인가.한선 .toau — 인가 판정 로직.
/Users/ef/crowny-ays/후속관리.한선 .toau — 후속관리 PSV 로직.
/Users/ef/crowny-ays/data/후속관리.psv — 신규 데이터 파일(테스트 항목 1건 포함, 실사용 데이터와 공존).
/Users/ef/crowny-fonts/디자인적용대상.psv — ays-admin 항목 추가.
/Users/ef/crowny-ays/CLAUDE.md — "관리자 콘솔 재정비" 섹션 추가.
잔여 이슈
- 탭2는 플랫폼(9929)에 "전체 잡 목록" 조회 API가 없어 job_id를 수동 입력하는 최소 구현(기본값 JOB1). 플랫폼 쪽에 목록 API가 추가되면 드롭다운으로 개선 가능.
/work/* 플랫폼 라우트 자체는 role=admin 쿼리파라미터 기반 자체 인가(플랫폼 내부 문제, AYS 콘솔의 SSO 게이트와는 별개 — 플랫폼 쪽 정식 인증 도입은 별도 과제).
ssoCache는 서버 메모리 캐시라 서버 재기동 시 초기화(문제 없음 — 재조회로 자동 채워짐).
- 패스코드 로그인 UI는 유지했으나 향후 SSO 전면 전환 시 제거 검토 가능(현재는 회귀 방지 목적으로 존치).
추가: 잡목록 API + /work 관리자 프록시 게이트 (2026-07-30, 후속)
무엇을 했는지
- GET /admin/jobs?role=admin (플랫폼 정본
/Users/ef/crowny-clean/서버.한선) — job.psv 유니크최신 캐시 기준 잡 요약(id·customer_id·state·building_type·created_at·zones_count·photos_count) 최신순 최대 200건. _관리자인가() 재사용(role != admin → 403). 헬퍼 _행찾기ById·_개수By잡·_잡요약JSON 추가. 다스패치 "/admin/jobs" 라우트 등록.
- 재컴파일(hanseonc_high, 69200 큐브) →
launchctl kickstart org.crowny.clean →
:9624/admin/jobs?role=admin 200, 무role 403.
-
crowny-ays/시공/동기화.sh 실행(판정=티) → 미러 서버.한선/toau 갱신 →
:9929/admin/jobs?role=admin 200 직접 확인(job JOB1 21구역/8사진, 미러 데이터 격리 확인).
/work 관리자 경로 프록시 게이트 (crowny-ays/server.js) — isGatedWorkPath(p, searchParams): 경로가 /work/admin/·/work/view/admin·/work/staff/ 접두이거나 쿼리 role=admin/role=staff면 관리자성 요청으로 판정, await isAdminAuthed(req,null) 통과 못 하면 401(/work,/work/)/403(/work/*) JSON 반환. 고객 경로(/work/home,/work/quotes,/work/chat 등, role 없음)는 무영향.
-
extractToken(req)에 쿼리
?crowny_token= 폴백 추가(헤더/쿠키 다음 순위) — iframe/새창 딥링크는 헤더를 못 붙이므로.
-
isAdmin(req,body)에 쿼리
?crowny_admin_pass= 폴백 추가(패스코드 세션의 딥링크용, 동일 패턴).
- 동반
/Users/ef/crowny-ays/프록시라우팅.한선에
관리자경로판정(경로, role값) 순수 판정 로직 추가 + 셀프테스트 5건 전부 정합(admin/jobs=1, role=admin=1, home=0, quotes=0, view/admin=1).
-
admin.html 탭2: "전체 잡 목록" 카드 신설 —
/work/admin/jobs?role=admin을
api()(Authorization Bearer/x-admin-pass 헤더 자동첨부)로 fetch, 클릭 시
selectJob(id)로 기존 Job ID 입력란 채움(수동 입력은 폴백으로 존속). 관리자 뷰/리포트 딥링크·iframe src에 세션 토큰(
crowny_token) 또는 패스코드(
crowny_admin_pass) 쿼리를 자동 첨부해 프록시 게이트 통과.
관련 파일
/Users/ef/crowny-clean/서버.한선(정본), 서버.toau(재컴파일)
/Users/ef/crowny-ays/시공/서버.한선·서버.toau(동기화 미러, 직접수정 금지)
/Users/ef/crowny-ays/server.js(isGatedWorkPath·extractToken·isAdmin 쿼리폴백·/work 라우트 게이트)
/Users/ef/crowny-ays/프록시라우팅.한선(동반 판정 로직 관리자경로판정, 셀프테스트)
/Users/ef/crowny-ays/public/admin.html(탭2 잡목록 카드+딥링크 토큰첨부)
검증(실측)
- clean :9624 —
/admin/jobs?role=admin 200 JSON, 무role 403. AYS :9929 직접 — 200 JSON(미러 데이터, JOB1 21구역/8사진 — clean과 격리 확인).
- 게이트:
curl :9920/work/admin/jobs?role=admin 무토큰 403. x-admin-pass: <plist값> 헤더 200(잡 목록 반환). 쿼리 ?crowny_admin_pass=<값> 200(/work/view/admin 200 HTML). 쿼리 ?crowny_token=bogus → 403 "유효하지 않은 토큰"(코드 정상 동작, 실 SSO 토큰은 미보유로 200 전량 검증은 패스코드 경로로 대체).
- 고객 경로 무영향:
/work/home 200, /work/quotes 200, /work/chat?job_id=JOB1 200(무토큰).
- 회귀:
/·/work·/landing·/api/stats·/admin.html·/imagegen.html 전부 200(:9920 로컬). 게이트웨이 ays.crowny.org:8443/admin.html 200, clean.crowny.org:8443/ 200.
:9929 직접 접근은 게이트 밖(로컬 전용) — 인가는 :9920(server.js 프록시 계층)에서만 강제됨. 외부 공개는 게이트웨이가 9920만 노출하는 구조라 실사용 경로는 안전하나, 로컬에서 9929로 직접 접속하면 우회 가능함을 문서화(플랫폼 자체 인증은 별개 과제로 잔존).
잔여 이슈
- 플랫폼(:9929) 자체에는 인증이 없다(role 쿼리 기반) — AYS 콘솔 SSO 게이트는 프록시 계층(:9920)에서만 작동. 9929를 외부에 직접 노출하지 않는 한 안전하지만, 근본 해결은 플랫폼 쪽에 자체 토큰 검증을 넣는 것(공용 엔진이라 clean 쪽 변경은 이번 범위에서 보류).
- 실제 AYS 소속 SSO 계정의 유효 비밀번호를 몰라
crowny_token 200 경로는 코드상 정상 동작만 확인(무효 토큰 403 정상)했고 유효 토큰 200 왕복은 패스코드 경로로 대체 검증함.
/admin/jobs는 job.psv 기준 최대 200건 — 200건 초과 시 오래된 잡은 응답에서 잘림(필요 시 페이지네이션 후속).
SSO 토큰 200 경로 종단 실증 (메인 세션, 2026-07-30)
- sso-login(ays)→Bearer
/work/admin/jobs?role=admin 200(JSON 잡목록) · ?crowny_token= 쿼리 폴백 200 · 무토큰 403 — 게이트 3분기 전부 실측 완료. 공개 root/work 200 회귀 확인.
후속: 엔진 레벨 관리자 인증 + 잡목록 페이지네이션 (2026-07-30)
개요
:9929(crowny-clean 서버.한선/AYS 시공 미러) 자체가 무인증이던 잔여 이슈를 엔진 레벨에서 해소.
설정.psv 구동형 서비스 토큰(
관리자토큰, 12번째 필드) 도입 — clean은 빈 값 유지(회귀 0), AYS만 실토큰 배선.
무엇을 했는지
- crowny-clean/서버.한선(SSOT):
_설정로드()에 13번째(idx12) 필드 관리자토큰 파싱. 헤더 리더 _헤더값(요청,이름) + _토큰검증(요청) 신설(토큰 빈 값=항상 통과). _관리자인가()·사진업로드(스태프/팀장)·스태프보고 3지점에 토큰검증 게이트.
/admin/jobs 페이지네이션: offset/limit(기본 0/50, 캡 200) 쿼리 지원, 응답에 total/offset/limit 추가.
- AYS 배선:
crowny-ays/시공/설정.psv 13번째 필드에 openssl rand -hex 24 토큰 추가(값은 미문서화 — plist에만 존재). com.crowny.ays.plist EnvironmentVariables에 AYS_SVC_TOKEN 추가(기존 AYS_ADMIN_PASS 보존).
- server.js:
proxyToPlatform(req,res,overridePath,extraHeaders) 4번째 인자로 헤더 병합 지원. isGatedWorkPath 게이트 통과(인가 성공) 시에만 x-crowny-svc-token: process.env.AYS_SVC_TOKEN 주입해 9929로 프록시.
- admin.html 탭2: "더보기" 버튼 —
jobListOffset/JOB_LIST_LIMIT(50) 누적 fetch, total 도달 시 버튼 숨김.
- 동기화.sh로 clean 정본 → AYS 시공 미러 전파(판정=티, 스모크 통과).
실측표
| 검증 | 결과 |
|---|
clean / | 200 |
clean /admin/jobs?role=admin(무토큰, 12열 빈값) | 200 |
clean /admin/jobs?role=admin&limit=2 응답에 total/offset/limit | 포함 확인 |
| 동기화.sh 판정 | 티(전파+스모크200) |
9929 직접 무토큰 /admin/jobs?role=admin | 403 |
9929 직접 x-crowny-svc-token 헤더 | 200 |
9920 /work/admin/jobs?role=admin 무인증 | 403 |
9920 /work/admin/jobs?role=admin + x-admin-pass(레거시 패스코드) | 200 (svc-token 자동 주입 종단 확인, total/offset/limit 응답 반영) |
9920 /work/home·/work/quotes(고객 경로, 무토큰) | 200 |
프록시라우팅.한선 svc토큰주입인가() 셀프테스트 | 3/3 PASS |
잡목록페이지네이션.한선 더보기인가() 셀프테스트 | 4/4 PASS |
public https://ays.crowny.org:8443/·/work·/admin.html | 200 |
public https://clean.crowny.org:8443/ | 200 |
| admin.html 내 script 구문검사(node --check) | OK |
관련 파일
/Users/ef/crowny-clean/서버.한선 (SSOT — _토큰검증/_헤더값/_관리자토큰/_처리잡목록 페이지네이션)
/Users/ef/crowny-ays/시공/설정.psv (13열, 관리자토큰 배선 — clean 원본은 12열 빈값 유지)
/Users/ef/Library/LaunchAgents/com.crowny.ays.plist (EnvironmentVariables.AYS_SVC_TOKEN 추가)
/Users/ef/crowny-ays/server.js (proxyToPlatform extraHeaders, /work 게이트 2지점 svc-token 주입)
/Users/ef/crowny-ays/프록시라우팅.한선 (svc토큰주입인가() 동반 판정 + 셀프테스트 3건 추가)
/Users/ef/crowny-ays/public/admin.html (탭2 "더보기" 버튼, loadJobList/renderJobList 재작성)
/Users/ef/crowny-ays/잡목록페이지네이션.한선 (더보기 판정 동반, 셀프테스트 4/4)
잔여 이슈
- 토큰 값 자체는 plist(
com.crowny.ays.plist)와 AYS 설정.psv에만 존재 — 문서/보고서에 값 기록하지 않음. 로테이션 시 두 곳(plist+설정.psv) 동시 갱신 + launchctl unload/load -w + 동기화.sh 재실행 필요.
- 스태프 사진업로드/보고 라우트도 토큰검증을 걸었으나, 현재 AYS server.js 프록시 계층이 이 두 라우트에 svc-token을 주입하는 특별 처리는 없음(스태프 앱 경로가 아직
/work/staff/*로 고정 매핑되어 isGatedWorkPath의 /staff/ 판정에 걸려 자동으로 svc-token이 실리므로 실사용상 문제 없음 — 별도 확인 완료했으나 향후 스태프앱 경로 변경 시 재점검 필요).
- clean(원본)
설정.psv는 그대로 12열 이하 유지 — 새 관리자토큰 필드는 AYS처럼 필요한 인스턴스만 13번째로 추가하는 옵트인 방식.