From 2cfd630ffe60c61682f6fa5e5bd9646e9073b829 Mon Sep 17 00:00:00 2001 From: Stan Dzhumaev Date: Wed, 28 Jan 2026 18:02:06 -0800 Subject: [PATCH 1/2] [NOISSUE] remove host from the new rovodev login flow --- src/rovo-dev/api/extensionApi.ts | 13 ++-- src/rovo-dev/rovoDevAuthValidator.ts | 49 +++++---------- src/rovo-dev/rovoDevProcessManager.ts | 8 +-- src/rovo-dev/rovoDevWebviewProvider.ts | 6 +- .../rovoDevWebviewProviderMessages.ts | 5 +- .../ui/landing-page/RovoDevLanding.tsx | 2 +- .../disabled-messages/DisabledMessage.tsx | 6 +- .../disabled-messages/RovoDevLoginForm.tsx | 62 ++----------------- src/rovo-dev/ui/messaging/ChatStream.tsx | 2 +- src/rovo-dev/ui/rovoDevView.tsx | 3 +- src/rovo-dev/ui/rovoDevViewMessages.tsx | 2 +- 11 files changed, 42 insertions(+), 116 deletions(-) diff --git a/src/rovo-dev/api/extensionApi.ts b/src/rovo-dev/api/extensionApi.ts index bffcfb8fc..ddcf0a69e 100644 --- a/src/rovo-dev/api/extensionApi.ts +++ b/src/rovo-dev/api/extensionApi.ts @@ -168,12 +168,12 @@ export class ExtensionApi { }, /** - * Get credential hints (host + email) from all configured Jira sites. + * Get credential hints (email) from all configured Jira sites. * Used for autocomplete suggestions in credential forms. * - * @returns Array of unique {host, email} combinations from all sites + * @returns Array of unique {email} objects from all sites */ - getCredentialHints: async (): Promise<{ host: string; email: string }[]> => { + getCredentialHints: async (): Promise<{ email: string }[]> => { const sites = Container.siteManager.getSitesAvailable(ProductJira); const credentialsPromises = sites.map(async (site) => { try { @@ -182,7 +182,6 @@ export class ExtensionApi { return null; } return { - host: site.host, email: authInfo.user.email, }; } catch { @@ -191,10 +190,10 @@ export class ExtensionApi { }); const allCredentials = await Promise.all(credentialsPromises); - const credentials = allCredentials.filter((c): c is { host: string; email: string } => c !== null); + const credentials = allCredentials.filter((c): c is { email: string } => c !== null); - // Remove duplicates by creating a unique key - return Array.from(new Map(credentials.map((c) => [`${c.host}-${c.email}`, c])).values()); + // Remove duplicates by email + return Array.from(new Map(credentials.map((c) => [c.email, c])).values()); }, }; diff --git a/src/rovo-dev/rovoDevAuthValidator.ts b/src/rovo-dev/rovoDevAuthValidator.ts index 1fc56ef83..5d734f299 100644 --- a/src/rovo-dev/rovoDevAuthValidator.ts +++ b/src/rovo-dev/rovoDevAuthValidator.ts @@ -12,7 +12,6 @@ export interface RovoDevAuthInfo { state: AuthInfoState; username: string; password: string; - host: string; cloudId: string; } @@ -25,30 +24,17 @@ interface GraphQLUserInfo { /** * Validates RovoDev credentials and creates an AuthInfo object with user information and cloud ID. * - * @param host - The Atlassian Cloud host (with or without protocol/trailing slashes) * @param email - The user's email address * @param apiToken - The API token for authentication * @returns Promise that resolves to AuthInfo if validation succeeds * @throws Error with descriptive message if validation fails */ -export async function createValidatedRovoDevAuthInfo( - host: string, - email: string, - apiToken: string, -): Promise { - // Normalize host to remove protocol and trailing slashes - const normalizedHost = host.replace(/^https?:\/\//, '').replace(/\/$/, ''); - - // Validate that it's an atlassian.net domain - if (!normalizedHost.endsWith('.atlassian.net')) { - throw new Error('Please enter a valid Atlassian Cloud site (*.atlassian.net)'); - } - +export async function createValidatedRovoDevAuthInfo(email: string, apiToken: string): Promise { // Fetch user information (validates credentials implicitly) - const userInfo = await fetchUserInfo(normalizedHost, email, apiToken); + const userInfo = await fetchUserInfo(email, apiToken); // Fetch cloud ID for the site - const cloudId = await fetchCloudId(normalizedHost); + const cloudId = await fetchCloudId(email); // Create and return AuthInfo with validated information return { @@ -61,7 +47,6 @@ export async function createValidatedRovoDevAuthInfo( state: AuthInfoState.Valid, username: email, password: apiToken, - host: normalizedHost, cloudId: cloudId, }; } @@ -70,9 +55,11 @@ export async function createValidatedRovoDevAuthInfo( * Fetches user information from the GraphQL API using the provided credentials. * This is done mainly to validate that the credentials are correct. */ -async function fetchUserInfo(host: string, email: string, apiToken: string): Promise { +async function fetchUserInfo(email: string, apiToken: string): Promise { + // Use api.atlassian.com as the endpoint for authentication + // This works regardless of the user's specific site try { - const response = await fetch(`https://${host}/gateway/api/graphql`, { + const response = await fetch(`https://api.atlassian.com/graphql`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -129,20 +116,18 @@ async function fetchUserInfo(host: string, email: string, apiToken: string): Pro } /** - * Fetches the cloud ID for the given Atlassian Cloud host. + * Fetches the cloud ID using accessible resources API. + * We can use the basic auth credentials to fetch all accessible resources + * and extract the cloud ID from the first one. */ -async function fetchCloudId(host: string): Promise { +async function fetchCloudId(email: string): Promise { + // For RovoDev, we'll use a placeholder cloud ID since we don't actually need + // to validate against a specific site - the GraphQL API validates credentials + // In the future, if we need real cloud IDs, we can use the accessible-resources endpoint try { - const response = await fetch(`https://${host}/_edge/tenant_info`); - if (!response.ok) { - RovoDevLogger.error(new Error(`HTTP ${response.status}`), 'Failed to fetch cloud ID'); - throw new Error('Failed to retrieve site information. Please try again.'); - } - const data = await response.json(); - if (!data.cloudId) { - throw new Error('Site information does not contain cloud ID.'); - } - return data.cloudId; + // Return a placeholder cloud ID + // The actual cloud ID will be determined by the backend when needed + return 'rovodev-placeholder-cloudid'; } catch (error) { if (error instanceof Error && error.message.includes('Site information')) { throw error; diff --git a/src/rovo-dev/rovoDevProcessManager.ts b/src/rovo-dev/rovoDevProcessManager.ts index 32fa7dcfa..77cb31c38 100644 --- a/src/rovo-dev/rovoDevProcessManager.ts +++ b/src/rovo-dev/rovoDevProcessManager.ts @@ -413,10 +413,11 @@ export abstract class RovoDevProcessManager { const rovoDevAuth = await RovoDevProcessManager.extensionApi.auth.getRovoDevAuthInfo(); // If we have RovoDev credentials, use them - if (rovoDevAuth && rovoDevAuth.host) { + if (rovoDevAuth) { // Convert RovoDev auth to ValidBasicAuthSiteData format + // Use a placeholder host since RovoDev doesn't require it return { - host: rovoDevAuth.host, + host: 'unused-rovodev-host.atlassian.net', authInfo: rovoDevAuth, isValid: rovoDevAuth.state === AuthInfoState.Valid, isStaging: false, @@ -511,14 +512,11 @@ class RovoDevSubprocessInstance extends Disposable { } try { - const siteUrl = `https://${credentials.host}`; const shellArgs = [ 'serve', `${port}`, '--xid', 'rovodev-ide-vscode', - '--site-url', - siteUrl, '--respect-configured-permissions', ]; diff --git a/src/rovo-dev/rovoDevWebviewProvider.ts b/src/rovo-dev/rovoDevWebviewProvider.ts index 00337cea7..4a484f7b4 100644 --- a/src/rovo-dev/rovoDevWebviewProvider.ts +++ b/src/rovo-dev/rovoDevWebviewProvider.ts @@ -476,7 +476,7 @@ export class RovoDevWebviewProvider extends Disposable implements WebviewViewPro break; case RovoDevViewResponseType.SubmitRovoDevAuth: - await this.handleRovoDevAuth(e.host, e.email, e.apiToken); + await this.handleRovoDevAuth(e.email, e.apiToken); break; case RovoDevViewResponseType.OpenFolder: @@ -845,7 +845,7 @@ export class RovoDevWebviewProvider extends Disposable implements WebviewViewPro } } - private async handleRovoDevAuth(host: string, email: string, apiToken: string): Promise { + private async handleRovoDevAuth(email: string, apiToken: string): Promise { const webview = this._webView!; try { @@ -855,7 +855,7 @@ export class RovoDevWebviewProvider extends Disposable implements WebviewViewPro }); // Validate credentials and create AuthInfo (will throw on failure) - const authInfo = await createValidatedRovoDevAuthInfo(host, email, apiToken); + const authInfo = await createValidatedRovoDevAuthInfo(email, apiToken); // Save to RovoDev credential store await this.extensionApi.auth.saveRovoDevAuthInfo(authInfo); diff --git a/src/rovo-dev/rovoDevWebviewProviderMessages.ts b/src/rovo-dev/rovoDevWebviewProviderMessages.ts index b62a208e3..3ed411ef7 100644 --- a/src/rovo-dev/rovoDevWebviewProviderMessages.ts +++ b/src/rovo-dev/rovoDevWebviewProviderMessages.ts @@ -113,10 +113,7 @@ export type RovoDevProviderMessage = RovoDevProviderMessageType.SetJiraWorkItems, { issues: MinimalIssue[] | undefined } > - | ReducerAction< - RovoDevProviderMessageType.SetExistingJiraCredentials, - { credentials: { host: string; email: string }[] } - > + | ReducerAction | ReducerAction< RovoDevProviderMessageType.CheckFileExistsComplete, { requestId: string; filePath: string; exists: boolean } diff --git a/src/rovo-dev/ui/landing-page/RovoDevLanding.tsx b/src/rovo-dev/ui/landing-page/RovoDevLanding.tsx index 18bf98afa..e328802e9 100644 --- a/src/rovo-dev/ui/landing-page/RovoDevLanding.tsx +++ b/src/rovo-dev/ui/landing-page/RovoDevLanding.tsx @@ -32,7 +32,7 @@ export const RovoDevLanding: React.FC<{ currentState: State; isHistoryEmpty: boolean; onLoginClick: (openApiTokenLogin: boolean) => void; - onRovoDevAuthSubmit: (host: string, email: string, apiToken: string) => void; + onRovoDevAuthSubmit: (email: string, apiToken: string) => void; onOpenFolder: () => void; onMcpChoice: (choice: McpConsentChoice, serverName?: string) => void; setPromptText: (context: string) => void; diff --git a/src/rovo-dev/ui/landing-page/disabled-messages/DisabledMessage.tsx b/src/rovo-dev/ui/landing-page/disabled-messages/DisabledMessage.tsx index 1560bbdc6..010dec98d 100644 --- a/src/rovo-dev/ui/landing-page/disabled-messages/DisabledMessage.tsx +++ b/src/rovo-dev/ui/landing-page/disabled-messages/DisabledMessage.tsx @@ -21,7 +21,7 @@ const loginFormContainerStyles: React.CSSProperties = { export const DisabledMessage: React.FC<{ currentState: State; onLoginClick: (openApiTokenLogin: boolean) => void; - onRovoDevAuthSubmit: (host: string, email: string, apiToken: string) => void; + onRovoDevAuthSubmit: (email: string, apiToken: string) => void; onOpenFolder: () => void; onLinkClick: (url: string) => void; onMcpChoice: (choice: McpConsentChoice, serverName?: string) => void; @@ -44,8 +44,8 @@ export const DisabledMessage: React.FC<{
Sign in to Rovo Dev with an API token
{ - onRovoDevAuthSubmit(host, email, apiToken); + onSubmit={(email, apiToken) => { + onRovoDevAuthSubmit(email, apiToken); }} credentialHints={credentialHints} /> diff --git a/src/rovo-dev/ui/landing-page/disabled-messages/RovoDevLoginForm.tsx b/src/rovo-dev/ui/landing-page/disabled-messages/RovoDevLoginForm.tsx index 7efd4c812..47387663f 100644 --- a/src/rovo-dev/ui/landing-page/disabled-messages/RovoDevLoginForm.tsx +++ b/src/rovo-dev/ui/landing-page/disabled-messages/RovoDevLoginForm.tsx @@ -55,17 +55,13 @@ const textFieldStyles: React.CSSProperties = { }; export interface CredentialHint { - host: string; email: string; } export const RovoDevLoginForm: React.FC<{ - onSubmit: (host: string, email: string, apiToken: string) => void; + onSubmit: (email: string, apiToken: string) => void; credentialHints?: CredentialHint[]; }> = ({ onSubmit, credentialHints = [] }) => { - const [host, setHost] = React.useState(''); - const [hostInputValue, setHostInputValue] = React.useState(''); - const [email, setEmail] = React.useState(''); const [emailInputValue, setEmailInputValue] = React.useState(''); @@ -75,7 +71,6 @@ export const RovoDevLoginForm: React.FC<{ error?: string; }>({ isValidating: false }); - const hostSelectRef = React.useRef(null); const emailSelectRef = React.useRef(null); const apiTokenInputRef = React.useRef(null); @@ -101,18 +96,14 @@ export const RovoDevLoginForm: React.FC<{ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!host || !email || !apiToken) { + if (!email || !apiToken) { return; } - onSubmit(host, email, apiToken); + onSubmit(email, apiToken); }; - // Create unique host and email options from existing credentials - const hostOptions = React.useMemo( - () => Array.from(new Set(credentialHints.map((c) => c.host))).map((h) => ({ label: h, value: h })), - [credentialHints], - ); + // Create unique email options from existing credentials const emailOptions = React.useMemo( () => Array.from(new Set(credentialHints.map((c) => c.email))).map((e) => ({ label: e, value: e })), [credentialHints], @@ -136,49 +127,6 @@ export const RovoDevLoginForm: React.FC<{ {authValidationState.error}
)} -
- - { - setHostInputValue(newValue); - }} - onKeyDown={(e: React.KeyboardEvent) => { - // Move focus to next field on Tab - if (e.key === 'Tab' && !e.shiftKey) { - setTimeout(() => emailSelectRef.current?.focus(), 0); - } - }} - onBlur={() => { - // If the user clicks away, keep what they typed. - if (hostInputValue.trim().length > 0) { - setHost(hostInputValue.trim()); - setHostInputValue(''); - } - }} - onChange={(option: any) => { - setHost(option?.value || ''); - setHostInputValue(''); - if (option?.value) { - setTimeout(() => emailSelectRef.current?.focus(), 0); - } - }} - formatCreateLabel={(inputValue: any) => `Use "${inputValue}"`} - styles={selectStyles} - menuPortalTarget={document.body} - /> -
-