Skip to content
This repository has been archived by the owner on Aug 3, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1471 from cloudflare/josh/login
Browse files Browse the repository at this point in the history
Wrangler Login
  • Loading branch information
jspspike authored Aug 3, 2020
2 parents 520a10c + 5349468 commit e048c15
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
if: matrix.build == 'linux'
run: |
rustup target add ${{ env.LINUX_TARGET }}
cargo build --release --target ${{ env.LINUX_TARGET }} --features vendored-openssl
cargo build --release --target ${{ env.LINUX_TARGET }}
- name: Build (MacOS)
if: matrix.build == 'macos'
Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ config = "0.10.1"
console = "0.11.3"
dirs = "3.0.1"
env_logger = "0.7.1"
eventual = "0.1.7"
exitfailure = "0.5.1"
failure = "0.1.8"
flate2 = "1.0.16"
Expand All @@ -37,7 +38,7 @@ lazy_static = "1.4.0"
log = "0.4.11"
notify = "4.0.15"
number_prefix = "0.4.0"
openssl = { version = '0.10.29', optional = true }
openssl = { version = "0.10.29", optional = true }
percent-encoding = "2.1.0"
predicates = "1.0.5"
prettytable-rs = "0.8.0"
Expand Down Expand Up @@ -67,10 +68,12 @@ fs_extra = "1.1.0"
predicates = "1.0.5"

[features]
# We build Linux binaries with vendored openssl
# to avoid SSL issues
# OpenSSL is vendored by default, can use system OpenSSL through feature flag.
default = ['openssl/vendored']
# Keeping feature for users already using this feature flag
vendored-openssl = ['openssl/vendored']
sys-openssl = ['openssl']

# Treat compiler warnings as a build error.
# This only runs in CI by default
strict = []
strict = ['openssl/vendored']
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ $ wrangler publish

