1use actix_session::{Session, SessionExt};
5use actix_web::{
6 body::EitherBody,
7 dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
8 web, Error, HttpResponse,
9};
10use arc_auth_core::{AuthError, Identity, IdentityStore};
11use arc_web::{ArcAppBuilder, ArcPlugin};
12use futures_util::future::LocalBoxFuture;
13use std::{
14 future::{ready, Ready},
15 sync::Arc,
16};
17
18pub const IDENTITY_SESSION_KEY: &str = "arc_auth_identity";
19
20pub fn identity(session: &Session) -> Option<Identity> {
21 session.get(IDENTITY_SESSION_KEY).ok().flatten()
22}
23
24pub fn cache_identity(session: &Session, identity: &Identity) {
25 let _ = session.insert(IDENTITY_SESSION_KEY, identity);
26 arc_web::helpers::session::set_session_user(
27 session,
28 &arc_web::helpers::session::SessionUser {
29 id: identity.id.clone(),
30 name: identity.name.clone(),
31 email: identity.email.clone(),
32 },
33 );
34}
35
36pub async fn authenticate(
37 session: &Session,
38 store: &dyn IdentityStore,
39 email: &str,
40 password: &str,
41) -> Result<Identity, AuthError> {
42 let identity = store.authenticate(email, password).await?;
43 cache_identity(session, &identity);
44 Ok(identity)
45}
46
47pub fn sign_out(session: &Session) {
48 session.remove(IDENTITY_SESSION_KEY);
49 arc_web::helpers::session::clear_session_user(session);
50}
51
52pub struct SessionAuthPlugin;
54#[async_trait::async_trait]
55impl ArcPlugin for SessionAuthPlugin {
56 fn name(&self) -> &'static str {
57 "auth-session"
58 }
59 fn register(&self, builder: ArcAppBuilder) -> ArcAppBuilder {
60 builder
61 }
62}
63
64pub struct RequireSession;
66impl<S, B> Transform<S, ServiceRequest> for RequireSession
67where
68 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
69 S::Future: 'static,
70 B: 'static,
71{
72 type Response = ServiceResponse<EitherBody<B>>;
73 type Error = Error;
74 type InitError = ();
75 type Transform = SessionCheck<S>;
76 type Future = Ready<Result<Self::Transform, ()>>;
77 fn new_transform(&self, service: S) -> Self::Future {
78 ready(Ok(SessionCheck {
79 service: Arc::new(service),
80 }))
81 }
82}
83pub struct SessionCheck<S> {
84 service: Arc<S>,
85}
86impl<S, B> Service<ServiceRequest> for SessionCheck<S>
87where
88 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
89 S::Future: 'static,
90 B: 'static,
91{
92 type Response = ServiceResponse<EitherBody<B>>;
93 type Error = Error;
94 type Future = LocalBoxFuture<'static, Result<Self::Response, Error>>;
95 forward_ready!(service);
96 fn call(&self, req: ServiceRequest) -> Self::Future {
97 if req
98 .get_session()
99 .get::<Identity>(IDENTITY_SESSION_KEY)
100 .ok()
101 .flatten()
102 .is_none()
103 {
104 return Box::pin(async move {
105 Ok(req.into_response(
106 HttpResponse::Found()
107 .insert_header(("Location", "/signin"))
108 .finish()
109 .map_into_right_body(),
110 ))
111 });
112 }
113 let future = self.service.call(req);
114 Box::pin(async move { future.await.map(ServiceResponse::map_into_left_body) })
115 }
116}
117
118pub type IdentityStoreData = web::Data<dyn IdentityStore>;