Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased

* feat: Password-protected identities now only need your password once per session. The session length defaults to 5 minutes and can be changed with `icp settings session-length <DURATION>` (e.g. `30m`, `1h`) or turned off with `icp settings session-length disabled`. You can also explicitly create or refresh a session with `icp identity login <NAME> [--duration <DURATION>]`.
* fix: `icp` no longer picks up a stale inherited `$PWD` when launched as a subprocess via `chdir(2)` + `execve` (e.g. from a test harness). The logical `$PWD` path is now validated against `getcwd()` by inode before use, preserving symlink-aware project root discovery while ignoring stale values.

# v0.2.6
Expand Down
101 changes: 0 additions & 101 deletions crates/icp-cli/src/commands/identity/login.rs

This file was deleted.

5 changes: 2 additions & 3 deletions crates/icp-cli/src/commands/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ pub(crate) mod export;
pub(crate) mod import;
pub(crate) mod link;
pub(crate) mod list;
pub(crate) mod login;
pub(crate) mod new;
pub(crate) mod principal;
pub(crate) mod reauth;
pub(crate) mod rename;

/// Manage your identities
Expand All @@ -26,8 +26,7 @@ pub(crate) enum Command {
#[command(subcommand)]
Link(link::Command),
List(list::ListArgs),
#[command(hide = true)] // todo remove when II login is out of beta
Login(login::LoginArgs),
Reauth(reauth::ReauthArgs),
New(new::NewArgs),
Principal(principal::PrincipalArgs),
Rename(rename::RenameArgs),
Expand Down
169 changes: 169 additions & 0 deletions crates/icp-cli/src/commands/identity/reauth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use std::time::Duration;

use clap::Args;
use icp::{
context::Context,
identity::{
key,
manifest::{IdentityList, IdentitySpec, PemFormat},
},
settings::Settings,
};
use snafu::{OptionExt, ResultExt, Snafu};
use tracing::info;

use crate::commands::identity::{delegation::sign::DurationArg, link::ii};

/// Re-authenticate an Internet Identity delegation or create a PEM session delegation
#[derive(Debug, Args)]
pub(crate) struct ReauthArgs {
/// Name of the identity to re-authenticate
name: String,

/// Session delegation duration (e.g. "30m", "8h", "1d"). Note that 2m extra is
/// added when creating the delegation to account for clock drift.
/// Required for PEM identities when session caching is disabled in settings.
/// Not applicable for Internet Identity (yet).
#[arg(long)]
duration: Option<DurationArg>,
}

pub(crate) async fn exec(ctx: &Context, args: &ReauthArgs) -> Result<(), LoginError> {
let spec = ctx
.dirs
.identity()?
.with_read(async |dirs| {
let list = IdentityList::load_from(dirs)?;
list.identities
.get(&args.name)
.cloned()
.context(IdentityNotFoundSnafu { name: &args.name })
})
.await??;

match spec {
IdentitySpec::InternetIdentity {
algorithm,
storage,
host,
..
} => {
if args.duration.is_some() {
return DurationSnafu { name: &args.name }.fail();
}

let password_func = ctx.password_func.clone();
let der_public_key = ctx
.dirs
.identity()?
.with_read(async |dirs| {
key::load_ii_session_public_key(dirs, &args.name, &algorithm, &storage, || {
password_func()
})
})
.await?
.context(LoadSessionKeySnafu)?;

let chain = ii::recv_delegation(&host, &der_public_key)
.await
.context(PollSnafu)?;

ctx.dirs
.identity()?
.with_write(async |dirs| key::update_ii_delegation(dirs, &args.name, &chain))
.await?
.context(UpdateDelegationSnafu)?;

info!("Identity `{}` re-authenticated", args.name);
}

IdentitySpec::Pem {
format: PemFormat::Pbes2,
algorithm,
..
} => {
let duration = match &args.duration {
Some(d) => Duration::from_nanos(d.as_nanos()) + Duration::from_secs(5 * 60),
None => {
let settings = ctx
.dirs
.settings()?
.with_read(async |dirs| Settings::load_from(dirs))
.await??;
settings
.session_length
.map(|m| Duration::from_secs((u64::from(m) + 2) * 60))
.context(DurationRequiredSnafu { name: &args.name })?
}
};

let password_func = ctx.password_func.clone();
ctx.dirs
.identity()?
.with_read(async |dirs| {
key::create_explicit_pem_session(
dirs,
&args.name,
&algorithm,
|| password_func(),
duration,
)
})
.await?
.context(CreatePemSessionSnafu)?;

info!("Session delegation created for identity `{}`", args.name);
}
_ => {
return UnsupportedIdentityTypeSnafu { name: &args.name }.fail();
}
}

Ok(())
}

#[derive(Debug, Snafu)]
pub(crate) enum LoginError {
#[snafu(transparent)]
LockDir { source: icp::fs::lock::LockError },

#[snafu(transparent)]
LoadManifest {
source: icp::identity::manifest::LoadIdentityManifestError,
},

#[snafu(transparent)]
LoadSettings {
source: icp::settings::LoadSettingsError,
},

#[snafu(display("no identity found with name `{name}`"))]
IdentityNotFound { name: String },

#[snafu(display("`--duration` cannot be used with Internet Identity `{name}`"))]
Duration { name: String },

#[snafu(display(
"session caching is disabled; specify `--duration` to create a session delegation for `{name}`"
))]
DurationRequired { name: String },

#[snafu(display("identity `{name}` does not support logins"))]
UnsupportedIdentityType { name: String },

#[snafu(display("failed to load II session key"))]
LoadSessionKey { source: key::LoadIdentityError },

#[snafu(display("failed during II authentication"))]
Poll { source: ii::IiRecvError },

#[snafu(display("failed to update delegation"))]
UpdateDelegation {
source: key::UpdateIiDelegationError,
},

#[snafu(display("failed to create PEM session delegation"))]
CreatePemSession {
source: key::CreateExplicitPemSessionError,
},
}
Loading
Loading