This commit is contained in:
parent
089163f79d
commit
9d6586063d
8 changed files with 40 additions and 47 deletions
|
@ -10,7 +10,7 @@ use crate::http::error::ApiError;
|
|||
|
||||
use super::{hash::random, user::User};
|
||||
|
||||
pub const USER_NOT_FOUND: ApiError<'static> = ApiError::ClientError {
|
||||
pub const USER_NOT_FOUND: ApiError<'static> = ApiError::Client {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
code: "user-not-found",
|
||||
message: "The logged-in user was not found",
|
||||
|
@ -49,7 +49,7 @@ impl Session {
|
|||
Ok(Self {
|
||||
id: result.id,
|
||||
actor: user_id,
|
||||
secret: secret,
|
||||
secret,
|
||||
created_at: now,
|
||||
expires_at: now + duration,
|
||||
})
|
||||
|
@ -109,7 +109,7 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn refresh(self: Self, pool: &Pool<Postgres>, duration: Duration) -> Result<Self> {
|
||||
pub async fn refresh(self, pool: &Pool<Postgres>, duration: Duration) -> Result<Self> {
|
||||
let expires_at = (Utc::now() + duration).naive_utc();
|
||||
|
||||
sqlx::query!(
|
||||
|
@ -123,13 +123,13 @@ impl Session {
|
|||
Ok(Session { expires_at, ..self })
|
||||
}
|
||||
|
||||
pub fn token(self: &Self) -> String {
|
||||
pub fn token(&self) -> String {
|
||||
format!("{}:{}", self.id.as_u128(), self.secret)
|
||||
}
|
||||
|
||||
pub fn parse_token(token: &str) -> Result<(Uuid, String)> {
|
||||
let (uuid_str, token_str) = token
|
||||
.split_once(":")
|
||||
.split_once(':')
|
||||
.ok_or_else(|| anyhow!("malformed token"))?;
|
||||
Ok((
|
||||
Uuid::from_u128(uuid_str.parse::<u128>()?),
|
||||
|
@ -137,7 +137,7 @@ impl Session {
|
|||
))
|
||||
}
|
||||
|
||||
pub async fn destroy(self: Self, pool: &Pool<Postgres>) -> Result<()> {
|
||||
pub async fn destroy(&self, pool: &Pool<Postgres>) -> Result<()> {
|
||||
sqlx::query!("DELETE FROM sessions WHERE id = $1", self.id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
@ -152,7 +152,7 @@ impl Session {
|
|||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
pub fn cookie(self: &Self, domain: &str, secure: bool) -> String {
|
||||
pub fn cookie(&self, domain: &str, secure: bool) -> String {
|
||||
Cookie::build("session", self.token())
|
||||
.domain(domain)
|
||||
.secure(secure)
|
||||
|
|
|
@ -10,5 +10,5 @@ pub enum Error {
|
|||
IdentifierNotAvailable,
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
DatabaseError(#[from] sqlx::Error),
|
||||
QueryFailed(#[from] sqlx::Error),
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ impl SiteRepository for Database {
|
|||
})?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(Error::NotFound.into());
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -90,7 +90,7 @@ impl SiteRepository for Database {
|
|||
};
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(Error::NotFound.into());
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -10,12 +10,12 @@ use thiserror::Error;
|
|||
use crate::content;
|
||||
|
||||
// Generic errors
|
||||
const ERR_NOT_FOUND: ApiError<'static> = ApiError::ClientError {
|
||||
const ERR_NOT_FOUND: ApiError<'static> = ApiError::Client {
|
||||
status: StatusCode::NOT_FOUND,
|
||||
code: "not-found",
|
||||
message: "resource not found",
|
||||
};
|
||||
const ERR_NOT_AVAILABLE: ApiError<'static> = ApiError::ClientError {
|
||||
const ERR_NOT_AVAILABLE: ApiError<'static> = ApiError::Client {
|
||||
status: StatusCode::CONFLICT,
|
||||
code: "id-not-available",
|
||||
message: "the chosen identifier is not available",
|
||||
|
@ -24,41 +24,41 @@ const ERR_NOT_AVAILABLE: ApiError<'static> = ApiError::ClientError {
|
|||
#[derive(Error, Debug)]
|
||||
pub enum ApiError<'a> {
|
||||
#[error("client error: <{code}> {message}")]
|
||||
ClientError {
|
||||
Client {
|
||||
status: StatusCode,
|
||||
code: &'a str,
|
||||
message: &'a str,
|
||||
},
|
||||
|
||||
#[error("unexpected internal error: {0}")]
|
||||
InternalError(#[from] anyhow::Error),
|
||||
Internal(#[from] anyhow::Error),
|
||||
|
||||
#[error("database returnede error: {0}")]
|
||||
DatabaseError(#[from] sqlx::Error),
|
||||
Database(#[from] sqlx::Error),
|
||||
|
||||
#[error("incoming JSON format error: {0}")]
|
||||
JSONFormatError(#[from] JsonRejection),
|
||||
JSONFormat(#[from] JsonRejection),
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError<'_> {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
ApiError::InternalError(err) => (
|
||||
ApiError::Internal(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"code":"server-error", "message": err.to_string()})),
|
||||
)
|
||||
.into_response(),
|
||||
ApiError::DatabaseError(err) => (
|
||||
ApiError::Database(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"code":"server-error", "message": err.to_string()})),
|
||||
)
|
||||
.into_response(),
|
||||
ApiError::ClientError {
|
||||
ApiError::Client {
|
||||
status,
|
||||
code,
|
||||
message,
|
||||
} => (status, Json(json!({"code":code, "message": message}))).into_response(),
|
||||
ApiError::JSONFormatError(rejection) => {
|
||||
ApiError::JSONFormat(rejection) => {
|
||||
let status = match rejection {
|
||||
JsonRejection::JsonDataError(_) => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
JsonRejection::JsonSyntaxError(_) => StatusCode::BAD_REQUEST,
|
||||
|
@ -80,7 +80,7 @@ impl From<content::Error> for ApiError<'_> {
|
|||
match err {
|
||||
content::Error::NotFound => ERR_NOT_FOUND,
|
||||
content::Error::IdentifierNotAvailable => ERR_NOT_AVAILABLE,
|
||||
content::Error::DatabaseError(err) => err.into(),
|
||||
content::Error::QueryFailed(err) => err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,14 @@ use crate::{
|
|||
state::AppState,
|
||||
};
|
||||
|
||||
pub const INVALID_SESSION: ApiError = ApiError::ClientError {
|
||||
pub const INVALID_SESSION: ApiError = ApiError::Client {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
code: "authentication-required",
|
||||
message: "Please log-in and submit a valid session as a cookie",
|
||||
};
|
||||
|
||||
fn extract_session_token(header: &HeaderValue) -> Result<(Uuid, String)> {
|
||||
Ok(Session::parse_token(
|
||||
Cookie::parse(header.to_str()?)?.value(),
|
||||
)?)
|
||||
Session::parse_token(Cookie::parse(header.to_str()?)?.value())
|
||||
}
|
||||
|
||||
pub struct RequireUser(pub User);
|
||||
|
@ -78,18 +76,17 @@ pub async fn refresh_sessions<B>(
|
|||
.get(COOKIE)
|
||||
.and_then(|header| extract_session_token(header).ok())
|
||||
{
|
||||
if let Some(Some((session, user))) = Session::find(&state.database, session_id).await.ok() {
|
||||
if let Ok(Some((session, user))) = Session::find(&state.database, session_id).await {
|
||||
// session validity requirements: secret must match, session must not have been expired
|
||||
if session.secret == session_secret && session.expires_at >= Utc::now().naive_utc() {
|
||||
// in the future we might wanna change the session secret, if we do, do it here!
|
||||
if let Some((session, user)) = session
|
||||
if let Ok((session, user)) = session
|
||||
.refresh(
|
||||
&state.database,
|
||||
Duration::seconds(state.config.session_duration),
|
||||
)
|
||||
.await
|
||||
.map(|s| (s, user))
|
||||
.ok()
|
||||
{
|
||||
let extensions = req.extensions_mut();
|
||||
extensions.insert(session.clone());
|
||||
|
|
|
@ -24,10 +24,10 @@ async fn bootstrap(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
|||
.map_err(anyhow::Error::from)?;
|
||||
|
||||
if !empty {
|
||||
return Err(ApiError::ClientError {
|
||||
return Err(ApiError::Client {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
code: "already-setup".into(),
|
||||
message: "The instance was already bootstrapped".into(),
|
||||
code: "already-setup",
|
||||
message: "The instance was already bootstrapped",
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ async fn bootstrap(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
|||
|
||||
User::create(
|
||||
&state.database,
|
||||
&username,
|
||||
username,
|
||||
&password,
|
||||
&[ROLE_SUPERADMIN].to_vec(),
|
||||
)
|
||||
|
|
|
@ -35,10 +35,10 @@ async fn login(
|
|||
.map_err(ApiError::from)?;
|
||||
|
||||
let invalid = || -> ApiError {
|
||||
ApiError::ClientError {
|
||||
ApiError::Client {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
code: "invalid-login".into(),
|
||||
message: "No matching user was found".into(),
|
||||
code: "invalid-login",
|
||||
message: "No matching user was found",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@ async fn login(
|
|||
let session = Session::create(
|
||||
&state.database,
|
||||
user.id,
|
||||
Duration::seconds(state.config.session_duration.into()),
|
||||
Duration::seconds(state.config.session_duration),
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
@ -64,7 +64,7 @@ async fn login(
|
|||
response.headers_mut().insert(
|
||||
SET_COOKIE,
|
||||
session
|
||||
.cookie(state.config.domain().as_str(), state.config.secure())
|
||||
.cookie(&state.config.domain(), state.config.secure())
|
||||
.parse()
|
||||
.map_err(anyhow::Error::from)?,
|
||||
);
|
||||
|
@ -85,7 +85,7 @@ async fn logout(
|
|||
let mut response: Response = Json(json!({ "ok": true })).into_response();
|
||||
response.headers_mut().insert(
|
||||
SET_COOKIE,
|
||||
Session::cookie_for_delete(state.config.domain().as_str(), state.config.secure())
|
||||
Session::cookie_for_delete(&state.config.domain(), state.config.secure())
|
||||
.parse()
|
||||
.map_err(anyhow::Error::from)?,
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
extract::Path,
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
|
@ -7,10 +7,7 @@ use axum::{
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
content::page::PageRepository,
|
||||
database::Database,
|
||||
http::{error::ApiError, session::RequireUser},
|
||||
state::AppState,
|
||||
content::page::PageRepository, database::Database, http::error::ApiError, state::AppState,
|
||||
};
|
||||
|
||||
async fn get_page<Repo: PageRepository>(
|
||||
|
@ -20,10 +17,9 @@ async fn get_page<Repo: PageRepository>(
|
|||
Ok(Json(repository.get_page_from_url(&site, &slug).await?))
|
||||
}
|
||||
|
||||
async fn create_page<Repo: PageRepository>(
|
||||
repository: Repo,
|
||||
Path(site): Path<String>,
|
||||
RequireUser(user): RequireUser,
|
||||
async fn create_page<Repo: PageRepository>(//repository: Repo,
|
||||
//Path(site): Path<String>,
|
||||
//RequireUser(user): RequireUser,
|
||||
) -> Result<impl IntoResponse, ApiError<'static>> {
|
||||
Ok(Json("todo"))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue