Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: consider dev/prod mode in more scenarios #1457

Merged
merged 5 commits into from
Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ProgressLocation,
window,
} from 'vscode';
import { OAuthService } from './oauth.service';
import { AuthConfig, OAuthService } from './oauth.service';
import { SecretSessionStore } from './secret-session-store';

export const AUTH_NAME = 'Nx Cloud';
Expand All @@ -28,7 +28,7 @@ export class NxCloudAuthenticationProvider
private _secretSessionStore: SecretSessionStore;
private _oAuthService: OAuthService;

constructor(readonly context: ExtensionContext, config: 'prod' | 'dev') {
constructor(readonly context: ExtensionContext, config: AuthConfig) {
this._secretSessionStore = new SecretSessionStore(context);
this._oAuthService = new OAuthService(context, config);

Expand Down
36 changes: 14 additions & 22 deletions libs/vscode/nx-cloud-view/src/lib/auth/oauth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,18 @@ import {
window,
} from 'vscode';

const staging_config = {
clientId: '11Zte67xGtfrGQhRVlz9zM8Fq0LvZYwe',
audience: 'https://api.staging.nrwl.io/',
domain: 'https://auth.staging.nx.app/login',
};

const prod_config = {
clientId: 'm6PYBsCK1t2DTKnbE30n029C22fqtTMm',
audience: 'https://api.nrwl.io/',
domain: 'https://nrwl.auth0.com/login',
export type AuthConfig = {
clientId: string;
audience: string;
domain: string;
};

export class OAuthService {
private _oAuthConfig: { clientId: string; audience: string; domain: string };
private _codeExchangePromise: Promise<string | undefined> | undefined;
private _stateId: string | undefined;
private _uriHandler = new UriEventHandler();

constructor(private context: ExtensionContext, config: 'prod' | 'dev') {
this._oAuthConfig = config === 'prod' ? prod_config : staging_config;
constructor(private context: ExtensionContext, private config: AuthConfig) {
window.registerUriHandler(this._uriHandler);
}
get redirectUri() {
Expand Down Expand Up @@ -80,15 +72,15 @@ export class OAuthService {

const searchParams = new URLSearchParams([
['response_type', 'code'],
['client_id', this._oAuthConfig.clientId],
['client_id', this.config.clientId],
['redirect_uri', this.redirectUri],
['state', this._stateId],
['scope', scopeString],
['audience', this._oAuthConfig.audience],
['audience', this.config.audience],
]);

const uri = Uri.parse(
`${this._oAuthConfig.domain}/authorize?${searchParams.toString()}`
`${this.config.domain}/authorize?${searchParams.toString()}`
);
await env.openExternal(uri);

Expand Down Expand Up @@ -129,15 +121,15 @@ export class OAuthService {
): Promise<{ access_token: string; refresh_token: string } | undefined> {
const searchParams = new URLSearchParams([
['grant_type', 'authorization_code'],
['client_id', this._oAuthConfig.clientId],
['client_id', this.config.clientId],
['code', authCode],
['redirect_uri', this.redirectUri],
['audience', this._oAuthConfig.audience],
['audience', this.config.audience],
]);
try {
return await xhr({
type: 'POST',
url: `${this._oAuthConfig.domain}/oauth/token`,
url: `${this.config.domain}/oauth/token`,
data: searchParams.toString(),
headers: { 'content-type': 'application/x-www-form-urlencoded' },
}).then((r) => {
Expand All @@ -151,7 +143,7 @@ export class OAuthService {
async getUserInfo(token: string) {
try {
const response = await xhr({
url: `${this._oAuthConfig.domain}/userinfo`,
url: `${this.config.domain}/userinfo`,
headers: {
Authorization: `Bearer ${token}`,
},
Expand Down Expand Up @@ -188,14 +180,14 @@ export class OAuthService {
const searchParams = new URLSearchParams([
['grant_type', 'refresh_token'],
['refresh_token', refreshToken],
['client_id', this._oAuthConfig.clientId],
['client_id', this.config.clientId],
['redirect_uri', this.redirectUri],
['scope', scopes.join(' ')],
]);
try {
return await xhr({
type: 'POST',
url: `${this._oAuthConfig.domain}/oauth/token`,
url: `${this.config.domain}/oauth/token`,
data: searchParams.toString(),
headers: {
'content-type': 'application/x-www-form-urlencoded',
Expand Down
29 changes: 29 additions & 0 deletions libs/vscode/nx-cloud-view/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type CloudConfig = {
authConfig: {
clientId: string;
audience: string;
domain: string;
};
endpoint: string;
appUrl: string;
};

export const stagingConfig = {
authConfig: {
clientId: '11Zte67xGtfrGQhRVlz9zM8Fq0LvZYwe',
audience: 'https://api.staging.nrwl.io/',
domain: 'https://auth.staging.nx.app/login',
},
endpoint: 'https://staging.nx.app/api',
appUrl: 'http://staging.nx.app',
};

export const prodConfig = {
authConfig: {
clientId: 'm6PYBsCK1t2DTKnbE30n029C22fqtTMm',
audience: 'https://api.nrwl.io/',
domain: 'https://auth.nx.app/login',
},
endpoint: 'https://cloud.nx.app/api',
appUrl: 'https://cloud.nx.app',
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { getNxCloudRunnerOptions } from '@nx-console/vscode/nx-workspace';
import { commands, ExtensionContext, window } from 'vscode';
import { NxCloudAuthenticationProvider } from './auth/nx-cloud-authentication-provider';
import { CloudConfig, prodConfig, stagingConfig } from './config';
import { REFRESH_COMMAND } from './nx-cloud-service/commands';
import { NxCloudService } from './nx-cloud-service/nx-cloud-service';
import { NxCloudWebviewProvider } from './nx-cloud-webview-provider';

export function initNxCloudOnboardingView(
export async function initNxCloudOnboardingView(
context: ExtensionContext,
production: boolean
) {
const config = production ? 'prod' : 'dev';
const config = await determineProdOrStagingConfig(production);

const nxCloudService = new NxCloudService(config);
const nxCloudWebviewProvider = new NxCloudWebviewProvider(
context.extensionUri,
Expand All @@ -18,7 +21,7 @@ export function initNxCloudOnboardingView(

const nxCloudAuthenticationProvider = new NxCloudAuthenticationProvider(
context,
config
config.authConfig
);

context.subscriptions.push(
Expand All @@ -30,3 +33,27 @@ export function initNxCloudOnboardingView(
})
);
}

/**
* Determines which endpoint to use for API calls and auth
* it's set to staging in dev mode and prod in production mode by default
* if the cloud runner is used, we will use that (as long as it matches either the staging or prod endpoint)
* */
async function determineProdOrStagingConfig(
productionEnv: boolean
): Promise<CloudConfig> {
const nxCloudRunnerOptions = await getNxCloudRunnerOptions();

// if we're using the cloud runner but no URL is defined, it means an implicit prod endpoint
const nxCloudRunnerUrl = nxCloudRunnerOptions
? nxCloudRunnerOptions.url ?? prodConfig.appUrl
: '';

if (nxCloudRunnerUrl === stagingConfig.appUrl) {
return stagingConfig;
}
if (nxCloudRunnerUrl === prodConfig.appUrl) {
return prodConfig;
}
return productionEnv ? prodConfig : stagingConfig;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,8 @@ import {
VCSIntegrationStatusResponse,
} from './models';

const stagingEndpoint = 'https://staging.nx.app/api';
const prodEndpoint = 'https://cloud.nx.app/api';

export class NxCloudApiService {
endpoint: string;

constructor(private config: 'prod' | 'dev') {
this.endpoint = config === 'dev' ? stagingEndpoint : prodEndpoint;
}
constructor(private endpoint: string) {}
async claimCloudWorkspace(
orgId: string
): Promise<ConnectWorkspaceUsingTokenResponse> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { getPackageManagerCommand } from '@nrwl/devkit';
import { WorkspaceConfigurationStore } from '@nx-console/vscode/configuration';
import { EXECUTE_ARBITRARY_COMMAND } from '@nx-console/vscode/nx-commands-view';
import { getNxWorkspace } from '@nx-console/vscode/nx-workspace';
import {
getNxCloudRunnerOptions,
getNxWorkspace,
} from '@nx-console/vscode/nx-workspace';
import {
getTelemetry,
getWorkspacePath,
Expand All @@ -26,6 +29,7 @@ import {
tasks,
window,
} from 'vscode';
import { CloudConfig, prodConfig, stagingConfig } from '../config';
import {
CLAIM_COMMAND,
INSPECT_RUN_COMMAND,
Expand Down Expand Up @@ -97,9 +101,9 @@ export type WebviewState = Pick<
export class NxCloudService extends StateBaseService<InternalState> {
private nxCloudApiService: NxCloudApiService;

constructor(config: 'prod' | 'dev') {
constructor(private config: CloudConfig) {
super(initialInternalState);
this.nxCloudApiService = new NxCloudApiService(config);
this.nxCloudApiService = new NxCloudApiService(config.endpoint);

this.listenForNxJsonChanges();
this.listenForWorkspaceDetails();
Expand Down Expand Up @@ -209,6 +213,14 @@ export class NxCloudService extends StateBaseService<InternalState> {
'latest'
);

const cloudRunnerUrl =
this.config.appUrl === stagingConfig.appUrl
? stagingConfig.appUrl
: undefined;
const env = cloudRunnerUrl
? { ...process.env, NX_CLOUD_API: this.config.appUrl }
: process.env;

window.withProgress(
{
location: ProgressLocation.Notification,
Expand All @@ -229,7 +241,7 @@ export class NxCloudService extends StateBaseService<InternalState> {
`${
getPackageManagerCommand().exec
} nx g @nrwl/nx-cloud:init`,
{ cwd: getWorkspacePath() }
{ cwd: getWorkspacePath(), env }
).then(() => resolve(true));
})
.catch((e) => {
Expand All @@ -254,7 +266,7 @@ export class NxCloudService extends StateBaseService<InternalState> {
? 'connect'
: 'connect-to-nx-cloud',
],
{ cwd: getWorkspacePath(), shell: true }
{ cwd: getWorkspacePath(), env, shell: true }
);

commandProcess.stdout.setEncoding('utf8');
Expand Down Expand Up @@ -394,8 +406,7 @@ export class NxCloudService extends StateBaseService<InternalState> {
}

private async openRunDetails(runLinkId: string) {
const url = await this.getNxCloudBaseUrl();

const url = await this.getCloudRunnerUrl();
commands.executeCommand(
'vscode.open',
`${url}/runs/${runLinkId}?utm_source=nxconsole`
Expand All @@ -412,7 +423,7 @@ export class NxCloudService extends StateBaseService<InternalState> {
}
const orgId = this.state.cloudWorkspaceOrgId;
const workspaceId = this.state.cloudWorkspaceId;
const baseUrl = await this.getNxCloudBaseUrl();
const baseUrl = await this.getCloudRunnerUrl();

const link = `${baseUrl}/orgs/${orgId}/workspaces/${workspaceId}/set-up-vcs-integration?utm_source=nxconsole`;

Expand Down Expand Up @@ -657,20 +668,9 @@ export class NxCloudService extends StateBaseService<InternalState> {
);
}

private async getNxCloudBaseUrl(): Promise<string | undefined> {
const nxConfig = (await getNxWorkspace()).workspace;

if (!nxConfig.tasksRunnerOptions) {
return;
}
const nxCloudTaskRunner = Object.values(nxConfig.tasksRunnerOptions).find(
(r) => r.runner == '@nrwl/nx-cloud'
);

// remove trailing slash
return (nxCloudTaskRunner?.options?.url ?? 'https://cloud.nx.app').replace(
/\/$/,
''
);
private async getCloudRunnerUrl(): Promise<string> {
return (
(await getNxCloudRunnerOptions())?.url ?? prodConfig.appUrl
).replace(/\/$/, '');
}
}
16 changes: 15 additions & 1 deletion libs/vscode/nx-workspace/src/lib/get-nx-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function getNxWorkspace(reset?: boolean): Promise<NxWorkspace> {
return sendRequest(NxWorkspaceRequest, { reset });
}

// shortcut to reduce repeated destructuring all over the codebase
// shortcuts to reduce repeated destructuring all over the codebase
export async function getNxWorkspaceProjects(reset?: boolean): Promise<{
[projectName: string]: ProjectConfiguration;
}> {
Expand All @@ -16,3 +16,17 @@ export async function getNxWorkspaceProjects(reset?: boolean): Promise<{
} = await getNxWorkspace(reset);
return projects;
}

export async function getNxCloudRunnerOptions(): Promise<
{ accessToken: string; url?: string } | undefined
> {
const nxConfig = (await getNxWorkspace()).workspace;

if (!nxConfig.tasksRunnerOptions) {
return;
}
const nxCloudTaskRunner = Object.values(nxConfig.tasksRunnerOptions).find(
(r) => r.runner == '@nrwl/nx-cloud'
);
return nxCloudTaskRunner?.options;
}