Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@enkryptify/cli",
"version": "0.3.5",
"version": "0.4.0",
"bin": {
"ek": "./dist/cli.js"
},
Expand Down
42 changes: 11 additions & 31 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { env } from "@/env";
import { config as configManager } from "@/lib/config";
import { CLIError } from "@/lib/errors";
import { logger } from "@/lib/logger";
import { keyring } from "@/lib/keyring";
import { secureStore } from "@/lib/secureStore";
import http from "@/api/httpClient";
import { createHash, randomBytes } from "crypto";
import open from "open";
Expand All @@ -28,18 +28,11 @@ type AuthResponse = {
expiresIn: number;
};

type StoredAuthData = {
accessToken: string;
userId: string;
email: string;
};

function base64Url(buf: Buffer) {
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+/g, "");
}

export class Auth {
private readonly KEYRING_KEY = "enkryptify";
private readonly CLIENT_ID = "enkryptify-cli";
private readonly REDIRECT_URL = "http://localhost:51823/callback";
private readonly CALLBACK_PORT = 51823;
Expand All @@ -58,7 +51,7 @@ export class Auth {

if (envToken) {
if (options?.force) {
await keyring.delete(this.KEYRING_KEY);
await secureStore.clearAll();
} else {
const isAuth = await this.getUserInfo(envToken).catch(() => false);
if (isAuth) {
Expand All @@ -69,7 +62,7 @@ export class Auth {
await configManager.markAuthenticated();
return;
} else {
await keyring.delete(this.KEYRING_KEY);
await secureStore.clearAll();
}
}
}
Expand Down Expand Up @@ -374,14 +367,11 @@ h1{font-size:18px;font-weight:600;color:#e4e8ec;letter-spacing:-.01em;margin:0}
}

private async markAuthenticated(accessToken: string, user: UserInfo): Promise<void> {
await keyring.set(
this.KEYRING_KEY,
JSON.stringify({
accessToken,
userId: user.id,
email: user.email,
}),
);
await secureStore.setAuth({
accessToken,
userId: user.id,
email: user.email,
});

await configManager.markAuthenticated();
}
Expand Down Expand Up @@ -410,21 +400,11 @@ h1{font-size:18px;font-weight:600;color:#e4e8ec;letter-spacing:-.01em;margin:0}
}

async getCredentials(): Promise<Credentials> {
const authDataString = await keyring.get(this.KEYRING_KEY);
if (!authDataString) {
const authData = await secureStore.getAuth();
if (!authData?.accessToken) {
throw CLIError.from("AUTH_NOT_LOGGED_IN");
}

try {
const authData = JSON.parse(authDataString) as StoredAuthData;
if (!authData || !authData.accessToken) {
throw CLIError.from("AUTH_NOT_LOGGED_IN");
}
return { accessToken: authData.accessToken };
} catch (error: unknown) {
if (error instanceof CLIError) throw error;
logger.debug(error instanceof Error ? error.message : String(error));
throw CLIError.from("AUTH_NOT_LOGGED_IN");
}
return { accessToken: authData.accessToken };
}
}
6 changes: 3 additions & 3 deletions src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ProjectConfig, config } from "@/lib/config";
import { type ConfigureScope, type ProjectConfig, config } from "@/lib/config";
import { CLIError } from "@/lib/errors";
import { logger } from "@/lib/logger";
import { getSecureInput, getTextInput } from "@/lib/input";
Expand Down Expand Up @@ -98,8 +98,8 @@ class EnkryptifyClient {
await this.auth.login(options);
}

async configure(options: string): Promise<ProjectConfig> {
const setup = await config.getConfigure(options);
async configure(options: string, configureOptions?: { scope?: ConfigureScope }): Promise<ProjectConfig> {
const setup = await config.getConfigure(options, configureOptions);
if (setup) {
const overwrite = await confirm("Setup already exists. Overwrite?");
if (!overwrite) {
Expand Down
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ if (isCompletion) {
}

setupTerminalCleanup();
await analytics.init();
// "scan" needs no authentication, so skip the keychain lookup to avoid a password prompt.
await analytics.init({ skipAuthLookup: process.argv[2] === "scan" });
Comment thread
SiebeBaree marked this conversation as resolved.

const isUpgrade = process.argv[2] === "upgrade";
if (!isCompletion && !isUpgrade) {
Expand Down
41 changes: 35 additions & 6 deletions src/cmd/configure.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
import { config } from "@/lib/config";
import { type ConfigureScope, config } from "@/lib/config";
import { analytics } from "@/lib/analytics";
import { CLIError } from "@/lib/errors";
import { getGitRepoInfo } from "@/lib/git";
import { logger } from "@/lib/logger";
import { client } from "@/api/client";
import { selectName } from "@/ui/SelectItem";
import type { Command } from "commander";

export async function configure(): Promise<Record<string, string>> {
type ConfigureCommandOptions = {
git?: boolean;
};

const GIT_SCOPE_LABEL = "Git repository (recommended)";
const PATH_SCOPE_LABEL = "This path only";

async function resolveConfigureScope(projectPath: string, options: ConfigureCommandOptions): Promise<ConfigureScope> {
if (options.git) {
return "git";
}

const gitRepo = await getGitRepoInfo(projectPath);
if (!gitRepo) {
return "path";
}

const selectedScope = await selectName(
[GIT_SCOPE_LABEL, PATH_SCOPE_LABEL],
"Connect this setup to this path or to the Git repository?",
);

return selectedScope === PATH_SCOPE_LABEL ? "path" : "git";
}
Comment thread
SiebeBaree marked this conversation as resolved.

export async function configure(options: ConfigureCommandOptions = {}): Promise<Record<string, string>> {
const authenticated = await config.isAuthenticated();
if (!authenticated) {
throw CLIError.from("AUTH_NOT_LOGGED_IN");
}

const projectPath = process.cwd();
const scope = await resolveConfigureScope(projectPath, options);

const projectConfig = await client.configure(projectPath);
const projectConfig = await client.configure(projectPath, { scope });

await config.createConfigure(projectPath, projectConfig);
await config.createConfigure(projectPath, projectConfig, { scope });

return projectConfig;
}
Expand All @@ -25,11 +53,12 @@ export function registerConfigureCommand(program: Command) {
.command("configure")
.alias("setup")
.description("The configure command is used to set up a project with Enkryptify.")
.action(async () => {
.option("--git", "Connect this setup to the current Git repository instead of only this path")
.action(async (options: ConfigureCommandOptions) => {
const tracker = analytics.trackCommand("command_configure");

try {
const projectConfig = await configure();
const projectConfig = await configure(options);
tracker.success({
workspace_slug: projectConfig.workspace_slug,
});
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { registerLoginCommand } from "@/cmd/login";
import { registerLogoutCommand } from "@/cmd/logout";
import { registerRunCommand } from "@/cmd/run";
import { registerRunFileCommand } from "@/cmd/run-file";
import { registerScanCommand } from "@/cmd/scan";
import { registerSdkCommand } from "@/cmd/sdk";
import { registerUpdateCommand } from "@/cmd/update";
import { registerUpgradeCommand } from "@/cmd/upgrade";
Expand All @@ -17,6 +18,7 @@ export function registerCommands(program: Command) {
registerLogoutCommand(program);
registerWhoamiCommand(program);
registerConfigureCommand(program);
registerScanCommand(program);
registerRunCommand(program);
registerRunFileCommand(program);
registerSdkCommand(program);
Expand Down
45 changes: 16 additions & 29 deletions src/cmd/login.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { analytics } from "@/lib/analytics";
import { logger } from "@/lib/logger";
import { keyring } from "@/lib/keyring";
import { secureStore } from "@/lib/secureStore";
import { Auth } from "@/api/auth";
import { config as configManager } from "@/lib/config";
import { LoginFlow } from "@/ui/LoginFlow";
Expand All @@ -21,25 +21,18 @@ export function registerLoginCommand(program: Command) {
// when the user is already logged in.
if (!options?.force) {
try {
const authDataString = await keyring.get("enkryptify");
if (authDataString) {
const authData = JSON.parse(authDataString) as {
accessToken: string;
userId: string;
email: string;
};
if (authData.accessToken) {
// Verify the token is still valid
const auth = new Auth();
const userInfo = await auth.getUserInfo(authData.accessToken).catch(() => null);
if (userInfo) {
logger.info(
'Already logged in. Use "ek login --force" to re-authenticate with a different account.',
);
await configManager.markAuthenticated();
tracker.success();
return;
}
const authData = await secureStore.getAuth();
if (authData?.accessToken) {
// Verify the token is still valid
const auth = new Auth();
const userInfo = await auth.getUserInfo(authData.accessToken).catch(() => null);
if (userInfo) {
logger.info(
'Already logged in. Use "ek login --force" to re-authenticate with a different account.',
);
await configManager.markAuthenticated();
tracker.success();
return;
}
}
} catch {
Expand All @@ -59,15 +52,9 @@ export function registerLoginCommand(program: Command) {
onComplete: async () => {
// Identify user in analytics after successful login
try {
const authDataString = await keyring.get("enkryptify");
if (authDataString) {
const authData = JSON.parse(authDataString) as {
userId: string;
email: string;
};
if (authData.userId && authData.email) {
analytics.identify(authData.userId, authData.email);
}
const authData = await secureStore.getAuth();
if (authData) {
analytics.identify(authData.userId, authData.email);
}
} catch {
// Best-effort
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/logout.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import http from "@/api/httpClient";
import { analytics } from "@/lib/analytics";
import { config as configManager } from "@/lib/config";
import { keyring } from "@/lib/keyring";
import { logger } from "@/lib/logger";
import { secureStore } from "@/lib/secureStore";
import type { Command } from "commander";

export function registerLogoutCommand(program: Command) {
Expand All @@ -13,8 +13,8 @@ export function registerLogoutCommand(program: Command) {
const tracker = analytics.trackCommand("command_logout");

try {
const authDataString = await keyring.get("enkryptify");
if (!authDataString) {
const authData = await secureStore.getAuth();
if (!authData?.accessToken) {
logger.info("You are not logged in.");
tracker.success();
return;
Expand All @@ -30,7 +30,7 @@ export function registerLogoutCommand(program: Command) {
logger.debug(revokeError instanceof Error ? revokeError.message : String(revokeError));
}

await keyring.delete("enkryptify");
await secureStore.clearAll();
await configManager.clearAuthentication();
logger.info("Successfully logged out.");
tracker.success();
Expand Down
Loading
Loading