Skip to content

Feat/tvc passkeys#161

Draft
emostov wants to merge 4 commits into
mainfrom
feat/tvc-passkeys
Draft

Feat/tvc passkeys#161
emostov wants to merge 4 commits into
mainfrom
feat/tvc-passkeys

Conversation

@emostov

@emostov emostov commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

TODO

  • test fully locally
  • support popping open browser to make initial login smoother with bootstrapping authenticator

@richardpringle richardpringle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure how useful this review is since this is in draft...

Basically all nits

Comment thread tvc/src/client.rs
if let Some(session) = StoredPasskeySession::load(alias, org_config).await? {
return Ok((
org_config.id.clone(),
org_config.api_base_url.clone(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you need to clone here?

Comment thread tvc/src/commands/auth.rs Outdated
}

#[derive(Debug, ClapArgs)]
pub struct ListArgs {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure why we don't have lint for this...

Suggested change
pub struct ListArgs {}
pub struct ListArgs;

Comment thread tvc/src/commands/auth.rs
pub enum AuthCommands {
/// Manage passkey authenticators.
Passkey(PasskeyArgs),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might be worth mentioning other types of planned auth? Otherwise this seems over-abstracted.

(This is a nit though)

Comment thread tvc/src/commands/login.rs

if args.create_org {
return run_dashboard_signup_handoff(args).await;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens if the passkey and create-org options are passed in? With the --create-org arg just get ignored?

We should have proper validation before we get here

Comment thread tvc/src/commands/login.rs
async fn run_passkey_login(args: Args, is_non_interactive: bool) -> Result<()> {
if is_non_interactive {
bail!("passkey authentication requires an interactive terminal; remove --non-interactive");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's cleaner to do validation outside of the function instead of early returning immediately

Comment thread tvc/src/commands/login.rs
None => bail!(
"Organization is required for passkey login. Pass --org or run `tvc login` first."
),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let-else?

Comment thread tvc/src/commands/login.rs
None => {
bail!("Organization '{org_query}' not found. Run `tvc login` without --passkey first.")
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another let-else

Comment thread tvc/src/passkey/mod.rs Outdated

fn attest(&self, _challenge: &str, _name: &str) -> Result<WebAuthnAttestation> {
bail!("passkey registration is not implemented for this transport")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not use subtraits here?

Comment thread tvc/src/passkey/mod.rs

impl WebAuthnStamper<FixtureCeremony> {
/// Construct a deterministic stamper for CI tests without hardware.
pub fn new_for_tests(assertion: WebAuthnAssertion) -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be behind a feature flag?

Comment thread tvc/tests/auth_env.rs Outdated
let header = stamper.stamp(b"{}").unwrap();

assert_eq!(header.name, "X-Stamp");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why put these in a separate test file?

@r-n-o r-n-o left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure I follow the intent of this PR. Is the idea that we want to support popping open a browser / create a new org from the CLI? Or do we want to support true passkey auth where actions are authorized by a CLI-initiated passkey prompt? (and the user would then have to tap their yubikey or use touchID to allow activities like "CREATE_DEPLOYMENT" to be signed)

If we're going for easier onboarding I believe we should work with the dashboard APIs and make them more CLI friendly so that a passkey isn't required to create an org, potentially? Might be a medium/large project I'm not sure. And if we want to work on logging into the CLI for the first time (with an existing org), I think the challenge will be to prompt users with the right passkey. E.g. if a user creates their org with a passkey in Chrome vs. system passkey vs. 1password passkey we'll run into a serious lack of context if we try to pull the same passkey. Another challenge is the fact that we have non-resident keys; so you need to do email verification first, and then you can pull a list of credential IDs. Without the credential IDs from the server you can't prompt the user for a passkey signature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants