From 263605f2b0404ffe04fedde644b0aaccc1e84b85 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 31 Jan 2020 22:59:10 -0500 Subject: Exchange and store strava tokens --- src/db.rs | 16 ++++++++++++++-- src/error.rs | 16 ++++++++++++++++ src/main.rs | 6 +----- src/models.rs | 13 ++++++++++++- src/schema.rs | 17 ++++++++++++++++- src/server.rs | 41 ++++++++++++++++++++++++++++++++--------- src/strava.rs | 20 ++++++++++++++------ 7 files changed, 105 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/db.rs b/src/db.rs index 077c165..55cef7b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -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 { 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 for Error { + fn from(e: serde_json::error::Error) -> Error { + Error::ParseError(e) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Error { + Error::CommunicationError(e) + } +} + impl From 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, +} diff --git a/src/schema.rs b/src/schema.rs index 055d6d0..326aac8 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -7,6 +7,15 @@ table! { } } +table! { + strava_tokens (username) { + username -> Varchar, + refresh_token -> Varchar, + access_token -> Varchar, + expires_at -> Timestamptz, + } +} + table! { users (username) { username -> Varchar, @@ -14,4 +23,10 @@ table! { } } -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 { + println!("trying to get logged in user"); let conn = request .guard::() .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, mut cookies: Cookies) -> Result { 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, mut cookies: Cookies) -> Result #[get("/link_strava_callback?")] fn link_strava_callback( - config: State, + conn: Db, + user: LoggedInUser, + params: State, code: String, -) -> Result { - strava::exchange_token("&config.client_id", "&config.client_secret", &code) - .map(|t| format!("{:#?}", t)) +) -> Result { + 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) -> Redirect { +fn link_strava(params: State) -> Redirect { Redirect::to(format!( concat!( "https://www.strava.com/oauth/authorize?", @@ -116,18 +134,23 @@ fn link_strava(config: State) -> 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, + pub refresh_token: String, + pub access_token: String, + pub athlete: AthleteSummary, } pub fn exchange_token( client_id: &str, client_secret: &str, code: &str, -) -> Result { +) -> Result { 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) } -- cgit v1.2.3