session extractor done
This commit is contained in:
parent
b3380648bb
commit
3402b67441
11 changed files with 299 additions and 210 deletions
|
@ -17,7 +17,7 @@ serde_json = { version = "1", features = ["raw_value"] }
|
||||||
figment = { version = "0.10", features = ["toml", "env"] }
|
figment = { version = "0.10", features = ["toml", "env"] }
|
||||||
chrono = { version = "0.4", features = ["serde", "clock"] }
|
chrono = { version = "0.4", features = ["serde", "clock"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argon2 = "0.5"
|
argon2 = { version = "0.5", features = ["std", "alloc"] }
|
||||||
url = "2.4"
|
url = "2.4"
|
||||||
|
|
||||||
[profile.dev.package.sqlx-macros]
|
[profile.dev.package.sqlx-macros]
|
||||||
|
|
195
src/auth.rs
195
src/auth.rs
|
@ -1,195 +0,0 @@
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use argon2::{
|
|
||||||
password_hash::{rand_core::OsRng, SaltString},
|
|
||||||
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
|
||||||
};
|
|
||||||
use chrono::{Duration, NaiveDateTime, Utc};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::{FromRow, Pool, Postgres};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
|
||||||
pub struct User {
|
|
||||||
/// User internal ID
|
|
||||||
pub id: Uuid,
|
|
||||||
|
|
||||||
/// User name (unique per instance, shows up in URLs)
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// User email (for validation/login)
|
|
||||||
pub email: Option<String>,
|
|
||||||
|
|
||||||
/// Hashed password
|
|
||||||
pub password: Option<String>,
|
|
||||||
|
|
||||||
/// User's chosen displayed name
|
|
||||||
pub display_name: Option<String>,
|
|
||||||
|
|
||||||
/// Biography / User description
|
|
||||||
pub bio: Option<String>,
|
|
||||||
|
|
||||||
/// User roles (as role IDs)
|
|
||||||
pub roles: Vec<Uuid>,
|
|
||||||
|
|
||||||
/// Times
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub modified_at: Option<NaiveDateTime>,
|
|
||||||
pub deleted_at: Option<NaiveDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for User {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
id: Uuid::nil(),
|
|
||||||
name: Default::default(),
|
|
||||||
email: Default::default(),
|
|
||||||
password: Default::default(),
|
|
||||||
display_name: Default::default(),
|
|
||||||
bio: Default::default(),
|
|
||||||
roles: Default::default(),
|
|
||||||
created_at: Default::default(),
|
|
||||||
modified_at: Default::default(),
|
|
||||||
deleted_at: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl User {
|
|
||||||
pub async fn create(
|
|
||||||
pool: &Pool<Postgres>,
|
|
||||||
username: &str,
|
|
||||||
password: &str,
|
|
||||||
roles: &Vec<Uuid>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let result = sqlx::query!(
|
|
||||||
r#"INSERT INTO users ( name, password, roles ) VALUES ( $1,$2,$3 ) RETURNING id, created_at"#,
|
|
||||||
username,
|
|
||||||
hash(&password)?,
|
|
||||||
roles,
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(Self {
|
|
||||||
id: result.id,
|
|
||||||
name: username.to_owned(),
|
|
||||||
roles: roles.to_owned(),
|
|
||||||
created_at: result.created_at,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find(pool: &Pool<Postgres>, name: &str) -> Result<Option<Self>> {
|
|
||||||
Ok(sqlx::query_as("SELECT * FROM users WHERE name = $1")
|
|
||||||
.bind(name)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
|
||||||
pub struct Role {
|
|
||||||
/// Role ID
|
|
||||||
pub id: Uuid,
|
|
||||||
|
|
||||||
/// Role scopes (permissions)
|
|
||||||
pub scopes: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, FromRow)]
|
|
||||||
pub struct Session {
|
|
||||||
/// Role ID
|
|
||||||
pub id: Uuid,
|
|
||||||
|
|
||||||
/// User ID
|
|
||||||
pub actor: Uuid,
|
|
||||||
|
|
||||||
/// Secret
|
|
||||||
pub secret: String,
|
|
||||||
|
|
||||||
/// Times
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub expires_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Session {
|
|
||||||
pub async fn create(pool: &Pool<Postgres>, user_id: Uuid, duration: Duration) -> Result<Self> {
|
|
||||||
let now = Utc::now().naive_utc();
|
|
||||||
let expires = now + duration;
|
|
||||||
let secret = random();
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"INSERT INTO sessions (actor, secret, created_at, expires_at) VALUES ($1, $2, $3, $4) RETURNING id",
|
|
||||||
user_id,
|
|
||||||
secret,
|
|
||||||
now,
|
|
||||||
expires
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(Self {
|
|
||||||
id: result.id,
|
|
||||||
actor: user_id,
|
|
||||||
secret: secret,
|
|
||||||
created_at: now,
|
|
||||||
expires_at: now + duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn find(pool: &Pool<Postgres>, id: Uuid) -> Result<Option<Self>> {
|
|
||||||
Ok(sqlx::query_as("SELECT * FROM sessions WHERE id = $1")
|
|
||||||
.bind(id)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn refresh(self: Self, pool: &Pool<Postgres>, duration: Duration) -> Result<Self> {
|
|
||||||
let expires_at = (Utc::now() + duration).naive_utc();
|
|
||||||
let secret = random();
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
"UPDATE sessions SET secret = $1, expires_at = $2 WHERE id = $3 RETURNING id",
|
|
||||||
secret,
|
|
||||||
expires_at,
|
|
||||||
self.id
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Session {
|
|
||||||
secret,
|
|
||||||
expires_at,
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn token(self: &Self) -> String {
|
|
||||||
format!("{}:{}", self.id.as_u128(), self.secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_token(token: String) -> Result<(Uuid, String)> {
|
|
||||||
let (uuid_str, token_str) = token.split_once(":").ok_or(anyhow!("malformed token"))?;
|
|
||||||
Ok((
|
|
||||||
Uuid::from_u128(uuid_str.parse::<u128>()?),
|
|
||||||
token_str.to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn random() -> String {
|
|
||||||
SaltString::generate(&mut OsRng).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hash(plaintext: &str) -> Result<String> {
|
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
|
||||||
let hashed = Argon2::default()
|
|
||||||
.hash_password(plaintext.as_bytes(), &salt)
|
|
||||||
.map_err(|err| anyhow!(err))?
|
|
||||||
.to_string();
|
|
||||||
Ok(hashed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(plaintext: &str, hash: &str) -> Result<bool> {
|
|
||||||
let parsed_hash = PasswordHash::new(hash).map_err(|err| anyhow!(err))?;
|
|
||||||
Ok(Argon2::default()
|
|
||||||
.verify_password(plaintext.as_bytes(), &parsed_hash)
|
|
||||||
.is_ok())
|
|
||||||
}
|
|
23
src/auth/hash.rs
Normal file
23
src/auth/hash.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{rand_core::OsRng, SaltString},
|
||||||
|
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn random() -> String {
|
||||||
|
SaltString::generate(&mut OsRng).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash(plaintext: &str) -> Result<String> {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
Ok(Argon2::default()
|
||||||
|
.hash_password(plaintext.as_bytes(), &salt)?
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(plaintext: &str, hash: &str) -> Result<bool> {
|
||||||
|
let parsed_hash = PasswordHash::new(hash)?;
|
||||||
|
Ok(Argon2::default()
|
||||||
|
.verify_password(plaintext.as_bytes(), &parsed_hash)
|
||||||
|
.is_ok())
|
||||||
|
}
|
3
src/auth/mod.rs
Normal file
3
src/auth/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod hash;
|
||||||
|
pub mod session;
|
||||||
|
pub mod user;
|
151
src/auth/session.rs
Normal file
151
src/auth/session.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequestParts, State},
|
||||||
|
http::{header::COOKIE, request::Parts, StatusCode},
|
||||||
|
};
|
||||||
|
use chrono::{Duration, NaiveDateTime, Utc};
|
||||||
|
use cookie::Cookie;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, Pool, Postgres};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{error::AppError, state::AppState};
|
||||||
|
|
||||||
|
use super::{hash::random, user::User};
|
||||||
|
|
||||||
|
const INVALID_SESSION: AppError = AppError::ClientError {
|
||||||
|
status: StatusCode::UNAUTHORIZED,
|
||||||
|
code: "authentication-required",
|
||||||
|
message: "Please log-in and submit a valid session as a cookie",
|
||||||
|
};
|
||||||
|
|
||||||
|
const USER_NOT_FOUND: AppError = AppError::ClientError {
|
||||||
|
status: StatusCode::UNAUTHORIZED,
|
||||||
|
code: "user-not-found",
|
||||||
|
message: "The logged-in user was not found",
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
|
pub struct Session {
|
||||||
|
/// Role ID
|
||||||
|
pub id: Uuid,
|
||||||
|
|
||||||
|
/// User ID
|
||||||
|
pub actor: Uuid,
|
||||||
|
|
||||||
|
/// Secret
|
||||||
|
pub secret: String,
|
||||||
|
|
||||||
|
/// Times
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub expires_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
pub async fn create(pool: &Pool<Postgres>, user_id: Uuid, duration: Duration) -> Result<Self> {
|
||||||
|
let now = Utc::now().naive_utc();
|
||||||
|
let expires = now + duration;
|
||||||
|
let secret = random();
|
||||||
|
let result = sqlx::query!(
|
||||||
|
"INSERT INTO sessions (actor, secret, created_at, expires_at) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||||
|
user_id,
|
||||||
|
secret,
|
||||||
|
now,
|
||||||
|
expires
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(Self {
|
||||||
|
id: result.id,
|
||||||
|
actor: user_id,
|
||||||
|
secret: secret,
|
||||||
|
created_at: now,
|
||||||
|
expires_at: now + duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find(pool: &Pool<Postgres>, id: Uuid) -> Result<Option<Self>> {
|
||||||
|
Ok(sqlx::query_as("SELECT * FROM sessions WHERE id = $1")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn refresh(self: Self, pool: &Pool<Postgres>, duration: Duration) -> Result<Self> {
|
||||||
|
let expires_at = (Utc::now() + duration).naive_utc();
|
||||||
|
let secret = random();
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE sessions SET secret = $1, expires_at = $2 WHERE id = $3 RETURNING id",
|
||||||
|
secret,
|
||||||
|
expires_at,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Session {
|
||||||
|
secret,
|
||||||
|
expires_at,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn token(self: &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(":")
|
||||||
|
.ok_or_else(|| anyhow!("malformed token"))?;
|
||||||
|
Ok((
|
||||||
|
Uuid::from_u128(uuid_str.parse::<u128>()?),
|
||||||
|
token_str.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user(self: &Self, pool: &Pool<Postgres>) -> Result<User, AppError<'static>> {
|
||||||
|
match User::get_id(pool, self.actor).await? {
|
||||||
|
Some(user) => Ok(user),
|
||||||
|
None => Err(USER_NOT_FOUND),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequestParts<Arc<AppState>> for Session {
|
||||||
|
type Rejection = AppError<'static>;
|
||||||
|
|
||||||
|
async fn from_request_parts(
|
||||||
|
parts: &mut Parts,
|
||||||
|
state: &Arc<AppState>,
|
||||||
|
) -> Result<Self, Self::Rejection> {
|
||||||
|
if let Some(cookie) = parts.headers.get(COOKIE) {
|
||||||
|
let cookie_str = cookie.to_str()?;
|
||||||
|
let cookie = Cookie::parse(cookie_str)?;
|
||||||
|
let (session_id, session_secret) = Session::parse_token(cookie.value())?;
|
||||||
|
|
||||||
|
let State(state) = State::<Arc<AppState>>::from_request_parts(parts, state).await?;
|
||||||
|
|
||||||
|
match Session::find(&state.database, session_id).await? {
|
||||||
|
None => Err(INVALID_SESSION),
|
||||||
|
Some(session) => {
|
||||||
|
println!("{:?}<{:?}", session.expires_at, Utc::now().naive_utc());
|
||||||
|
if session.secret != session_secret {
|
||||||
|
return Err(INVALID_SESSION);
|
||||||
|
}
|
||||||
|
if session.expires_at < Utc::now().naive_utc() {
|
||||||
|
return Err(INVALID_SESSION);
|
||||||
|
}
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(INVALID_SESSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
src/auth/user.rs
Normal file
101
src/auth/user.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, Pool, Postgres};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::hash::hash;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
|
pub struct User {
|
||||||
|
/// User internal ID
|
||||||
|
pub id: Uuid,
|
||||||
|
|
||||||
|
/// User name (unique per instance, shows up in URLs)
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// User email (for validation/login)
|
||||||
|
pub email: Option<String>,
|
||||||
|
|
||||||
|
/// Hashed password
|
||||||
|
pub password: Option<String>,
|
||||||
|
|
||||||
|
/// User's chosen displayed name
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
|
||||||
|
/// Biography / User description
|
||||||
|
pub bio: Option<String>,
|
||||||
|
|
||||||
|
/// User roles (as role IDs)
|
||||||
|
pub roles: Vec<Uuid>,
|
||||||
|
|
||||||
|
/// Times
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub modified_at: Option<NaiveDateTime>,
|
||||||
|
pub deleted_at: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for User {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::nil(),
|
||||||
|
name: Default::default(),
|
||||||
|
email: Default::default(),
|
||||||
|
password: Default::default(),
|
||||||
|
display_name: Default::default(),
|
||||||
|
bio: Default::default(),
|
||||||
|
roles: Default::default(),
|
||||||
|
created_at: Default::default(),
|
||||||
|
modified_at: Default::default(),
|
||||||
|
deleted_at: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub async fn create(
|
||||||
|
pool: &Pool<Postgres>,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
roles: &Vec<Uuid>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let result = sqlx::query!(
|
||||||
|
r#"INSERT INTO users ( name, password, roles ) VALUES ( $1,$2,$3 ) RETURNING id, created_at"#,
|
||||||
|
username,
|
||||||
|
hash(&password)?,
|
||||||
|
roles,
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(Self {
|
||||||
|
id: result.id,
|
||||||
|
name: username.to_owned(),
|
||||||
|
roles: roles.to_owned(),
|
||||||
|
created_at: result.created_at,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_id(pool: &Pool<Postgres>, id: Uuid) -> Result<Option<Self>> {
|
||||||
|
Ok(sqlx::query_as("SELECT * FROM users WHERE id = $1")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find(pool: &Pool<Postgres>, name: &str) -> Result<Option<Self>> {
|
||||||
|
Ok(sqlx::query_as("SELECT * FROM users WHERE name = $1")
|
||||||
|
.bind(name)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, FromRow)]
|
||||||
|
pub struct Role {
|
||||||
|
/// Role ID
|
||||||
|
pub id: Uuid,
|
||||||
|
|
||||||
|
/// Role scopes (permissions)
|
||||||
|
pub scopes: Vec<String>,
|
||||||
|
}
|
10
src/error.rs
10
src/error.rs
|
@ -5,16 +5,16 @@ use axum::{
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub enum AppError {
|
pub enum AppError<'a> {
|
||||||
ServerError(anyhow::Error),
|
ServerError(anyhow::Error),
|
||||||
ClientError {
|
ClientError {
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
code: String,
|
code: &'a str,
|
||||||
message: String,
|
message: &'a str,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError<'_> {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
match self {
|
match self {
|
||||||
AppError::ServerError(err) => (
|
AppError::ServerError(err) => (
|
||||||
|
@ -31,7 +31,7 @@ impl IntoResponse for AppError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> From<E> for AppError
|
impl<E> From<E> for AppError<'_>
|
||||||
where
|
where
|
||||||
E: Into<anyhow::Error>,
|
E: Into<anyhow::Error>,
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,10 +5,12 @@ use axum::Json;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::auth::{random, User};
|
use crate::{
|
||||||
use crate::error::AppError;
|
auth::{hash::random, user::User},
|
||||||
use crate::roles::ROLE_SUPERADMIN;
|
error::AppError,
|
||||||
use crate::state::AppState;
|
roles::ROLE_SUPERADMIN,
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
pub async fn bootstrap(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
// Only allow this request if the user table is completely empty!
|
// Only allow this request if the user table is completely empty!
|
||||||
|
|
|
@ -11,7 +11,7 @@ use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{verify, Session, User},
|
auth::{hash::verify, session::Session, user::User},
|
||||||
error::AppError,
|
error::AppError,
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
@ -72,6 +72,10 @@ pub async fn login(
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn me() -> String {
|
pub async fn me(
|
||||||
"test".into()
|
State(state): State<Arc<AppState>>,
|
||||||
|
session: Session,
|
||||||
|
) -> Result<String, AppError<'static>> {
|
||||||
|
let user = session.user(&state.database).await?;
|
||||||
|
Ok(user.name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{content::Page, error::AppError, state::AppState};
|
||||||
pub async fn page(
|
pub async fn page(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Path((site, slug)): Path<(String, String)>,
|
Path((site, slug)): Path<(String, String)>,
|
||||||
) -> Result<String, AppError> {
|
) -> Result<String, AppError<'static>> {
|
||||||
let page_query = sqlx::query_as(
|
let page_query = sqlx::query_as(
|
||||||
"SELECT p.* FROM pages p JOIN sites s ON p.site = s.id WHERE p.slug = $1 AND s.name = $2",
|
"SELECT p.* FROM pages p JOIN sites s ON p.site = s.id WHERE p.slug = $1 AND s.name = $2",
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,7 +30,7 @@ impl Default for Config {
|
||||||
Config {
|
Config {
|
||||||
bind: "127.0.0.1:3000".into(),
|
bind: "127.0.0.1:3000".into(),
|
||||||
database_url: "postgres://artificiale:changeme@localhost/artificiale".into(),
|
database_url: "postgres://artificiale:changeme@localhost/artificiale".into(),
|
||||||
session_duration: 1440, // 24min
|
session_duration: 3600, // 60min
|
||||||
base_url: "http://localhost".into(),
|
base_url: "http://localhost".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue