Skip to content

Commit

Permalink
First implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
NasyrovYuri committed Oct 19, 2017
0 parents commit 79b11a7
Show file tree
Hide file tree
Showing 19 changed files with 1,433 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target/
**/*.rs.bk
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: rust
rust:
- night
sudo: false
30 changes: 30 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "auth_rocket"
version = "0.1.0"
authors = ["y.nasyrov <[email protected]>"]

[dependencies]
log = "0.3"
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
r2d2 = "0.7"
rust-crypto = "0.2"
chrono = "0.4"
double = "0.1"
rocket = "0.3"
rand = "0.3"
rocket_codegen = "0.3"
rocket_contrib = "0.3"

[dependencies.redis]
optional = true
version = "0.7"

[dependencies.r2d2_redis]
optional = true
version = "0.5"

[features]
default = ['with-redis']
with-redis = ['redis', 'r2d2_redis']
97 changes: 97 additions & 0 deletions examples/redisdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#![feature(plugin, custom_derive)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate auth_rocket;
extern crate r2d2_redis;
extern crate r2d2;
extern crate redis;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use redis::RedisError;
use r2d2::Pool;
use r2d2_redis::RedisConnectionManager;
use std::io::{ Error, ErrorKind };
use auth_rocket::redisdb::RedisEntity;
use rocket::request::{self, Request, FromRequest };
use auth_rocket::{ PrivateKey, AuthEntity, User, Role, user_from_request };
use rocket::Outcome;
use rocket::http::Status;

fn main() {
let redis = connect_pool("redis://127.0.0.1/", true);
let redis_entity = RedisEntity::new(&redis, "test_example:".to_string());


#[derive(Deserialize, Serialize)]
pub struct BatmanCustomUser(User);

impl<'a, 'r> FromRequest<'a, 'r> for BatmanCustomUser {
type Error = ();

fn from_request(request: &'a Request<'r>) -> request::Outcome<BatmanCustomUser, ()> {
match user_from_request(request, vec!(Role::Custom("Batman".to_string()))) {
Outcome::Success(user) => {
Outcome::Success(BatmanCustomUser(user))
},
Outcome::Failure(e) => Outcome::Failure(e),
_ => Outcome::Failure((Status::Unauthorized, ()))
}
}
}

rocket::ignite().mount("/api", routes!(get_user, get_user_bat))
.manage(PrivateKey::new("my_secret_key".to_string()))
.manage(AuthEntity::new(Box::new(redis_entity))).launch();

#[get("/user")]
pub fn get_user(user: auth_rocket::AuthorizedUser) -> String {
format!("{}", json!(user).to_string())
}
#[get("/user/batman")]
pub fn get_user_bat(user: BatmanCustomUser) -> String {
format!("{}", json!(user).to_string())
}
}

pub fn connect_pool(connect_str: &str, reconnect: bool) -> Pool<RedisConnectionManager> {
let cache = Default::default();

match RedisConnectionManager::new(connect_str) {
Ok(m) => {
match Pool::new(cache, m) {
Ok(pool) => {
pool
},
Err(_) => {
match reconnect {
true => {
connect_pool(connect_str, false)
},
false => {
std::thread::sleep(std::time::Duration::from_millis(10000u64));
connect_pool(connect_str, false)
}
}
}
}
},
Err(_) => {
match reconnect {
true => {
connect_pool(connect_str, false)
},
false => {
std::thread::sleep(std::time::Duration::from_millis(10000u64));
connect_pool(connect_str, false)
}
}
}
}
}

pub fn error(e: &str) -> RedisError {
RedisError::from(Error::new(ErrorKind::Other, e))
}
55 changes: 55 additions & 0 deletions src/api/condition/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use rocket::request::{FromForm, FormItems};

pub struct LimitOffset {
limit: isize,
offset: isize
}

impl LimitOffset {
pub fn new(limit: isize, offset: isize) -> Self {
LimitOffset {
limit: limit,
offset: offset
}
}

pub fn get_limit(&self) -> isize {
self.limit.clone()
}

pub fn get_offset(&self) -> isize {
self.offset.clone()
}
}

impl<'f> FromForm<'f> for LimitOffset {
// In practice, we'd use a more descriptive error type.
type Error = ();

fn from_form(items: &mut FormItems<'f>, _: bool) -> Result<LimitOffset, ()> {
let mut limit = 0;
let mut offset = 10;

for (key, value) in items {
match key.as_str() {
"limit" => {
if let Ok(Ok(l)) = value.url_decode().map(|l| { l.to_string().parse::<isize>()}) {
if limit < l {
limit = l;
}
}
},
"offset" => {
if let Ok(Ok(o)) = value.url_decode().map(|o| { o.to_string().parse::<isize>()}) {
offset = o;
}
},
_ => {

}
}
}

Ok(LimitOffset::new(limit, offset))
}
}
16 changes: 16 additions & 0 deletions src/api/form/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::collections::HashMap;

#[derive(Deserialize)]
pub struct SignIn {
pub username: String,
pub password: String
}

#[derive(Deserialize)]
pub struct SignUp {
pub username: String,
pub email: String,
pub password: String,
pub re_password: String,
pub attributes: HashMap<String, String>
}
95 changes: 95 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
mod condition;
mod form;

use rocket_contrib::Json;
use rocket::request::{ State };
use ::net::key::{ generate_api_key, PrivateKey };
use ::limitation::user::{ AuthorizedUser, AdminUser };
use ::{ AuthEntity, Entity, Role };
use ::api::condition::LimitOffset;
use ::api::form::{ SignIn, SignUp };
use rocket::response::{ status, Redirect };
use rocket::http::Status;
use rocket::Route;
use ::net::uri::RequestedUriString;

#[post("/users/sign_up", format = "application/json", data="<sign_up>")]
pub fn sign_up(entity: State<AuthEntity>, sign_up: Json<SignUp>, uri: RequestedUriString) -> Result<status::Created<Json>, status::Custom<Json>> {

if sign_up.password != sign_up.re_password {
return Err(status::Custom(Status::BadRequest, Json(json!({
"error": ""
}))))
}

match entity.inner().add_user(sign_up.username.as_str(), sign_up.email.as_str(), sign_up.password.as_str(), sign_up.attributes.clone()) {
Ok(user) => {
let mut uri_str = uri.to_string();
uri_str.push_str(format!("{}", user.id).as_str());
Ok(status::Created(uri_str.replace("sign_up/", "user/"), Some(Json(json!({
"data": match entity.inner().enable_user(user.name.as_str()) {
Ok(u) => u,
Err(e) => {
error!("{}", e);
user
}
}
})))))
},
Err(e) => Err(status::Custom(Status::Conflict, Json(json!({
"error": format!("{}", e)
}))))
}
}

#[post("/users/sign_in", format = "application/json", data="<sign_in>")]
pub fn sign_in(private_key: State<PrivateKey>, entity: State<AuthEntity>, sign_in: Json<SignIn>) -> status::Custom<Json> {

if let Err(e) = entity.inner().get_user_by_name_and_pwd(sign_in.username.as_str(), sign_in.password.as_str()) {
return status::Custom(Status::Unauthorized, Json(json!({"error": format!("{}", e)})))
}

let token: String = generate_api_key(private_key.inner().as_str()).unwrap();

if let Some(e) = entity.inner().add_token(sign_in.username.as_str(), token.as_str()) {
return status::Custom(Status::Unauthorized, Json(json!({"error": format!("{}", e)})))
}

status::Custom(Status::Ok,Json(json!({"data": {"token": token}})))
}

/*
#[patch("/users/user/<id>", format = "application/json", data="<new_user>")]
pub fn up_user(entity: State<AuthEntity>, new_user: Json<User>, user: AuthorizedUser, id: i32) -> Result<status::Custom<Json>, status::Custom<Json>> {
Ok(status::Custom(Status::Ok, Json(json!({
"data": user.get_user()
}))))
}*/

