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

@ -23,3 +23,4 @@ rust-crypto = "^0.2"
juniper = "0.10"
juniper_codegen = "0.10"
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 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

View File

@ -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<Article> {
use crate::schema::articles::dsl::*;
use crate::schema::articles;
if include_unpublished {
articles::table.order(publish_at.desc()).load::<Article>(&**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<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]
@ -126,3 +144,53 @@ impl <'a> SerializeFlashMessage<'a> {
}
}
}
#[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 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<String> {
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>")]
fn get_graphql_handler(context: DbConn, request: GraphQLRequest, state: State<Schema>) -> GraphQLResponse {
let schema = state;

View File

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