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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ek
ek
.DS_Store
4 changes: 4 additions & 0 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ This command will open a web browser to complete the OAuth authentication flow.
After successful authentication, your credentials will be securely stored
in your system's keyring.

For server environments where browser authentication is not possible, you can
set the ENKRYPTIFY_TOKEN environment variable with your API token:
export ENKRYPTIFY_TOKEN=your_api_token_here

Examples:
ek login # Login with default provider (Enkryptify)
ek login --force # Force re-authentication even if already logged in
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
)

var version = "0.1.8"
var version = "0.1.9"

var rootCmd = &cobra.Command{
Use: "ek",
Expand Down
62 changes: 57 additions & 5 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/Enkryptify/cli/internal/auth"
"github.com/Enkryptify/cli/internal/config"
"github.com/Enkryptify/cli/internal/providers/enkryptify"
"github.com/Enkryptify/cli/internal/ui"
Expand Down Expand Up @@ -36,9 +38,6 @@ func runSetup(cmd *cobra.Command, args []string) error {
return err
}

ui.ShowBrandHeader()
ui.PrintTitle("🔗 Enkryptify Repository Setup")

currentPath, err := config.GetCurrentWorkingDirectory()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
Expand All @@ -49,6 +48,61 @@ func runSetup(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to load setup configuration: %w", err)
}

client := enkryptify.NewClient()

// Check if using environment variable token (for server environments)
if token := os.Getenv(auth.EnvTokenKey); token != "" {
return runNonInteractiveSetup(currentPath, setupStorage, client)
}

// Interactive setup for user authentication
return runInteractiveSetup(currentPath, setupStorage, client)
}

