diff options
author | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2020-01-31 22:59:10 -0500 |
---|---|---|
committer | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2020-01-31 22:59:10 -0500 |
commit | 263605f2b0404ffe04fedde644b0aaccc1e84b85 (patch) | |
tree | a54f1dcfcd78ec4b10834eb183ef29850538d12b | |
parent | 40b3e685b00f9a9f25908a85f79960913e668622 (diff) |
Exchange and store strava tokens
-rw-r--r-- | Cargo.lock | 33 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | migrations/2020-02-01-032552_strava_tokens/down.sql | 1 | ||||
-rw-r--r-- | migrations/2020-02-01-032552_strava_tokens/up.sql | 6 | ||||
-rw-r--r-- | src/db.rs | 16 | ||||
-rw-r--r-- | src/error.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/models.rs | 13 | ||||
-rw-r--r-- | src/schema.rs | 17 | ||||
-rw-r--r-- | src/server.rs | 41 | ||||
-rw-r--r-- | src/strava.rs | 20 |
11 files changed, 148 insertions, 26 deletions
@@ -164,6 +164,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "chrono" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "clap" version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -246,6 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -802,6 +814,23 @@ dependencies = [ ] [[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "num_cpus" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -970,6 +999,7 @@ version = "0.1.0" dependencies = [ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "bcrypt 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1856,6 +1886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cookie 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fac5e7bdefb6160fb181ee0eaa6f96704b625c70e6d61c465cb35750a4ea12" @@ -1928,6 +1959,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" @@ -11,8 +11,9 @@ reqwest = { version = "0.10.1", features = ["blocking", "json"] } clap = "2" rocket = "0.4.2" rocket_contrib = { version = "0.4.2", default-features = false, features = ["handlebars_templates", "diesel_postgres_pool"] } -diesel = { version = "1.0.0", features = ["postgres"] } +diesel = { version = "1.0.0", features = ["postgres", "chrono"] } dotenv = "0.9.0" bcrypt = "0.6" base64 = "0.11" -rand = "0.7"
\ No newline at end of file +rand = "0.7" +chrono = { version = "0.4", features = ["serde"] }
\ No newline at end of file diff --git a/migrations/2020-02-01-032552_strava_tokens/down.sql b/migrations/2020-02-01-032552_strava_tokens/down.sql new file mode 100644 index 0000000..0301581 --- /dev/null +++ b/migrations/2020-02-01-032552_strava_tokens/down.sql @@ -0,0 +1 @@ +drop table strava_tokens; diff --git a/migrations/2020-02-01-032552_strava_tokens/up.sql b/migrations/2020-02-01-032552_strava_tokens/up.sql new file mode 100644 index 0000000..bf7e9b1 --- /dev/null +++ b/migrations/2020-02-01-032552_strava_tokens/up.sql @@ -0,0 +1,6 @@ +create table strava_tokens ( + username varchar not null primary key references users(username), + refresh_token varchar(64) not null, + access_token varchar(64) not null, + expires_at timestamptz not null +); @@ -2,8 +2,6 @@ use crate::error::Error; use crate::models; use bcrypt; use diesel::connection::Connection; -use diesel::dsl::exists; -use diesel::dsl::select; use diesel::pg::PgConnection; use diesel::ExpressionMethods; use diesel::QueryDsl; @@ -24,6 +22,20 @@ pub fn create_config(conn: &PgConnection, config: &models::Config) -> Result<(), }) } +pub fn insert_strava_token(conn: &PgConnection, token: &models::StravaToken) + -> Result<(), Error> { + use crate::schema::strava_tokens; + conn.transaction(|| { + diesel::delete(strava_tokens::table) + .filter(strava_tokens::username.eq(&token.username)) + .execute(conn)?; + diesel::insert_into(strava_tokens::table) + .values(token) + .execute(conn)?; + Ok(()) + }) +} + pub fn get_config(conn: &PgConnection) -> Result<models::Config, Error> { use crate::schema::config; config::table diff --git a/src/error.rs b/src/error.rs index 523922a..7e68288 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,8 @@ use std::fmt; pub enum Error { DieselError(DieselErr), PasswordError(BcryptError), + CommunicationError(reqwest::Error), + ParseError(serde_json::error::Error), AlreadyExists, NotFound, InternalError, @@ -18,6 +20,8 @@ impl fmt::Display for Error { match *self { Error::DieselError(ref e) => e.fmt(f), Error::PasswordError(ref e) => e.fmt(f), + Error::CommunicationError(ref e) => e.fmt(f), + Error::ParseError(ref e) => e.fmt(f), Error::AlreadyExists => f.write_str("AlreadyExists"), Error::NotFound => f.write_str("NotFound"), Error::InternalError => f.write_str("InternalError"), @@ -25,6 +29,18 @@ impl fmt::Display for Error { } } +impl From<serde_json::error::Error> for Error { + fn from(e: serde_json::error::Error) -> Error { + Error::ParseError(e) + } +} + +impl From<reqwest::Error> for Error { + fn from(e: reqwest::Error) -> Error { + Error::CommunicationError(e) + } +} + impl From<DieselErr> for Error { fn from(e: DieselErr) -> Error { Error::DieselError(e) diff --git a/src/main.rs b/src/main.rs index 6026f12..d8d1283 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#[macro_use] extern crate clap; use clap::App; use clap::Arg; @@ -81,9 +80,6 @@ fn main() { let password = matches.value_of("PASSWORD").unwrap(); pjournal::db::adduser(&conn, user, password).unwrap(); } else { - let config = pjournal::server::Params { - base_url: base_url.to_string(), - }; - pjournal::server::start(conn, db_url, config); + pjournal::server::start(conn, db_url, base_url); } } diff --git a/src/models.rs b/src/models.rs index 3b35693..fd946b9 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,8 @@ -use crate::error::Error; use crate::schema::config; use crate::schema::users; +use crate::schema::strava_tokens; +use chrono::Utc; +use chrono::DateTime; #[derive(Insertable, Queryable)] #[table_name = "config"] @@ -23,3 +25,12 @@ pub struct User { pub username: String, pub password: String, } + +#[derive(Insertable, Queryable)] +#[table_name = "strava_tokens"] +pub struct StravaToken { + pub username: String, + pub refresh_token: String, + pub access_token: String, + pub expires_at: DateTime<Utc>, +} diff --git a/src/schema.rs b/src/schema.rs index 055d6d0..326aac8 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -8,10 +8,25 @@ table! { } table! { + strava_tokens (username) { + username -> Varchar, + refresh_token -> Varchar, + access_token -> Varchar, + expires_at -> Timestamptz, + } +} + +table! { users (username) { username -> Varchar, password -> Varchar, } } -allow_tables_to_appear_in_same_query!(config, users,); +joinable!(strava_tokens -> users (username)); + +allow_tables_to_appear_in_same_query!( + config, + strava_tokens, + users, +); diff --git a/src/server.rs b/src/server.rs index 275ffed..a2ed2b1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use rocket::config::Config; use rocket::config::Environment; use rocket::config::Value; +use rocket::http; use rocket::http::Cookie; use rocket::http::Cookies; use rocket::http::Status; @@ -17,9 +18,12 @@ use std::collections::HashMap; use crate::db; use crate::error::Error; use crate::strava; +use crate::models; pub struct Params { pub base_url: String, + pub strava_client_id: String, + pub strava_client_secret: String, } #[database("db")] @@ -34,6 +38,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for LoggedInUser { type Error = Error; fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { + println!("trying to get logged in user"); let conn = request .guard::<Db>() .map_failure(|(s, ())| (s, Error::InternalError))?; @@ -44,9 +49,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for LoggedInUser { .get_private("user") .map(|cookie| cookie.value().to_string()) .ok_or(Error::NotFound)?; + println!("username: {:?}", username); db::get_user(&conn, &username)?; Ok(LoggedInUser { username: username }) })(); + println!("user: {:#?}", user); use request::Outcome; match user { @@ -88,7 +95,9 @@ struct LoginData { fn login_submit(conn: Db, data: Form<LoginData>, mut cookies: Cookies) -> Result<Redirect, Error> { match db::authenticate(&*conn, &data.username, &data.password) { Ok(_user) => { - cookies.add_private(Cookie::new("user", data.username.clone())); + let mut cookie = Cookie::new("user", data.username.clone()); + cookie.set_same_site(http::SameSite::Lax); + cookies.add_private(cookie); Ok(Redirect::to(uri!(index).to_string())) } Err(Error::NotFound) => Ok(Redirect::to(uri!(login: failed = true).to_string())), @@ -98,15 +107,24 @@ fn login_submit(conn: Db, data: Form<LoginData>, mut cookies: Cookies) -> Result #[get("/link_strava_callback?<code>")] fn link_strava_callback( - config: State<Params>, + conn: Db, + user: LoggedInUser, + params: State<Params>, code: String, -) -> Result<String, impl std::error::Error> { - strava::exchange_token("&config.client_id", "&config.client_secret", &code) - .map(|t| format!("{:#?}", t)) +) -> Result<String, Error> { + let token = strava::exchange_token(¶ms.strava_client_id, ¶ms.strava_client_secret, &code)?; + let result = format!("{:#?}", token); + db::insert_strava_token(&*conn, &models::StravaToken { + username: user.username, + refresh_token: token.refresh_token, + access_token: token.access_token, + expires_at: token.expires_at + })?; + Ok(result) } #[get("/link_strava")] -fn link_strava(config: State<Params>) -> Redirect { +fn link_strava(params: State<Params>) -> Redirect { Redirect::to(format!( concat!( "https://www.strava.com/oauth/authorize?", @@ -116,18 +134,23 @@ fn link_strava(config: State<Params>) -> Redirect { "approval_prompt=force&", "scope=read", ), - "config.client_id", - format!("{}/link_strava_callback", config.base_url) + params.strava_client_id, + format!("{}/link_strava_callback", params.base_url) )) } -pub fn start(conn: diesel::PgConnection, db_url: &str, params: Params) { +pub fn start(conn: diesel::PgConnection, db_url: &str, base_url: &str) { let mut database_config = HashMap::new(); let mut databases = HashMap::new(); database_config.insert("url", Value::from(db_url)); databases.insert("db", Value::from(database_config)); let persistent_config = db::get_config(&conn).expect("loading config"); + let params = Params { + base_url: base_url.to_string(), + strava_client_id: persistent_config.strava_client_id, + strava_client_secret: persistent_config.strava_client_secret, + }; let config = Config::build(Environment::Development) .extra("databases", databases) diff --git a/src/strava.rs b/src/strava.rs index dcd8dc1..df7d2a4 100644 --- a/src/strava.rs +++ b/src/strava.rs @@ -1,6 +1,12 @@ use reqwest; use serde::Deserialize; use serde::Serialize; +use chrono::Utc; +use chrono::DateTime; +use chrono::serde::ts_seconds; +use serde_json::Value; +use serde_json::from_value; +use crate::error::Error; #[derive(Serialize, Deserialize, Debug)] pub struct AthleteSummary { @@ -12,17 +18,18 @@ pub struct AthleteSummary { #[derive(Serialize, Deserialize, Debug)] pub struct Token { - expires_in: i64, - refresh_token: String, - access_token: String, - athlete: AthleteSummary, + #[serde(with = "ts_seconds")] + pub expires_at: DateTime<Utc>, + pub refresh_token: String, + pub access_token: String, + pub athlete: AthleteSummary, } pub fn exchange_token( client_id: &str, client_secret: &str, code: &str, -) -> Result<Token, reqwest::Error> { +) -> Result<Token, Error> { let client = reqwest::blocking::Client::new(); let params = [ ("client_id", client_id), @@ -31,5 +38,6 @@ pub fn exchange_token( ]; let uri = "https://www.strava.com/oauth/token"; let req = client.post(uri).form(¶ms); - req.send().map(|r| r.json())? + let json: Value = req.send().map(|r| r.json())??; + from_value(json).map_err(From::from) } |