Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ bump. Currently experimental: sync plugins.

# 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>]`.

# v0.2.7

* feat: `script` sync steps now receive `ICP_CLI_ENVIRONMENT`, `ICP_CLI_NETWORK`, `ICP_CLI_CID` (the current canister's principal), and `ICP_CLI_CID_<NAME>` (every canister's principal) as environment variables.
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