summaryrefslogtreecommitdiff
path: root/src/login/extract.rs
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-09-15 23:50:41 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-09-16 11:03:22 -0400
commit491cb3eb34d20140aed80dbb9edc39c4db5335d2 (patch)
treee1e2e009f064dc6dfc8c98d2bf97d8d1f7b45615 /src/login/extract.rs
parent99b33023332393e46f5a661901b980b78e6fb133 (diff)
Consolidate most repository types into a repo module.
Having them contained in the individual endpoint groups conveyed an unintended sense that their intended scope was _only_ that endpoint group. It also made most repo-related import paths _quite_ long. This splits up the repos as follows: * "General applicability" repos - those that are only loosely connected to a single task, and are likely to be shared between tasks - go in crate::repo. * Specialized repos - those tightly connected to a specific task - go in the module for that task, under crate::PATH::repo. In both cases, each repo goes in its own submodule, to make it easier to use the module name as a namespace. Which category a repo goes in is a judgment call. `crate::channel::repo::broadcast` (formerly `channel::repo::messages`) is used outside of `crate::channel`, for example, but its main purpose is to support channel message broadcasts. It could arguably live under `crate::event::repo::channel`, but the resulting namespace is less legible to me.
Diffstat (limited to 'src/login/extract.rs')
-rw-r--r--src/login/extract.rs68
1 files changed, 68 insertions, 0 deletions
diff --git a/src/login/extract.rs b/src/login/extract.rs
new file mode 100644
index 0000000..735bc22
--- /dev/null
+++ b/src/login/extract.rs
@@ -0,0 +1,68 @@
+use axum::{
+ extract::FromRequestParts,
+ http::request::Parts,
+ response::{IntoResponseParts, ResponseParts},
+};
+use axum_extra::extract::cookie::{Cookie, CookieJar};
+
+// The usage pattern here - receive the extractor as an argument, return it in
+// the response - is heavily modelled after CookieJar's own intended usage.
+pub struct IdentityToken {
+ cookies: CookieJar,
+}
+
+impl IdentityToken {
+ /// Get the identity secret sent in the request, if any. If the identity
+ /// was not sent, or if it has previously been [clear]ed, then this will
+ /// return [None]. If the identity has previously been [set], then this
+ /// will return that secret, regardless of what the request originally
+ /// included.
+ pub fn secret(&self) -> Option<&str> {
+ self.cookies.get(IDENTITY_COOKIE).map(Cookie::value)
+ }
+
+ /// Positively set the identity secret, and ensure that it will be sent
+ /// back to the client when this extractor is included in a response.
+ pub fn set(self, secret: &str) -> Self {
+ let identity_cookie = Cookie::build((IDENTITY_COOKIE, String::from(secret)))
+ .http_only(true)
+ .permanent()
+ .build();
+
+ Self {
+ cookies: self.cookies.add(identity_cookie),
+ }
+ }
+
+ /// Remove the identity secret and ensure that it will be cleared when this
+ /// extractor is included in a response.
+ pub fn clear(self) -> Self {
+ Self {
+ cookies: self.cookies.remove(IDENTITY_COOKIE),
+ }
+ }
+}
+
+const IDENTITY_COOKIE: &str = "identity";
+
+#[async_trait::async_trait]
+impl<S> FromRequestParts<S> for IdentityToken
+where
+ S: Send + Sync,
+{
+ type Rejection = <CookieJar as FromRequestParts<S>>::Rejection;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
+ let cookies = CookieJar::from_request_parts(parts, state).await?;
+ Ok(Self { cookies })
+ }
+}
+
+impl IntoResponseParts for IdentityToken {
+ type Error = <CookieJar as IntoResponseParts>::Error;
+
+ fn into_response_parts(self, res: ResponseParts) -> Result<ResponseParts, Self::Error> {
+ let Self { cookies } = self;
+ cookies.into_response_parts(res)
+ }
+}