-
Notifications
You must be signed in to change notification settings - Fork 3
feat: implement auth into stash cli #330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cipherstash/stack": minor | ||
| --- | ||
|
|
||
| Implement stack auth into stash cli flow. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { login } from './login.js' | ||
| import { bindDevice } from './login.js' | ||
|
|
||
| const HELP = ` | ||
| Usage: stash auth <command> | ||
|
|
||
| Commands: | ||
| login Authenticate with CipherStash | ||
|
|
||
| Examples: | ||
| stash auth login | ||
| `.trim() | ||
|
|
||
| export async function authCommand(args: string[]) { | ||
| const subcommand = args[0] | ||
|
|
||
| if (!subcommand || subcommand === '--help' || subcommand === '-h') { | ||
| console.log(HELP) | ||
| return | ||
| } | ||
|
|
||
| switch (subcommand) { | ||
| case 'login': | ||
| await login() | ||
| await bindDevice() | ||
| break | ||
| default: | ||
| console.error(`Unknown auth command: ${subcommand}\n`) | ||
| console.log(HELP) | ||
| process.exit(1) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as p from '@clack/prompts' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import auth from '@cipherstash/auth' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { beginDeviceCodeFlow, bindClientDevice } = auth | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function login() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const s = p.spinner() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pending = await beginDeviceCodeFlow('ap-southeast-2.aws', 'cli') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
calvinbrewer marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.info(`Your code is: ${pending.userCode}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.info(`Visit: ${pending.verificationUriComplete}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.info(`Code expires in: ${pending.expiresIn}s`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const opened = pending.openInBrowser() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!opened) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.warn('Could not open browser — please visit the URL above manually.') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.start('Waiting for authorization...') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const auth = await pending.pollForToken() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.stop('Authenticated! Token saved to ~/.cipherstash/auth.json') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `Token expires at: ${new Date(auth.expiresAt * 1000).toISOString()}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether login()/bindDevice() failures are handled at call sites.
set -euo pipefail
echo "== login()/bindDevice() call sites =="
rg -nP --type=ts -C2 '\b(await\s+)?(login|bindDevice)\s*\('
echo
echo "== try/catch around init pipeline execution =="
rg -nP --type=ts -C4 'for\s*\(const step of STEPS\)|catch\s*\(err\)'
echo
echo "== direct process exits in command helpers =="
rg -nP --type=ts -C2 '\bprocess\.exit\s*\('Repository: cipherstash/stack Length of output: 21213 🏁 Script executed: cat -n packages/stack/src/bin/commands/auth/login.tsRepository: cipherstash/stack Length of output: 1556 Add error handling and fix variable shadowing in Lines 8 and 20 can reject without a catch block. If line 20 rejects, the spinner started at line 19 is never stopped. Additionally, line 20 shadows the imported Wrap the async operations in a try/catch block, stop the spinner on error, and rename the local variable to avoid shadowing: Fix export async function login() {
const s = p.spinner()
-
- const pending = await beginDeviceCodeFlow('ap-southeast-2.aws', 'cli')
-
- p.log.info(`Your code is: ${pending.userCode}`)
- p.log.info(`Visit: ${pending.verificationUriComplete}`)
- p.log.info(`Code expires in: ${pending.expiresIn}s`)
-
- const opened = pending.openInBrowser()
- if (!opened) {
- p.log.warn('Could not open browser — please visit the URL above manually.')
- }
-
- s.start('Waiting for authorization...')
- const auth = await pending.pollForToken()
- s.stop('Authenticated! Token saved to ~/.cipherstash/auth.json')
-
- p.log.info(
- `Token expires at: ${new Date(auth.expiresAt * 1000).toISOString()}`,
- )
+ try {
+ const pending = await beginDeviceCodeFlow('ap-southeast-2.aws', 'cli')
+
+ p.log.info(`Your code is: ${pending.userCode}`)
+ p.log.info(`Visit: ${pending.verificationUriComplete}`)
+ p.log.info(`Code expires in: ${pending.expiresIn}s`)
+
+ const opened = pending.openInBrowser()
+ if (!opened) {
+ p.log.warn('Could not open browser — please visit the URL above manually.')
+ }
+
+ s.start('Waiting for authorization...')
+ const token = await pending.pollForToken()
+ s.stop('Authenticated! Token saved to ~/.cipherstash/auth.json')
+
+ p.log.info(
+ `Token expires at: ${new Date(token.expiresAt * 1000).toISOString()}`,
+ )
+ } catch (error) {
+ s.stop('Authentication failed.')
+ p.log.error(error instanceof Error ? error.message : 'Unknown error')
+ throw error
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function bindDevice() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const s = p.spinner() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.start('Binding device to the default Keyset...') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await bindClientDevice() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.stop('Your device has been bound to the default Keyset!') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.stop('Failed to bind your device to the default Keyset!') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| p.log.error(error instanceof Error ? error.message : 'Unknown error') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid Line 38 hard-exits from a reusable helper, which makes orchestration brittle. Line 37 logs uncontrolled Suggested change } catch (error) {
s.stop('Failed to bind your device to the default Keyset!')
- p.log.error(error instanceof Error ? error.message : 'Unknown error')
- process.exit(1)
+ p.log.error('Failed to bind device to the default Keyset.')
+ throw error instanceof Error
+ ? new Error('Device binding failed')
+ : new Error('Device binding failed')
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { bindDevice, login } from '../../auth/login.js' | ||
| import type { InitProvider, InitState, InitStep } from '../types.js' | ||
|
|
||
| export const authenticateStep: InitStep = { | ||
| id: 'authenticate', | ||
| name: 'Authenticate with CipherStash', | ||
| async run(state: InitState, _provider: InitProvider): Promise<InitState> { | ||
| await login() | ||
| await bindDevice() | ||
| return { ...state, authenticated: true } | ||
| }, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging raw subcommand input.
subcommandis user-provided; echoing it directly can leak plaintext if sensitive data is pasted by mistake.Proposed fix
As per coding guidelines, "Do NOT log plaintext; the library never logs plaintext by design and logs should never leak sensitive data".
📝 Committable suggestion
🤖 Prompt for AI Agents