본문으로 건너뛰기

테넌트 온보딩

owner 회원가입은 성공했지만 사용자가 /onboarding에서 벗어나지 못하거나, 온보딩 마법사가 테넌트 주소를 거부하거나, 마지막 완료 단계가 403, 404, 409, 또는 custom-domain 해석 실패를 반환할 때 이 페이지를 사용하세요.

이 페이지는 현재 owner 온보딩 흐름을 위한 troubleshooting 런북입니다. 과거의 스키마 프로비저닝 모델을 설명하는 문서가 아닙니다.

소스 표면

영역소스 경로
Owner 등록 엔드포인트lumie-backend/modules/auth/src/main/java/com/lumie/auth/adapter/in/web/AuthController.java
Owner 등록 서비스lumie-backend/modules/auth/src/main/java/com/lumie/auth/application/service/AuthRegistrationService.java
테넌트 자동 등록lumie-backend/modules/tenant/src/main/java/com/lumie/tenant/application/service/TenantRegistrationService.java
테넌트 온보딩 명령lumie-backend/modules/tenant/src/main/java/com/lumie/tenant/application/service/TenantCommandService.java
테넌트 공개 조회lumie-backend/modules/tenant/src/main/java/com/lumie/tenant/application/service/TenantQueryService.java
테넌트 엔티티 규칙lumie-backend/modules/tenant/src/main/java/com/lumie/tenant/domain/entity/Tenant.java
프론트엔드 온보딩 게이트lumie-frontend/src/entities/session/model/onboarding.ts
프론트엔드 온보딩 레이아웃lumie-frontend/app/(onboarding)/layout.tsx
온보딩 마법사lumie-frontend/src/features/onboarding/ui/OnboardingWizard.tsx
Custom ID 단계lumie-frontend/src/features/onboarding/ui/steps/CustomIdStep.tsx
테넌트 API query wrapperlumie-frontend/src/entities/tenant/api/queries.ts
테넌트 리다이렉트 로직lumie-frontend/src/shared/lib/tenantRedirect.ts
프론트엔드 /api 프록시와 custom-domain 해석lumie-frontend/app/api/[...path]/route.ts

증상

증상가능성 높은 원인
로그인 후 owner가 항상 /onboarding으로 이동세션의 tenantOnboardingCompletedAt가 아직 null이거나 완료 호출이 성공하지 못함
/onboarding이 즉시 /admin으로 리다이렉트온보딩이 이미 완료되어 레이아웃 게이트가 설계대로 동작함
Custom ID가 비어 보이는데 마법사가 사용할 수 없다고 표시공개 조회가 200을 반환했거나, 값이 예약어이거나, 패턴이 잘못되었거나, 가용성 요청이 실패해 UI가 사용할 수 없다고 처리함
최종 제출이 403 반환요청 경로 slug와 인증된 테넌트 컨텍스트가 일치하지 않아 TENANT_019가 발생
최종 제출이 409 반환온보딩이 이미 완료되었거나(TENANT_018) 요청한 customId가 이미 사용 중
custom-domain 로그인 또는 온보딩 제출이 프론트엔드 프록시에서 503 반환프론트엔드가 인증 쓰기 요청을 전달하기 전에 해당 host의 테넌트를 해석하지 못함

식별자 맵

온보딩 흐름은 역할이 다른 세 가지 테넌트 식별자를 사용합니다.

식별자예시소유자목적
tenant sluginst-a1b2c3d4backend인증된 테넌트 컨텍스트와 내부 라우팅
custom IDa1b2c3d4 또는 myacademybackend와 frontend공개 lumie-edu.com/{customId} 경로와 온보딩 주소 선택
custom domainacademy.example.comfrontend와 backend선택적인 host 기반 공개 진입점

