From c56384ae0553a734cf3186b444bcc385530af9e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 27 Aug 2026 14:35:08 +0000 Subject: [PATCH 1/2] impl(#406): scaffold Assisted-by: claude-sonnet-5 (agent) --- apps/admin/src/i18n/locales/en.json | 3 ++- apps/admin/src/i18n/locales/ru.json | 3 ++- apps/admin/src/services/auth-service.ts | 15 +++++++++++++++ .../src/tests/services/auth-service.test.ts | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/apps/admin/src/i18n/locales/en.json b/apps/admin/src/i18n/locales/en.json index a4af7a24..b7083802 100644 --- a/apps/admin/src/i18n/locales/en.json +++ b/apps/admin/src/i18n/locales/en.json @@ -128,7 +128,8 @@ "resetPasswordTokenMissing": "This reset link is missing its token. Request a new one.", "resetPasswordTokenDead": "This reset link is invalid or has expired. Request a new one.", "requestNewResetLink": "Request a new reset link", - "newPassword": "New password" + "newPassword": "New password", + "signInWithSso": "Sign in with SSO" }, "dashboard": { "title": "Dashboard", diff --git a/apps/admin/src/i18n/locales/ru.json b/apps/admin/src/i18n/locales/ru.json index bbf2cad7..7cc56239 100644 --- a/apps/admin/src/i18n/locales/ru.json +++ b/apps/admin/src/i18n/locales/ru.json @@ -128,7 +128,8 @@ "resetPasswordTokenMissing": "В ссылке для сброса отсутствует токен. Запросите новую.", "resetPasswordTokenDead": "Ссылка для сброса недействительна или истекла. Запросите новую.", "requestNewResetLink": "Запросить новую ссылку", - "newPassword": "Новый пароль" + "newPassword": "Новый пароль", + "signInWithSso": "Войти через SSO" }, "dashboard": { "title": "Панель управления", diff --git a/apps/admin/src/services/auth-service.ts b/apps/admin/src/services/auth-service.ts index 98a68698..7f2843b9 100644 --- a/apps/admin/src/services/auth-service.ts +++ b/apps/admin/src/services/auth-service.ts @@ -58,6 +58,21 @@ export const authService = { return response.data.data; }, + /** + * Resolve whether the current tenant (by host/subdomain) has made SSO + * mandatory. Unauthenticated, same pre-auth shape as getRegistrationStatus(). + */ + getSsoStatus: async (): Promise<{ enforceSso: boolean }> => { + const response = await api.get<{ success: boolean; data: { enforceSso: boolean } }>( + // Path matches #414's specified route contract (backend endpoint, + // tracked separately as this slice's blocking prerequisite). Inlined + // rather than added to api-constants.ts; mirrors auth.registrationStatus()'s + // `/api/v1/auth/registration-status` naming. + '/api/v1/auth/sso-status' + ); + return response.data.data; + }, + logout: async (): Promise => { await api.post(API_ENDPOINTS.auth.logout()); }, diff --git a/apps/admin/src/tests/services/auth-service.test.ts b/apps/admin/src/tests/services/auth-service.test.ts index 1c4e40e5..86d33535 100644 --- a/apps/admin/src/tests/services/auth-service.test.ts +++ b/apps/admin/src/tests/services/auth-service.test.ts @@ -131,6 +131,25 @@ describe('Auth Service', () => { }); }); + describe('getSsoStatus', () => { + it('should return enforceSso: true when SSO is mandatory for the tenant', async () => { + vi.mocked(api.get).mockResolvedValueOnce({ + data: { success: true, data: { enforceSso: true } }, + } as AxiosResponse); + + const result = await authService.getSsoStatus(); + + expect(result).toEqual({ enforceSso: true }); + expect(api.get).toHaveBeenCalledWith('/api/v1/auth/sso-status'); + }); + + it('should propagate API errors', async () => { + vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error')); + + await expect(authService.getSsoStatus()).rejects.toThrow('Network error'); + }); + }); + describe('login', () => { it('should login with email and password', async () => { const mockResponse = { From fa44d47420492729127e9000852150ce523b80de Mon Sep 17 00:00:00 2001 From: coder-abt Date: Fri, 28 Aug 2026 20:26:39 +0500 Subject: [PATCH 2/2] fix(#406): address PR #422 review findings - Add missing auth.signInWithSso key to kk.json (locale-sync drift Copilot flagged - en/ru had it, kk was missing it). - Move the hardcoded /api/v1/auth/sso-status path into API_ENDPOINTS.auth.ssoStatus(), matching every other auth-service method's use of the centralized endpoint builder. - Add the symmetric enforceSso: false success-path test for getSsoStatus, mirroring getRegistrationStatus's true/false coverage. Assisted-by: claude-opus-5 (agent) --- apps/admin/src/i18n/locales/kk.json | 3 ++- apps/admin/src/lib/api-constants.ts | 1 + apps/admin/src/services/auth-service.ts | 6 +----- apps/admin/src/tests/services/auth-service.test.ts | 10 ++++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/admin/src/i18n/locales/kk.json b/apps/admin/src/i18n/locales/kk.json index b1a6157e..6126215c 100644 --- a/apps/admin/src/i18n/locales/kk.json +++ b/apps/admin/src/i18n/locales/kk.json @@ -128,7 +128,8 @@ "resetPasswordTokenMissing": "Сілтемеде токен жоқ. Жаңасын сұраңыз.", "resetPasswordTokenDead": "Бұл сілтеме жарамсыз немесе мерзімі өтіп кетті. Жаңасын сұраңыз.", "requestNewResetLink": "Жаңа сілтеме сұрау", - "newPassword": "Жаңа құпиясөз" + "newPassword": "Жаңа құпиясөз", + "signInWithSso": "SSO арқылы кіру" }, "dashboard": { "title": "Басқару тақтасы", diff --git a/apps/admin/src/lib/api-constants.ts b/apps/admin/src/lib/api-constants.ts index b0f1167e..713b15e9 100644 --- a/apps/admin/src/lib/api-constants.ts +++ b/apps/admin/src/lib/api-constants.ts @@ -22,6 +22,7 @@ export const API_ENDPOINTS = { magicLogin: () => `${API_VERSION}/auth/magic-login`, register: () => `${API_VERSION}/auth/register`, registrationStatus: () => `${API_VERSION}/auth/registration-status`, + ssoStatus: () => `${API_VERSION}/auth/sso-status`, me: () => `${API_VERSION}/auth/me`, verifyEmail: () => `${API_VERSION}/auth/verify-email`, resendVerification: () => `${API_VERSION}/auth/resend-verification`, diff --git a/apps/admin/src/services/auth-service.ts b/apps/admin/src/services/auth-service.ts index 7f2843b9..72bd93bb 100644 --- a/apps/admin/src/services/auth-service.ts +++ b/apps/admin/src/services/auth-service.ts @@ -64,11 +64,7 @@ export const authService = { */ getSsoStatus: async (): Promise<{ enforceSso: boolean }> => { const response = await api.get<{ success: boolean; data: { enforceSso: boolean } }>( - // Path matches #414's specified route contract (backend endpoint, - // tracked separately as this slice's blocking prerequisite). Inlined - // rather than added to api-constants.ts; mirrors auth.registrationStatus()'s - // `/api/v1/auth/registration-status` naming. - '/api/v1/auth/sso-status' + API_ENDPOINTS.auth.ssoStatus() ); return response.data.data; }, diff --git a/apps/admin/src/tests/services/auth-service.test.ts b/apps/admin/src/tests/services/auth-service.test.ts index 86d33535..05a413f3 100644 --- a/apps/admin/src/tests/services/auth-service.test.ts +++ b/apps/admin/src/tests/services/auth-service.test.ts @@ -143,6 +143,16 @@ describe('Auth Service', () => { expect(api.get).toHaveBeenCalledWith('/api/v1/auth/sso-status'); }); + it('should return enforceSso: false when SSO is not mandatory for the tenant', async () => { + vi.mocked(api.get).mockResolvedValueOnce({ + data: { success: true, data: { enforceSso: false } }, + } as AxiosResponse); + + const result = await authService.getSsoStatus(); + + expect(result).toEqual({ enforceSso: false }); + }); + it('should propagate API errors', async () => { vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error'));