feat: add authorization for graphql

This commit is contained in:
Kilerd Chan 2018-11-21 18:27:23 +08:00
parent 7ef8f7d2b2
commit d51fc6fb8c
7 changed files with 124 additions and 6 deletions

View File

@ -22,4 +22,5 @@ chrono = { version = "*", features = ["serde"] }
rust-crypto = "^0.2" rust-crypto = "^0.2"
juniper = "0.10" juniper = "0.10"
juniper_codegen = "0.10" juniper_codegen = "0.10"
juniper_rocket = "0.1.3" juniper_rocket = "0.1.3"
rand = "0.6.0"

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP TABLE token;

View File

@ -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)
);

View File

@ -19,6 +19,8 @@ extern crate serde;
extern crate serde_derive; extern crate serde_derive;
extern crate tera; extern crate tera;
extern crate rand;
use dotenv::dotenv; use dotenv::dotenv;
use rocket_contrib::Template; use rocket_contrib::Template;
@ -50,6 +52,8 @@ fn main() {
article::single_article, article::single_article,
article::get_article_by_url, article::get_article_by_url,
article::static_content, article::static_content,
graphql::graphql_authorization,
graphql::graphiql, graphql::graphiql,
graphql::get_graphql_handler, graphql::get_graphql_handler,
graphql::post_graphql_handler graphql::post_graphql_handler

View File

@ -2,15 +2,14 @@ use chrono::NaiveDateTime;
use chrono::prelude::*; use chrono::prelude::*;
use crate::pg_pool::DbConn; use crate::pg_pool::DbConn;
use crate::request::ArticleEditForm; 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::digest::Digest;
use crypto::sha3::Sha3; use crypto::sha3::Sha3;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::result::Error; use diesel::result::Error;
use rocket::request::FlashMessage; use rocket::request::FlashMessage;
use crate::schema::{articles, users, tokens, setting};
use rand;
#[derive(Queryable, Debug, Serialize, Insertable, AsChangeset, GraphQLObject)] #[derive(Queryable, Debug, Serialize, Insertable, AsChangeset, GraphQLObject)]
pub struct Article { pub struct Article {
@ -26,6 +25,8 @@ pub struct Article {
impl Article { impl Article {
pub fn load_all(include_unpublished: bool, conn: &DbConn) -> Vec<Article> { pub fn load_all(include_unpublished: bool, conn: &DbConn) -> Vec<Article> {
use crate::schema::articles::dsl::*;
use crate::schema::articles;
if include_unpublished { if include_unpublished {
articles::table.order(publish_at.desc()).load::<Article>(&**conn).expect("something wrong") articles::table.order(publish_at.desc()).load::<Article>(&**conn).expect("something wrong")
} else { } else {
@ -100,6 +101,23 @@ impl User {
hasher.input_str(password); hasher.input_str(password);
hasher.result_str() hasher.result_str()
} }
pub fn find_by_id(id:i32, conn: &DbConn) -> Option<User> {
use crate::schema::users;
let fetched_user = users::table.filter(users::id.eq(id)).first::<User>(&**conn);
match fetched_user {
Ok(user) => Some(user),
Err(_) => None,
}
}
pub fn find_by_username(username:&str, conn: &DbConn) -> Option<User> {
use crate::schema::users;
let fetched_user = users::table.filter(users::username.eq(username.to_string())).first::<User>(&**conn);
match fetched_user {
Ok(user) => Some(user),
Err(_) => None,
}
}
} }
#[derive_FromForm] #[derive_FromForm]
@ -125,4 +143,54 @@ impl <'a> SerializeFlashMessage<'a> {
Some(f) => Some(SerializeFlashMessage{ name: &f.name().clone(), message: &f.msg().clone() }) 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<User> {
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::<Token>(&**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::<u8>() % 255) as char);
}
ret
}
} }

View File

@ -3,13 +3,35 @@ use crate::pg_pool::DbConn;
use rocket::State; use rocket::State;
use crate::graphql::Schema; use crate::graphql::Schema;
use juniper_rocket::{GraphQLRequest, GraphQLResponse}; 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")] #[get("/graphiql")]
fn graphiql() -> content::Html<String> { fn graphiql() -> content::Html<String> {
juniper_rocket::graphiql_source("/graphql") juniper_rocket::graphiql_source("/graphql")
} }
#[post("/graphql/authorization", data = "<user>")]
pub fn graphql_authorization(user: Form<LoginForm>, conn: DbConn) -> Result<Json<Token>, 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?<request>")] #[get("/graphql?<request>")]
fn get_graphql_handler(context: DbConn, request: GraphQLRequest, state: State<Schema>) -> GraphQLResponse { fn get_graphql_handler(context: DbConn, request: GraphQLRequest, state: State<Schema>) -> GraphQLResponse {
let schema = state; let schema = state;

View File

@ -17,6 +17,15 @@ table! {
} }
} }
table! {
tokens (id) {
id -> Int4,
user_id -> Int4,
value -> Text,
expire_at -> Timestamp,
}
}
table! { table! {
users (id) { users (id) {
id -> Int4, id -> Int4,
@ -28,9 +37,11 @@ table! {
} }
joinable!(articles -> users (user_id)); joinable!(articles -> users (user_id));
joinable!(tokens -> users (user_id));
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
articles, articles,
setting, setting,
tokens,
users, users,
); );