aboutsummaryrefslogtreecommitdiff
path: root/src/commands/auth.rs
blob: 08588dd41428b0877aa82c30190bb5a829b4a639 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use display::DisplayInfo;
use tw;
use std;
use std::collections::HashMap;
use hyper;
use ::Queryer;

use commands::Command;

static FAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/create.json";
static UNFAV_TWEET_URL: &str = "https://api.twitter.com/1.1/favorites/destroy.json";

pub static AUTH: Command = Command {
    keyword: "auth",
    params: 0,
    exec: auth,
    param_str: "",
    // TODO: support account-specific auth? profile name spec?
    help_str: "Begin PIN-based account auth process. Second step is the `pin` command."
};

static OAUTH_REQUEST_TOKEN_URL: &str = "https://api.twitter.com/oauth/request_token";
static OAUTH_AUTHORIZE_URL: &str = "https://api.twitter.com/oauth/authorize";
static OAUTH_ACCESS_TOKEN_URL: &str = "https://api.twitter.com/oauth/access_token";

fn auth(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, display_info: &mut DisplayInfo) {
    // step 0: get an oauth token.
    // https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token with
    // callback set to oob so the user will later get a PIN.
    // step 1: now present the correect oauth/authorize URL
    // this is as far as auth can get (rest depends on user PIN'ing with the right thing)
    let res = queryer.raw_issue_request(
        ::signed_api_req(
            OAUTH_REQUEST_TOKEN_URL,
            &vec![("oauth_callback", "oob")],
            hyper::Method::Post,
            &tweeter.app_key
        )
    );
    match res {
        Ok(bytes) =>
            match std::str::from_utf8(&bytes) {
                Ok(url) => {
                    let parts: Vec<Vec<&str>> = url.split("&").map(|part| part.split("=").collect()).collect();
                    let mut as_map: HashMap<&str, &str> = HashMap::new();
                    for part in parts {
                        as_map.insert(part[0], part[1]);
                    }
                    tweeter.WIP_auth = Some(tw::Credential {
                        key: as_map["oauth_token"].to_owned(),
                        secret: as_map["oauth_token_secret"].to_owned()
                    });
                    display_info.status(format!("Now enter `pin` with the code at {}?oauth_token={}", OAUTH_AUTHORIZE_URL, as_map["oauth_token"]));
                }
                Err(_) =>
                    display_info.status("couldn't rebuild url".to_owned())
            },
        Err(e) =>
            display_info.status(format!("error starting auth: {}", e))
    };
}

pub static PIN: Command = Command {
    keyword: "pin",
    params: 1,
    exec: pin,
    param_str: " <PIN>",
    help_str: "Complete account auth. Enter PIN from prior `auth` link to connect an account."
};

fn pin(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, display_info: &mut DisplayInfo) {
    if tweeter.WIP_auth.is_none() {
        display_info.status("Begin authorizing an account with `auth` first.".to_owned());
        return;
    }

    let res = queryer.raw_issue_request(
        ::signed_api_req_with_token(
            OAUTH_ACCESS_TOKEN_URL,
            &vec![("oauth_verifier", &line)],
            hyper::Method::Post,
            &tweeter.app_key,
            &tweeter.WIP_auth.clone().unwrap()
        )
    );
    match res {
        Ok(bytes) =>
            match std::str::from_utf8(&bytes) {
                Ok(url) => {
                    let parts: Vec<Vec<&str>> = url.split("&").map(|part| part.split("=").collect()).collect();
                    let mut as_map: HashMap<&str, &str> = HashMap::new();
                    for part in parts {
                        as_map.insert(part[0], part[1]);
                    }
                    // turns out the "actual" oauth creds are different
                    // TODO: profile names?
                    /*
                     * Option 1:
                     *  ask user.
                     *  yes, but I want this to be optional though (auth, pin 1234, profile now
                     *  named main or after you or something)
                     * Option 2:
                     *  make a request for profile settings when auth succeeds
                     *  this becomes the fallback when nothing is provided in option 1
                     *  what happens when you successfully auth, internet drops, and you fail to
                     *  request settings?
                     *
                     *  fallback to asking user to name the profile, i guess?
                     */
                    let user_credential = tw::Credential {
                        key: as_map["oauth_token"].to_owned(),
                        secret: as_map["oauth_token_secret"].to_owned()
                    };

                    match queryer.do_api_get_noparam(::ACCOUNT_SETTINGS_URL, &tweeter.app_key, &user_credential) {
                        Ok(settings) => {
                            let user_handle = settings["screen_name"].as_str().unwrap().to_owned();
                            /*
                             * kinda gotta hand-crank this to set up the user profile...
                             * largely the same logic as `look_up_user`.
                             */
                            let looked_up_user = queryer.do_api_get(
                                ::USER_LOOKUP_URL,
                                &vec![("screen_name", &user_handle)],
                                &tweeter.app_key,
                                &user_credential
                            ).and_then(|json| tw::user::User::from_json(json));

                            let profile_user = match looked_up_user {
                                Ok(user) => user,
                                Err(e) => {
                                    display_info.status("Couldn't look up you up - handle and display name are not set. You'll have to manually adjust cache/profile.json.".to_owned());
                                    display_info.status(format!("Lookup error: {}", e));
                                    tw::user::User::default()
                                }
                            };
                            display_info.status(format!("wtf at auth curr_profile is {:?}", tweeter.curr_profile));
                            if tweeter.curr_profile.is_none() {
                                tweeter.curr_profile = Some(user_handle.to_owned());
                            }
                            tweeter.add_profile(tw::TwitterProfile::new(user_credential, profile_user), Some(user_handle.clone()), display_info);
                            tweeter.WIP_auth = None;
                            tweeter.state = tw::AppState::Reconnect(user_handle);
                            display_info.status("Looks like you authed! Connecting...".to_owned());
                        },
                        Err(_) => {
                            display_info.status("Auth failed - couldn't find your handle.".to_owned());
                        }
                    };
                },
                Err(_) =>
                    display_info.status("couldn't rebuild url".to_owned())
            },
        Err(e) =>
            display_info.status(format!("pin submission error: {}", e))
    };
}