Additionally, you can configure different [environments](https://developers.cloudflare.com/workers/tooling/wrangler/configuration/environments).


### 🔓 `login`

Authenticate Wrangler with your Cloudflare login. This will prompt you with a Cloudflare account login page and is the alternative to `wrangler config`.

### 🔧 `config`

Configure your global Cloudflare user. This is an interactive command that will prompt you for your API token:
Authenticate Wrangler with a Cloudflare API Token. This is an interactive command that will prompt you for your API token:

```bash
wrangler config
Expand Down
5 changes: 5 additions & 0 deletions src/commands/login.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::login;

pub fn run() -> Result<(), failure::Error> {
login::run()
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod dev;
pub mod generate;
pub mod init;
pub mod kv;
pub mod login;
mod preview;
pub mod publish;
pub mod route;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod http;
pub mod install;
pub mod installer;
pub mod kv;
pub mod login;
pub mod settings;
pub mod sites;
pub mod tail;
Expand Down
103 changes: 103 additions & 0 deletions src/login/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use eventual::Timer;
use indicatif::{ProgressBar, ProgressStyle};
use openssl::base64;
use openssl::rsa::{Padding, Rsa};
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use serde::Deserialize;
use std::collections::HashMap;
use std::str;

use crate::commands::config::global_config;
use crate::settings::global_user::GlobalUser;
use crate::terminal::{interactive, open_browser};

pub fn run() -> Result<(), failure::Error> {
let rsa = Rsa::generate(1024)?;
let pubkey = rsa.public_key_to_pem_pkcs1()?;

// Convert key to string and remove header and footer
let pubkey_str = str::from_utf8(&pubkey)?;
let pubkey_filtered = pubkey_str
.lines()
.filter(|line| !line.starts_with("---"))
.fold(String::new(), |mut data, line| {
data.push_str(&line);
data
});
let pubkey_encoded = percent_encode(pubkey_filtered.as_bytes(), NON_ALPHANUMERIC).to_string();

let browser_permission =
interactive::confirm("Allow Wrangler to open a page in your browser?")?;
if !browser_permission {
failure::bail!("In order to log in you must allow Wrangler to open your browser. If you don't want to do this consider using `wrangler config`");
}

open_browser(&format!(
"https://dash.cloudflare.com/wrangler?key={0}",
pubkey_encoded
))?;

let encrypted_token_str = poll_token(pubkey_filtered)?;
let encrypted_token = base64::decode_block(encrypted_token_str.as_str())?;
let mut token_bytes: [u8; 128] = [0; 128];

rsa.private_decrypt(&encrypted_token, &mut token_bytes, Padding::PKCS1)?;
let token = str::from_utf8(&token_bytes)?.trim_matches(char::from(0));

let user = GlobalUser::TokenAuth {
api_token: token.to_string(),
};
global_config(&user, true)?;

Ok(())
}

#[derive(Deserialize)]
struct TokenResponse {
result: String,
}

/// Poll for token, bail after 500 seconds.
fn poll_token(token_id: String) -> Result<String, failure::Error> {
let mut request_params = HashMap::new();
request_params.insert("token-id", token_id);

let client = reqwest::blocking::Client::new();
let timer = Timer::new().interval_ms(1000).iter();

let style = ProgressStyle::default_spinner().template("{spinner} {msg}");
let spinner = ProgressBar::new_spinner().with_style(style);
spinner.set_message("Waiting for API token...");
spinner.enable_steady_tick(20);

for (seconds, _) in timer.enumerate() {
let res = client
.get("https://api.cloudflare.com/client/v4/workers/token")
.json(&request_params)
.send()?;

if res.status().is_success() {
let body: TokenResponse = res.json()?;
return Ok(body.result);
}

if seconds >= 500 {
break;
}
}

failure::bail!(
"Timed out while waiting for API token. Try using `wrangler config` if login fails to work."
);
}

#[cfg(test)]
mod tests {
use openssl::rsa::Rsa;

#[test]
fn test_rsa() {
let rsa = Rsa::generate(1024).unwrap();
rsa.public_key_to_pem_pkcs1().unwrap();
}
}
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ fn run() -> Result<(), failure::Error> {
.subcommand(
SubCommand::with_name("config")
.about(&*format!(
"{} Set up wrangler with your Cloudflare account",
"{} Authenticate Wrangler with a Cloudflare API Token or Global API Key",
emoji::SLEUTH
))
.arg(
Expand Down Expand Up @@ -615,6 +615,9 @@ fn run() -> Result<(), failure::Error> {
)
.arg(verbose_arg.clone())
)
.subcommand(
SubCommand::with_name("login")
.about(&*format!("{} Authenticate Wrangler with your Cloudflare username and password", emoji::UNLOCKED)))
.get_matches();

let mut is_preview = false;
Expand Down Expand Up @@ -1069,6 +1072,8 @@ fn run() -> Result<(), failure::Error> {
let verbose = matches.is_present("verbose");

commands::tail::start(&target, &user, tunnel_port, metrics_port, verbose)?;
} else if matches.subcommand_matches("login").is_some() {
commands::login::run()?;
}
Ok(())
}
19 changes: 1 addition & 18 deletions src/preview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub use request_payload::RequestPayload;
mod upload;
pub use upload::upload;

use std::process::Command;
use std::sync::mpsc::channel;
use std::thread;

Expand All @@ -22,7 +21,7 @@ use crate::build;
use crate::http;
use crate::settings::global_user::GlobalUser;
use crate::settings::toml::Target;
use crate::terminal::message;
use crate::terminal::{message, open_browser};
use crate::watch::watch_and_build;

pub fn preview(
Expand Down Expand Up @@ -93,22 +92,6 @@ pub struct PreviewOpt {
pub headless: bool,
}

fn open_browser(url: &str) -> Result<(), failure::Error> {
let _output = if cfg!(target_os = "windows") {
let url_escaped = url.replace("&", "^&");
let windows_cmd = format!("start {}", url_escaped);
Command::new("cmd").args(&["/C", &windows_cmd]).output()?
} else if cfg!(target_os = "linux") {
let linux_cmd = format!(r#"xdg-open "{}""#, url);
Command::new("sh").arg("-c").arg(&linux_cmd).output()?
} else {
let mac_cmd = format!(r#"open "{}""#, url);
Command::new("sh").arg("-c").arg(&mac_cmd).output()?
};

Ok(())
}

fn client_request(payload: &RequestPayload, script_id: &str, sites_preview: bool) {
let client = http::client();

Expand Down
17 changes: 17 additions & 0 deletions src/terminal/browser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::process::Command;

pub fn open_browser(url: &str) -> Result<(), failure::Error> {
let _output = if cfg!(target_os = "windows") {
let url_escaped = url.replace("&", "^&");
let windows_cmd = format!("start {}", url_escaped);
Command::new("cmd").args(&["/C", &windows_cmd]).output()?
} else if cfg!(target_os = "linux") {
let linux_cmd = format!(r#"xdg-open "{}""#, url);
Command::new("sh").arg("-c").arg(&linux_cmd).output()?
} else {
let mac_cmd = format!(r#"open "{}""#, url);
Command::new("sh").arg("-c").arg(&mac_cmd).output()?
};

Ok(())
}
1 change: 1 addition & 0 deletions src/terminal/emoji.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ pub static UP: Emoji = Emoji("🆙 ", "");
pub static WARN: Emoji = Emoji("⚠️ ", "");
pub static WAVING: Emoji = Emoji("👋 ", "");
pub static WORKER: Emoji = Emoji("👷 ", "");
pub static UNLOCKED: Emoji = Emoji("🔓", "");
2 changes: 2 additions & 0 deletions src/terminal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod browser;
pub mod emoji;
pub mod interactive;
pub mod message;
pub mod styles;
pub use browser::open_browser;

0 comments on commit e048c15

Please sign in to comment.