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
5 changes: 5 additions & 0 deletions .changeset/violet-emus-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cipherstash/cli": minor
---

Fixed issue where the wizard was checking CipherStash auth based on path and now leverages the auth npm package.
10 changes: 1 addition & 9 deletions packages/cli/src/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@ import auth from '@cipherstash/auth'
import * as p from '@clack/prompts'
const { beginDeviceCodeFlow, bindClientDevice } = auth

// TODO(CIP-2996): @cipherstash/auth@0.35.0 (latest on npm as of 2026-04-17)
// writes the device-code token to a profile dir that later auth reads do not
// find, so subsequent CLI invocations fail to resolve credentials. The fix is
// upstream — bump this catalog pin once a newer @cipherstash/auth that
// aligns the write + read paths ships. Do not paper over it in the CLI:
// divergent profile-dir logic across tools is exactly what caused the
// regression in the first place.

// TODO: pull from the CTS API
export const regions = [
{ value: 'us-east-1.aws', label: 'us-east-1 (Virginia, USA)' },
Expand Down Expand Up @@ -53,7 +45,7 @@ export async function login(region: string, _referrer: string | undefined) {

s.start('Waiting for authorization...')
const auth = await pending.pollForToken()
s.stop('Authenticated! Token saved to ~/.cipherstash/auth.json')
s.stop('Authenticated!')

p.log.info(
`Token expires at: ${new Date(auth.expiresAt * 1000).toISOString()}`,
Expand Down
28 changes: 22 additions & 6 deletions packages/cli/src/commands/wizard/lib/prerequisites.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { homedir } from 'node:os'
import auth from '@cipherstash/auth'

interface PrerequisiteResult {
ok: boolean
Expand All @@ -12,18 +12,17 @@ interface PrerequisiteResult {
* 1. CipherStash authentication exists
* 2. stash.config.ts exists in the project
*/
export function checkPrerequisites(cwd: string): PrerequisiteResult {
export async function checkPrerequisites(
cwd: string,
): Promise<PrerequisiteResult> {
const missing: string[] = []

// Check CipherStash auth
const authPath = resolve(homedir(), '.cipherstash', 'auth.json')
if (!existsSync(authPath)) {
if (!(await hasCredentials())) {
missing.push(
'Not authenticated with CipherStash. Run: npx @cipherstash/cli auth login',
)
}

// Check stash.config.ts
if (!findStashConfig(cwd)) {
missing.push(
'No stash.config.ts found. Run: npx @cipherstash/cli db install',
Expand All @@ -33,6 +32,23 @@ export function checkPrerequisites(cwd: string): PrerequisiteResult {
return { ok: missing.length === 0, missing }
}

// Ask @cipherstash/auth to resolve credentials via its own profile logic
// rather than probing a hardcoded path — the on-disk layout has shifted
// between auth versions and duplicating it in the CLI is what caused
// CIP-2996 in the first place.
async function hasCredentials(): Promise<boolean> {
try {
await auth.AutoStrategy.detect().getToken()
return true
} catch (error) {
const code = (error as { code?: string } | null)?.code
if (code === 'NOT_AUTHENTICATED' || code === 'MISSING_WORKSPACE_CRN') {
return false
}
throw error
}
}

/** Walk up from cwd to find stash.config.ts. */
function findStashConfig(startDir: string): string | undefined {
let dir = resolve(startDir)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/wizard/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function run(options: RunOptions) {
)

// Phase 1: Prerequisites
const prereqs = checkPrerequisites(options.cwd)
const prereqs = await checkPrerequisites(options.cwd)
if (!prereqs.ok) {
trackPrerequisiteMissing(prereqs.missing)
p.log.error('Missing prerequisites:')
Expand Down
Loading