Tenant.createWithAutoSlug(...)는 owner 테넌트를 inst-<uuid8> slug와, 그 slug에서 파생한 기본 customId로 생성합니다. 사용자가 처음 설정하는 공개 주소는 온보딩 중에 적용할 수도 있고, 테넌트가 여전히 그 기본 slug 파생 값을 쓰는 동안에는 일반 테넌트 갱신 경로를 통해 나중에 적용할 수도 있습니다.

현재 런타임 계약

1. Owner 회원가입은 즉시 활성 테넌트를 만듭니다

AuthRegistrationService.registerOwner(...)TenantService.createTenant(...) 를 호출하고, 이는 다시 TenantRegistrationService.registerTenant(...)로 위임됩니다.

이 서비스는 다음을 수행합니다.

  • tenant row를 삽입
  • 상태를 PENDING -> PROVISIONING -> ACTIVE로 전환
  • 커밋 후 TenantCreatedEvent 발행

현재 흐름에는 런타임 Flyway나 테넌트별 스키마 프로비저닝이 없습니다. owner 회원가입이 성공했다면 온보딩 마법사가 열리기 전에 이미 활성 테넌트가 있어야 합니다.

2. 프론트엔드 게이트는 역할과 타임스탬프 기반입니다

needsTenantOnboarding(user)는 다음과 같습니다.

return user.role === 'OWNER' && user.tenantOnboardingCompletedAt === null;

즉,

  • OWNER 사용자만 /onboarding으로 보냅니다
  • STUDENT 사용자는 /dashboard로 리다이렉트합니다
  • 온보딩 타임스탬프가 null이 아닌 비학생 사용자는 /admin으로 리다이렉트합니다

3. 완료 엔드포인트는 owner 전용이며 테넌트에 묶여 있습니다

마법사는 POST /v1/tenants/{slug}/complete-onboarding으로 제출합니다.

TenantCommandService.completeOnboarding(...)가 요구하는 조건:

  • 인증된 OWNER
  • TenantContextHolder.getTenant(){slug} 경로와 일치
  • 선택적 customId가 형식과 예약어 규칙을 통과
  • 요청한 customId가 아직 사용 중이 아님
  • onboardingCompletedAt가 아직 null

성공하면 전화번호, 최초 1회 설정하는 선택적 customId, 숨긴 sidebar 항목, onboardingCompletedAt를 갱신합니다.

4. 공개 custom ID 가용성은 404가 곧 사용 가능을 의미합니다

프론트엔드 가용성 검사는 GET /api/v1/tenants/public/by-custom-id/{customId}를 호출합니다.

UI 의미는 다음과 같습니다.

  • 200 OK: 활성 테넌트가 이미 그 customId를 소유하므로 사용할 수 없음
  • 404 Not Found: 활성 테넌트가 소유하지 않으므로 사용할 수 있음
  • 네트워크 또는 프록시 실패: 사용할 수 없음으로 처리

5. Custom-domain 인증 쓰기는 안전 실패합니다

/api/v1/login, /api/v1/register에 대해 app/api/[...path]/route.ts는 요청이 custom domain에서 오면 host를 기준으로 테넌트를 해석합니다. 해석에 실패하면 프록시는 tenantless 쓰기 요청을 상류로 전달하지 않고 503을 반환합니다.

