Giter Club home page Giter Club logo

Comments (6)

maxcountryman avatar maxcountryman commented on May 20, 2024

Would your use case be able to leverage this pattern?

let PgStore = axum_login::PostgresStore<User, UserRole>;

Or do you need to reimplement load_user altogether ?

from axum-login.

sean-clarke avatar sean-clarke commented on May 20, 2024

Where exactly would I use that? Currently, when I define user_store, I do:

  let user_store = PostgresStore::<User, UserRole>::new(pool.clone());

Additionally, if I mock the load_user fn to just return a standard user instead of going into the db, when I hit the endpoint that uses AuthContext, I get this error:

thread 'tokio-runtime-worker' panicked at 'Auth extension missing. Is the auth layer installed?: MissingExtension(MissingExtension(Error { inner: "Extension of type `axum_login::extractors::AuthContext<be::entities::user::User, axum_login::sqlx_store::SqlxStore<sqlx_core::pool::Pool<sqlx_core::postgres::database::Postgres>, be::entities::user::User>, be::entities::user::UserRole>` was not found. Perhaps you forgot to add it? See `axum::Extension`." }))

My main.rs looks like this:

use axum::{
    extract::State,
    routing::{get, post},
    Json, Router,
};
use axum_login::{
    axum_sessions::{async_session::MemoryStore, SessionLayer},
    AuthLayer, PostgresStore,
};
use rand::Rng;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use std::net::SocketAddr;

mod entities;

use entities::user::{User, UserRole};

#[tokio::main]
async fn main() {
    let secret = rand::thread_rng().gen::<[u8; 64]>();
    let session_store = MemoryStore::new();
    let session_layer = SessionLayer::new(session_store, &secret).with_secure(false);

    dotenvy::dotenv().ok();
    let database_connection_uri = dotenvy::var("DATABASE_URL").unwrap();

    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_connection_uri)
        .await
        .unwrap();

    let user_store = PostgresStore::<User, UserRole>::new(pool.clone());
    let auth_layer = AuthLayer::new(user_store, &secret);

    let app = Router::new()
        .route("/login", post(login))
        .layer(auth_layer)
        .layer(session_layer)
        .with_state(pool);

    let addr = SocketAddr::from(([127, 0, 0, 1], 5000));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

type AuthContext = axum_login::extractors::AuthContext<User, PostgresStore<User>, UserRole>;

#[derive(serde::Deserialize)]
struct Login {
    username: String,
    password: String,
}

#[axum::debug_handler]
async fn login(
    mut auth: AuthContext,
    State(pool): State<PgPool>,
    Json(payload): Json<Login>,
) -> impl axum::response::IntoResponse {
    let password_hash = hash_password(payload.password);
    let user_result =
        sqlx::query_as::<_, User>("SELECT * FROM users WHERE username = $1 AND password_hash = $2")
            .bind(payload.username)
            .bind(password_hash)
            .fetch_one(&pool)
            .await;

    let user: User = match user_result {
        Ok(value) => value,
        Err(_) => return Err((axum::http::StatusCode::UNAUTHORIZED, ())),
    };

    auth.login(&user).await.unwrap();
    Ok(())
}

from axum-login.

maxcountryman avatar maxcountryman commented on May 20, 2024

Without a more complete example, it's going to be difficult to advise.

The auth context missing usually means that the middleware isn't installed for that route.

from axum-login.

sean-clarke avatar sean-clarke commented on May 20, 2024

I've updated my above comment to include the full main.rs, and the error

from axum-login.

maxcountryman avatar maxcountryman commented on May 20, 2024

Are you able to share your routes as well? The error is about the middleware not being present.

Edit: Oh I see it's in your main.rs. I'll try to take a closer look.

from axum-login.

maxcountryman avatar maxcountryman commented on May 20, 2024

All right, so I'm not going to be able to run your code directly since there's a few missing dependencies and modules internal to your project.

That said, I tried my best to replicate your situation with the SQLite example. Here's the diff in case you'd like to see (you'll also have to modify the user_store.db with the role column):

diff --git a/examples/sqlite/src/main.rs b/examples/sqlite/src/main.rs
index 565e23e..0e84e3a 100644
--- a/examples/sqlite/src/main.rs
+++ b/examples/sqlite/src/main.rs
@@ -8,19 +8,26 @@ use axum::{response::IntoResponse, routing::get, Extension, Router};
 use axum_login::{
     axum_sessions::{async_session::MemoryStore, SessionLayer},
     secrecy::SecretVec,
-    AuthLayer, AuthUser, RequireAuthorizationLayer, SqliteStore,
+    AuthLayer, AuthUser, RequireAuthorizationLayer,
 };
 use rand::Rng;
 use sqlx::sqlite::SqlitePoolOptions;
 
-#[derive(Debug, Default, Clone, sqlx::FromRow)]
+#[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type)]
+pub enum UserRole {
+    Standard,
+    Admin,
+}
+
+#[derive(Debug, Clone, sqlx::FromRow)]
 struct User {
     id: i64,
     password_hash: String,
     name: String,
+    role: UserRole,
 }
 
-impl AuthUser for User {
+impl AuthUser<UserRole> for User {
     fn get_id(&self) -> String {
         format!("{}", self.id)
     }
@@ -28,9 +35,14 @@ impl AuthUser for User {
     fn get_password_hash(&self) -> SecretVec<u8> {
         SecretVec::new(self.password_hash.clone().into())
     }
+
+    fn get_role(&self) -> Option<UserRole> {
+        Some(self.role.clone())
+    }
 }
 
-type AuthContext = axum_login::extractors::AuthContext<User, SqliteStore<User>>;
+type SqliteStore = axum_login::SqliteStore<User, UserRole>;
+type AuthContext = axum_login::extractors::AuthContext<User, SqliteStore, UserRole>;
 
 #[tokio::main]
 async fn main() {
@@ -44,8 +56,8 @@ async fn main() {
         .await
         .unwrap();
 
-    let user_store = SqliteStore::<User>::new(pool);
-    let auth_layer = AuthLayer::new(user_store, &secret);
+    let user_store = SqliteStore::new(pool);
+    let auth_layer = AuthLayer::<SqliteStore, User, UserRole>::new(user_store, &secret);
 
     async fn login_handler(mut auth: AuthContext) {
         let pool = SqlitePoolOptions::new()
@@ -71,7 +83,7 @@ async fn main() {
 
     let app = Router::new()
         .route("/protected", get(protected_handler))
-        .route_layer(RequireAuthorizationLayer::<User>::login())
+        .route_layer(RequireAuthorizationLayer::<User, UserRole>::login())
         .route("/login", get(login_handler))
         .route("/logout", get(logout_handler))
         .layer(auth_layer)
diff --git a/examples/sqlite/user_store.db b/examples/sqlite/user_store.db
index d26f080..f0b7743 100644
Binary files a/examples/sqlite/user_store.db and b/examples/sqlite/user_store.db differ

In your example, this line seems to be missing something:

type AuthContext = axum_login::extractors::AuthContext<User, PostgresStore<User>, UserRole>;

The PostgresStore should also indicate the concrete role type because otherwise it will default to the unit type. Does changing that line address the issue for you?

from axum-login.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.