upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/login.rs
blob: f07b8f13bf71df4dba8108ea6bf4e848db5fcf81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use anyhow::{bail, Context, Result};
use nostr::{prelude::FromSkStr, secp256k1::XOnlyPublicKey};
use zeroize::Zeroize;

#[cfg(not(test))]
use crate::client::Client;
#[cfg(test)]
use crate::client::MockConnect;
use crate::{
    cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms},
    config::{ConfigManagement, ConfigManager, UserRef},
    key_handling::{
        encryption::{EncryptDecrypt, Encryptor},
        users::{UserManagement, UserManager},
    },
};

/// handles the encrpytion and storage of key material
pub async fn launch(
    nsec: &Option<String>,
    password: &Option<String>,
    #[cfg(test)] client: Option<&MockConnect>,
    #[cfg(not(test))] client: Option<&Client>,
) -> Result<(nostr::Keys, UserRef)> {
    // if nsec parameter
    let key = if let Some(nsec_unwrapped) = nsec {
        // get key or fail without prompts
        let key = nostr::Keys::from_sk_str(nsec_unwrapped).context("invalid nsec parameter")?;

        // if password, add user to enable password login in future
        if password.is_some() {
            UserManager::default()
                .add(nsec, password)
                .context("could not store identity")?;
        } else {
            UserManager::default().add_user_to_config(key.public_key(), None, false)?;
        }
        key
    } else {
        let cfg = ConfigManager
            .load()
            .context("failed to load application config")?;
        // if encrypted nsec present
        if cfg.users.last().is_some() && !cfg.users.last().unwrap().encrypted_key.is_empty() {
            // unfortunately this line is unstable in rust:
            // if let Some(user) = cfg.users.last()  && !user.encrypted_key.is_empty() {
            let user = cfg.users.last().unwrap();
            let mut pass = if let Some(p) = password.clone() {
                p
            } else {
                println!("login as {}", &user.metadata.name);
                Interactor::default()
                    .password(PromptPasswordParms::default().with_prompt("password"))
                    .context("failed to get password input from interactor.password")?
            };

            let key_result = Encryptor
                .decrypt_key(&user.encrypted_key, pass.as_str())
                .context("failed to decrypt key with provided password");
            pass.zeroize();

            key_result.context(format!("failed to log in as {}", &user.metadata.name))?
        }
        // no encrypted nsec present
        else {
            // no nsec but password supplied
            if password.is_some() {
                bail!("no nsec available to decrypt with specified password");
            }
            // otherwise add new user with nsec and password prompts
            UserManager::default()
                .add(nsec, password)
                .context("failed to add user")?
        }
    };

    // get user details
    let user_ref = if let Some(client) = client {
        get_user_details(&key.public_key(), client).await?
    } else {
        // this will get user details with name as npub
        UserManager::default()
            .get_user_from_cache(&key.public_key())?
            .clone()
    };

    // print logged in
    println!("logged in as {}", user_ref.metadata.name);

    Ok((key, user_ref.clone()))
}

async fn get_user_details(
    public_key: &XOnlyPublicKey,
    #[cfg(test)] client: &crate::client::MockConnect,
    #[cfg(not(test))] client: &Client,
) -> Result<UserRef> {
    let term = console::Term::stdout();
    term.write_line("searching for your details...")?;
    let user_manager = UserManager::default();
    let user_ref = user_manager
        .get_user(
            client,
            public_key,
            // use cache for 3 minutes
            3 * 60,
        )
        .await?;
    term.clear_last_lines(1)?;
    if user_ref.metadata.created_at.eq(&0) {
        println!("cannot find your account metadata (name, etc) on relays",);
        // TODO use secondary fallback list of relays.
        // TODO better reporting of what relays were checked and what the user
        //      here is a starter:
        //      cannot find account details on relays:
        //       - purplepages.xyz
        //       - fallbackrelay1
        //       - ...
        //      would you like to:
        //        [-] proceed anyway
        //         - add custom fallback relays
    } else if user_ref.relays.created_at.eq(&0) {
        println!(
            "cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience."
        );
        // TODO better guidance on how to do this
    }
    Ok(user_ref)
}