1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//! Opinionated request identity service for Actix Web apps.
//!
//! [`IdentityService`] middleware can be used with different policies types to store
//! identity information.
//!
//! A cookie based policy is provided. [`CookieIdentityPolicy`] uses cookies as identity storage.
//!
//! To access current request identity, use the [`Identity`] extractor.
//!
//! ```no_run
//! use actix_web::*;
//! use actix_identity::{Identity, CookieIdentityPolicy, IdentityService};
//!
//! #[get("/")]
//! async fn index(id: Identity) -> String {
//! // access request identity
//! if let Some(id) = id.identity() {
//! format!("Welcome! {}", id)
//! } else {
//! "Welcome Anonymous!".to_owned()
//! }
//! }
//!
//! #[post("/login")]
//! async fn login(id: Identity) -> HttpResponse {
//! id.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish()
//! }
//!
//! #[post("/logout")]
//! async fn logout(id: Identity) -> HttpResponse {
//! id.forget(); // <- remove identity
//! HttpResponse::Ok().finish()
//! }
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(move || {
//! // create cookie identity backend
//! let policy = CookieIdentityPolicy::new(&[0; 32])
//! .name("auth-cookie")
//! .secure(false);
//!
//! App::new()
//! // wrap policy into middleware identity middleware
//! .wrap(IdentityService::new(policy))
//! .service(services![index, login, logout])
//! })
//! .bind(("0.0.0.0", 8080u16))?
//! .run()
//! .await
//! }
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
use std::future::Future;
use actix_web::{
dev::{ServiceRequest, ServiceResponse},
Error, HttpMessage, Result,
};
mod cookie;
mod identity;
mod middleware;
pub use self::cookie::CookieIdentityPolicy;
pub use self::identity::Identity;
pub use self::middleware::IdentityService;
/// Identity policy.
pub trait IdentityPolicy: Sized + 'static {
/// The return type of the middleware
type Future: Future<Output = Result<Option<String>, Error>>;
/// The return type of the middleware
type ResponseFuture: Future<Output = Result<(), Error>>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, req: &mut ServiceRequest) -> Self::Future;
/// Write changes to response
fn to_response<B>(
&self,
identity: Option<String>,
changed: bool,
response: &mut ServiceResponse<B>,
) -> Self::ResponseFuture;
}
/// Helper trait that allows to get Identity.
///
/// It could be used in middleware but identity policy must be set before any other middleware that
/// needs identity. RequestIdentity is implemented both for `ServiceRequest` and `HttpRequest`.
pub trait RequestIdentity {
fn get_identity(&self) -> Option<String>;
}
impl<T> RequestIdentity for T
where
T: HttpMessage,
{
fn get_identity(&self) -> Option<String> {
Identity::get_identity(&self.extensions())
}
}
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use actix_web::{
body::{BoxBody, EitherBody},
dev::ServiceResponse,
test, web, App, Error,
};
use super::*;
pub(crate) const COOKIE_KEY_MASTER: [u8; 32] = [0; 32];
pub(crate) const COOKIE_NAME: &str = "actix_auth";
pub(crate) const COOKIE_LOGIN: &str = "test";
#[allow(clippy::enum_variant_names)]
pub(crate) enum LoginTimestampCheck {
NoTimestamp,
NewTimestamp,
OldTimestamp(SystemTime),
}
#[allow(clippy::enum_variant_names)]
pub(crate) enum VisitTimeStampCheck {
NoTimestamp,
NewTimestamp,
}
pub(crate) async fn create_identity_server<
F: Fn(CookieIdentityPolicy) -> CookieIdentityPolicy + Sync + Send + Clone + 'static,
>(
f: F,
) -> impl actix_service::Service<
actix_http::Request,
Response = ServiceResponse<EitherBody<BoxBody>>,
Error = Error,
> {
test::init_service(
App::new()
.wrap(IdentityService::new(f(CookieIdentityPolicy::new(
&COOKIE_KEY_MASTER,
)
.secure(false)
.name(COOKIE_NAME))))
.service(web::resource("/").to(|id: Identity| async move {
let identity = id.identity();
if identity.is_none() {
id.remember(COOKIE_LOGIN.to_string())
}
web::Json(identity)
})),
)
.await
}
}