wip: implementing auth further
This commit is contained in:
parent
e19d5db7c6
commit
b029b4b975
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -143,6 +143,12 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
@ -490,6 +496,23 @@ dependencies = [
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-messages"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0d06050ddcb05eaaffe44c5f73443ea936eadae6a8332dd47b8c8c8cb1af98a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.3",
|
||||
"http 1.0.0",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tower",
|
||||
"tower-sessions-core",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
@ -576,6 +599,19 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@ -772,6 +808,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
@ -2431,6 +2473,8 @@ dependencies = [
|
||||
"async-trait",
|
||||
"axum 0.7.4",
|
||||
"axum-login",
|
||||
"axum-messages",
|
||||
"blake3",
|
||||
"entity",
|
||||
"log",
|
||||
"password-auth",
|
||||
|
@ -23,6 +23,8 @@ sea-orm = { version = "0.12.10", features = [
|
||||
"sqlx-postgres",
|
||||
] }
|
||||
axum-login = "0.15.3"
|
||||
axum-messages = "0.6.1"
|
||||
async-trait = "0.1.80"
|
||||
password-auth = "1.0.0"
|
||||
thiserror = "1.0.61"
|
||||
blake3 = "1.5.1"
|
||||
|
2
rear_auth/roadmap.md
Normal file
2
rear_auth/roadmap.md
Normal file
@ -0,0 +1,2 @@
|
||||
Atm. it is not clear to me if rear_auth will really be it's own package, as it needs to interact a lot with rear, or if I split up rear into modules instead.
|
||||
|
@ -1 +1,3 @@
|
||||
pub mod users;
|
||||
pub mod models;
|
||||
pub mod user_admin_repository;
|
||||
pub mod views;
|
||||
|
@ -1,3 +1 @@
|
||||
mod users;
|
||||
|
||||
pub fn main() {}
|
||||
|
230
rear_auth/src/models.rs
Normal file
230
rear_auth/src/models.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use axum_login::{AuthUser, AuthzBackend};
|
||||
use axum_login::{AuthnBackend, UserId};
|
||||
use log::debug;
|
||||
use password_auth::{generate_hash, is_hash_obsolete, verify_password};
|
||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::task;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserRepository {
|
||||
pub(crate) connection: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl UserRepository {
|
||||
pub fn new(connection: DatabaseConnection) -> Self {
|
||||
UserRepository {
|
||||
connection: connection,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encode_password(password: String) -> String {
|
||||
// This function will try to avoid re-encoding an encoded password.
|
||||
// This is why it is not public outside of the crate.
|
||||
if let Ok(_is_obsolete) = is_hash_obsolete(password.as_str()) {
|
||||
// Not sure what to do if it is obsolete.
|
||||
if _is_obsolete {
|
||||
debug!("UserRepository::encode_password: found obsolete password hash.");
|
||||
}
|
||||
return password;
|
||||
}
|
||||
// As checking for obsoleteness errored out, we assume this a raw password.
|
||||
generate_hash(password)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct AuthenticatedUser {
|
||||
id: i64,
|
||||
pub username: String,
|
||||
password: String,
|
||||
session_token: Vec<u8>, // Stores the hash
|
||||
}
|
||||
|
||||
impl AuthenticatedUser {
|
||||
fn new(id: i64, username: String, password: String) -> Self {
|
||||
AuthenticatedUser {
|
||||
id: id,
|
||||
username: username,
|
||||
session_token: blake3::hash(&password.as_bytes()).as_bytes().to_vec(),
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_password(&mut self, new_password: String) {
|
||||
self.password = new_password;
|
||||
self.session_token = blake3::hash(self.password.as_bytes()).as_bytes().to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
// Here we've implemented `Debug` manually to avoid accidentally logging the
|
||||
// password hash.
|
||||
impl std::fmt::Debug for AuthenticatedUser {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("User")
|
||||
.field("id", &self.id)
|
||||
.field("username", &self.username)
|
||||
.field("password", &"[redacted]")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthUser for AuthenticatedUser {
|
||||
type Id = i64;
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn session_auth_hash(&self) -> &[u8] {
|
||||
&self.session_token
|
||||
}
|
||||
}
|
||||
|
||||
impl From<entity::user::Model> for AuthenticatedUser {
|
||||
fn from(value: entity::user::Model) -> Self {
|
||||
AuthenticatedUser::new(value.id, value.username, value.password)
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to extract the authentication fields from forms. We use this
|
||||
// to authenticate requests with the backend.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Credentials {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub next: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct NotFoundError {
|
||||
pub details: String,
|
||||
}
|
||||
|
||||
impl NotFoundError {
|
||||
pub fn new(details: &str) -> Self {
|
||||
NotFoundError {
|
||||
details: details.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NotFoundError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Not Found... Error: {}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
DbErr(#[from] sea_orm::DbErr),
|
||||
|
||||
#[error("Not Found: {0}")]
|
||||
NotFound(#[from] NotFoundError),
|
||||
|
||||
#[error(transparent)]
|
||||
TaskJoin(#[from] task::JoinError),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthnBackend for UserRepository {
|
||||
type User = AuthenticatedUser;
|
||||
type Credentials = Credentials;
|
||||
type Error = Error;
|
||||
|
||||
async fn authenticate(
|
||||
&self,
|
||||
creds: Self::Credentials,
|
||||
) -> Result<Option<Self::User>, Self::Error> {
|
||||
let user_found = entity::User::find()
|
||||
.filter(entity::user::Column::Username.eq(creds.username))
|
||||
.one(&self.connection)
|
||||
.await?;
|
||||
|
||||
let user = if let Some(user) = user_found {
|
||||
let rear_user: AuthenticatedUser = user.into();
|
||||
Some(rear_user)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Verifying the password is blocking and potentially slow, so we'll do so via
|
||||
// `spawn_blocking`.
|
||||
task::spawn_blocking(|| {
|
||||
// We're using password-based authentication--this works by comparing our form
|
||||
// input with an argon2 password hash.
|
||||
Ok(user.filter(|user| verify_password(creds.password, &user.password).is_ok()))
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||
let user_found = entity::User::find_by_id(*user_id)
|
||||
.one(&self.connection)
|
||||
.await?;
|
||||
if let Some(user) = user_found {
|
||||
let rear_user: AuthenticatedUser = user.into();
|
||||
Ok(Some(rear_user))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Permission {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl From<&str> for Permission {
|
||||
fn from(name: &str) -> Self {
|
||||
Permission {
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<entity::permission::Model> for Permission {
|
||||
fn from(model: entity::permission::Model) -> Self {
|
||||
Permission {
|
||||
name: model.codename,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthzBackend for UserRepository {
|
||||
type Permission = Permission;
|
||||
|
||||
async fn get_group_permissions(
|
||||
&self,
|
||||
user: &Self::User,
|
||||
) -> Result<HashSet<Self::Permission>, Self::Error> {
|
||||
let user = entity::User::find_by_id(user.id)
|
||||
.one(&self.connection)
|
||||
.await?;
|
||||
if let Some(user) = user {
|
||||
let permissions = user
|
||||
.find_related(entity::Permission)
|
||||
.all(&self.connection)
|
||||
.await?;
|
||||
|
||||
Ok(permissions
|
||||
.into_iter()
|
||||
.map(|item| Permission::from(item))
|
||||
.collect())
|
||||
} else {
|
||||
Ok(HashSet::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use a type alias for convenience.
|
||||
//
|
||||
// Note that we've supplied our concrete backend here.
|
||||
pub type AuthSession = axum_login::AuthSession<UserRepository>;
|
@ -1,21 +1,11 @@
|
||||
use crate::admin::domain::*;
|
||||
use crate::admin::state::AdminRegistry;
|
||||
use async_trait::async_trait;
|
||||
use log::debug;
|
||||
use rear::admin::domain::*;
|
||||
use rear::admin::state::AdminRegistry;
|
||||
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, ModelTrait, Set};
|
||||
use serde_json::Value;
|
||||
|
||||
struct UserRepository {
|
||||
connection: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl UserRepository {
|
||||
pub fn new(connection: DatabaseConnection) -> Self {
|
||||
UserRepository {
|
||||
connection: connection,
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::models::UserRepository;
|
||||
|
||||
#[async_trait]
|
||||
impl AdminRepository for UserRepository {
|
||||
@ -91,41 +81,47 @@ impl AdminRepository for UserRepository {
|
||||
}
|
||||
|
||||
async fn create(&mut self, model: &RepositoryContext, data: Value) -> Option<RepositoryItem> {
|
||||
let username = data.get("username").unwrap().as_str().unwrap();
|
||||
if let Value::Object(data) = data {
|
||||
let username = data.get("username").unwrap().as_str().unwrap();
|
||||
|
||||
let mut user = entity::user::ActiveModel {
|
||||
username: Set(username.to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut user = entity::user::ActiveModel {
|
||||
username: Set(username.to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let keys = [
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"is_staff",
|
||||
"is_active",
|
||||
"is_superuser",
|
||||
];
|
||||
let keys = [
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"is_staff",
|
||||
"is_active",
|
||||
"is_superuser",
|
||||
];
|
||||
|
||||
for key in &keys {
|
||||
if let Some(value) = data.get(*key) {
|
||||
match *key {
|
||||
"password" => user.password = Set(value.as_str().unwrap().to_owned()),
|
||||
"first_name" => user.first_name = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"last_name" => user.last_name = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"email" => user.email = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"is_staff" => user.is_staff = Set(value.as_bool().unwrap_or(false)),
|
||||
"is_active" => user.is_active = Set(value.as_bool().unwrap_or(false)),
|
||||
"is_superuser" => user.is_superuser = Set(value.as_bool().unwrap_or(false)),
|
||||
_ => (),
|
||||
for key in &keys {
|
||||
if let Some(value) = data.get(*key) {
|
||||
match *key {
|
||||
"password" => {
|
||||
user.password = Set(UserRepository::encode_password(
|
||||
value.as_str().unwrap().to_owned(),
|
||||
))
|
||||
}
|
||||
"first_name" => user.first_name = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"last_name" => user.last_name = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"email" => user.email = Set(value.as_str().map(|s| s.to_owned())),
|
||||
"is_staff" => user.is_staff = Set(value.as_bool().unwrap_or(false)),
|
||||
"is_active" => user.is_active = Set(value.as_bool().unwrap_or(false)),
|
||||
"is_superuser" => user.is_superuser = Set(value.as_bool().unwrap_or(false)),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(user) = user.insert(&self.connection).await {
|
||||
let id = user.id.to_string();
|
||||
return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap()));
|
||||
if let Ok(user) = user.insert(&self.connection).await {
|
||||
let id = user.id.to_string();
|
||||
return Some(model.build_item(&*id, serde_json::to_value(&user).unwrap()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||
use password_auth::verify_password;
|
||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::task;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserDatabase {
|
||||
connection: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl UserDatabase {
|
||||
pub fn new(connection: DatabaseConnection) -> Self {
|
||||
UserDatabase {
|
||||
connection: connection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
id: i64,
|
||||
pub username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
// Here we've implemented `Debug` manually to avoid accidentally logging the
|
||||
// password hash.
|
||||
impl std::fmt::Debug for User {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("User")
|
||||
.field("id", &self.id)
|
||||
.field("username", &self.username)
|
||||
.field("password", &"[redacted]")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthUser for User {
|
||||
type Id = i64;
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn session_auth_hash(&self) -> &[u8] {
|
||||
self.password.as_bytes() // We use the password hash as the auth
|
||||
// hash--what this means
|
||||
// is when the user changes their password the
|
||||
// auth session becomes invalid.
|
||||
}
|
||||
}
|
||||
|
||||
impl From<entity::user::Model> for User {
|
||||
fn from(value: entity::user::Model) -> Self {
|
||||
User {
|
||||
id: value.id,
|
||||
username: value.username,
|
||||
password: "".to_owned(), // TODO: password field.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to extract the authentication fields from forms. We use this
|
||||
// to authenticate requests with the backend.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Credentials {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub next: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct NotFoundError {
|
||||
pub details: String,
|
||||
}
|
||||
|
||||
impl NotFoundError {
|
||||
pub fn new(details: &str) -> Self {
|
||||
NotFoundError {
|
||||
details: details.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NotFoundError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Not Found... Error: {}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
DbErr(#[from] sea_orm::DbErr),
|
||||
|
||||
#[error("Not Found: {0}")]
|
||||
NotFound(#[from] NotFoundError),
|
||||
|
||||
#[error(transparent)]
|
||||
TaskJoin(#[from] task::JoinError),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthnBackend for UserDatabase {
|
||||
type User = User;
|
||||
type Credentials = Credentials;
|
||||
type Error = Error;
|
||||
|
||||
async fn authenticate(
|
||||
&self,
|
||||
creds: Self::Credentials,
|
||||
) -> Result<Option<Self::User>, Self::Error> {
|
||||
let user_found = entity::User::find()
|
||||
.filter(entity::user::Column::Username.eq(creds.username))
|
||||
.one(&self.connection)
|
||||
.await?;
|
||||
|
||||
let user = if let Some(user) = user_found {
|
||||
let rear_user: User = user.into();
|
||||
Some(rear_user)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Verifying the password is blocking and potentially slow, so we'll do so via
|
||||
// `spawn_blocking`.
|
||||
task::spawn_blocking(|| {
|
||||
// We're using password-based authentication--this works by comparing our form
|
||||
// input with an argon2 password hash.
|
||||
Ok(user.filter(|user| verify_password(creds.password, &user.password).is_ok()))
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||
let user_found = entity::User::find_by_id(*user_id)
|
||||
.one(&self.connection)
|
||||
.await?;
|
||||
if let Some(user) = user_found {
|
||||
let rear_user: User = user.into();
|
||||
Ok(Some(rear_user))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use a type alias for convenience.
|
||||
//
|
||||
// Note that we've supplied our concrete backend here.
|
||||
pub type AuthSession = axum_login::AuthSession<UserDatabase>;
|
96
rear_auth/src/views.rs
Normal file
96
rear_auth/src/views.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use axum::{
|
||||
extract::Query,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{get, post},
|
||||
Form, Router,
|
||||
};
|
||||
use axum_messages::{Message, Messages};
|
||||
use rear::service::templates::Templates;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{AuthSession, Credentials};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LoginTemplate {
|
||||
messages: Vec<Message>,
|
||||
next: Option<String>,
|
||||
}
|
||||
|
||||
// This allows us to extract the "next" field from the query string. We use this
|
||||
// to redirect after log in.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NextUrl {
|
||||
next: Option<String>,
|
||||
}
|
||||
|
||||
pub fn router() -> Router<Templates> {
|
||||
Router::new()
|
||||
.route("/login", post(self::post::login))
|
||||
.route("/login", get(self::get::login))
|
||||
.route("/logout", get(self::get::logout))
|
||||
}
|
||||
|
||||
mod post {
|
||||
use super::*;
|
||||
|
||||
pub async fn login(
|
||||
mut auth_session: AuthSession,
|
||||
messages: Messages,
|
||||
Form(creds): Form<Credentials>,
|
||||
) -> impl IntoResponse {
|
||||
let user = match auth_session.authenticate(creds.clone()).await {
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) => {
|
||||
messages.error("Invalid credentials");
|
||||
|
||||
let mut login_url = "/login".to_string();
|
||||
if let Some(next) = creds.next {
|
||||
login_url = format!("{}?next={}", login_url, next);
|
||||
};
|
||||
|
||||
return Redirect::to(&login_url).into_response();
|
||||
}
|
||||
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
};
|
||||
|
||||
if auth_session.login(&user).await.is_err() {
|
||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||
}
|
||||
|
||||
messages.success(format!("Successfully logged in as {}", user.username));
|
||||
|
||||
if let Some(ref next) = creds.next {
|
||||
Redirect::to(next)
|
||||
} else {
|
||||
Redirect::to("/")
|
||||
}
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
mod get {
|
||||
use super::*;
|
||||
use axum::extract::State;
|
||||
|
||||
pub async fn login(
|
||||
messages: Messages,
|
||||
templates: State<Templates>,
|
||||
Query(NextUrl { next }): Query<NextUrl>,
|
||||
) -> impl IntoResponse {
|
||||
let context = LoginTemplate {
|
||||
messages: messages.into_iter().collect(),
|
||||
next,
|
||||
};
|
||||
templates.render_html("login.html", context)
|
||||
}
|
||||
|
||||
pub async fn logout(mut auth_session: AuthSession) -> impl IntoResponse {
|
||||
match auth_session.logout().await {
|
||||
Ok(_) => Redirect::to("/login").into_response(),
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this was taken from the axum_login examples and modified. Redirection might need reverse-routing support.
|
@ -1,4 +1,3 @@
|
||||
pub mod empty_repository;
|
||||
pub mod file_repository;
|
||||
pub mod static_repository;
|
||||
pub mod user_repository;
|
||||
pub mod file_repository;
|
@ -5,9 +5,8 @@ mod howto;
|
||||
mod state;
|
||||
|
||||
use crate::state::AppState;
|
||||
use admin_examples::static_repository;
|
||||
use admin_examples::user_repository;
|
||||
use admin_examples::file_repository;
|
||||
use admin_examples::static_repository;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
extract::MatchedPath,
|
||||
@ -53,7 +52,7 @@ async fn main() {
|
||||
|
||||
// Register Admin Apps
|
||||
static_repository::register(&mut admin);
|
||||
user_repository::register(&mut admin, db_connection);
|
||||
//user_repository::register(&mut admin, db_connection);
|
||||
file_repository::register(&mut admin, "static/admin");
|
||||
|
||||
// Create global Application State.
|
||||
|
Loading…
Reference in New Issue
Block a user