diff --git a/samples/data-fabric-app-angular/src/app/core/auth.service.ts b/samples/data-fabric-app-angular/src/app/core/auth.service.ts index 78225c23a..1a1897795 100644 --- a/samples/data-fabric-app-angular/src/app/core/auth.service.ts +++ b/samples/data-fabric-app-angular/src/app/core/auth.service.ts @@ -48,7 +48,6 @@ export class AuthService { this.isLoading.set(true) this.error.set(null) try { - this.sdk.setMultiLogin() await this.sdk.initialize() this.isAuthenticated.set(this.sdk.isAuthenticated()) } catch (err) { diff --git a/src/core/auth/service.ts b/src/core/auth/service.ts index fcdb3c141..8896b70ea 100644 --- a/src/core/auth/service.ts +++ b/src/core/auth/service.ts @@ -7,12 +7,9 @@ import { hasOAuthConfig } from '../config/sdk-config'; import { isBrowser } from '../../utils/platform'; import { IDENTITY_ENDPOINTS } from '../../utils/constants/endpoints'; -const GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - export class AuthService { private config: Config; private tokenManager: TokenManager; - private skipAcrValues: boolean = false; constructor(config: Config, executionContext: ExecutionContext) { // Only use stored OAuth context when completing an active callback (URL has ?code=). @@ -118,15 +115,6 @@ export class AuthService { return this.tokenManager; } - /** - * Enables the UiPath login picker during OAuth sign-in. - * - * @internal - */ - public setMultiLogin(): void { - this.skipAcrValues = true; - } - /** * Authenticates the user based on the provided SDK configuration. * This method handles OAuth 2.0 authentication flow only. @@ -340,10 +328,6 @@ export class AuthService { scope: string; state?: string; }): string { - const orgName = this.config.orgName; - const isGuid = GUID_REGEX.test(orgName); - const acrValues = isGuid ? `tenant:${orgName}` : `tenantName:${orgName}`; - const queryParams = new URLSearchParams({ response_type: 'code', client_id: params.clientId, @@ -354,10 +338,10 @@ export class AuthService { state: params.state || this.generateCodeVerifier().slice(0, 16) }); - const authorizeUrl = `${this.config.baseUrl}/${IDENTITY_ENDPOINTS.AUTHORIZE}?${queryParams.toString()}`; - return this.skipAcrValues - ? authorizeUrl - : `${authorizeUrl}&acr_values=${acrValues}`; + // acr_values is intentionally omitted: Identity resolves the target org from + // client_id, and sending acr_values routes directly to the org's SAML IdP, + // which blocks Basic Auth users in mixed-auth orgs. + return `${this.config.baseUrl}/${IDENTITY_ENDPOINTS.AUTHORIZE}?${queryParams.toString()}`; } /** diff --git a/src/core/types.ts b/src/core/types.ts index 06ee141f8..bea699bca 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -22,11 +22,6 @@ export interface IUiPath { */ initialize(): Promise; - /** - * Enables the UiPath login picker during OAuth sign-in. - */ - setMultiLogin(): void; - /** * Check if the SDK has been initialized */ diff --git a/src/core/uipath.ts b/src/core/uipath.ts index 9fe756352..806e5a857 100644 --- a/src/core/uipath.ts +++ b/src/core/uipath.ts @@ -48,7 +48,6 @@ export class UiPath implements IUiPath { #authService?: AuthService; #initialized: boolean = false; #partialConfig?: PartialUiPathConfig; - #multiLogin: boolean = false; // Folder key sourced only from `` (coded-app // deployments). Not accepted via the public constructor; lives here so the // SDK can flow it through to BaseService.config without polluting BaseConfig. @@ -101,9 +100,6 @@ export class UiPath implements IUiPath { const executionContext = new ExecutionContext(); this.#authService = new AuthService(internalConfig, executionContext); - if (this.#multiLogin) { - this.#authService.setMultiLogin(); - } this.#config = internalConfig; // Store internals in SDKInternalsRegistry (not visible on instance). @@ -212,16 +208,6 @@ export class UiPath implements IUiPath { } } - /** - * Enables the UiPath login picker during OAuth sign-in. - * - * @internal - */ - public setMultiLogin(): void { - this.#multiLogin = true; - this.#authService?.setMultiLogin(); - } - /** * Check if the SDK has been initialized */ diff --git a/tests/unit/core/auth/service.test.ts b/tests/unit/core/auth/service.test.ts index ab5fc8a2e..24d15528e 100644 --- a/tests/unit/core/auth/service.test.ts +++ b/tests/unit/core/auth/service.test.ts @@ -40,7 +40,7 @@ describe('AuthService', () => { expect(url).toContain('offline_access'); }); - it('should include all required OAuth params alongside acr_values', () => { + it('should include all required OAuth params', () => { const service = createService(TEST_CONSTANTS.ORGANIZATION_ID); const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); const params = new URLSearchParams(url.split('?')[1]); @@ -50,37 +50,21 @@ describe('AuthService', () => { expect(params.get('code_challenge')).toBe(codeChallenge); expect(params.get('code_challenge_method')).toBe('S256'); expect(params.get('scope')).toContain(scope); - expect(params.get('acr_values')).toBe(`tenantName:${TEST_CONSTANTS.ORGANIZATION_ID}`); }); - it('should set acr_values with tenant: prefix when orgName is a GUID', () => { - const service = createService(TEST_CONSTANTS.GUID_ORG_ID); - const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); - const parsedUrl = new URL(url); - expect(parsedUrl.searchParams.get('acr_values')).toBe(`tenant:${TEST_CONSTANTS.GUID_ORG_ID}`); - }); - - it('should set acr_values with tenantName: prefix when orgName is a human-readable name', () => { - const service = createService(TEST_CONSTANTS.ORGANIZATION_ID); - const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); - const parsedUrl = new URL(url); - expect(parsedUrl.searchParams.get('acr_values')).toBe(`tenantName:${TEST_CONSTANTS.ORGANIZATION_ID}`); - }); - - it('should set acr_values with tenantName: prefix for a GUID-like string with wrong segment length', () => { - const service = createService(TEST_CONSTANTS.INVALID_GUID_ORG_ID); - const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); - const parsedUrl = new URL(url); - expect(parsedUrl.searchParams.get('acr_values')).toBe(`tenantName:${TEST_CONSTANTS.INVALID_GUID_ORG_ID}`); - }); - - it('should omit acr_values when multi-login is enabled', () => { - const service = createService(TEST_CONSTANTS.ORGANIZATION_ID); - service.setMultiLogin(); - const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); - const parsedUrl = new URL(url); - expect(parsedUrl.searchParams.has('acr_values')).toBe(false); - expect(url).not.toContain('acr_values='); + // acr_values is never sent: Identity resolves the org from client_id, and + // sending it routes directly to the org's SAML IdP, blocking Basic Auth users. + it('should never emit acr_values, regardless of orgName format', () => { + for (const orgName of [ + TEST_CONSTANTS.ORGANIZATION_ID, + TEST_CONSTANTS.GUID_ORG_ID, + TEST_CONSTANTS.INVALID_GUID_ORG_ID, + ]) { + const service = createService(orgName); + const url = service.getAuthorizationUrl({ clientId, redirectUri, codeChallenge, scope }); + expect(new URL(url).searchParams.has('acr_values')).toBe(false); + expect(url).not.toContain('acr_values'); + } }); }); diff --git a/tests/unit/core/uipath.test.ts b/tests/unit/core/uipath.test.ts index efcc31a3b..20e03a442 100644 --- a/tests/unit/core/uipath.test.ts +++ b/tests/unit/core/uipath.test.ts @@ -17,7 +17,6 @@ const mockTokenManager = { }; const mockLogout = vi.fn(); -const mockSetMultiLogin = vi.fn(); vi.mock('../../../src/core/auth/service', () => { const AuthService: any = vi.fn().mockImplementation(function () { return ({ @@ -26,7 +25,6 @@ vi.mock('../../../src/core/auth/service', () => { getToken: () => 'mock-access-token', authenticateWithSecret: vi.fn(), authenticate: vi.fn().mockResolvedValue(true), - setMultiLogin: mockSetMultiLogin, logout: mockLogout }); }); @@ -104,30 +102,6 @@ describe('UiPath Core', () => { expect(sdk.isInitialized()).toBe(false); // OAuth requires initialize() }); - it('should enable multi-login on OAuth config', () => { - mockSetMultiLogin.mockClear(); - const sdk = new UiPath({ - baseUrl: TEST_CONSTANTS.BASE_URL, - orgName: TEST_CONSTANTS.ORGANIZATION_ID, - tenantName: TEST_CONSTANTS.TENANT_ID, - clientId: TEST_CONSTANTS.CLIENT_ID, - redirectUri: 'http://localhost:3000/callback', - scope: 'offline_access' - }); - - sdk.setMultiLogin(); - - expect(mockSetMultiLogin).toHaveBeenCalledOnce(); - }); - - it('should allow multi-login before config is loaded', () => { - mockSetMultiLogin.mockClear(); - const sdk = new UiPath(); - - expect(() => sdk.setMultiLogin()).not.toThrow(); - expect(mockSetMultiLogin).not.toHaveBeenCalled(); - }); - it('should validate required config fields', async () => { const sdk = new UiPath({ baseUrl: '',