GET/api/auth/callback
index-pm OIDC 콜백. code→token→userinfo → 이메일로 user find/create →
/api/auth/callback — index-pm OIDC 콜백. code→token→userinfo → 이메일로 user find/create →
기존 signToken()/COOKIE_OPTIONS 재사용해 `token` 세션 쿠키 발급 → next 로 복귀.
게이트: 인증되면 로그인(자체 로그인과 동일 세션). entitlement 세분 게이트는 후속.
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/auth/callback \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/callback", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/auth/exchange
패밀리 BFF(sentivex-web 등) 세션 교환. (서버↔서버 전용)
/api/auth/exchange — 패밀리 BFF(sentivex-web 등) 세션 교환. (서버↔서버 전용)
목적(왜): sentivex-web 은 자체 OIDC 세션(svx_session)으로 로그인한다. 그 사용자가 index-pm 의
인시던트/티켓/알림 데이터를 **자기 신원으로** 읽도록, sentivex-web BFF 가 svx_session 을 검증해
얻은 사용자(email+roles)를 이 엔드포인트로 넘기면, index-pm 이 동일 사용자(이메일 find/create)의
pm 세션 토큰을 발급해 돌려준다. BFF 는 그 토큰을 index-pm 호출의 `token` 쿠키로 실어 per-user
접근을 한다. → **OPEN 모드(익명 admin) 의존을 제거**하고 운영에서도 동작(docs/integrations/24·25).
인증: `Authorization: Bearer <BFF_EXCHANGE_SECRET>` — 서버간 공유 시크릿(constant-time 비교).
미설정 시 503(교환 비활성, fail-closed). 사용자 신원의 진위는 호출 BFF 가 svx_session 검증으로
보증하고, 이 엔드포인트는 그 BFF 를 시크릿으로 신뢰한다(브라우저 직접 호출 불가 — 쿠키 게이트
면제 경로이므로 PUBLIC_PATHS 에 등재하되 자체 bearer 로 보호).
보안: 발급 토큰 scopes 는 roles→pm:* 로컬 매핑(role-scopes.ts, IdP 시드 미러)으로 산정한다.
읽기 데이터(GET)는 스코프 게이트가 없어 누구나 통과하고, 쓰기/HITL(process/resume 등)은
가드(requireScope)가 토큰 scopes 로 통제한다(역할에 pm:respond 없으면 거부 = fail-closed).
★테넌트 격리(#203): SOC 테넌트 프로젝트(in-bridge/kumho/hanssem)는 공유 워크스페이스를 써서
멤버십만으론 구분이 안 되고 tenantScope(JWT allowedTenants)가 유일한 per-tenant 경계다. 따라서
BFF 는 svx_session 의 allowed_tenants 를 이 교환에 함께 넘겨야 하며, 그 값으로 pm 토큰이 서버강제된다
(OIDC callback 과 동일). 미전달 시 BFF_EXCHANGE_TENANT_STRICT 로 fail-closed([]) 또는 레거시(전체)+경고.
과거엔 이 클레임을 아예 안 실어 교환 세션이 전 테넌트 인시던트를 읽는 크로스테넌트 유출이 있었다(라이브 실증).
Request body
emailstring<email>required
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/auth/exchange \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","name":"string","roles":"string","allowed_tenants":"string"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/exchange", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"email": "[email protected]",
"name": "string",
"roles": "string",
"allowed_tenants": "string"
})
});
const data = await res.json();
POST/api/auth/login
이메일 + 비밀번호 기반 로그인 엔드포인트.
API 라우트: POST /api/auth/login
이메일 + 비밀번호 기반 로그인 엔드포인트.
인증 성공 시 JWT를 httpOnly 쿠키(token)에 설정한다.
proxy.ts가 이 쿠키를 검증해 모든 보호된 경로를 게이트한다.
보안 장치:
1. IP+이메일 조합으로 15분 내 5회 시도 제한 (Redis 기반 rate limit)
2. 계정 존재 여부를 노출하지 않도록 사용자 없음/비밀번호 불일치 메시지 통일
3. 로그인 성공 시 rate limit 카운터 초기화 (정상 사용자 보호)
바디: { email: string, password: string }
응답: { user: { id, email, fullName } } + Set-Cookie: token=<JWT>
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/auth/login \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":null,"password":"string","redirect":"string"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/login", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"email": null,
"password": "string",
"redirect": "string"
})
});
const data = await res.json();
GET/api/auth/me
현재 로그인 사용자 정보 조회 라우트
GET /api/auth/me — 현재 로그인 사용자 정보 조회 라우트
JWT 쿠키(`token`)로 인증된 사용자의 프로필을 반환한다.
클라이언트가 페이지 로드 시 자신의 세션을 확인하거나, 사용자 정보를
최신화할 때 호출하는 진입점이다.
- 권한: 로그인 필수 (`requireUser` — 미인증 시 401)
- 부작용 없음 (읽기 전용)
- 반환: `{ user: { id, email, fullName, username, avatarUrl } }`
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/auth/me \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/me", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
GET/api/auth/oidc/start
index-pm OIDC 로그인 시작. state·next 쿠키 굽고 IdP authorize 로 리다이렉트.
/api/auth/oidc/start — index-pm OIDC 로그인 시작. state·next 쿠키 굽고 IdP authorize 로 리다이렉트.
Responses
200 성공401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X GET https://sit.in-bridge.com/api/auth/oidc/start \
-H "Authorization: Bearer $TOKEN"
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/oidc/start", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
POST/api/auth/register
신규 사용자 회원가입 라우트
POST /api/auth/register — 신규 사용자 회원가입 라우트
이메일·비밀번호·이름을 받아 사용자를 생성하고,
즉시 JWT를 발급해 httpOnly 쿠키에 세팅함으로써 가입 후 별도 로그인 없이
바로 인증 상태가 되도록 한다.
- 권한: 인증 불필요 (공개 엔드포인트)
- 입력(JSON body): `{ email, password, fullName }`
- 부작용: users 테이블 INSERT + `token` 쿠키 설정
- 반환: `{ user: { id, email, fullName } }` (HTTP 201)
- 실패: 이메일 중복 → 409 ConflictError, 유효성 오류 → 422 ZodError
@author jijuta ([email protected])
@since 2026-05-03
@updated 2026-06-07 — JSDoc 표준(@author/@since/@param 등) 적용
@updated 2026-06-07 — #44 IP/사용자별 rate limit 추가(가입 남용·brute-force·bcrypt DoS 차단)
Responses
200 성공400 입력 검증 실패(zod)401 미인증(JWT 쿠키 필요)403 권한 없음404 없음
cURLcurl -X POST https://sit.in-bridge.com/api/auth/register \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":null,"password":"string","fullName":"string","redirect":"string"}'
JavaScriptconst res = await fetch("https://sit.in-bridge.com/api/auth/register", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"email": null,
"password": "string",
"fullName": "string",
"redirect": "string"
})
});
const data = await res.json();