다크모드
FE handoff — Organization·Workspace 로그인 IA와 접근 상태
목표 로그인 IA (2026-08-22)
- 최상위 컨트롤은 계정 직급이 아니라 조직 / 워크스페이스 로그인 문맥을 고르는 segmented-looking route navigation이다. 서로 다른 URL로 이동하므로
<nav>+RouterLink+aria-current="page"의미를 유지하고,ESegmentedToggleGroup wrapper나 tab semantics는 사용하지 않는다. Leysys 본사 staff용ops-center는 이 내비게이션에 넣지 않는다. - canonical route는
/auth/sign-in/workspace,/auth/sign-in/organization,/auth/sign-in/workspace-select,/auth/sign-in/organization-select다. - 이전 인증 route alias는 제공하지 않는다.
/와 보호 경로의 미인증 기본 목적지, 일반 로그아웃 목적지는/auth/sign-in/organization이다./는 조직 화면을 URL만 바꾸지 않고 그리는 alias가 아니라 canonical route로 정규화한다.- 조직 화면의 기본 폼은
조직 코드 + 아이디 + 패스워드다. Admin/Manager/ Viewer는 이 방식을 사용한다. active Owner용 이메일 인증은 동등한 2차 탭으로 노출하지 않고 조직 코드 label row 우측의이메일로 로그인switch로 같은 route의 자격증명 field를 전환한다. - 인증 방식 switch는 OFF에서 조직 코드를 활성화하고 아이디를 표시한다. ON에서는 조직 코드 field를
로그인 후 조직 선택placeholder로 비활성화하고 아이디 자리를 이메일로 바꾼다.role="switch",aria-checked,aria-label="이메일로 로그인",aria-controls를 제공한다. ON 전환 후 이메일, OFF 전환 후 조직 코드에 focus하고, 각 방식의 입력값·오류·autocomplete section은 분리해 유지한다. - 카드 footer 왼쪽의
계정 찾기는 기존 Owner 이메일 패스워드 재설정으로 이동하고 오른쪽계정 생성은 조직 로그인 두 방식에서 항상 노출한다. username self-service 복구를 새로 제공하지 않으며, 워크스페이스 로그인에는 Organization Owner 계정 생성을 노출하지 않는다. - 기본 진입은 조직 username 폼이지만
/auth/sign-in/organization?method=email,?sign-up=confirmed,?password=changed는 이메일 폼으로 시작한다. 계정 생성과 이메일 패스워드 재설정의 return flow도 이 계약을 사용한다. - 워크스페이스는
워크스페이스 코드 + 아이디 + 패스워드, 조직 username 방식은조직 코드 + 아이디 + 패스워드를 받는다. 코드와 username은 별도 입력이고 화면에서username@code를 직접 받지 않는다. SignInAccountContextNavigation.vue가 두 페이지의 상위 문맥 내비게이션을 소유한다. 별도 CSS·override 없이 EDS segmented visual recipe를 anchor-compatible하게 사용한다.- 가시
계정 유형라벨과 대상 사용자·이메일 대상·비활성 조직 코드 아래 설명·username 패스워드 문의 supportive copy는 반복하지 않는다. 내비게이션의aria-label="계정 유형", 필드 라벨, 오류·성공 안내와 실행 가능한계정 찾기action은 유지한다.
구현 상태와 연결 경계
- ✅ 세 로그인 방식 모두
iam-sign-inEdge Function을 사용한다. 브라우저는 Code와 Username을 구조화해서 보내며 composite 식별자 key를 조립하거나 Auth 이메일을 탐색하지 않는다. - ✅ 조직 이메일은
identifierType=EMAIL, 조직 username은ORGANIZATION_USERNAME, 워크스페이스 username은WORKSPACE_USERNAME으로 전송한다. - ✅
iamAuthRepo.signInIam()이 Edge Function 결과의 access/refresh token으로 Supabase session을 시작한다. - ✅ 복수 조직 Owner 이메일 로그인은 선택 확정 시
bindOrganizationContext()→iam-context를 호출해 현재 Auth session, active Organization Membership,organization.owner를 다시 검증한다. - ✅
workspaceAuthRepo.loadCurrentWorkspacePrincipal()은workforce_identities/workspace_memberships가 아니라 canonical Principal·Session·Affiliation·Workspace Membership·Role Assignment에서 재수화한다. - ❌
platform/superadmin고객 Role이나 form·route·toggle을 만들지 않는다. Leysys 본사 staff는 별도ops-centersurface와 staff 인증을 사용한다. - 자격증명 오류는 조직·Workspace·사용자 존재 여부를 구분하지 않는 동일 메시지로 유지한다.
- Supabase Auth와 RLS는 Prototype 실행 어댑터다. 목표 AWS API 계약은
IAM-ARCHITECTURE-REVIEW-2026-07-25.md이며 브라우저 구조는 그대로 유지한다.
출시 전 전체 메뉴 검수 프로필
prototypeOrganizationAccess.js가 전체 메뉴 검수 조직 slug와PRODUCT_MODULES전체 키를 정의한다.- 인증 후
useAuthPrincipal().principal.organizationSlug === 'leysys'일 때useModuleSubscription().enabledModules는 조직·워크스페이스 범위 모두 전체 상품 키를 반환한다. - 실제
selectedModules, 결제 상태, 워크스페이스 entitlement를 덮어쓰지 않는다. 상품 관리 화면은 계약 정본을 유지하고, 서버 데이터 접근은 기존 RLS/API 권한을 따른다. organizationLandingPath()는 이 검수 조직에 한해 비활성 Prototype 계약이어도/workspace/home으로 보낸다.- 다른 조직의 메뉴·라우트 게이트는 기존 계약 및 워크스페이스 배정 규칙을 그대로 사용한다.
목표 정본 경계
- Principal: 로그인 방법과 분리된 불변 주체
- Organization Affiliation: 조직별 프로필·관리·좌석·수명주기, 접근권 없음
- Organization/Workspace Membership: 실제 접근 관계
- Authentication Assurance: 민감 작업을 제한하지만 권한을 생성하지 않음
- 컨텍스트:
useAuthPrincipal은 서버가 돌려준principalId,signInContext, 최초 워크스페이스 문맥을 보관한다.useAccountContext는 현재 조직/워크스페이스 표시 범위를 담당하며 Active Context 자체는 권한 근거가 아니다. - 목록·역할 Prototype:
useWorkforceDirectory - 감사 Prototype:
useAccountAuditTrail
Role 표시·입력 계약
| Scope | 기본 Role UI |
|---|---|
| Organization | 소유자 / 최고관리자 / 중간관리자 / 조회자 |
| Workspace | 소유자 / 최고관리자 / 중간관리자 / 조회자 |
- 저장·API key는
organization.owner | organization.admin | organization.manager | organization.viewer와workspace.owner | workspace.admin | workspace.manager | workspace.viewerdotted namespace만 사용한다. - Role select는 두 scope 모두
owner > admin > manager > viewer순으로 보이고 별도operator | auditor | contract_admin | accountant를 노출하지 않는다. - 소유자/최고관리자/중간관리자/조회자 경계와 Organization→Workspace 같은 등급 투영을 설명한다. 직접 Workspace Role 선택에는 Organization 투영 Role보다 엄격히 높은 등급만 표시한다. 같거나 낮은 중복 Role은 저장하지 않으며 해당 Workspace에서만 상승된 효과 Role을 표시한다.
- 회계·급여·구매처럼 더 좁은 편집·승인 능력은 기본 Role select에 새 직무 Role을 늘리지 않고 도메인 정책으로 제한한다. 초기 UI에는 사용자별 Functional Permission 편집 표면을 만들지 않는다.
organization-member/workspace-member는 로그인 문맥 표시값이며 Role label이나 역할 선택지로 노출하지 않는다.
복수 조직 선택 흐름
OrganizationSignInPag.vue는 인증 Edge Function이 active Organization Membership과organization.owner를 확인해 반환한 현재 사용자가 active Owner인 Organization option만 현재 Auth user에 묶인sessionStorageSignIn envelope에 보존한다. 아직 Organization context가 바인딩되지 않은 상태에서 일반 Data API로 Organization 목록을 다시 읽거나 전체 tenant RLS를 완화하지 않는다.- 0개면 업무 진입을 막고, 1개면 서버가 해당 Organization을 현재 session에 원자적으로 바인딩한 뒤
loadOrganizationAccount(organizationId, client, authenticatedUser.id)로 진입한다. - 2개 이상이면
/auth/sign-in/organization-select로 이동한다.OrganizationSelectPag.vue는 조직 용어, 조직명·사업자등록번호 검색과 공식radio-card선택 패턴을 사용한다. - 선택 확정 시 먼저
bindOrganizationContext(organizationId, client)가 active Membership과organization.owner를 검증하고 session context를 기록한다. 이후applyOrganizationAccountSession()이useAuthPrincipal,useAccountContext,useModuleSubscription을 같은 조직 데이터로 교체한다. - Organization context는 한 Auth session에서 한 번만 바인딩하고 다른 Organization으로 재바인딩하지 않는다.
AccountScopeSwitcherMol의 다른 조직 계정으로 로그인은 현재 session을 종료한 뒤 Organization 로그인 화면으로 돌아간다. 선택 화면을 같은 bound session에서 다시 열어 cross-tenant 전환하는 흐름은 금지한다. - 선택 화면은 인증 세션이 필요한 보호 라우트다. 표시 목록은 로그인 Edge 응답에서 검증된 option만 사용하며, 선택 확정 시
bindOrganizationContext()가 active Membership,organization.owner, identifier type, session의 authorized Organization IDs를 다시 검증한다. organizationAccountRepo의 선택 단건 조회는.eq('auth_user_id', authUserId), active status, SignIn envelope의 허용 Organization IDs를 함께 적용한다. 호출부가 이미auth.getUser()결과를 가지고 있으면 그 검증된 ID를 전달하고, 직접 호출 경로만 repository 내부에서auth.getUser()로 보완한다.- redirect는 내부 절대 경로만 허용하며
//로 시작하는 값은 거부한다.
SignIn 상태머신
resolveAccountAccess(principal) 결과는 ready | choose | no-workspaces | invited | suspended다. 로딩과 통신 오류는 loading | error로 별도 표시한다. 권한 없음이나 오류에서 임의 Workspace로 fallback하지 않는다.
워크스페이스가 1개면 즉시 진입하고 여러 개면 /auth/sign-in/workspace-select로 보낸다. 조직이 1개면 즉시 진입하고 여러 개면 /auth/sign-in/organization-select로 보낸다. redirect는 내부 절대 경로만 허용한다.
워크스페이스 전환 UX
AccountScopeSwitcherMol은 워크스페이스 범위를 고르는 확정형 dialog다. 검색어는 이름·약칭· 코드·주소를 포함한다. 선택만으로 컨텍스트를 변경하지 않고 전환 버튼에서 커밋한다. organization:{id} 가상 행은 표시하지 않으며 useAccountContext.selectScope()도 이를 거부한다. 저장 문맥이 현재 서버의 Workspace 목록과 일치하지 않으면 번호순 첫 Workspace, 일반적인 직접 생성 계정의 Workspace 0으로 복구한다. 다른 Organization으로 이동할 때는 bound session을 재사용하지 않고 다른 조직 계정으로 로그인에서 sign-out한 뒤 새 Organization 로그인 session을 시작한다. 모바일에서는 아이콘 버튼, dialog 내부는 스크롤 목록을 사용한다.
Organization 설정·구성원·Workspace 권한 진입은 workspace/AppAsideOrg.vue와 관리비 LineAsideOrg.vue의 mt-auto 하단 조직관리가 담당한다. 목적지는 /administration/organization/account/general이며 organization.owner 또는 organization.admin을 가진 Organization 문맥 사용자에게만 보인다. organization.manager와 organization.viewer, Workspace 문맥 사용자에게는 숨긴다. 계약·결제 action은 그중 organization.owner에게만 노출한다. 이 표시는 FE discovery gate일 뿐 서버 권한을 새로 만들지 않는다. 개인 설정은 일반 업무 메뉴의 내 계정으로 분리한다.
워크스페이스 0·순번 표시 계약 (2026-07-16)
organizationAccountRepo.loadOrganizationAccount(organizationId)는workspaces.workspace_number를 조회하고.order('workspace_number')로 정렬해workspaceNumber로 매핑한다.useAccountContext는 canonicalworkspaceNumber를 오름차순으로 노출한다. 번호가 없거나 중복된 저장값을 이전 schema로 추론하지 않는다.- 계정 생성 완료 뒤 워크스페이스 0이 첫 활성 범위다. 범위 라벨, 로그인 워크스페이스 선택, 전환 dialog, 조직 워크스페이스 목록·상세에
워크스페이스 N을 일관되게 표시한다. SheetCreateWorkspaceArt의 다음 번호는 안내용이다. 실제 번호는create_organization_workspace_with_property가 잠금 안에서 발급하며 응답의workspaceNumber를 정본으로addWorkspaceRecord()에 넣는다.- 워크스페이스 생성 RPC에는 현재 선택된
account.organization.value.id를p_organization_id로 반드시 보낸다. 복수 조직 계정에서 첫 조직 fallback을 두지 않는다. - 긴 순번 정책 설명은 생성 시트의
InfoHint에 두고 화면에는워크스페이스 N으로 생성됩니다만 유지한다. - 워크스페이스 번호는 편집 폼 필드가 아니다. 워크스페이스 0에는 삭제 액션을 노출하지 않는다.
- 워크스페이스 등록코드(
workspace.code)도 생성 뒤 불변이다. 편집 화면을 추가하더라도 이름·주소 등 가변 속성과 분리하고 등록코드 입력을 다시 활성화하지 않는다. DB trigger가 직접 변경도 거부한다.
운영 전환 시:
- 서버 검색·커서 페이지네이션 또는 가상 스크롤
- 선택 시 현재 페이지가 새 워크스페이스에서도 유효한지 검사
- 미저장 draft가 있으면 이탈 확인
- 캐시·API query key에 조직/워크스페이스 scope 포함
- 전환 성공 후 메뉴·권한·AI 컨텍스트 동시 갱신
공통 통합 상태
- ✅
authSession.js는 보호 라우트 첫 진입·access token 변경 시getUser()로 사용자를 검증하고hydrateAuthenticatedAccount()로 서버 조직/워크스페이스/상품 범위를 재수화한다. 같은 token의 후속 이동만 검증 캐시를 재사용한다. - ✅
workspaceAuthRepo.loadCurrentWorkspacePrincipal()은 현재 Principal의 유효한 Workspace session, Affiliation, Workspace Membership, Role Assignment, module activation adapter를 다시 조립한다. 조직 사용자와 워크스페이스 사용자 모두 오래된 localStorage principal을 라우트 권한의 정본으로 사용하지 않는다. - ✅ 복수 조직/워크스페이스 선택 상태는 각각 선택 화면에서 확정하기 전 업무 경로로 진행하지 않는다. 선택 완료 시
markAuthenticatedContextReady()가 현재 token의 재수화 상태를 완료로 전환한다. - ✅
pageHelpCatalog와assistantFeatureCatalog에는 조직 계정·워크스페이스 전환 도움말 목적지가 연결돼 있다. - i18n locale에는 본 화면의 한국어 문자열을 이전해야 한다.
회귀 테스트:
src/composables/__tests__/authSession.spec.jssrc/composables/__tests__/accountSessionHydration.spec.jssrc/composables/__tests__/workspaceAuthRepo.spec.jssrc/composables/__tests__/organizationAuthRepo.spec.jssrc/composables/__tests__/iamAuthRepo.spec.jssrc/composables/__tests__/iamArchitectureMigration.spec.jssrc/composables/__tests__/organizationAccountRepo.spec.jssrc/components/auth/organization-select/OrganizationSelectPag.spec.js
셸별 워크스페이스 화면 (2026-07-13)
- 조직관리:
/administration/organization/workspace/general→ administration의AppLayoutOrg와AppAsideOrg를 유지한다. - 조직 콘솔:
/headquarters/console/workspace/general→ headquarters 셸을 유지한다. - administration 뷰는
src/views/administration/organization/workspace/general/IndexView.vue이며, 워크스페이스 본문은 headquarters의MainOrg를 재사용하고 헤더만 계정·계약 문맥으로 분리한다. AppAsideOrg.vue와 조직 기본정보의 워크스페이스 링크는 administration 경로만 사용한다.src/router/__tests__/globalIaIntegrity.spec.js에서 administration matched chain을 회귀 검사한다.