@vectorplane/ctrl-cli 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,3 +77,4 @@ vp sync
77
77
  ## Notas de fluxo
78
78
 
79
79
  O plano de endurecimento do login local está em [docs/cli-auth-hardening-phases.md](/home/developer/Documentos/Projetos/vectorplane-ctrl-cli/docs/cli-auth-hardening-phases.md).
80
+ O desenho multicanal atual do login está em [docs/cli-login-multichannel.md](/home/developer/Documentos/Projetos/vectorplane-ctrl-cli/docs/cli-login-multichannel.md).
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from "./logger.js";
2
- import type { CurrentUserResponse, AuthCodeExchangeRequest, AuthTokenExchangeResponse, RefreshTokenRequest } from "../types/auth.js";
2
+ import type { CurrentUserResponse, AuthCodeExchangeRequest, AuthTokenExchangeResponse, CreateLoginAttemptRequest, CreateLoginAttemptResponse, LoginAttemptStatusResponse, RefreshTokenRequest } from "../types/auth.js";
3
3
  import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse } from "../types/api.js";
4
4
  export declare class VectorPlaneApiClient {
5
5
  private readonly apiBaseUrl;
@@ -7,6 +7,8 @@ export declare class VectorPlaneApiClient {
7
7
  private readonly logger;
8
8
  constructor(apiBaseUrl: string, timeoutMs: number, logger: Logger);
9
9
  exchangeToken(payload: AuthCodeExchangeRequest): Promise<AuthTokenExchangeResponse>;
10
+ createLoginAttempt(payload: CreateLoginAttemptRequest): Promise<CreateLoginAttemptResponse>;
11
+ getLoginAttemptStatus(attemptId: string, pollToken: string): Promise<LoginAttemptStatusResponse>;
10
12
  refreshToken(payload: RefreshTokenRequest): Promise<AuthTokenExchangeResponse>;
11
13
  getCurrentUser(accessToken: string): Promise<CurrentUserResponse>;
12
14
  sync(accessToken: string, payload: SyncRequest): Promise<SyncResponse>;
package/dist/core/api.js CHANGED
@@ -71,6 +71,20 @@ export class VectorPlaneApiClient {
71
71
  body: JSON.stringify(payload),
72
72
  }, this.timeoutMs, this.logger);
73
73
  }
