summaryrefslogtreecommitdiff
path: root/src/login/extract
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/extract')
-rw-r--r--src/login/extract/identity_token.rs69
-rw-r--r--src/login/extract/login.rs61
-rw-r--r--src/login/extract/mod.rs4
3 files changed, 134 insertions, 0 deletions
diff --git a/src/login/extract/identity_token.rs b/src/login/extract/identity_token.rs
new file mode 100644
index 0000000..d39e3df
--- /dev/null
+++ b/src/login/extract/identity_token.rs
@@ -0,0 +1,69 @@
+use std::convert::Infallible;
+
+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 token 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 token.
+ pub fn token(&self) -> Option<&str> {
+ self.cookies.get(IDENTITY_COOKIE).map(Cookie::value)
+ }
+
+ /// Positively set the identity token, and ensure that it will be sent back
+ /// to the client when this extractor is included in a response.
+ pub fn set(self, token: &str) -> Self {
+ let identity_cookie = Cookie::build((IDENTITY_COOKIE, String::from(token)))
+ .http_only(true)
+ .permanent()
+ .build();
+
+ IdentityToken {
+ cookies: self.cookies.add(identity_cookie),
+ }
+ }
+
+ /// Remove the identity token and ensure that it will be cleared when this
+ /// extractor is included in a response.
+ pub fn clear(self) -> Self {
+ IdentityToken {
+ 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 = Infallible;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
+ let cookies = CookieJar::from_request_parts(parts, state).await?;
+ Ok(IdentityToken { cookies })
+ }
+}
+
+impl IntoResponseParts for IdentityToken {
+ type Error = Infallible;
+
+ fn into_response_parts(self, res: ResponseParts) -> Result<ResponseParts, Self::Error> {
+ let IdentityToken { cookies } = self;
+ cookies.into_response_parts(res)
+ }
+}
diff --git a/src/login/extract/login.rs b/src/login/extract/login.rs
new file mode 100644
index 0000000..f49933a
--- /dev/null
+++ b/src/login/extract/login.rs
@@ -0,0 +1,61 @@
+use axum::{
+ extract::{FromRequestParts, State},
+ http::{request::Parts, StatusCode},
+ response::{IntoResponse, Response},
+};
+use sqlx::sqlite::SqlitePool;
+
+use crate::{
+ error::InternalError,
+ login::{
+ extract::IdentityToken,
+ repo::{logins::Login, tokens::Provider as _},
+ },
+};
+
+#[async_trait::async_trait]
+impl FromRequestParts<SqlitePool> for Login {
+ type Rejection = LoginError<InternalError>;
+
+ async fn from_request_parts(
+ parts: &mut Parts,
+ state: &SqlitePool,
+ ) -> Result<Self, Self::Rejection> {
+ let identity_token = IdentityToken::from_request_parts(parts, state).await?;
+
+ let token = identity_token.token().ok_or(LoginError::Forbidden)?;
+
+ let db = State::<SqlitePool>::from_request_parts(parts, state).await?;
+ let mut tx = db.begin().await?;
+ let login = tx.tokens().validate(token).await?;
+ tx.commit().await?;
+
+ login.ok_or(LoginError::Forbidden)
+ }
+}
+
+pub enum LoginError<E> {
+ Failure(E),
+ Forbidden,
+}
+
+impl<E> IntoResponse for LoginError<E>
+where
+ E: IntoResponse,
+{
+ fn into_response(self) -> Response {
+ match self {
+ Self::Forbidden => (StatusCode::FORBIDDEN, "forbidden").into_response(),
+ Self::Failure(e) => e.into_response(),
+ }
+ }
+}
+
+impl<E> From<E> for LoginError<InternalError>
+where
+ E: Into<InternalError>,
+{
+ fn from(err: E) -> Self {
+ Self::Failure(err.into())
+ }
+}
diff --git a/src/login/extract/mod.rs b/src/login/extract/mod.rs
new file mode 100644
index 0000000..ba943a6
--- /dev/null
+++ b/src/login/extract/mod.rs
@@ -0,0 +1,4 @@
+mod identity_token;
+mod login;
+
+pub use self::identity_token::IdentityToken;