diff --git a/Cargo.toml b/Cargo.toml index ed32a1b..c951bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ chrono = { version = "*", features = ["serde"] } rust-crypto = "^0.2" juniper = "0.10" juniper_codegen = "0.10" -juniper_rocket = "0.1.3" \ No newline at end of file +juniper_rocket = "0.1.3" +rand = "0.6.0" \ No newline at end of file diff --git a/migrations/2018-11-21-070343_add token table/down.sql b/migrations/2018-11-21-070343_add token table/down.sql new file mode 100644 index 0000000..8522dd5 --- /dev/null +++ b/migrations/2018-11-21-070343_add token table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE token; \ No newline at end of file diff --git a/migrations/2018-11-21-070343_add token table/up.sql b/migrations/2018-11-21-070343_add token table/up.sql new file mode 100644 index 0000000..21e137e --- /dev/null +++ b/migrations/2018-11-21-070343_add token table/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here +-- Table Definition ---------------------------------------------- + +CREATE TABLE tokens ( + id SERIAL NOT NULL PRIMARY KEY, + user_id integer NOT NULL REFERENCES users(id), + value text NOT NULL, + expire_at timestamp without time zone NOT NULL DEFAULT (CURRENT_TIMESTAMP + '1 day'::interval) +); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3b8578e..9f69b66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,8 @@ extern crate serde; extern crate serde_derive; extern crate tera; +extern crate rand; + use dotenv::dotenv; use rocket_contrib::Template; @@ -50,6 +52,8 @@ fn main() { article::single_article, article::get_article_by_url, article::static_content, + + graphql::graphql_authorization, graphql::graphiql, graphql::get_graphql_handler, graphql::post_graphql_handler diff --git a/src/models.rs b/src/models.rs index a8c6416..35afb3b 100644 --- a/src/models.rs +++ b/src/models.rs @@ -2,15 +2,14 @@ use chrono::NaiveDateTime; use chrono::prelude::*; use crate::pg_pool::DbConn; use crate::request::ArticleEditForm; -use crate::schema::articles; -use crate::schema::articles::dsl::*; -use crate::schema::setting; -use crate::schema::users; use crypto::digest::Digest; use crypto::sha3::Sha3; use diesel::prelude::*; use diesel::result::Error; use rocket::request::FlashMessage; +use crate::schema::{articles, users, tokens, setting}; +use rand; + #[derive(Queryable, Debug, Serialize, Insertable, AsChangeset, GraphQLObject)] pub struct Article { @@ -26,6 +25,8 @@ pub struct Article { impl Article { pub fn load_all(include_unpublished: bool, conn: &DbConn) -> Vec
{ + use crate::schema::articles::dsl::*; + use crate::schema::articles; if include_unpublished { articles::table.order(publish_at.desc()).load::
(&**conn).expect("something wrong") } else { @@ -100,6 +101,23 @@ impl User { hasher.input_str(password); hasher.result_str() } + + pub fn find_by_id(id:i32, conn: &DbConn) -> Option { + use crate::schema::users; + let fetched_user = users::table.filter(users::id.eq(id)).first::(&**conn); + match fetched_user { + Ok(user) => Some(user), + Err(_) => None, + } + } + pub fn find_by_username(username:&str, conn: &DbConn) -> Option { + use crate::schema::users; + let fetched_user = users::table.filter(users::username.eq(username.to_string())).first::(&**conn); + match fetched_user { + Ok(user) => Some(user), + Err(_) => None, + } + } } #[derive_FromForm] @@ -125,4 +143,54 @@ impl <'a> SerializeFlashMessage<'a> { Some(f) => Some(SerializeFlashMessage{ name: &f.name().clone(), message: &f.msg().clone() }) } } +} + +#[derive(Queryable, Debug, Serialize, Insertable, AsChangeset, GraphQLObject)] +pub struct Token { + pub id: i32, + pub user_id: i32, + pub value: String, + pub expire_at: NaiveDateTime, +} + +#[derive(Insertable, AsChangeset)] +#[table_name="tokens"] +pub struct NewToken{ + pub user_id: i32, + pub value: String, +} + + +impl Token { + + pub fn new(user_id: i32, conn: &DbConn) -> Token { + let token = NewToken{ + user_id: user_id, + value: Token::rand(), + }; + diesel::insert_into(tokens::table).values(&token).get_result(&**conn).expect("can not create token") + } + + pub fn validate(token:String, conn: &DbConn) -> Option { + use crate::schema::{tokens, tokens::dsl::*}; + let now = Utc::now().naive_utc(); + let fetched_token = tokens::table.filter(value.eq(token)).filter(expire_at.gt(now)).first::(&**conn); + match fetched_token { + Ok(token) => { + User::find_by_id(token.user_id, conn) + }, + Err(_) => None + } + + } + + pub fn rand() -> String { + use rand::RngCore; + let mut ret = String::new(); + for _ in (1..32) { + ret.push((rand::random::() % 255) as char); + } + + ret + } } \ No newline at end of file diff --git a/src/routers/graphql.rs b/src/routers/graphql.rs index c28e895..b060f55 100644 --- a/src/routers/graphql.rs +++ b/src/routers/graphql.rs @@ -3,13 +3,35 @@ use crate::pg_pool::DbConn; use rocket::State; use crate::graphql::Schema; use juniper_rocket::{GraphQLRequest, GraphQLResponse}; - +use rocket_contrib::json::Json; +use rocket::request::Form; +use crate::request::LoginForm; +use rocket::response::Failure; +use rocket::http::Status; +use crate::models::{User, Token}; #[get("/graphiql")] fn graphiql() -> content::Html { juniper_rocket::graphiql_source("/graphql") } +#[post("/graphql/authorization", data = "")] +pub fn graphql_authorization(user: Form, conn: DbConn) -> Result, Failure> { + let user_form = user.get(); + let fetched_user = User::find_by_username(&user_form.username, &conn); + + if let None = fetched_user { + return Err(Failure(Status::Unauthorized)); + } + let user: User = fetched_user.unwrap(); + + if !user.authenticated(user_form.password.as_str()) { + return Err(Failure(Status::Unauthorized)); + } + Ok(Json(Token::new(user.id, &conn))) +} + + #[get("/graphql?")] fn get_graphql_handler(context: DbConn, request: GraphQLRequest, state: State) -> GraphQLResponse { let schema = state; diff --git a/src/schema.rs b/src/schema.rs index dccbb06..b9aa1d9 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -17,6 +17,15 @@ table! { } } +table! { + tokens (id) { + id -> Int4, + user_id -> Int4, + value -> Text, + expire_at -> Timestamp, + } +} + table! { users (id) { id -> Int4, @@ -28,9 +37,11 @@ table! { } joinable!(articles -> users (user_id)); +joinable!(tokens -> users (user_id)); allow_tables_to_appear_in_same_query!( articles, setting, + tokens, users, );