#[get("/users/user/<id>", format = "application/json")]
pub fn get_user(user: AuthorizedUser, id: i32, entity: State<AuthEntity>, uri: RequestedUriString) -> Result<status::Custom<Json>, Redirect> {
if user.get_user().id == id {
Ok(status::Custom(Status::Ok, Json(json!({"data": user}))))
} else if user.get_user().role == Role::Admins {
match entity.inner().get_user_by_id(id) {
Ok(u) => Ok(status::Custom(Status::Ok, Json(json!({ "data": u })))),
Err(e) => Ok(status::Custom(Status::NotFound, Json(json!({ "error": format!("{}", e) }))))
}
} else {
Err(Redirect::found(uri.to_string().replace(format!("{}", id).as_str(), format!("{}", user.get_user().id).as_str()).as_str()))
}
}

#[get("/users/list", format = "application/json")]
pub fn get_user_list(entity: State<AuthEntity>, user: AdminUser) -> Json {
Json(json!({"data": entity.list_users(0, 10).unwrap()}))
}

#[get("/users/list?<limit>", format = "application/json")]
pub fn get_user_list_with_limit(entity: State<AuthEntity>, limit: LimitOffset, user: AdminUser) -> Json {
Json(json!({"data": entity.list_users(limit.get_limit(), limit.get_offset()).unwrap()}))
}

