diff options
Diffstat (limited to 'src/login/extract')
| -rw-r--r-- | src/login/extract/identity_token.rs | 69 | ||||
| -rw-r--r-- | src/login/extract/login.rs | 61 | ||||
| -rw-r--r-- | src/login/extract/mod.rs | 4 |
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; |