74
+ async createLoginAttempt(payload) {
75
+ return requestJson(`${this.apiBaseUrl}/cli/login/attempts`, {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify(payload),
79
+ }, this.timeoutMs, this.logger);
80
+ }
81
+ async getLoginAttemptStatus(attemptId, pollToken) {
82
+ const url = new URL(`${this.apiBaseUrl}/cli/login/attempts/${attemptId}`);
83
+ url.searchParams.set("poll_token", pollToken);
84
+ return requestJson(url.toString(), {
85
+ method: "GET",
86
+ }, this.timeoutMs, this.logger);
87
+ }
74
88
  async refreshToken(payload) {
75
89
  return requestJson(`${this.apiBaseUrl}/cli/token/refresh`, {
76
90
  method: "POST",
package/dist/core/auth.js CHANGED
@@ -1,14 +1,8 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
- import { CALLBACK_TIMEOUT_MS, CLI_CLIENT_ID } from "./constants.js";
3
+ import { CALLBACK_TIMEOUT_MS, CLI_CLIENT_ID, LOGIN_ATTEMPT_POLL_INTERVAL_MS } from "./constants.js";
4
4
  import { AuthError } from "./errors.js";
5
5
  import { createCallbackServer } from "./server.js";
6
- function buildLoginUrl(profile, callbackUrl, state) {
7
- const url = new URL("/cli/login", profile.appBaseUrl);
8
- url.searchParams.set("redirect_uri", callbackUrl);
9
- url.searchParams.set("state", state);
10
- return url.toString();
11
- }
12
6
  export function generateLoginState() {
13
7
  return randomBytes(24).toString("hex");
14
8
  }
@@ -35,7 +29,12 @@ export async function runLoginFlow(params) {
35
29
  const state = generateLoginState();
36
30
  const callbackServer = await createCallbackServer(params.config.callbackHost, params.config.callbackPort, params.config.callbackPath, state);
37
31
  try {
38
- const loginUrl = buildLoginUrl(params.profile, callbackServer.callbackUrl, state);
32
+ const loginAttempt = await params.apiClient.createLoginAttempt({
33
+ redirectUri: callbackServer.callbackUrl,
34
+ state,
35
+ client: CLI_CLIENT_ID,
36
+ });
37
+ const loginUrl = loginAttempt.loginUrl;
39
38
  params.logger.info("abrindo navegador para autenticação...");
40
39
  const opened = await openBrowser(loginUrl);
41
40
  if (!opened) {
@@ -43,9 +42,20 @@ export async function runLoginFlow(params) {
43
42
  process.stdout.write(`${loginUrl}\n`);
44
43
  }
45
44
  params.logger.info("aguardando confirmação...");
46
- const callback = await callbackServer.waitForCallback(CALLBACK_TIMEOUT_MS);
45
+ const code = await Promise.any([
46
+ callbackServer.waitForCallback(CALLBACK_TIMEOUT_MS).then((callback) => callback.code),
47
+ waitForAuthorizedAttempt({
48
+ apiClient: params.apiClient,
49
+ attemptId: loginAttempt.attemptId,
50
+ pollToken: loginAttempt.pollToken,
51
+ timeoutMs: CALLBACK_TIMEOUT_MS,
52
+ logger: params.logger,
53
+ }),
54
+ ]).catch((error) => {
55
+ throw new AuthError("Não foi possível concluir o login do CLI.", error);
56
+ });
47
57
  const payload = {
48
- code: callback.code,
58
+ code,
49
59
  client: CLI_CLIENT_ID,
50
60
  device: params.machine,
51
61
  runtime: params.runtime,
@@ -68,4 +78,32 @@ export async function runLoginFlow(params) {
68
78
  await callbackServer.close();
69
79
  }
70
80
  }
81
+ async function waitForAuthorizedAttempt(params) {
82
+ const startedAt = Date.now();
83
+ let announcedAuthorization = false;
84
+ while (Date.now() - startedAt < params.timeoutMs) {
85
+ const status = await params.apiClient.getLoginAttemptStatus(params.attemptId, params.pollToken);
86
+ const code = pickAuthorizedCode(status);
87
+ if (code) {
88
+ return code;
89
+ }
90
+ if (status.status === "authorized" && !announcedAuthorization) {
91
+ announcedAuthorization = true;
92
+ params.logger.info("autorização recebida pela API. finalizando sessão...");
93
+ }
94
+ await delay(LOGIN_ATTEMPT_POLL_INTERVAL_MS);
95
+ }
96
+ throw new AuthError("Tempo esgotado aguardando a confirmação do login do CLI.");
97
+ }
98
+ function pickAuthorizedCode(status) {
99
+ if ((status.status === "authorized" || status.status === "completed") && typeof status.code === "string" && status.code.length > 0) {
100
+ return status.code;
101
+ }
102
+ return null;
103
+ }
104
+ function delay(ms) {
105
+ return new Promise((resolve) => {
106
+ setTimeout(resolve, ms);
107
+ });
108
+ }
71
109
  //# sourceMappingURL=auth.js.map
@@ -18,5 +18,6 @@ export declare const DEFAULT_STATE: CliState;
18
18
  export declare const DEFAULT_QUEUE: QueueStore;
19
19
  export declare const DEFAULT_WORKSPACE_BINDINGS: WorkspaceBindingStore;
20
20
  export declare const CALLBACK_TIMEOUT_MS = 120000;
21
+ export declare const LOGIN_ATTEMPT_POLL_INTERVAL_MS = 1000;
21
22
  export declare const SENSITIVE_KEYS: string[];
22
23
  export declare const SAFE_ENV_KEYS: string[];
@@ -56,6 +56,7 @@ export const DEFAULT_WORKSPACE_BINDINGS = {
56
56
  bindings: {},
57
57
  };
58
58
  export const CALLBACK_TIMEOUT_MS = 120_000;
59
+ export const LOGIN_ATTEMPT_POLL_INTERVAL_MS = 1_000;
59
60
  export const SENSITIVE_KEYS = ["token", "authorization", "refresh", "secret", "password", "cookie"];
60
61
  export const SAFE_ENV_KEYS = [
61
62
  "SHELL",
@@ -5,6 +5,26 @@ export interface AuthCodeExchangeRequest {
5
5
  device: MachineContext;
6
6
  runtime: RuntimeContext;
7
7
  }
8
+ export interface CreateLoginAttemptRequest {
9
+ redirectUri: string;
10
+ state: string;
11
+ client: "vp-cli";
12
+ }
13
+ export interface CreateLoginAttemptResponse {
14
+ attemptId: string;
15
+ pollToken: string;
16
+ expiresAt: string;
17
+ loginUrl: string;
18
+ }
19
+ export interface LoginAttemptStatusResponse {
20
+ attemptId: string;
21
+ status: "pending" | "authorized" | "completed";
22
+ code: string | null;
23
+ workspace: string | null;
24
+ expiresAt: string;
25
+ authorizedAt: string | null;
26
+ completedAt: string | null;
27
+ }
8
28
  export interface AuthTokenExchangeResponse {
9
29
  access_token: string;
10
30
  refresh_token: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorplane/ctrl-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Official VectorPlane CLI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",