b782bb298f
충돌 해결 5개 파일: - .gitignore: .envrc/.direnv (hjjeong direnv 셋업) + .omc/ (gbpark) 양쪽 보존 - docs/MULTI_TENANCY_ARCHITECTURE.md: *.localhost dev 분기 + *.invyone.com/solution.invyone.com 통합 - frontend/lib/api/client.ts: 1-b *.localhost:8081 dev + 1-c DEV_TENANT_HOST(nip.io):8083 + invyone.com 신 도메인 - frontend/lib/tenant/subdomain.ts: IPv4 차단 + *.invyone.com + DEV_TENANT_HOST + *.localhost 모두 처리 - frontend/app/(auth)/login/page.tsx: B안 채택 — buttons 항상 렌더, className 만 mounted 가드 (next-themes 표준 패턴) 검증: - backend: ./gradlew compileJava 성공 (Java 21) - frontend: 머지된 4개 파일 관련 타입 에러 0개 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
/**
|
|
* 테넌트 서브도메인 헬퍼.
|
|
* 메인 사이트(solution, www, admin 등 예약어) 와 베이스 도메인은 null 을 리턴해서
|
|
* TenantGuard 가 체크를 스킵하게 한다.
|
|
*
|
|
* 백엔드 SubdomainResolverFilter.extractSubdomain() 와 동일한 규칙을 따라야 함:
|
|
* - bare localhost / IP / 베이스 도메인 → null (메타)
|
|
* - {sub}.localhost (dev) → 첫 파트 (예약어 제외)
|
|
* - {sub}.invyone.com (운영) → 첫 파트 (예약어 제외)
|
|
*
|
|
* 백엔드 provisioning 의 RESERVED_SUBDOMAINS 와 같은 값을 유지할 것.
|
|
*/
|
|
const RESERVED_MAIN = new Set([
|
|
"solution",
|
|
"www",
|
|
"admin",
|
|
"api",
|
|
"app",
|
|
"static",
|
|
"assets",
|
|
"main",
|
|
"mail",
|
|
"blog",
|
|
"test",
|
|
"staging",
|
|
"prod",
|
|
"console",
|
|
]);
|
|
|
|
const IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
|
|
// 개발 환경 가짜 서브도메인 패턴: <prefix>.<IPv4>(.nip.io | .sslip.io)?
|
|
// 사무실 도커처럼 운영 와일드카드 DNS 가 없는 환경에서, hosts 매핑 또는 nip.io 외부 DNS 로 prefix 를 표현.
|
|
// backend SubdomainResolverFilter 도 동일 의도로 호스트 첫 라벨을 prefix 로 추출.
|
|
const DEV_TENANT_HOST = /^([a-z0-9-]+)\.\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\.(?:nip|sslip)\.io)?$/;
|
|
|
|
export function extractTenantSubdomain(host: string): string | null {
|
|
if (!host) return null;
|
|
|
|
const cleanHost = host.split(":")[0].toLowerCase();
|
|
if (!cleanHost) return null;
|
|
|
|
if (cleanHost === "localhost") return null;
|
|
if (IPV4.test(cleanHost)) return null;
|
|
|
|
// 운영 *.invyone.com — 베이스 도메인은 null
|
|
if (cleanHost.endsWith(".invyone.com")) {
|
|
const prefix = cleanHost.substring(0, cleanHost.length - ".invyone.com".length);
|
|
if (!prefix) return null;
|
|
if (RESERVED_MAIN.has(prefix)) return null;
|
|
return prefix;
|
|
}
|
|
|
|
// dev 가짜 서브도메인 (<prefix>.<IPv4>(.nip.io|.sslip.io)?)
|
|
const devMatch = cleanHost.match(DEV_TENANT_HOST);
|
|
if (devMatch) {
|
|
const prefix = devMatch[1];
|
|
if (RESERVED_MAIN.has(prefix)) return null;
|
|
return prefix;
|
|
}
|
|
|
|
// dev *.localhost (RFC 6761) — bare "localhost" 는 위에서 제외됨
|
|
const parts = cleanHost.split(".");
|
|
if (parts.length === 2 && parts[1] === "localhost") {
|
|
const first = parts[0];
|
|
if (!first) return null;
|
|
if (RESERVED_MAIN.has(first)) return null;
|
|
return first;
|
|
}
|
|
|
|
return null;
|
|
}
|