// runNonInteractiveSetup handles setup for server environments using env token
func runNonInteractiveSetup(currentPath string, setupStorage *config.SetupStorage, client *enkryptify.Client) error {
// Fetch project details from the token
tokenDetails, err := client.GetProjectTokenDetails()
if err != nil {
return fmt.Errorf("failed to fetch project token details: %w", err)
}

// Check if setup already exists
if setupStorage.HasSetupForPath(currentPath) {
existingSetup := setupStorage.GetSetupForPath(currentPath)
// In non-interactive mode, we just overwrite silently
fmt.Printf("Updating existing setup for %s\n", currentPath)
fmt.Printf("Previous: workspace=%s, project=%s, environment=%s\n",
existingSetup.WorkspaceSlug, existingSetup.ProjectSlug, existingSetup.EnvironmentID)
Comment on lines +74 to +76
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use ui package functions for consistent output formatting.

The code uses fmt.Printf directly instead of the ui package functions. For consistency with the rest of the codebase, consider using ui.PrintInfo for informational messages.

Apply this diff:

-		fmt.Printf("Updating existing setup for %s\n", currentPath)
-		fmt.Printf("Previous: workspace=%s, project=%s, environment=%s\n", 
-			existingSetup.WorkspaceSlug, existingSetup.ProjectSlug, existingSetup.EnvironmentID)
+		ui.PrintInfo(fmt.Sprintf("Updating existing setup for %s", currentPath))
+		ui.PrintInfo(fmt.Sprintf("Previous: workspace=%s, project=%s, environment=%s", 
+			existingSetup.WorkspaceSlug, existingSetup.ProjectSlug, existingSetup.EnvironmentID))
🤖 Prompt for AI Agents
In cmd/setup.go around lines 74 to 76, the code uses fmt.Printf for
informational output; replace those calls with ui.PrintInfo to keep output
consistent with the codebase (e.g., ui.PrintInfo("Updating existing setup for
%s", currentPath) and ui.PrintInfo("Previous: workspace=%s, project=%s,
environment=%s", existingSetup.WorkspaceSlug, existingSetup.ProjectSlug,
existingSetup.EnvironmentID)); ensure the ui package is imported and remove the
unused fmt import if no longer needed.

}

// Create setup config from token details
setupConfig := config.SetupConfig{
Path: currentPath,
WorkspaceSlug: tokenDetails.Workspace.Slug,
ProjectSlug: tokenDetails.Project.Slug,
EnvironmentID: tokenDetails.EnvironmentID,
}

setupStorage.AddOrUpdateSetup(setupConfig)
if err := setupStorage.Save(); err != nil {
return fmt.Errorf("failed to save setup configuration: %w", err)
}

fmt.Printf("✓ Setup completed successfully!\n")
fmt.Printf(" Workspace: %s\n", tokenDetails.Workspace.Slug)
fmt.Printf(" Project: %s\n", tokenDetails.Project.Slug)
fmt.Printf(" Environment: %s\n", tokenDetails.EnvironmentID)
fmt.Printf(" Path: %s\n", currentPath)
Comment on lines +92 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use ui package functions for consistent output formatting.

Similar to the previous comment, use ui.PrintSuccess and ui.PrintInfo instead of fmt.Printf for consistent output formatting throughout the CLI.

Apply this diff:

-	fmt.Printf("✓ Setup completed successfully!\n")
-	fmt.Printf("  Workspace: %s\n", tokenDetails.Workspace.Slug)
-	fmt.Printf("  Project: %s\n", tokenDetails.Project.Slug)
-	fmt.Printf("  Environment: %s\n", tokenDetails.EnvironmentID)
-	fmt.Printf("  Path: %s\n", currentPath)
+	ui.PrintSuccess("Setup completed successfully!")
+	ui.PrintInfo(fmt.Sprintf("Workspace: %s", tokenDetails.Workspace.Slug))
+	ui.PrintInfo(fmt.Sprintf("Project: %s", tokenDetails.Project.Slug))
+	ui.PrintInfo(fmt.Sprintf("Environment: %s", tokenDetails.EnvironmentID))
+	ui.PrintInfo(fmt.Sprintf("Path: %s", currentPath))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Printf("✓ Setup completed successfully!\n")
fmt.Printf(" Workspace: %s\n", tokenDetails.Workspace.Slug)
fmt.Printf(" Project: %s\n", tokenDetails.Project.Slug)
fmt.Printf(" Environment: %s\n", tokenDetails.EnvironmentID)
fmt.Printf(" Path: %s\n", currentPath)
ui.PrintSuccess("Setup completed successfully!")
ui.PrintInfo(fmt.Sprintf("Workspace: %s", tokenDetails.Workspace.Slug))
ui.PrintInfo(fmt.Sprintf("Project: %s", tokenDetails.Project.Slug))
ui.PrintInfo(fmt.Sprintf("Environment: %s", tokenDetails.EnvironmentID))
ui.PrintInfo(fmt.Sprintf("Path: %s", currentPath))
🤖 Prompt for AI Agents
In cmd/setup.go around lines 92 to 96, replace direct fmt.Printf calls with the
CLI ui package helpers to ensure consistent formatting: use ui.PrintSuccess("✓
Setup completed successfully!") for the success line and ui.PrintInfo for each
detail line (Workspace, Project, Environment, Path), passing formatted strings
or using ui.PrintInfof if available; also remove the fmt import if it becomes
unused.


return nil
}

// runInteractiveSetup handles setup with interactive prompts for user authentication
func runInteractiveSetup(currentPath string, setupStorage *config.SetupStorage, client *enkryptify.Client) error {
ui.ShowBrandHeader()
ui.PrintTitle("🔗 Enkryptify Repository Setup")

if setupStorage.HasSetupForPath(currentPath) {
existingSetup := setupStorage.GetSetupForPath(currentPath)
ui.PrintWarning("Setup already exists for this directory")
Expand All @@ -61,8 +115,6 @@ func runSetup(cmd *cobra.Command, args []string) error {
}
}

client := enkryptify.NewClient()

ui.ShowProgress(1, 3, "Fetching workspaces...")
workspaces, err := client.GetWorkspaces()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

VERSION="0.1.8"
VERSION="0.1.9"
set -e

if [ -t 1 ]; then
Expand Down
18 changes: 18 additions & 0 deletions internal/auth/enkryptify.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"net/http"
"net/url"
"os"
"strings"
"time"

Expand All @@ -28,6 +29,7 @@ const (
RedirectURL = "http://localhost:51823/callback"
CallbackPort = "51823"
DefaultScopes = "openid profile email secrets:read secrets:write"
EnvTokenKey = "ENKRYPTIFY_TOKEN"
)

// EnkryptifyAuth handles authentication with Enkryptify
Expand Down Expand Up @@ -76,6 +78,12 @@ func generateCodeChallenge(verifier string) string {

// Login performs the OAuth login flow with Enkryptify
func (e *EnkryptifyAuth) Login(ctx context.Context) error {
// Check if using environment variable token
if token := os.Getenv(EnvTokenKey); token != "" {
ui.PrintInfo(fmt.Sprintf("Authenticated using %s environment variable", EnvTokenKey))
return nil
}
Comment on lines +81 to +85
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Validate the environment token before accepting it.

The code accepts any non-empty value from ENKRYPTIFY_TOKEN without validation. Consider validating that the token is not empty and optionally verify its format or make a test API call to ensure it's valid before short-circuiting the OAuth flow.

Apply this diff to add basic validation:

 func (e *EnkryptifyAuth) Login(ctx context.Context) error {
 	// Check if using environment variable token
 	if token := os.Getenv(EnvTokenKey); token != "" {
+		// Validate token is not just whitespace
+		if strings.TrimSpace(token) == "" {
+			return fmt.Errorf("invalid %s: token cannot be empty or whitespace", EnvTokenKey)
+		}
 		ui.PrintInfo(fmt.Sprintf("Authenticated using %s environment variable", EnvTokenKey))
 		return nil
 	}

🏁 Script executed:

#!/bin/bash
# Search for all uses of the environment token without validation
rg -nP --type Go 'os\.Getenv\(\s*EnvTokenKey\s*\)' -C3

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Locate all occurrences of os.Getenv(EnvTokenKey) in .go files
rg -n 'os\.Getenv\(\s*EnvTokenKey\s*\)' -C3 --glob '*.go'

Length of output: 1510


🏁 Script executed:

#!/bin/bash
# Search for existing use of strings.TrimSpace in the repo
rg -n 'TrimSpace' --glob '*.go'

Length of output: 144


🏁 Script executed:

#!/bin/bash
# Show import block in enkryptify.go
rg -n 'import \(' -C5 --glob 'internal/auth/enkryptify.go'

Length of output: 341


🏁 Script executed:

sed -n '1,20p' internal/auth/enkryptify.go

Length of output: 281


Trim and reject whitespace-only ENKRYPTIFY_TOKEN

  • Login, IsAuthenticated, and GetAccessToken accept non-empty but whitespace-only tokens.
  • Use token := strings.TrimSpace(os.Getenv(EnvTokenKey)) and return an error if token == "" to avoid invalid values.

Apply in Login (mirror in the other methods):

 func (e *EnkryptifyAuth) Login(ctx context.Context) error {
-	if token := os.Getenv(EnvTokenKey); token != "" {
+	if token := strings.TrimSpace(os.Getenv(EnvTokenKey)); token != "" {
 		ui.PrintInfo(fmt.Sprintf("Authenticated using %s environment variable", EnvTokenKey))
 		return nil
-	}
+	} else if os.Getenv(EnvTokenKey) != "" {
+		return fmt.Errorf("invalid %s: token cannot be empty or whitespace", EnvTokenKey)
+	}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In internal/auth/enkryptify.go around lines 81 to 85, the code reads the
ENKRYPTIFY_TOKEN env var without trimming, allowing whitespace-only values;
update the env var read to use strings.TrimSpace(os.Getenv(EnvTokenKey)) and if
the result is "" return an error (or treat as unauthenticated) instead of
accepting it; apply the same trimmed-check pattern to Login and mirror the
change in IsAuthenticated and GetAccessToken to reject whitespace-only tokens.


// Load configuration
cfg, err := config.LoadConfig()
if err != nil {
Expand Down Expand Up @@ -410,6 +418,11 @@ func (e *EnkryptifyAuth) GetUserInfo(accessToken string) (*UserInfo, error) {

// IsAuthenticated checks if the user is authenticated
func (e *EnkryptifyAuth) IsAuthenticated() (bool, error) {
// Check for environment variable token first (for server environments)
if token := os.Getenv(EnvTokenKey); token != "" {
return true, nil
}
Comment on lines +421 to +424
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ensure consistency with GetAccessToken for empty tokens.

If ENKRYPTIFY_TOKEN is set but empty (e.g., export ENKRYPTIFY_TOKEN=""), this function returns true, but GetAccessToken would return an empty string. This inconsistency could lead to confusing error messages downstream. Consider trimming and checking for non-empty values in both places.

Apply this diff to ensure consistency:

 func (e *EnkryptifyAuth) IsAuthenticated() (bool, error) {
 	// Check for environment variable token first (for server environments)
 	if token := os.Getenv(EnvTokenKey); token != "" {
-		return true, nil
+		return strings.TrimSpace(token) != "", nil
 	}
 	
 	return e.keyring.IsAuthenticated("enkryptify")
 }
🤖 Prompt for AI Agents
In internal/auth/enkryptify.go around lines 421 to 424, the code treats
ENKRYPTIFY_TOKEN as present when the environment variable is set even if empty;
change the env check to trim whitespace and ensure non-empty so it mirrors
GetAccessToken behavior: read os.Getenv(EnvTokenKey), strings.TrimSpace it, and
only return true when the trimmed value is not an empty string (otherwise
continue to other checks or return false).


return e.keyring.IsAuthenticated("enkryptify")
}

Expand All @@ -425,6 +438,11 @@ func (e *EnkryptifyAuth) Logout() error {

// GetAccessToken retrieves the current access token
func (e *EnkryptifyAuth) GetAccessToken() (string, error) {
// Check for environment variable token first (for server environments)
if token := os.Getenv(EnvTokenKey); token != "" {
return token, nil
}
Comment on lines +441 to +444
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add validation for the environment token.

Similar to the concern in Login, this code returns the token without validating it's non-empty or properly formatted. An empty or invalid token would cause downstream API calls to fail with unclear error messages.

Apply this diff to add validation:

 func (e *EnkryptifyAuth) GetAccessToken() (string, error) {
 	// Check for environment variable token first (for server environments)
 	if token := os.Getenv(EnvTokenKey); token != "" {
-		return token, nil
+		token = strings.TrimSpace(token)
+		if token == "" {
+			return "", fmt.Errorf("invalid %s: token cannot be empty or whitespace", EnvTokenKey)
+		}
+		return token, nil
 	}
 	
 	authInfo, err := e.keyring.GetAuthInfo("enkryptify")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check for environment variable token first (for server environments)
if token := os.Getenv(EnvTokenKey); token != "" {
return token, nil
}
func (e *EnkryptifyAuth) GetAccessToken() (string, error) {
// Check for environment variable token first (for server environments)
if token := os.Getenv(EnvTokenKey); token != "" {
token = strings.TrimSpace(token)
if token == "" {
return "", fmt.Errorf("invalid %s: token cannot be empty or whitespace", EnvTokenKey)
}
return token, nil
}
authInfo, err := e.keyring.GetAuthInfo("enkryptify")
// ...
}
🤖 Prompt for AI Agents
In internal/auth/enkryptify.go around lines 441 to 444, the code returns an
environment token without validating it; change this to retrieve the EnvTokenKey
value, check it's non-empty, run it through the same token validation used in
Login (or call the existing validateToken/validateAuthToken helper) and only
return it if validation passes; if empty or invalid, do not return the token and
instead proceed as before or return a clear error indicating the env token is
malformed so downstream calls fail fast with a helpful message.


authInfo, err := e.keyring.GetAuthInfo("enkryptify")
if err != nil {
return "", err
Expand Down
23 changes: 23 additions & 0 deletions internal/providers/enkryptify/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ type SecretValue struct {
Value string `json:"value"`
}

type ProjectTokenResponse struct {
ID string `json:"id"`
Workspace struct {
ID string `json:"id"`
Slug string `json:"slug"`
} `json:"workspace"`
Project struct {
ID string `json:"id"`
Slug string `json:"slug"`
} `json:"project"`
EnvironmentID string `json:"environmentId"`
}
Comment on lines +72 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider removing the unused ID field.

The ID field on line 73 is not used by the consuming code in cmd/setup.go (lines 80-85), which only references Workspace.Slug, Project.Slug, and EnvironmentID. Unless this field serves a future purpose, consider removing it to keep the API surface minimal.

🤖 Prompt for AI Agents
internal/providers/enkryptify/provider.go around lines 72 to 83: remove the
unused top-level ID field from ProjectTokenResponse (the code only consumes
Workspace.Slug, Project.Slug and EnvironmentID), so delete the ID line and its
json tag, keep Workspace, Project and EnvironmentID intact, then run go fmt / go
vet and the test suite to ensure no references remain.


func NewClient() *Client {
return &Client{
httpClient: &http.Client{Timeout: 30 * time.Second},
Expand Down Expand Up @@ -154,4 +167,14 @@ func (c *Client) GetSecrets(workspaceSlug, projectSlug, environmentID string) ([
}

return secrets, nil
}

func (c *Client) GetProjectTokenDetails() (*ProjectTokenResponse, error) {
var tokenDetails ProjectTokenResponse

if err := c.makeRequest("GET", "/auth/project-token", &tokenDetails); err != nil {
return nil, fmt.Errorf("failed to get project token details: %w", err)
}

return &tokenDetails, nil
}
Comment on lines +172 to 180
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider validating the API response.

The method correctly uses the existing makeRequest helper and error handling pattern. However, consider adding validation for the returned data to ensure that critical fields like Workspace.Slug, Project.Slug, and EnvironmentID are non-empty, as these are required by the consuming setup logic in cmd/setup.go.

Apply this diff to add validation:

 func (c *Client) GetProjectTokenDetails() (*ProjectTokenResponse, error) {
 	var tokenDetails ProjectTokenResponse
 
 	if err := c.makeRequest("GET", "/auth/project-token", &tokenDetails); err != nil {
 		return nil, fmt.Errorf("failed to get project token details: %w", err)
 	}
+
+	// Validate required fields
+	if tokenDetails.Workspace.Slug == "" || tokenDetails.Project.Slug == "" || tokenDetails.EnvironmentID == "" {
+		return nil, fmt.Errorf("incomplete project token details: missing required fields")
+	}
 
 	return &tokenDetails, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *Client) GetProjectTokenDetails() (*ProjectTokenResponse, error) {
var tokenDetails ProjectTokenResponse
if err := c.makeRequest("GET", "/auth/project-token", &tokenDetails); err != nil {
return nil, fmt.Errorf("failed to get project token details: %w", err)
}
return &tokenDetails, nil
}
func (c *Client) GetProjectTokenDetails() (*ProjectTokenResponse, error) {
var tokenDetails ProjectTokenResponse
if err := c.makeRequest("GET", "/auth/project-token", &tokenDetails); err != nil {
return nil, fmt.Errorf("failed to get project token details: %w", err)
}
// Validate required fields
if tokenDetails.Workspace.Slug == "" || tokenDetails.Project.Slug == "" || tokenDetails.EnvironmentID == "" {
return nil, fmt.Errorf("incomplete project token details: missing required fields")
}
return &tokenDetails, nil
}
🤖 Prompt for AI Agents
In internal/providers/enkryptify/provider.go around lines 172 to 180, the
GetProjectTokenDetails function returns API data without validating required
fields; add post-request validation to check that tokenDetails.Workspace.Slug,
tokenDetails.Project.Slug, and tokenDetails.EnvironmentID are non-empty and
return a descriptive error if any are missing. Keep the existing makeRequest
call and error wrapping, then perform simple nil/empty-string checks and
fmt.Errorf to surface which field is invalid (e.g., "missing workspace slug"),
ensuring callers receive a clear failure instead of corrupt data.