summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kjetil.orbekk@gmail.com>2020-01-31 22:59:10 -0500
committerKjetil Orbekk <kjetil.orbekk@gmail.com>2020-01-31 22:59:10 -0500
commit263605f2b0404ffe04fedde644b0aaccc1e84b85 (patch)
treea54f1dcfcd78ec4b10834eb183ef29850538d12b
parent40b3e685b00f9a9f25908a85f79960913e668622 (diff)
Exchange and store strava tokens
-rw-r--r--Cargo.lock33
-rw-r--r--Cargo.toml5
-rw-r--r--migrations/2020-02-01-032552_strava_tokens/down.sql1
-rw-r--r--migrations/2020-02-01-032552_strava_tokens/up.sql6
-rw-r--r--src/db.rs16
-rw-r--r--src/error.rs16
-rw-r--r--src/main.rs6
-rw-r--r--src/models.rs13
-rw-r--r--src/schema.rs17
-rw-r--r--src/server.rs41
-rw-r--r--src/strava.rs20
11 files changed, 148 insertions, 26 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2e21f58..4ff6b8e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 6e9836a..4b4dc3c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
+);
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<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(&params.strava_client_id, &params.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(&params);
- req.send().map(|r| r.json())?
+ let json: Value = req.send().map(|r| r.json())??;
+ from_value(json).map_err(From::from)
}