Twenty sdk cli oauth (#18638)
<img width="1418" height="804" alt="image" src="https://github.com/user-attachments/assets/de6c8222-6496-4a71-bc21-7e5e1269d5cb" /> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { type OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
|
||||
import { BuildManifestOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/build-manifest-orchestrator-step';
|
||||
import { CheckServerOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step';
|
||||
import { EnsureValidTokensOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/ensure-valid-tokens-orchestrator-step';
|
||||
import { GenerateApiClientOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/generate-api-client-orchestrator-step';
|
||||
import { RegisterAppOrchestratorStep } from '@/cli/utilities/dev/orchestrator/steps/register-app-orchestrator-step';
|
||||
import {
|
||||
@@ -33,7 +32,6 @@ export class DevModeOrchestrator {
|
||||
private clientService: ClientService;
|
||||
private skipTypecheck = true;
|
||||
private checkServerStep: CheckServerOrchestratorStep;
|
||||
private ensureValidTokensStep: EnsureValidTokensOrchestratorStep;
|
||||
private buildManifestStep: BuildManifestOrchestratorStep;
|
||||
private registerAppStep: RegisterAppOrchestratorStep;
|
||||
private uploadFilesStep: UploadFilesOrchestratorStep;
|
||||
@@ -55,11 +53,6 @@ export class DevModeOrchestrator {
|
||||
...stepDeps,
|
||||
apiService,
|
||||
});
|
||||
this.ensureValidTokensStep = new EnsureValidTokensOrchestratorStep({
|
||||
...stepDeps,
|
||||
apiService,
|
||||
configService,
|
||||
});
|
||||
this.buildManifestStep = new BuildManifestOrchestratorStep(stepDeps);
|
||||
this.registerAppStep = new RegisterAppOrchestratorStep({
|
||||
...stepDeps,
|
||||
@@ -167,10 +160,6 @@ export class DevModeOrchestrator {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.ensureValidTokensStep.execute({
|
||||
applicationId: this.state.steps.resolveApplication.output.applicationId,
|
||||
});
|
||||
|
||||
const buildResult = await this.buildManifestStep.execute({
|
||||
appPath: this.state.appPath,
|
||||
});
|
||||
@@ -244,10 +233,6 @@ export class DevModeOrchestrator {
|
||||
{ message: 'Application created', status: 'success' },
|
||||
]);
|
||||
|
||||
await this.ensureValidTokensStep.exchangeTokens({
|
||||
applicationId: createResult.data.id,
|
||||
});
|
||||
|
||||
this.uploadFilesStep.initialize({
|
||||
appPath: this.state.appPath,
|
||||
universalIdentifier: manifest.application.universalIdentifier,
|
||||
|
||||
+16
-2
@@ -34,7 +34,17 @@ export class CheckServerOrchestratorStep {
|
||||
step.output = { isReady: false, errorLogged: true };
|
||||
step.status = 'error';
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Cannot reach server', status: 'error' },
|
||||
{
|
||||
message:
|
||||
'Cannot reach Twenty at localhost:3000.\n\n' +
|
||||
' Start a local server with Docker:\n' +
|
||||
' curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml -o docker-compose.yml\n' +
|
||||
' docker compose up -d\n\n' +
|
||||
' Or from the monorepo:\n' +
|
||||
' yarn start\n\n' +
|
||||
' Waiting for server...',
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
this.state.updatePipeline({ status: 'error' });
|
||||
}
|
||||
@@ -47,7 +57,11 @@ export class CheckServerOrchestratorStep {
|
||||
step.output = { isReady: false, errorLogged: true };
|
||||
step.status = 'error';
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Authentication failed', status: 'error' },
|
||||
{
|
||||
message:
|
||||
'Authentication failed. Run `twenty remote add --local` to authenticate.',
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
this.state.updatePipeline({ status: 'error' });
|
||||
}
|
||||
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
import { type ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { type ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { type OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
|
||||
|
||||
export class EnsureValidTokensOrchestratorStep {
|
||||
private apiService: ApiService;
|
||||
private configService: ConfigService;
|
||||
private state: OrchestratorState;
|
||||
private notify: () => void;
|
||||
|
||||
constructor({
|
||||
apiService,
|
||||
configService,
|
||||
state,
|
||||
notify,
|
||||
}: {
|
||||
apiService: ApiService;
|
||||
configService: ConfigService;
|
||||
state: OrchestratorState;
|
||||
notify: () => void;
|
||||
}) {
|
||||
this.apiService = apiService;
|
||||
this.configService = configService;
|
||||
this.state = state;
|
||||
this.notify = notify;
|
||||
}
|
||||
|
||||
async execute(input: { applicationId: string | null }): Promise<void> {
|
||||
if (!input.applicationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = this.state.steps.ensureValidTokens;
|
||||
|
||||
step.status = 'in_progress';
|
||||
this.notify();
|
||||
|
||||
const config = await this.configService.getConfig();
|
||||
|
||||
if (
|
||||
config.applicationAccessToken &&
|
||||
!this.isTokenExpired(config.applicationAccessToken)
|
||||
) {
|
||||
step.status = 'done';
|
||||
this.notify();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
config.applicationRefreshToken &&
|
||||
!this.isTokenExpired(config.applicationRefreshToken)
|
||||
) {
|
||||
const renewResult = await this.apiService.renewApplicationToken(
|
||||
config.applicationRefreshToken,
|
||||
);
|
||||
|
||||
if (renewResult.success) {
|
||||
await this.configService.setConfig({
|
||||
applicationAccessToken: renewResult.data.applicationAccessToken.token,
|
||||
applicationRefreshToken:
|
||||
renewResult.data.applicationRefreshToken.token,
|
||||
});
|
||||
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Renewing application tokens', status: 'info' },
|
||||
{ message: 'Application tokens renewed', status: 'success' },
|
||||
]);
|
||||
step.status = 'done';
|
||||
this.notify();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Renewing application tokens', status: 'info' },
|
||||
{
|
||||
message: `Failed to renew application tokens: ${JSON.stringify(renewResult.error, null, 2)}`,
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
|
||||
await this.exchangeTokens({ applicationId: input.applicationId });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.exchangeTokens({ applicationId: input.applicationId });
|
||||
}
|
||||
|
||||
async exchangeTokens(input: { applicationId: string }): Promise<void> {
|
||||
const tokenResult = await this.apiService.generateApplicationToken(
|
||||
input.applicationId,
|
||||
);
|
||||
|
||||
if (!tokenResult.success) {
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Generating application tokens', status: 'info' },
|
||||
{
|
||||
message: `Failed to generate application tokens: ${JSON.stringify(tokenResult.error, null, 2)}`,
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
this.state.steps.ensureValidTokens.status = 'error';
|
||||
this.notify();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.configService.setConfig({
|
||||
applicationAccessToken: tokenResult.data.applicationAccessToken.token,
|
||||
applicationRefreshToken: tokenResult.data.applicationRefreshToken.token,
|
||||
});
|
||||
|
||||
this.state.applyStepEvents([
|
||||
{ message: 'Generating application tokens', status: 'info' },
|
||||
{ message: 'Application tokens stored in config', status: 'success' },
|
||||
]);
|
||||
this.state.steps.ensureValidTokens.status = 'done';
|
||||
this.notify();
|
||||
}
|
||||
|
||||
private isTokenExpired(token: string): boolean {
|
||||
try {
|
||||
const payload = JSON.parse(
|
||||
Buffer.from(token.split('.')[1], 'base64').toString(),
|
||||
);
|
||||
|
||||
return Date.now() >= payload.exp * 1000 - 60_000;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ export class GenerateApiClientOrchestratorStep {
|
||||
|
||||
await this.clientService.generateCoreClient({
|
||||
appPath: input.appPath,
|
||||
authToken: config.applicationAccessToken,
|
||||
authToken: config.accessToken,
|
||||
});
|
||||
|
||||
step.status = 'done';
|
||||
|
||||
-1
@@ -88,7 +88,6 @@ export class RegisterAppOrchestratorStep {
|
||||
|
||||
await this.configService.setConfig({
|
||||
oauthClientId: createResult.data.applicationRegistration.oAuthClientId,
|
||||
oauthClientSecret: createResult.data.clientSecret,
|
||||
});
|
||||
|
||||
this.state.applyStepEvents([
|
||||
|
||||
Reference in New Issue
Block a user