Skip to content

Commit

Permalink
feature(bit-login) - auto set cloud domain config when bit login to a…
Browse files Browse the repository at this point in the history
… custom domain (#9173)
  • Loading branch information
GiladShoham authored Sep 4, 2024
1 parent 4a6c530 commit 47f85f9
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 24 deletions.
102 changes: 85 additions & 17 deletions scopes/cloud/cloud/cloud.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ import {
SYMPHONY_GRAPHQL,
getLoginUrl,
DEFAULT_ANALYTICS_DOMAIN,
DEFAULT_REGISTRY_URL,
getRegistryUrl,
clearCachedUrls,
CENTRAL_BIT_HUB_URL,
CENTRAL_BIT_HUB_NAME,
CFG_USER_TOKEN_KEY,
CFG_USER_NAME_KEY,
DEFAULT_CLOUD_DOMAIN,
CFG_CLOUD_DOMAIN_KEY,
} from '@teambit/legacy/dist/constants';
import { ScopeAspect, ScopeMain } from '@teambit/scope';
import globalFlags from '@teambit/legacy/dist/cli/global-flags';
Expand Down Expand Up @@ -72,14 +75,18 @@ type CloudOrganizationAPIResponse = {
};
};

const HTTPS: string = 'https://';

export type OnSuccessLogin = ({
username,
token,
npmrcUpdateResult,
updatedConfigs,
}: {
username?: string;
token?: string;
npmrcUpdateResult?: NpmConfigUpdateResult;
updatedConfigs?: Record<string, string | undefined>;
}) => void;
export type OnSuccessLoginSlot = SlotRegistry<OnSuccessLogin>;
export type NpmConfigUpdateResult = {
Expand Down Expand Up @@ -210,10 +217,12 @@ export class CloudMain {
port: portFromParams,
clientId = v4(),
skipConfigUpdate,
cloudDomain,
}: {
port?: number;
clientId?: string;
skipConfigUpdate?: boolean;
cloudDomain?: string;
} = {}): Promise<CloudAuthListener | null> {
return new Promise((resolve, reject) => {
const port = portFromParams || this.getLoginPort();
Expand Down Expand Up @@ -292,19 +301,21 @@ export class CloudMain {
});

const onLoggedInFns = this.onSuccessLoginSlot.values();
const updatedConfigs = this.updateGlobalConfigOnLogin(cloudDomain);

if (!skipConfigUpdate) {
this.updateNpmConfig({ authToken: token as string, username: username as string })
.then((configUpdates) => {
onLoggedInFns.forEach((fn) =>
fn({ username, token: token as string, npmrcUpdateResult: configUpdates })
fn({ username, token: token as string, npmrcUpdateResult: configUpdates, updatedConfigs })
);
})
.catch((error) => {
onLoggedInFns.forEach((fn) =>
fn({
username,
token: token as string,
updatedConfigs,
npmrcUpdateResult: {
error: new Error(`failed to update npmrc. error ${error?.toString}`),
},
Expand Down Expand Up @@ -395,13 +406,28 @@ export class CloudMain {
return this.globalConfig.getSync(CFG_USER_NAME_KEY);
}

private ensureHttps(url: string): string {
if (!url.startsWith('http')) {
return `${HTTPS}${url}`;
}
return url;
}

private calculateLoginDomain(cloudDomain: string) {
let finalCloudDomain = this.ensureHttps(cloudDomain);
if (!finalCloudDomain.endsWith('/bit-login')) {
finalCloudDomain = `${finalCloudDomain}/bit-login`;
}
return finalCloudDomain;
}

async getLoginUrl({
redirectUrl,
machineName,
cloudDomain,
port: portFromParams,
}: { redirectUrl?: string; machineName?: string; cloudDomain?: string; port?: string } = {}): Promise<string | null> {
const loginUrl = cloudDomain || this.getLoginDomain();
const loginUrl = cloudDomain ? this.calculateLoginDomain(cloudDomain) : this.getLoginDomain();
const port = Number(portFromParams) || this.getLoginPort();
if (redirectUrl) {
this.REDIRECT_URL = redirectUrl;
Expand Down Expand Up @@ -440,19 +466,55 @@ export class CloudMain {
return currentUser.username;
}

private globalConfigsToUpdateOnLogin(domain?: string): Record<string, string | undefined> {
const currentCloudDomain = this.globalConfig.getSync(CFG_CLOUD_DOMAIN_KEY);
const res = {};
if (!domain || domain === DEFAULT_CLOUD_DOMAIN) {
if (currentCloudDomain && currentCloudDomain !== DEFAULT_CLOUD_DOMAIN) {
res[CFG_CLOUD_DOMAIN_KEY] = '';
}
return res;
}
if (currentCloudDomain !== domain) {
res[CFG_CLOUD_DOMAIN_KEY] = domain;
}
return res;
}

private updateGlobalConfigOnLogin(domain?: string) {
const configsToUpdate = this.globalConfigsToUpdateOnLogin(domain);

Object.entries(configsToUpdate).forEach(([key, value]) => {
if (value) {
this.globalConfig.setSync(key, value);
} else {
this.globalConfig.delSync(key);
}
});
// Refresh the config after updating
clearCachedUrls();
this.config = CloudMain.calculateConfig();
return configsToUpdate;
}

async login(
port?: string,
suppressBrowserLaunch?: boolean,
machineName?: string,
cloudDomain?: string,
redirectUrl?: string,
skipConfigUpdate?: boolean
skipConfigUpdate?: boolean,
defaultCloudDomain?: boolean
): Promise<{
isAlreadyLoggedIn?: boolean;
username?: string;
token?: string;
npmrcUpdateResult?: NpmConfigUpdateResult;
globalConfigUpdates?: Record<string, string | undefined>;
} | null> {
if (defaultCloudDomain) {
cloudDomain = DEFAULT_CLOUD_DOMAIN;
}
return new Promise((resolve, reject) => {
if (this.isLoggedIn()) {
resolve({
Expand All @@ -470,6 +532,7 @@ export class CloudMain {
username: loggedInParams.username,
token: loggedInParams.token,
npmrcUpdateResult: loggedInParams.npmrcUpdateResult,
globalConfigUpdates: loggedInParams.updatedConfigs,
});
});

Expand All @@ -496,6 +559,7 @@ export class CloudMain {
this.setupAuthListener({
port: Number(port),
skipConfigUpdate,
cloudDomain,
})
.then(promptLogin)
.catch((e) => reject(e));
Expand Down Expand Up @@ -532,7 +596,7 @@ export class CloudMain {
getUserOrganizations {
id
name
}
}
}
`;

Expand Down Expand Up @@ -604,6 +668,20 @@ export class CloudMain {
return null;
}
}
static calculateConfig(): CloudWorkspaceConfig {
return {
cloudDomain: getCloudDomain(),
cloudHubDomain: DEFAULT_HUB_DOMAIN,
cloudApi: getSymphonyUrl(),
cloudGraphQL: SYMPHONY_GRAPHQL,
loginDomain: getLoginUrl(),
analyticsDomain: DEFAULT_ANALYTICS_DOMAIN,
registryUrl: getRegistryUrl(),
cloudExporterUrl: CENTRAL_BIT_HUB_URL,
cloudHubName: CENTRAL_BIT_HUB_NAME,
loginPort: CloudMain.DEFAULT_PORT,
};
}

static slots = [Slot.withType<OnSuccessLogin>()];
static dependencies = [
Expand All @@ -617,18 +695,8 @@ export class CloudMain {
UIAspect,
];
static runtime = MainRuntime;
static defaultConfig: CloudWorkspaceConfig = {
cloudDomain: getCloudDomain(),
cloudHubDomain: DEFAULT_HUB_DOMAIN,
cloudApi: getSymphonyUrl(),
cloudGraphQL: SYMPHONY_GRAPHQL,
loginDomain: getLoginUrl(),
analyticsDomain: DEFAULT_ANALYTICS_DOMAIN,
registryUrl: DEFAULT_REGISTRY_URL,
cloudExporterUrl: CENTRAL_BIT_HUB_URL,
cloudHubName: CENTRAL_BIT_HUB_NAME,
loginPort: CloudMain.DEFAULT_PORT,
};
static defaultConfig: CloudWorkspaceConfig = CloudMain.calculateConfig();

static async provider(
[loggerMain, graphql, express, workspace, scope, globalConfig, cli, ui]: [
LoggerMain,
Expand Down
45 changes: 42 additions & 3 deletions scopes/cloud/cloud/login.cmd.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import chalk from 'chalk';
import yesno from 'yesno';
import { Command, CommandOptions } from '@teambit/cli';
import { isEmpty } from 'lodash';
import { BitError } from '@teambit/bit-error';
import { CloudMain } from './cloud.main.runtime';

export class LoginCmd implements Command {
Expand All @@ -12,6 +14,7 @@ export class LoginCmd implements Command {
['', 'skip-config-update', 'skip writing to the .npmrc file'],
['', 'refresh-token', 'force refresh token even when logged in'],
['d', 'cloud-domain <domain>', 'login cloud domain (default bit.cloud)'],
['', 'default-cloud-domain', 'login to default cloud domain (bit.cloud)'],
['p', 'port <port>', 'port number to open for localhost server (default 8085)'],
['', 'no-browser', 'do not open a browser for authentication'],
[
Expand All @@ -36,6 +39,7 @@ export class LoginCmd implements Command {
[], // eslint-disable-line no-empty-pattern
{
cloudDomain,
defaultCloudDomain,
port,
suppressBrowserLaunch,
noBrowser,
Expand All @@ -44,6 +48,7 @@ export class LoginCmd implements Command {
refreshToken,
}: {
cloudDomain?: string;
defaultCloudDomain: boolean;
port: string;
suppressBrowserLaunch?: boolean;
noBrowser?: boolean;
Expand All @@ -54,6 +59,10 @@ export class LoginCmd implements Command {
): Promise<string> {
noBrowser = noBrowser || suppressBrowserLaunch;

if (defaultCloudDomain && cloudDomain) {
throw new BitError('use either --default-cloud-domain or --cloud-domain, not both');
}

if (refreshToken) {
this.cloud.logout();
}
Expand All @@ -78,7 +87,8 @@ export class LoginCmd implements Command {
machineName,
cloudDomain,
undefined,
skipConfigUpdate
skipConfigUpdate,
defaultCloudDomain
);

let message = chalk.green(`Logged in as ${result?.username}`);
Expand All @@ -94,6 +104,8 @@ export class LoginCmd implements Command {
message += this.getNpmrcUpdateMessage(result?.npmrcUpdateResult?.error);
}

message = this.getGlobalConfigUpdatesMessage(result?.globalConfigUpdates) + message;

return message;
}

Expand All @@ -106,23 +118,39 @@ export class LoginCmd implements Command {
noBrowser,
machineName,
skipConfigUpdate,
defaultCloudDomain,
}: {
cloudDomain?: string;
port: string;
suppressBrowserLaunch?: boolean;
noBrowser?: boolean;
machineName?: string;
skipConfigUpdate?: boolean;
defaultCloudDomain?: boolean;
}
): Promise<{ username?: string; token?: string; successfullyUpdatedNpmrc?: boolean }> {
): Promise<{
username?: string;
token?: string;
successfullyUpdatedNpmrc?: boolean;
globalConfigUpdates?: Record<string, string | undefined>;
}> {
if (suppressBrowserLaunch) {
noBrowser = true;
}
const result = await this.cloud.login(port, noBrowser, machineName, cloudDomain, undefined, skipConfigUpdate);
const result = await this.cloud.login(
port,
noBrowser,
machineName,
cloudDomain,
undefined,
skipConfigUpdate,
defaultCloudDomain
);
return {
username: result?.username,
token: result?.token,
successfullyUpdatedNpmrc: !!result?.npmrcUpdateResult?.configUpdates,
globalConfigUpdates: result?.globalConfigUpdates,
};
}

Expand Down Expand Up @@ -168,4 +196,15 @@ Modification: ${chalk.green(conflict.modifications)}`
)} for instructions on how to update it manually.`
);
}

getGlobalConfigUpdatesMessage = (globalConfigUpdates: Record<string, string | undefined> | undefined): string => {
if (!globalConfigUpdates || isEmpty(globalConfigUpdates)) return '';
const updates = Object.entries(globalConfigUpdates)
.map(([key, value]) => {
const entryStr = value === '' ? `${key} (removed)` : `${key}: ${value} (updated)`;
return entryStr;
})
.join('\n');
return chalk.greenBright(`\nGlobal config changes:\n${updates}\n\n`);
};
}
Loading

0 comments on commit 47f85f9

Please sign in to comment.