yew_oauth2/components/redirect/
mod.rs

1//! Components for redirecting the user
2
3pub mod location;
4#[cfg(feature = "yew-nested-router")]
5pub mod router;
6
7use super::missing_context;
8use crate::agent::{Client, OAuth2Operations};
9use crate::components::context::Agent;
10use crate::context::{OAuth2Context, Reason};
11use yew::{context::ContextHandle, prelude::*};
12
13pub trait Redirector: 'static {
14    type Properties: RedirectorProperties;
15
16    fn new<COMP: Component>(ctx: &Context<COMP>) -> Self;
17
18    fn logout(&self, props: &Self::Properties);
19}
20
21pub trait RedirectorProperties: yew::Properties {
22    fn children(&self) -> &Html;
23}
24
25#[derive(Debug, Clone)]
26pub enum Msg<C: Client> {
27    Context(OAuth2Context),
28    Agent(Agent<C>),
29}
30
31/// A component which redirect the user in case the context is not authenticated.
32pub struct Redirect<C, R>
33where
34    C: Client,
35    R: Redirector,
36{
37    auth: Option<OAuth2Context>,
38    agent: Option<Agent<C>>,
39
40    _auth_handler: Option<ContextHandle<OAuth2Context>>,
41    _agent_handler: Option<ContextHandle<Agent<C>>>,
42
43    redirector: R,
44}
45
46impl<C, R> Component for Redirect<C, R>
47where
48    C: Client,
49    R: Redirector,
50{
51    type Message = Msg<C>;
52    type Properties = R::Properties;
53
54    fn create(ctx: &Context<Self>) -> Self {
55        let (auth, auth_handler) = match ctx
56            .link()
57            .context::<OAuth2Context>(ctx.link().callback(Msg::Context))
58        {
59            Some((auth, handler)) => (Some(auth), Some(handler)),
60            None => (None, None),
61        };
62        let (agent, agent_handler) = match ctx
63            .link()
64            .context::<Agent<C>>(ctx.link().callback(Msg::Agent))
65        {
66            Some((agent, handler)) => (Some(agent), Some(handler)),
67            None => (None, None),
68        };
69
70        log::debug!("Initial state: {auth:?}");
71
72        let mut result = Self {
73            auth: None,
74            agent,
75            _auth_handler: auth_handler,
76            _agent_handler: agent_handler,
77            redirector: R::new(ctx),
78        };
79
80        if let Some(auth) = auth {
81            result.apply_state(ctx, auth);
82        }
83
84        result
85    }
86
87    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
88        log::debug!("update: {msg:?}");
89
90        match msg {
91            Self::Message::Context(auth) => {
92                let changed = self.auth.as_ref() != Some(&auth);
93                self.apply_state(ctx, auth);
94                changed
95            }
96            Self::Message::Agent(agent) => {
97                self.agent = Some(agent);
98                // we never re-render based on an agent change
99                false
100            }
101        }
102    }
103
104    fn view(&self, ctx: &Context<Self>) -> Html {
105        match self.auth {
106            None => missing_context(),
107            Some(OAuth2Context::Authenticated(..)) => ctx.props().children().clone(),
108            _ => html!(),
109        }
110    }
111}
112
113impl<C, R> Redirect<C, R>
114where
115    C: Client,
116    R: Redirector,
117{
118    fn apply_state(&mut self, ctx: &Context<Self>, auth: OAuth2Context) {
119        if self.auth.as_ref() == Some(&auth) {
120            return;
121        }
122
123        log::debug!("Current state: {:?}, new state: {:?}", self.auth, auth);
124
125        match &auth {
126            OAuth2Context::NotInitialized
127            | OAuth2Context::Failed(..)
128            | OAuth2Context::Authenticated { .. } => {
129                // nothing that we should handle
130            }
131            OAuth2Context::NotAuthenticated { reason } => match reason {
132                Reason::NewSession => {
133                    // new session, then start the login
134                    if let Some(agent) = &mut self.agent {
135                        let _ = agent.start_login();
136                    }
137                }
138                Reason::Expired | Reason::Logout => {
139                    match self.auth {
140                        None | Some(OAuth2Context::NotInitialized) => {
141                            if let Some(agent) = &mut self.agent {
142                                let _ = agent.start_login();
143                            }
144                        }
145                        _ => {
146                            // expired or logged out explicitly, then redirect to the logout page
147                            self.logout(ctx.props());
148                        }
149                    }
150                }
151            },
152        }
153
154        self.auth = Some(auth);
155    }
156
157    fn logout(&self, props: &R::Properties) {
158        self.redirector.logout(props);
159    }
160}