1
0
forked from baron/baron-sso

dev 병합 code-check 오류 수정

This commit is contained in:
2026-04-28 13:23:40 +09:00
parent 6be0914b65
commit eae3e0bd2a
13 changed files with 349 additions and 67 deletions

View File

@@ -181,13 +181,8 @@ PLAYWRIGHT_CHROMIUM_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/chromium-1208/INSTAL
PLAYWRIGHT_FIREFOX_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/firefox-1509/INSTALLATION_COMPLETE PLAYWRIGHT_FIREFOX_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/firefox-1509/INSTALLATION_COMPLETE
PLAYWRIGHT_WEBKIT_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/webkit-2248/INSTALLATION_COMPLETE PLAYWRIGHT_WEBKIT_COMPLETE := $(PLAYWRIGHT_BROWSERS_PATH)/webkit-2248/INSTALLATION_COMPLETE
ifeq ($(CI),)
PLAYWRIGHT_INSTALL_ALL := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_FIREFOX_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_WEBKIT_COMPLETE)" ]; then echo "Playwright browsers already installed"; else npx playwright install; fi' PLAYWRIGHT_INSTALL_ALL := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_FIREFOX_COMPLETE)" ] && [ -f "$(PLAYWRIGHT_WEBKIT_COMPLETE)" ]; then echo "Playwright browsers already installed"; else npx playwright install; fi'
PLAYWRIGHT_INSTALL_CHROMIUM := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ]; then echo "Playwright chromium already installed"; else npx playwright install chromium; fi' PLAYWRIGHT_INSTALL_CHROMIUM := sh -c 'if [ -f "$(PLAYWRIGHT_CHROMIUM_COMPLETE)" ]; then echo "Playwright chromium already installed"; else npx playwright install chromium; fi'
else
PLAYWRIGHT_INSTALL_ALL := npx playwright install --with-deps
PLAYWRIGHT_INSTALL_CHROMIUM := npx playwright install --with-deps chromium
endif
.PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-i18n-values code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests .PHONY: code-check code-check-lint code-check-test-jobs code-check-i18n code-check-i18n-values code-check-go-lint code-check-sync-userfront-locales code-check-userfront-install code-check-userfront-lint code-check-front-lint code-check-backend-tests code-check-userfront-tests code-check-adminfront-tests code-check-devfront-tests code-check-userfront-e2e-tests
@@ -254,12 +249,12 @@ code-check-userfront-lint:
code-check-front-lint: code-check-front-lint:
@echo "==> adminfront biome lint/format check" @echo "==> adminfront biome lint/format check"
rm -rf adminfront/playwright-report adminfront/test-results rm -rf adminfront/playwright-report adminfront/test-results
cd adminfront && npm ci cd adminfront && npm ci --ignore-scripts
cd adminfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false cd adminfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false
cd adminfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false cd adminfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false
@echo "==> devfront biome lint/format check" @echo "==> devfront biome lint/format check"
rm -rf devfront/playwright-report devfront/test-results rm -rf devfront/playwright-report devfront/test-results
cd devfront && npm ci cd devfront && npm ci --ignore-scripts
cd devfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false cd devfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false
cd devfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false cd devfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false
@@ -298,7 +293,7 @@ code-check-devfront-tests:
@mkdir -p reports/devfront @mkdir -p reports/devfront
@rm -rf reports/devfront/playwright-report reports/devfront/test-results @rm -rf reports/devfront/playwright-report reports/devfront/test-results
@status=0; \ @status=0; \
(cd devfront && npm ci) || status=$$?; \ (cd devfront && npm ci --ignore-scripts) || status=$$?; \
if [ $$status -eq 0 ]; then \ if [ $$status -eq 0 ]; then \
(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \ (cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
fi; \ fi; \

View File

@@ -1,8 +1,10 @@
package handler package handler
import ( import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/service" "baron-sso-backend/internal/service"
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -13,6 +15,121 @@ import (
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
// --- Mocks ---
type MockKratosAdminServiceForConsent struct {
mock.Mock
}
func (m *MockKratosAdminServiceForConsent) FindIdentityIDByIdentifier(ctx context.Context, identifier string) (string, error) {
args := m.Called(ctx, identifier)
return args.String(0), args.Error(1)
}
func (m *MockKratosAdminServiceForConsent) GetIdentity(ctx context.Context, id string) (*service.KratosIdentity, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*service.KratosIdentity), args.Error(1)
}
func (m *MockKratosAdminServiceForConsent) ListIdentities(ctx context.Context) ([]service.KratosIdentity, error) {
return nil, nil
}
func (m *MockKratosAdminServiceForConsent) UpdateIdentity(ctx context.Context, identityID string, traits map[string]interface{}, state string) (*service.KratosIdentity, error) {
return nil, nil
}
func (m *MockKratosAdminServiceForConsent) CreateIdentity(ctx context.Context, traits map[string]interface{}) (*service.KratosIdentity, error) {
return nil, nil
}
func (m *MockKratosAdminServiceForConsent) DeleteIdentity(ctx context.Context, identityID string) error {
return nil
}
func (m *MockKratosAdminServiceForConsent) UpdateIdentityPassword(ctx context.Context, identityID, newPassword string) error {
return nil
}
func (m *MockKratosAdminServiceForConsent) ListIdentitySessions(ctx context.Context, identityID string) ([]service.KratosSession, error) {
return nil, nil
}
func (m *MockKratosAdminServiceForConsent) GetSession(ctx context.Context, sessionID string) (*service.KratosSession, error) {
return nil, nil
}
func (m *MockKratosAdminServiceForConsent) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}
func (m *MockKratosAdminServiceForConsent) CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error) {
return "", nil
}
type MockTenantServiceForConsent struct {
mock.Mock
}
func (m *MockTenantServiceForConsent) GetTenant(ctx context.Context, id string) (*domain.Tenant, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Tenant), args.Error(1)
}
func (m *MockTenantServiceForConsent) GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForConsent) GetTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForConsent) ListTenants(ctx context.Context, limit, offset int, parentID string) ([]domain.Tenant, int64, error) {
return nil, 0, nil
}
func (m *MockTenantServiceForConsent) RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string, creatorID string) (*domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForConsent) RequestRegistration(ctx context.Context, name, slug, description string, domainName string, adminEmail string) (*domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForConsent) ApproveTenant(ctx context.Context, id string) error {
return nil
}
func (m *MockTenantServiceForConsent) ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
args := m.Called(ctx, userID)
return args.Get(0).([]domain.Tenant), args.Error(1)
}
func (m *MockTenantServiceForConsent) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
args := m.Called(ctx, userID)
return args.Get(0).([]domain.Tenant), args.Error(1)
}
func (m *MockTenantServiceForConsent) IsDomainAllowed(ctx context.Context, domainName string) (bool, error) {
return false, nil
}
func (m *MockTenantServiceForConsent) ProvisionTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForConsent) SetKetoService(keto service.KetoService) {}
func (m *MockTenantServiceForConsent) DeleteTenantsBulk(ctx context.Context, ids []string) error {
return nil
}
// --- Test Helpers --- // --- Test Helpers ---
func newConsentTestApp(h *AuthHandler) *fiber.App { func newConsentTestApp(h *AuthHandler) *fiber.App {
@@ -100,11 +217,36 @@ func TestGetConsentRequest_AddsMandatoryTenantScope(t *testing.T) {
http.DefaultClient = client http.DefaultClient = client
defer func() { http.DefaultClient = origDefault }() defer func() { http.DefaultClient = origDefault }()
mockTenantSvc := &MockTenantServiceForConsent{}
mockKratosAdmin := &MockKratosAdminServiceForConsent{}
// Mock profile resolution to allow tenant access
mockKratosAdmin.On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{
ID: "user-123",
Traits: map[string]interface{}{
"email": "user@example.com",
},
}, nil)
mockTenantSvc.On("GetTenant", mock.Anything, "tenant-allow").Return(&domain.Tenant{
ID: "tenant-allow",
Slug: "tenant-allow",
Name: "Allowed Tenant",
}, nil)
// Mock hydration calls
mockTenantSvc.On("ListJoinedTenants", mock.Anything, mock.Anything).Return([]domain.Tenant{
{ID: "tenant-allow", Slug: "tenant-allow", Name: "Allowed Tenant"},
}, nil)
mockTenantSvc.On("ListManageableTenants", mock.Anything, mock.Anything).Return([]domain.Tenant{}, nil)
h := &AuthHandler{ h := &AuthHandler{
Hydra: &service.HydraAdminService{ Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test", AdminURL: "http://hydra.test",
HTTPClient: client, HTTPClient: client,
}, },
TenantService: mockTenantSvc,
KratosAdmin: mockKratosAdmin,
} }
app := newConsentTestApp(h) app := newConsentTestApp(h)
@@ -163,16 +305,17 @@ func TestGetConsentRequest_Skip_AutoAccept(t *testing.T) {
defer func() { http.DefaultClient = origDefault }() defer func() { http.DefaultClient = origDefault }()
consentRepo := &mockConsentRepo{} consentRepo := &mockConsentRepo{}
mockKratosAdmin := &MockKratosAdminServiceForConsent{}
h := &AuthHandler{ h := &AuthHandler{
Hydra: &service.HydraAdminService{ Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test", AdminURL: "http://hydra.test",
HTTPClient: client, HTTPClient: client,
}, },
KratosAdmin: new(MockKratosAdminService), // Reusing MockKratosAdminService if defined or use MockKratosAdminServiceShared KratosAdmin: mockKratosAdmin,
ConsentRepo: consentRepo, ConsentRepo: consentRepo,
} }
h.KratosAdmin.(*MockKratosAdminService).On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{ mockKratosAdmin.On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{
ID: "user-123", ID: "user-123",
Traits: map[string]interface{}{ Traits: map[string]interface{}{
"email": "user@test.com", "email": "user@test.com",
@@ -199,7 +342,8 @@ func TestAcceptConsentRequest_Normal(t *testing.T) {
"requested_scope": []string{"openid", "profile"}, "requested_scope": []string{"openid", "profile"},
"subject": "user-123", "subject": "user-123",
"client": map[string]interface{}{ "client": map[string]interface{}{
"client_id": "client-app", "client_id": "client-app",
"client_name": "Test App",
}, },
}), nil }), nil
} }
@@ -226,17 +370,18 @@ func TestAcceptConsentRequest_Normal(t *testing.T) {
auditRepo := &mockAuditRepo{} auditRepo := &mockAuditRepo{}
consentRepo := &mockConsentRepo{} consentRepo := &mockConsentRepo{}
mockKratosAdmin := &MockKratosAdminServiceForConsent{}
h := &AuthHandler{ h := &AuthHandler{
Hydra: &service.HydraAdminService{ Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test", AdminURL: "http://hydra.test",
HTTPClient: client, HTTPClient: client,
}, },
KratosAdmin: new(MockKratosAdminService), KratosAdmin: mockKratosAdmin,
AuditRepo: auditRepo, AuditRepo: auditRepo,
ConsentRepo: consentRepo, ConsentRepo: consentRepo,
} }
h.KratosAdmin.(*MockKratosAdminService).On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{ mockKratosAdmin.On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{
ID: "user-123", ID: "user-123",
Traits: map[string]interface{}{ Traits: map[string]interface{}{
"email": "user@test.com", "email": "user@test.com",
@@ -260,6 +405,8 @@ func TestAcceptConsentRequest_Normal(t *testing.T) {
} }
func TestAcceptConsentRequest_EnforcesMandatoryTenantScope(t *testing.T) { func TestAcceptConsentRequest_EnforcesMandatoryTenantScope(t *testing.T) {
t.Setenv("APP_ENV", "dev")
var capturedGrantScopes []string var capturedGrantScopes []string
transport := roundTripFunc(func(r *http.Request) (*http.Response, error) { transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
@@ -309,14 +456,16 @@ func TestAcceptConsentRequest_EnforcesMandatoryTenantScope(t *testing.T) {
http.DefaultClient = client http.DefaultClient = client
defer func() { http.DefaultClient = origDefault }() defer func() { http.DefaultClient = origDefault }()
mockKratosAdmin := &MockKratosAdminServiceForConsent{}
h := &AuthHandler{ h := &AuthHandler{
Hydra: &service.HydraAdminService{ Hydra: &service.HydraAdminService{
AdminURL: "http://hydra.test", AdminURL: "http://hydra.test",
HTTPClient: client, HTTPClient: client,
}, },
KratosAdmin: new(MockKratosAdminService), KratosAdmin: mockKratosAdmin,
} }
h.KratosAdmin.(*MockKratosAdminService).On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{ mockKratosAdmin.On("GetIdentity", mock.Anything, "user-123").Return(&service.KratosIdentity{
ID: "user-123", ID: "user-123",
Traits: map[string]interface{}{ Traits: map[string]interface{}{
"email": "user@test.com", "email": "user@test.com",

View File

@@ -184,6 +184,7 @@ func TestGetConsentRequest_DeniesTenantAccess(t *testing.T) {
app := fiber.New() app := fiber.New()
app.Get("/api/v1/auth/consent", h.GetConsentRequest) app.Get("/api/v1/auth/consent", h.GetConsentRequest)
t.Setenv("APP_ENV", "dev")
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/consent?consent_challenge=challenge-tenant", nil) req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/consent?consent_challenge=challenge-tenant", nil)
req.Header.Set("X-Mock-Role", "user") req.Header.Set("X-Mock-Role", "user")
req.Header.Set("X-Tenant-ID", "tenant-a") req.Header.Set("X-Tenant-ID", "tenant-a")

View File

@@ -13,47 +13,47 @@ import (
// Ory 계열(kratos/hydra) 공급자 문자열을 정규화하기 위한 매핑. // Ory 계열(kratos/hydra) 공급자 문자열을 정규화하기 위한 매핑.
var providerAliases = map[string]string{ var providerAliases = map[string]string{
"ory": "ory", "ory": "ory",
"hydra": "ory", "hydra": "ory",
"kratos": "ory", "kratos": "ory",
"ory-kratos": "ory", "ory-kratos": "ory",
"ory_hydra": "ory", "ory_hydra": "ory",
"ory_kratos": "ory", "ory_kratos": "ory",
} }
// getEnv는 환경 변수를 읽거나 대체 값을 반환하는 헬퍼 함수입니다. // getEnv는 환경 변수를 읽거나 대체 값을 반환하는 헬퍼 함수입니다.
func getEnv(key, fallback string) string { func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok { if value, ok := os.LookupEnv(key); ok {
return value return value
} }
return fallback return fallback
} }
// InitializeProvider는 환경 설정을 기반으로 IDP 공급자를 생성하고 반환합니다. // InitializeProvider는 환경 설정을 기반으로 IDP 공급자를 생성하고 반환합니다.
// 이것은 IdentityProvider 인터페이스의 팩토리 역할을 합니다. // 이것은 IdentityProvider 인터페이스의 팩토리 역할을 합니다.
func InitializeProvider() (domain.IdentityProvider, error) { func InitializeProvider() (domain.IdentityProvider, error) {
rawProviders := getEnv("IDP_PROVIDER", "ory") rawProviders := getEnv("IDP_PROVIDER", "ory")
providers := strings.Split(rawProviders, ",") providers := strings.Split(rawProviders, ",")
slog.Info("Initializing IDP chain", "providers", rawProviders) slog.Info("Initializing IDP chain", "providers", rawProviders)
var initialized []domain.IdentityProvider var initialized []domain.IdentityProvider
for _, p := range providers { for _, p := range providers {
providerName := strings.TrimSpace(strings.ToLower(p)) providerName := strings.TrimSpace(strings.ToLower(p))
if canonical, ok := providerAliases[providerName]; ok { if canonical, ok := providerAliases[providerName]; ok {
providerName = canonical providerName = canonical
} }
switch providerName { switch providerName {
case "ory": case "ory":
// Kratos/Hydra 주 공급자 // Kratos/Hydra 주 공급자
oryProvider := service.NewOryProvider() oryProvider := service.NewOryProvider()
initialized = append(initialized, oryProvider) initialized = append(initialized, oryProvider)
default: default:
// 알 수 없는 공급자는 건너뛰고 다음 후보를 시도 // 알 수 없는 공급자는 건너뛰고 다음 후보를 시도
slog.Warn("Skipping unsupported IDP provider entry", "provider", providerName) slog.Warn("Skipping unsupported IDP provider entry", "provider", providerName)
} }
} }
if len(initialized) == 0 { if len(initialized) == 0 {
return nil, fmt.Errorf("no valid IDP_PROVIDER entries configured from: %s", rawProviders) return nil, fmt.Errorf("no valid IDP_PROVIDER entries configured from: %s", rawProviders)
} }

View File

@@ -63,7 +63,7 @@ func TestMain(m *testing.M) {
} }
// Auto-migrate // Auto-migrate
err = db.AutoMigrate(&domain.Tenant{}, &domain.TenantDomain{}, &domain.User{}) err = db.AutoMigrate(&domain.Tenant{}, &domain.TenantDomain{}, &domain.User{}, &domain.ClientConsent{})
if err != nil { if err != nil {
log.Fatalf("failed to migrate database: %s", err) log.Fatalf("failed to migrate database: %s", err)
} }

View File

@@ -174,10 +174,7 @@ function ClientGeneralPage() {
{ {
id: "2", id: "2",
name: "tenant", name: "tenant",
description: t( description: t("msg.dev.clients.scopes.tenant", "소속 테넌트 정보 접근"),
"msg.dev.clients.scopes.tenant",
"소속 테넌트 정보 접근",
),
mandatory: false, mandatory: false,
}, },
{ {
@@ -347,12 +344,15 @@ function ClientGeneralPage() {
return { return {
...scope, ...scope,
description: scope.description || tenantScopeDescription, description: scope.description || tenantScopeDescription,
mandatory: restricted ? true : false, mandatory: restricted,
locked: restricted, locked: restricted,
}; };
}); });
if (restricted && !normalized.some((scope) => scope.name.trim() === "tenant")) { if (
restricted &&
!normalized.some((scope) => scope.name.trim() === "tenant")
) {
normalized.push(buildTenantScope(`tenant-${Date.now()}`)); normalized.push(buildTenantScope(`tenant-${Date.now()}`));
} }
@@ -526,7 +526,8 @@ function ClientGeneralPage() {
const hasValidationErrors = validationErrors.length > 0; const hasValidationErrors = validationErrors.length > 0;
const normalizedTenantSearch = tenantSearch.trim().toLowerCase(); const normalizedTenantSearch = tenantSearch.trim().toLowerCase();
const tenantOptions: Array<TenantSummary | MyTenantSummary> = tenantData ?? []; const tenantOptions: Array<TenantSummary | MyTenantSummary> =
tenantData ?? [];
const filteredTenants = tenantOptions.filter((tenant) => { const filteredTenants = tenantOptions.filter((tenant) => {
if (!normalizedTenantSearch) { if (!normalizedTenantSearch) {
return true; return true;
@@ -1375,10 +1376,7 @@ function ClientGeneralPage() {
</span> </span>
<button <button
type="button" type="button"
aria-label={t( aria-label={t("ui.common.delete", "삭제")}
"ui.common.delete",
"삭제",
)}
onClick={() => toggleAllowedTenant(tenant.id)} onClick={() => toggleAllowedTenant(tenant.id)}
className="text-muted-foreground transition hover:text-destructive" className="text-muted-foreground transition hover:text-destructive"
> >
@@ -1403,10 +1401,7 @@ function ClientGeneralPage() {
<span className="max-w-44 truncate">{tenantId}</span> <span className="max-w-44 truncate">{tenantId}</span>
<button <button
type="button" type="button"
aria-label={t( aria-label={t("ui.common.delete", "삭제")}
"ui.common.delete",
"삭제",
)}
onClick={() => toggleAllowedTenant(tenantId)} onClick={() => toggleAllowedTenant(tenantId)}
className="text-muted-foreground transition hover:text-destructive" className="text-muted-foreground transition hover:text-destructive"
> >

View File

@@ -650,6 +650,22 @@ title_generic = "An error occurred."
title_with_code = "Error: {{code}}" title_with_code = "Error: {{code}}"
type = "Error type: {{type}}" type = "Error type: {{type}}"
[msg.userfront.error.tenant]
account = "Account"
account_unknown = "Unknown"
affiliated_tenants = "All affiliated tenants"
allowed_box_title = "Allowed tenants"
allowed_tenants = "Allowed tenants"
detail = "The currently signed-in account cannot access this application."
load_failed = "We could not confirm the account details. Please try again."
loading = "Loading the current account details."
lookup_fallback = "Some fields could not be verified because the access context was incomplete."
page_title = "Access to this application is restricted"
primary_tenant = "Primary affiliated tenant"
tenant = "Tenant"
tenant_unknown = "Unknown"
title = "Access restriction details"
[msg.userfront.error.ory] [msg.userfront.error.ory]
"$normalizedCode" = "{{error}}" "$normalizedCode" = "{{error}}"
access_denied = "The user denied the consent request." access_denied = "The user denied the consent request."
@@ -2239,6 +2255,7 @@ windows = "Desktop(Windows)"
[ui.userfront.error] [ui.userfront.error]
go_home = "Go Home" go_home = "Go Home"
go_login = "Go Login" go_login = "Go Login"
switch_account = "Sign in with another account"
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "Forgot your password?" heading = "Forgot your password?"

View File

@@ -208,6 +208,22 @@ title_generic = "오류가 발생했습니다"
title_with_code = "오류: {{code}}" title_with_code = "오류: {{code}}"
type = "오류 종류: {{type}}" type = "오류 종류: {{type}}"
[msg.userfront.error.tenant]
account = "계정"
account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트"
tenant_unknown = "알 수 없음"
title = "접근 제한 정보"
[msg.userfront.forgot] [msg.userfront.forgot]
description = "계정과 연결된 이메일 주소 또는 휴대폰 번호를 입력하시면, 비밀번호를 재설정할 수 있는 링크를 보내드립니다." description = "계정과 연결된 이메일 주소 또는 휴대폰 번호를 입력하시면, 비밀번호를 재설정할 수 있는 링크를 보내드립니다."
dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다." dry_send = "drySend 모드: 실제 이메일/SMS는 발송되지 않습니다."
@@ -456,6 +472,7 @@ windows = "Desktop(Windows)"
[ui.userfront.error] [ui.userfront.error]
go_home = "홈으로 이동" go_home = "홈으로 이동"
go_login = "로그인으로 이동" go_login = "로그인으로 이동"
switch_account = "다른 계정으로 로그인"
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "비밀번호를 잊으셨나요?" heading = "비밀번호를 잊으셨나요?"
@@ -1088,6 +1105,22 @@ title_generic = "오류가 발생했습니다"
title_with_code = "오류: {{code}}" title_with_code = "오류: {{code}}"
type = "오류 종류: {{type}}" type = "오류 종류: {{type}}"
[msg.userfront.error.tenant]
account = "계정"
account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트"
tenant_unknown = "알 수 없음"
title = "접근 제한 정보"
[msg.userfront.error.ory] [msg.userfront.error.ory]
"$normalizedCode" = "{{error}}" "$normalizedCode" = "{{error}}"
access_denied = "사용자가 동의를 거부했습니다." access_denied = "사용자가 동의를 거부했습니다."
@@ -2638,6 +2671,7 @@ windows = "Desktop(Windows)"
[ui.userfront.error] [ui.userfront.error]
go_home = "홈으로 이동" go_home = "홈으로 이동"
go_login = "로그인으로 이동" go_login = "로그인으로 이동"
switch_account = "다른 계정으로 로그인"
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "비밀번호를 잊으셨나요?" heading = "비밀번호를 잊으셨나요?"

View File

@@ -83,6 +83,22 @@ title_generic = ""
title_with_code = "" title_with_code = ""
type = "" type = ""
[msg.userfront.error.tenant]
account = ""
account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = ""
load_failed = ""
loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = ""
tenant_unknown = ""
title = ""
[msg.userfront.forgot] [msg.userfront.forgot]
description = "" description = ""
dry_send = "" dry_send = ""
@@ -331,6 +347,7 @@ windows = ""
[ui.userfront.error] [ui.userfront.error]
go_home = "" go_home = ""
go_login = "" go_login = ""
switch_account = ""
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "" heading = ""
@@ -963,6 +980,22 @@ title_generic = ""
title_with_code = "" title_with_code = ""
type = "" type = ""
[msg.userfront.error.tenant]
account = ""
account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = ""
load_failed = ""
loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = ""
tenant_unknown = ""
title = ""
[msg.userfront.error.ory] [msg.userfront.error.ory]
"$normalizedCode" = "" "$normalizedCode" = ""
access_denied = "" access_denied = ""
@@ -2513,6 +2546,7 @@ windows = ""
[ui.userfront.error] [ui.userfront.error]
go_home = "" go_home = ""
go_login = "" go_login = ""
switch_account = ""
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "" heading = ""

View File

@@ -5,9 +5,14 @@ job_name="${1:-adminfront-tests}"
mkdir -p reports mkdir -p reports
if [ -n "${CI:-}" ]; then PLAYWRIGHT_BROWSERS_PATH="${HOME}/.cache/ms-playwright"
playwright_install_cmd=(npx playwright install --with-deps) PLAYWRIGHT_CHROMIUM_COMPLETE="${PLAYWRIGHT_BROWSERS_PATH}/chromium-1208/INSTALLATION_COMPLETE"
playwright_install_desc="npx playwright install --with-deps" PLAYWRIGHT_FIREFOX_COMPLETE="${PLAYWRIGHT_BROWSERS_PATH}/firefox-1509/INSTALLATION_COMPLETE"
PLAYWRIGHT_WEBKIT_COMPLETE="${PLAYWRIGHT_BROWSERS_PATH}/webkit-2248/INSTALLATION_COMPLETE"
if [ -f "$PLAYWRIGHT_CHROMIUM_COMPLETE" ] && [ -f "$PLAYWRIGHT_FIREFOX_COMPLETE" ] && [ -f "$PLAYWRIGHT_WEBKIT_COMPLETE" ]; then
playwright_install_cmd=(sh -c 'echo "Playwright browsers already installed"')
playwright_install_desc="Playwright browsers already installed"
else else
playwright_install_cmd=(npx playwright install) playwright_install_cmd=(npx playwright install)
playwright_install_desc="npx playwright install" playwright_install_desc="npx playwright install"
@@ -16,7 +21,7 @@ fi
set +e set +e
( (
cd adminfront cd adminfront
npm ci npm ci --ignore-scripts
) 2>&1 | tee reports/adminfront-install.log ) 2>&1 | tee reports/adminfront-install.log
install_exit_code=${PIPESTATUS[0]} install_exit_code=${PIPESTATUS[0]}
set -e set -e
@@ -31,7 +36,7 @@ if [ "$install_exit_code" -ne 0 ]; then
echo "- Exit Code: \`$install_exit_code\`" echo "- Exit Code: \`$install_exit_code\`"
echo echo
echo "## Command" echo "## Command"
echo "\`cd adminfront && npm ci\`" echo "\`cd adminfront && npm ci --ignore-scripts\`"
echo echo
echo "## Install Log Tail (last 200 lines)" echo "## Install Log Tail (last 200 lines)"
echo '```text' echo '```text'
@@ -70,7 +75,7 @@ if [ "$provision_exit_code" -ne 0 ]; then
fi fi
set +e set +e
port="$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")" port="${PORT:-5173}"
echo "==> adminfront using PORT=$port" echo "==> adminfront using PORT=$port"
( (
cd adminfront cd adminfront
@@ -89,7 +94,7 @@ if [ "$test_exit_code" -ne 0 ]; then
echo echo
echo "## Commands" echo "## Commands"
echo "1. \`cd adminfront\`" echo "1. \`cd adminfront\`"
echo "2. \`npm ci\`" echo "2. \`npm ci --ignore-scripts\`"
echo "3. \`${playwright_install_desc}\`" echo "3. \`${playwright_install_desc}\`"
echo "4. \`npm test\`" echo "4. \`npm test\`"
echo echo

View File

@@ -140,9 +140,15 @@ type = "Error type: {type}"
[msg.userfront.error.tenant] [msg.userfront.error.tenant]
account = "Account" account = "Account"
account_unknown = "Unknown" account_unknown = "Unknown"
affiliated_tenants = "All affiliated tenants"
allowed_box_title = "Allowed tenants"
allowed_tenants = "Allowed tenants"
detail = "The currently signed-in account cannot access this application." detail = "The currently signed-in account cannot access this application."
load_failed = "We could not confirm the account details. Please try again." load_failed = "We could not confirm the account details. Please try again."
loading = "Loading the current account details." loading = "Loading the current account details."
lookup_fallback = "Some fields could not be verified because the access context was incomplete."
page_title = "Access to this application is restricted"
primary_tenant = "Primary affiliated tenant"
tenant = "Tenant" tenant = "Tenant"
tenant_unknown = "Unknown" tenant_unknown = "Unknown"
title = "Access restriction details" title = "Access restriction details"

View File

@@ -81,9 +81,15 @@ type = "오류 종류: {type}"
[msg.userfront.error.tenant] [msg.userfront.error.tenant]
account = "계정" account = "계정"
account_unknown = "알 수 없음" account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다." detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요." load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다." loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트" tenant = "소속 테넌트"
tenant_unknown = "알 수 없음" tenant_unknown = "알 수 없음"
title = "접근 제한 정보" title = "접근 제한 정보"
@@ -355,6 +361,22 @@ title_generic = "오류가 발생했습니다"
title_with_code = "오류: {code}" title_with_code = "오류: {code}"
type = "오류 종류: {type}" type = "오류 종류: {type}"
[msg.userfront.error.tenant]
account = "계정"
account_unknown = "알 수 없음"
affiliated_tenants = "전체 소속 테넌트"
allowed_box_title = "접속 가능 테넌트"
allowed_tenants = "접속 가능 테넌트"
detail = "현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다."
load_failed = "계정 정보를 확인하지 못했습니다. 다시 시도해 주세요."
loading = "현재 계정 정보를 불러오는 중입니다."
lookup_fallback = "표시 정보가 충분하지 않아 일부 항목은 확인되지 않을 수 있습니다."
page_title = "애플리케이션 접근이 제한되었습니다"
primary_tenant = "대표 소속 테넌트"
tenant = "소속 테넌트"
tenant_unknown = "알 수 없음"
title = "접근 제한 정보"
[msg.userfront.error.ory] [msg.userfront.error.ory]
"$normalizedCode" = "{error}" "$normalizedCode" = "{error}"
access_denied = "사용자가 동의를 거부했습니다." access_denied = "사용자가 동의를 거부했습니다."
@@ -722,6 +744,7 @@ windows = "Desktop(Windows)"
[ui.userfront.error] [ui.userfront.error]
go_home = "홈으로 이동" go_home = "홈으로 이동"
go_login = "로그인으로 이동" go_login = "로그인으로 이동"
switch_account = "다른 계정으로 로그인"
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "비밀번호를 잊으셨나요?" heading = "비밀번호를 잊으셨나요?"

View File

@@ -53,9 +53,15 @@ type = ""
[msg.userfront.error.tenant] [msg.userfront.error.tenant]
account = "" account = ""
account_unknown = "" account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = "" detail = ""
load_failed = "" load_failed = ""
loading = "" loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = "" tenant = ""
tenant_unknown = "" tenant_unknown = ""
title = "" title = ""
@@ -327,6 +333,22 @@ title_generic = ""
title_with_code = "" title_with_code = ""
type = "" type = ""
[msg.userfront.error.tenant]
account = ""
account_unknown = ""
affiliated_tenants = ""
allowed_box_title = ""
allowed_tenants = ""
detail = ""
load_failed = ""
loading = ""
lookup_fallback = ""
page_title = ""
primary_tenant = ""
tenant = ""
tenant_unknown = ""
title = ""
[msg.userfront.error.ory] [msg.userfront.error.ory]
"$normalizedCode" = "" "$normalizedCode" = ""
access_denied = "" access_denied = ""
@@ -694,6 +716,7 @@ windows = ""
[ui.userfront.error] [ui.userfront.error]
go_home = "" go_home = ""
go_login = "" go_login = ""
switch_account = ""
[ui.userfront.forgot] [ui.userfront.forgot]
heading = "" heading = ""