pub fn get_user_routes() -> Vec<Route> {
routes!( sign_up, get_user, sign_in, get_user_list, get_user_list_with_limit)
}
69 changes: 69 additions & 0 deletions src/decorator/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::{ Entity, User, AuthError, Role, PrivateUser };
use std::collections::HashMap;

pub struct AuthEntity {
component: Box<Entity>
}

impl AuthEntity {
pub fn new(component: Box<Entity>) -> Self {
AuthEntity {
component: component
}
}
}

impl Entity for AuthEntity {

fn add_user(&self, name: &str, email: &str, password: &str, attributes: HashMap<String, String>) -> Result<User, AuthError> {
self.component.add_user(name, email, password, attributes)
}

fn get_user_by_id(&self, user_id: i32) -> Result<User, AuthError> {
self.component.get_user_by_id(user_id)
}

fn get_user_by_name(&self, username: &str) -> Result<PrivateUser, AuthError> {
self.component.get_user_by_name(username)
}

fn get_user_by_name_and_pwd(&self, username: &str, password: &str) -> Result<User, AuthError> {
self.component.get_user_by_name_and_pwd(username, password)
}

fn delete_user(&self, user_id: i32) -> Option<AuthError> {
self.component.delete_user(user_id)
}

fn list_users(&self, from: isize, count:isize) -> Result<Vec<User>, AuthError> {
self.component.list_users(from, count)
}

fn enable_user(&self, username: &str) -> Result<User, AuthError> {
self.component.enable_user(username)
}

fn disable_user(&self, username: &str) -> Result<User, AuthError> {
self.component.disable_user(username)
}

fn get_token(&self, username: &str) -> Result<String, AuthError> {
self.component.get_token(username)
}

fn add_token(&self, username: &str, token: &str) -> Option<AuthError> {
self.component.add_token(username, token)
}

fn get_user_by_token(&self, token: &str) -> Result<User, AuthError> {
self.component.get_user_by_token(token)
}

fn delete_token(&self, token: &str) -> Option<AuthError> {
self.component.delete_token(token)
}

fn add_user_role(&self, username: &str, role: Role) -> Result<User, AuthError> {
self.component.add_user_role(username, role)
}
}
Loading

0 comments on commit 79b11a7

Please sign in to comment.