진단 경로

  1. 실패가 owner 등록, 로그인 리다이렉트, custom ID 가용성, 완료 제출, 완료 후 리다이렉트 중 어디에서 발생하는지 먼저 구분합니다.
  2. 브라우저 네트워크 패널의 GET /api/v1/me 응답에서 인증된 사용자 payload를 확인합니다. role, tenantSlug, tenantCustomId, tenantCustomDomain, tenantOnboardingCompletedAt를 검증하세요.
  3. Owner가 /onboarding에 갇혀 있으면 tenantOnboardingCompletedAt가 여전히 null인지 확인합니다. null이 아니라면 문제는 미완료 온보딩이 아니라 오래된 클라이언트 상태 또는 리다이렉트 경로입니다.
  4. Custom ID 검사가 실패하면 정확한 규칙을 확인합니다. 소문자, 숫자, 하이픈만 허용되며, 길이는 3~30자이고, admin, api, auth, dashboard, pricing, _next 같은 최상위 예약 경로는 사용할 수 없습니다.
  5. 완료 제출이 403이면 요청 경로 slug와 인증된 tenant slug를 비교하세요. TenantCommandService는 불일치를 TENANT_019로 거부합니다.
  6. 완료 제출이 409이면 이미 완료된 온보딩(TENANT_018)인지, 중복 customId 충돌인지 구분합니다.
  7. custom domain이 관련되면 먼저 프론트엔드 프록시 응답을 확인합니다. /api/v1/login 또는 /api/v1/register503은 대개 백엔드가 요청을 보기 전에 테넌트 해석이 실패했다는 뜻입니다.

해결

  • 온보딩이 이미 완료되었다면 세션 상태를 새로고침하세요. 프론트엔드 마법사는 이미 TENANT_018을 새로고침 후 성공으로 처리하고 /admin으로 이동합니다.
  • 선택한 customId가 잘못되었거나 예약되어 있다면 새 소문자 공개용 값을 선택하세요. 사용자가 처음 설정한 값은 저장 후 변경할 수 없습니다.
  • Owner가 custom ID 단계를 건너뛰었다면 백엔드는 slug에서 파생한 기본 customId를 유지한 채 온보딩을 완료합니다. 테넌트가 아직 그 기본값을 쓰는 동안에는 사용자가 처음 설정하는 공개 주소를 나중에 적용할 수도 있습니다.
  • 제출이 TENANT_019로 실패하면, 재시도 전에 올바른 테넌트를 해석하는 로그인 경로로 다시 들어오세요. 루트 로그인은 public.owner_directory를 통해 owner를 처리할 수 있고, custom-domain 또는 path 기반 로그인은 인증 세션과 같은 테넌트를 해석해야 합니다.
  • custom-domain 로그인에서 503이 나오면 GET /api/v1/tenants/public/by-domain?host=<host>로 host가 해석되는지, 프론트엔드 환경이 여전히 custom-domain 해석을 켜고 있는지 확인하세요.

검증

먼저 읽기 전용 검사를 사용하세요.

cd /Users/bluemayne/Projects/Lumie
rg -n "needsTenantOnboarding|completeOnboarding|TENANT_018|TENANT_019|getPublicTenantByCustomId|buildTenantLoginUrl" \
lumie-frontend lumie-backend
curl -i "https://lumie-edu.com/api/v1/tenants/public/by-custom-id/<candidate-custom-id>"
curl -i "https://lumie-edu.com/api/v1/tenants/public/by-domain?host=<candidate-host>"

성공 신호:

  • GET /api/v1/meOWNER와 기대한 tenant slug를 보여줍니다
  • 완료된 테넌트는 tenantOnboardingCompletedAt가 null이 아닙니다
  • 공개 by-custom-id 조회의 404는 그 custom ID를 사용할 수 있음을 뜻합니다
  • custom-domain 해석이 프록시 503이 아니라 실제 테넌트를 반환합니다

예방

  • tenant slug와 공개 custom ID를 서로 다른 식별자로 취급하세요. 온보딩 페이지는 내부 slug가 아니라 공개 식별자를 바꿉니다.
  • 프론트엔드 예약 custom-ID 목록과 백엔드 예약 custom-ID 목록을 동기화 상태로 유지하세요.
  • custom-domain 인증 쓰기에서 프론트엔드 프록시를 우회하지 마세요. host에서 테넌트 컨텍스트를 주입하는 곳이 바로 이 프록시입니다.
  • 잘못된 온보딩 루프를 조사할 때는 백엔드 테넌트 상태를 바꾸기 전에 세션 최신성을 먼저 확인하세요.