mirror of
https://github.com/actix/examples
synced 2025-06-28 09:50:36 +02:00
update deps
This commit is contained in:
@ -2,18 +2,22 @@
|
||||
name = "simple-auth-server"
|
||||
version = "0.1.0"
|
||||
authors = ["mygnu <tech@hgill.io>"]
|
||||
edition = "2018"
|
||||
workspace = ".."
|
||||
|
||||
[dependencies]
|
||||
actix = "0.7.7"
|
||||
actix-web = "0.7.14"
|
||||
actix = { version = "0.8.0-alpha.2", features = ["http"] }
|
||||
actix-web = "1.0.0-alpha.1"
|
||||
actix-files = "0.1.0-alpha.1"
|
||||
|
||||
bcrypt = "0.2.1"
|
||||
chrono = { version = "0.4.6", features = ["serde"] }
|
||||
diesel = { version = "1.3.3", features = ["postgres", "uuid", "r2d2", "chrono"] }
|
||||
dotenv = "0.13.0"
|
||||
derive_more = "0.14"
|
||||
env_logger = "0.6.0"
|
||||
failure = "0.1.3"
|
||||
jsonwebtoken = "5.0"
|
||||
futures = "0.1"
|
||||
futures = "0.1.25"
|
||||
r2d2 = "0.8.3"
|
||||
serde_derive="1.0.80"
|
||||
serde_json="1.0"
|
||||
|
@ -1,56 +0,0 @@
|
||||
use actix::prelude::*;
|
||||
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::{fs, http::Method, middleware::Logger, App};
|
||||
use auth_routes::{get_me, login, logout};
|
||||
use chrono::Duration;
|
||||
use invitation_routes::register_email;
|
||||
use models::DbExecutor;
|
||||
use register_routes::register_user;
|
||||
|
||||
pub struct AppState {
|
||||
pub db: Addr<DbExecutor>,
|
||||
}
|
||||
|
||||
/// creates and returns the app after mounting all routes/resources
|
||||
pub fn create_app(db: Addr<DbExecutor>) -> App<AppState> {
|
||||
// secret is a random minimum 32 bytes long base 64 string
|
||||
let secret: String =
|
||||
std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8));
|
||||
let domain: String =
|
||||
std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string());
|
||||
|
||||
App::with_state(AppState { db })
|
||||
.middleware(Logger::default())
|
||||
.middleware(IdentityService::new(
|
||||
CookieIdentityPolicy::new(secret.as_bytes())
|
||||
.name("auth")
|
||||
.path("/")
|
||||
.domain(domain.as_str())
|
||||
.max_age(Duration::days(1))
|
||||
.secure(false), // this can only be true if you have https
|
||||
))
|
||||
// everything under '/api/' route
|
||||
.scope("/api", |api| {
|
||||
// routes for authentication
|
||||
api.resource("/auth", |r| {
|
||||
r.method(Method::POST).with(login);
|
||||
r.method(Method::DELETE).with(logout);
|
||||
r.method(Method::GET).with(get_me);
|
||||
})
|
||||
// routes to invitation
|
||||
.resource("/invitation", |r| {
|
||||
r.method(Method::POST).with(register_email);
|
||||
})
|
||||
// routes to register as a user after the
|
||||
.resource("/register/{invitation_id}", |r| {
|
||||
r.method(Method::POST).with(register_user);
|
||||
})
|
||||
})
|
||||
// serve static files
|
||||
.handler(
|
||||
"/",
|
||||
fs::StaticFiles::new("./static/")
|
||||
.unwrap()
|
||||
.index_file("index.html"),
|
||||
)
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
use actix::{Handler, Message};
|
||||
use actix_web::{middleware::identity::RequestIdentity, FromRequest, HttpRequest};
|
||||
use actix_web::{dev::ServiceFromRequest, Error};
|
||||
use actix_web::{middleware::identity::Identity, FromRequest, HttpRequest};
|
||||
use bcrypt::verify;
|
||||
use diesel::prelude::*;
|
||||
use errors::ServiceError;
|
||||
use models::{DbExecutor, SlimUser, User};
|
||||
use utils::decode_token;
|
||||
|
||||
use crate::errors::ServiceError;
|
||||
use crate::models::{DbExecutor, SlimUser, User};
|
||||
use crate::utils::decode_token;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AuthData {
|
||||
@ -19,7 +21,7 @@ impl Message for AuthData {
|
||||
impl Handler<AuthData> for DbExecutor {
|
||||
type Result = Result<SlimUser, ServiceError>;
|
||||
fn handle(&mut self, msg: AuthData, _: &mut Self::Context) -> Self::Result {
|
||||
use schema::users::dsl::{email, users};
|
||||
use crate::schema::users::dsl::{email, users};
|
||||
let conn: &PgConnection = &self.0.get().unwrap();
|
||||
|
||||
let mut items = users.filter(email.eq(&msg.email)).load::<User>(conn)?;
|
||||
@ -44,14 +46,15 @@ impl Handler<AuthData> for DbExecutor {
|
||||
// simple aliasing makes the intentions clear and its more readable
|
||||
pub type LoggedUser = SlimUser;
|
||||
|
||||
impl<S> FromRequest<S> for LoggedUser {
|
||||
type Config = ();
|
||||
type Result = Result<LoggedUser, ServiceError>;
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
if let Some(identity) = req.identity() {
|
||||
impl<P> FromRequest<P> for LoggedUser {
|
||||
type Error = Error;
|
||||
type Future = Result<LoggedUser, Error>;
|
||||
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
if let Some(identity) = Identity::from_request(req)?.identity() {
|
||||
let user: SlimUser = decode_token(&identity)?;
|
||||
return Ok(user as LoggedUser);
|
||||
}
|
||||
Err(ServiceError::Unauthorized)
|
||||
Err(ServiceError::Unauthorized.into())
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,32 @@
|
||||
use actix_web::middleware::identity::RequestIdentity;
|
||||
use actix_web::{
|
||||
AsyncResponder, FutureResponse, HttpRequest, HttpResponse, Json, ResponseError,
|
||||
};
|
||||
use futures::future::Future;
|
||||
use utils::create_token;
|
||||
use actix::Addr;
|
||||
use actix_web::middleware::identity::Identity;
|
||||
use actix_web::{web, Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
use futures::Future;
|
||||
|
||||
use app::AppState;
|
||||
use auth_handler::{AuthData, LoggedUser};
|
||||
use crate::auth_handler::{AuthData, LoggedUser};
|
||||
use crate::models::DbExecutor;
|
||||
use crate::utils::create_token;
|
||||
|
||||
pub fn login(
|
||||
(auth_data, req): (Json<AuthData>, HttpRequest<AppState>),
|
||||
) -> FutureResponse<HttpResponse> {
|
||||
req.state()
|
||||
.db
|
||||
.send(auth_data.into_inner())
|
||||
auth_data: web::Json<AuthData>,
|
||||
id: Identity,
|
||||
db: web::Data<Addr<DbExecutor>>,
|
||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
db.send(auth_data.into_inner())
|
||||
.from_err()
|
||||
.and_then(move |res| match res {
|
||||
Ok(user) => {
|
||||
let token = create_token(&user)?;
|
||||
req.remember(token);
|
||||
id.remember(token);
|
||||
Ok(HttpResponse::Ok().into())
|
||||
}
|
||||
Err(err) => Ok(err.error_response()),
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
||||
pub fn logout(req: HttpRequest<AppState>) -> HttpResponse {
|
||||
req.forget();
|
||||
HttpResponse::Ok().into()
|
||||
pub fn logout(id: Identity) -> impl Responder {
|
||||
id.forget();
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
pub fn get_me(logged_user: LoggedUser) -> HttpResponse {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use models::Invitation;
|
||||
use crate::models::Invitation;
|
||||
use sparkpost::transmission::{
|
||||
EmailAddress, Message, Options, Recipient, Transmission, TransmissionResponse,
|
||||
};
|
||||
|
@ -1,17 +1,18 @@
|
||||
use actix_web::{error::ResponseError, HttpResponse};
|
||||
use derive_more::Display;
|
||||
use diesel::result::{DatabaseErrorKind, Error};
|
||||
use std::convert::From;
|
||||
use uuid::ParseError;
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
#[derive(Debug, Display)]
|
||||
pub enum ServiceError {
|
||||
#[fail(display = "Internal Server Error")]
|
||||
#[display(fmt = "Internal Server Error")]
|
||||
InternalServerError,
|
||||
|
||||
#[fail(display = "BadRequest: {}", _0)]
|
||||
#[display(fmt = "BadRequest: {}", _0)]
|
||||
BadRequest(String),
|
||||
|
||||
#[fail(display = "Unauthorized")]
|
||||
#[display(fmt = "Unauthorized")]
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
use actix::{Handler, Message};
|
||||
use chrono::{Duration, Local};
|
||||
use diesel::{self, prelude::*};
|
||||
use errors::ServiceError;
|
||||
use models::{DbExecutor, Invitation};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::ServiceError;
|
||||
use crate::models::{DbExecutor, Invitation};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateInvitation {
|
||||
pub email: String,
|
||||
@ -18,7 +19,7 @@ impl Handler<CreateInvitation> for DbExecutor {
|
||||
type Result = Result<Invitation, ServiceError>;
|
||||
|
||||
fn handle(&mut self, msg: CreateInvitation, _: &mut Self::Context) -> Self::Result {
|
||||
use schema::invitations::dsl::*;
|
||||
use crate::schema::invitations::dsl::*;
|
||||
let conn: &PgConnection = &self.0.get().unwrap();
|
||||
|
||||
// creating a new Invitation object with expired at time that is 24 hours from now
|
||||
|
@ -1,18 +1,16 @@
|
||||
use actix_web::{
|
||||
AsyncResponder, FutureResponse, HttpResponse, Json, ResponseError, State,
|
||||
};
|
||||
use actix::Addr;
|
||||
use actix_web::{web, Error, HttpResponse, ResponseError};
|
||||
use futures::future::Future;
|
||||
|
||||
use app::AppState;
|
||||
use email_service::send_invitation;
|
||||
use invitation_handler::CreateInvitation;
|
||||
use crate::email_service::send_invitation;
|
||||
use crate::invitation_handler::CreateInvitation;
|
||||
use crate::models::DbExecutor;
|
||||
|
||||
pub fn register_email(
|
||||
(signup_invitation, state): (Json<CreateInvitation>, State<AppState>),
|
||||
) -> FutureResponse<HttpResponse> {
|
||||
state
|
||||
.db
|
||||
.send(signup_invitation.into_inner())
|
||||
signup_invitation: web::Json<CreateInvitation>,
|
||||
db: web::Data<Addr<DbExecutor>>,
|
||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
db.send(signup_invitation.into_inner())
|
||||
.from_err()
|
||||
.and_then(|db_response| match db_response {
|
||||
Ok(invitation) => {
|
||||
@ -21,5 +19,4 @@ pub fn register_email(
|
||||
}
|
||||
Err(err) => Ok(err.error_response()),
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
@ -1,26 +1,21 @@
|
||||
// to avoid the warning from diesel macros
|
||||
#![allow(proc_macro_derive_resolution_fallback)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate bcrypt;
|
||||
extern crate chrono;
|
||||
extern crate dotenv;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate jsonwebtoken as jwt;
|
||||
extern crate r2d2;
|
||||
extern crate serde;
|
||||
extern crate sparkpost;
|
||||
extern crate uuid;
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
|
||||
mod app;
|
||||
use actix::prelude::*;
|
||||
use actix_files as fs;
|
||||
use actix_web::middleware::{
|
||||
identity::{CookieIdentityPolicy, IdentityService},
|
||||
Logger,
|
||||
};
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use chrono::Duration;
|
||||
use diesel::{r2d2::ConnectionManager, PgConnection};
|
||||
use dotenv::dotenv;
|
||||
|
||||
mod auth_handler;
|
||||
mod auth_routes;
|
||||
mod email_service;
|
||||
@ -33,20 +28,17 @@ mod register_routes;
|
||||
mod schema;
|
||||
mod utils;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_web::server;
|
||||
use diesel::{r2d2::ConnectionManager, PgConnection};
|
||||
use dotenv::dotenv;
|
||||
use models::DbExecutor;
|
||||
use std::env;
|
||||
use crate::models::DbExecutor;
|
||||
|
||||
fn main() {
|
||||
fn main() -> std::io::Result<()> {
|
||||
dotenv().ok();
|
||||
std::env::set_var("RUST_LOG", "simple-auth-server=debug,actix_web=info");
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
"simple-auth-server=debug,actix_web=info,actix_server=info",
|
||||
);
|
||||
env_logger::init();
|
||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
let sys = actix::System::new("Actix_Tutorial");
|
||||
|
||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
|
||||
// create db connection pool
|
||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||
@ -57,10 +49,49 @@ fn main() {
|
||||
let address: Addr<DbExecutor> =
|
||||
SyncArbiter::start(4, move || DbExecutor(pool.clone()));
|
||||
|
||||
server::new(move || app::create_app(address.clone()))
|
||||
.bind("127.0.0.1:3000")
|
||||
.expect("Can not bind to '127.0.0.1:3000'")
|
||||
.start();
|
||||
HttpServer::new(move || {
|
||||
// secret is a random minimum 32 bytes long base 64 string
|
||||
let secret: String =
|
||||
std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8));
|
||||
let domain: String =
|
||||
std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string());
|
||||
|
||||
sys.run();
|
||||
App::new()
|
||||
.data(address.clone())
|
||||
.wrap(Logger::default())
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(secret.as_bytes())
|
||||
.name("auth")
|
||||
.path("/")
|
||||
.domain(domain.as_str())
|
||||
.max_age(Duration::days(1))
|
||||
.secure(false), // this can only be true if you have https
|
||||
))
|
||||
// everything under '/api/' route
|
||||
.service(
|
||||
web::scope("/api")
|
||||
// routes for authentication
|
||||
.service(
|
||||
web::resource("/auth")
|
||||
.route(web::post().to_async(auth_routes::login))
|
||||
.route(web::delete().to(auth_routes::logout))
|
||||
.route(web::get().to_async(auth_routes::get_me)),
|
||||
)
|
||||
// routes to invitation
|
||||
.service(
|
||||
web::resource("/invitation").route(
|
||||
web::post().to_async(invitation_routes::register_email),
|
||||
),
|
||||
)
|
||||
// routes to register as a user after the
|
||||
.service(
|
||||
web::resource("/register/{invitation_id}")
|
||||
.route(web::post().to_async(register_routes::register_user)),
|
||||
),
|
||||
)
|
||||
// serve static files
|
||||
.service(fs::Files::new("/", "./static/").index_file("index.html"))
|
||||
})
|
||||
.bind("127.0.0.1:3000")?
|
||||
.run()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use std::convert::From;
|
||||
use uuid::Uuid;
|
||||
|
||||
use schema::{invitations, users};
|
||||
use crate::schema::{invitations, users};
|
||||
|
||||
/// This is db executor actor. can be run in parallel
|
||||
pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>);
|
||||
|
@ -1,11 +1,12 @@
|
||||
use actix::{Handler, Message};
|
||||
use chrono::Local;
|
||||
use diesel::prelude::*;
|
||||
use errors::ServiceError;
|
||||
use models::{DbExecutor, Invitation, SlimUser, User};
|
||||
use utils::hash_password;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::ServiceError;
|
||||
use crate::models::{DbExecutor, Invitation, SlimUser, User};
|
||||
use crate::utils::hash_password;
|
||||
|
||||
// UserData is used to extract data from a post request by the client
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UserData {
|
||||
@ -26,8 +27,8 @@ impl Message for RegisterUser {
|
||||
impl Handler<RegisterUser> for DbExecutor {
|
||||
type Result = Result<SlimUser, ServiceError>;
|
||||
fn handle(&mut self, msg: RegisterUser, _: &mut Self::Context) -> Self::Result {
|
||||
use schema::invitations::dsl::{id, invitations};
|
||||
use schema::users::dsl::users;
|
||||
use crate::schema::invitations::dsl::{id, invitations};
|
||||
use crate::schema::users::dsl::users;
|
||||
let conn: &PgConnection = &self.0.get().unwrap();
|
||||
|
||||
// try parsing the string provided by the user as url parameter
|
||||
|
@ -1,27 +1,25 @@
|
||||
use actix_web::{
|
||||
AsyncResponder, FutureResponse, HttpResponse, Json, Path, ResponseError, State,
|
||||
};
|
||||
use futures::future::Future;
|
||||
use actix::Addr;
|
||||
use actix_web::{web, Error, HttpResponse, ResponseError};
|
||||
use futures::Future;
|
||||
|
||||
use app::AppState;
|
||||
use register_handler::{RegisterUser, UserData};
|
||||
use crate::models::DbExecutor;
|
||||
use crate::register_handler::{RegisterUser, UserData};
|
||||
|
||||
pub fn register_user(
|
||||
(invitation_id, user_data, state): (Path<String>, Json<UserData>, State<AppState>),
|
||||
) -> FutureResponse<HttpResponse> {
|
||||
invitation_id: web::Path<String>,
|
||||
user_data: web::Json<UserData>,
|
||||
db: web::Data<Addr<DbExecutor>>,
|
||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
let msg = RegisterUser {
|
||||
// into_inner() returns the inner string value from Path
|
||||
invitation_id: invitation_id.into_inner(),
|
||||
password: user_data.password.clone(),
|
||||
};
|
||||
|
||||
state
|
||||
.db
|
||||
.send(msg)
|
||||
db.send(msg)
|
||||
.from_err()
|
||||
.and_then(|db_response| match db_response {
|
||||
Ok(slim_user) => Ok(HttpResponse::Ok().json(slim_user)),
|
||||
Err(service_error) => Ok(service_error.error_response()),
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use chrono::{Duration, Local};
|
||||
use errors::ServiceError;
|
||||
use jwt::{decode, encode, Header, Validation};
|
||||
use models::SlimUser;
|
||||
use std::convert::From;
|
||||
use std::env;
|
||||
use jsonwebtoken::{decode, encode, Header, Validation};
|
||||
|
||||
use crate::errors::ServiceError;
|
||||
use crate::models::SlimUser;
|
||||
|
||||
pub fn hash_password(plain: &str) -> Result<String, ServiceError> {
|
||||
// get the hashing cost from the env variable or use default
|
||||
let hashing_cost: u32 = match env::var("HASH_ROUNDS") {
|
||||
let hashing_cost: u32 = match std::env::var("HASH_ROUNDS") {
|
||||
Ok(cost) => cost.parse().unwrap_or(DEFAULT_COST),
|
||||
_ => DEFAULT_COST,
|
||||
};
|
||||
@ -64,5 +63,5 @@ pub fn decode_token(token: &str) -> Result<SlimUser, ServiceError> {
|
||||
}
|
||||
|
||||
fn get_secret() -> String {
|
||||
env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into())
|
||||
std::env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into())
|
||||
}
|
||||
|
Reference in New Issue
Block a user