Code Monkey home page Code Monkey logo

axum-login's People

Contributors

beckend avatar bngo92 avatar czocher avatar czotomo avatar dave42w avatar epilys avatar iamsauravsharma avatar kingsleyh avatar laskowski avatar maverich avatar maxcountryman avatar rigellute avatar weiznich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

axum-login's Issues

postgres: operator does not exist: bigint = text

hi there,

i've been trying to axum_login working with my postgres DB. i have the postgres feature flag enabled and I am using sqlx. I've changed my user table to users and made sure it has an id column to line up with what axum_login is expecting, but I am now getting the following error:

select
  *
from
  users
where
  id = $1
  
2023-02-10T04:44:24.236910Z ERROR axum_login::auth: Could not get user: error returned from database: operator does not exist: bigint = text

Caused by:
    operator does not exist: bigint = text

I have been able to use my postgres DB just fine up to this point with axum/sqlx. my user model (that i implementAuthUser over) is defined as follows:

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
pub struct User {
    pub id: i64,
    pub username: String,
    pub pw_hash: String
}

the type of users.id is declared bigserial.

when i first got this error, i had the type of id as an uuid (both in postgres and on my struct) and thought maybe that was the issue but that clearly wasn't the case.

what am i doing wrong?

`AuthContext` usage panics in streaming handler

As demonstrated at:
https://github.com/OvermindDL1/issue_axum_session_streaming.git

I have a large'ish app using graphql and a liveview-like thing and wanted to toss in axum-login and axum-sessions for obvious usefulness, however attempting to use it in either of these things, which are streaming handlers, panics with:

thread 'tokio-runtime-worker' panicked at 'Session handle still has owners.: RwLock { mr: 536870911, s: Semaphore { permits: 536870911 }, c: UnsafeCell { .. } }'

I reduced the issue to the SSCCE in the above git repo to strip all external things and just keep to axum and future_utils. Given this handler:

// Doesn't work, axum-login is blowing up
async fn route_streaming_login(mut auth: AuthContext) -> StreamBody<impl Stream<Item = std::io::Result<&'static str>>> {
	let stream = stream::once(async move {
		// performing some long operation
		tokio::time::sleep(std::time::Duration::from_secs(2)).await;
		// And need to access auth 'later' in the stream
		auth.login(&User { id: 1 }).await.unwrap();
		Ok("logged in")
	});
	StreamBody::new(stream)
}

When it hits the auth.login(&User { id: 1 }).await.unwrap(); it panics, not because of the unwrap but internally (it never returns).

It looks like internally it's clearing, resetting, something the session from the handler, but that shouldn't be done because it's still being processed axum/hyper to finish processing and sending information mid-stream.

What should 'probably' be done is that AuthContext is a fully owned type that resets itself in the store internally when it is dropped (which yes it would likely need to hold an Arc to the store internally in that case unless some re-architecting is done first), rather than when the handler returns (which is definitely not when the work is complete in axum/hyper). This will match what other things in the axum ecosystem do and will allow it to work within an axum streaming context like other things do as well.

Idea: `AuthUser` should not be `Clone` and should require the `Zeroize` trait on the `password` field

Currently AuthUser requires the Clone trait to be implemented on the User struct. This is not bad per-say but is not good in terms of secret management hygiene.

I suggest the AuthUser should handle the password hash very carefully and require the password field to be Zeroizable, so that the password is not cloned nor left in memory. Returning a &str instead would possibly also be beneficial rather than String.

This is possibly a breaking change.

Idea: AuthUser can return a SecretString of the password.

New Session For Every Request

I'm trying to implement a /login and /logout routes as well as some protected routes. However, after logging in, it seems like the log in isn't saved. When I ping /login it adds the user to a session, but by the time I try to access a protected route, the session and user are gone. I got the examples to work, but my repo based off of them just doesn't. Every protected route always returns forbidden and even the logout route says the current_user is None.

I inserted tracing everywhere to figure out what was happening and noticed that between each request a new session is created. I don't know if this is what's supposed to happen, but all of the data stored in the session is lost.

My SessionLayer is built with a MemoryStore and my AuthLayer is built with an SqliteStore. I'm using sqlx and sqlite as a database. I made sure my users are stored in a table called users in my database.

If it helps, here's my setup for the layers:

let session_store = MemoryStore::new();
let session_layer = SessionLayer::new(session_store, &config.session_secret);

let user_store =
    SqliteStore::<User, Role>::new(SqlitePool::connect(&config.database_file).await?);
let auth_layer = AuthLayer::new(user_store, &config.session_secret);

config.session_secret is a 64 byte array, and config.database_file is the path to my sqlite db.

type RequireAuth = RequireAuthorizationLayer<User, Role>;

let app = Router::new()
    .route(
        "/lists",
        get(handlers::fetch_user_lists).layer(RequireAuth::login()),
    )
    .route(
        "/list",
        get(handlers::fetch_list).layer(RequireAuth::login()),
    )
    .route(
        "/get-lists",
        get(handlers::get_lists).layer(RequireAuth::login_with_role(Role::Admin..)),
    )
    .route(
        "/get-users",
        get(handlers::get_users).layer(RequireAuth::login_with_role(Role::Admin..)),
    )
    .route("/status", get(|| async { StatusCode::OK }))
    .route("/login", get(handlers::login))
    .route("/logout", get(handlers::logout))
    .layer(auth_layer)
    .layer(session_layer)
    .with_state(app_state);

The app_state contains a sqlx pool for the database. The enum Roletype looks like:

#[derive(
    Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, sqlx::Type,
)]
#[repr(i32)]
pub enum Role {
    /// Regular user access
    #[default]
    User = 0,
    /// Full access to the database
    Admin = 1,
}

I'm new to axum-login, so sorry for the information dump, I just wanted to put anything that might be useful.

The /login route takes a json body which describes the user to log into, a name and password. The handler looks like this:

pub type AuthContext = axum_login::extractors::AuthContext<User, SqliteStore<User, Role>, Role>;

#[debug_handler]
pub async fn login(
    State(app): State<AppState>,
    mut auth: AuthContext,
    Json(user): Json<LoginUser>,
) -> AppResult<()> {
    debug!("logging in user: {user:?}");
    let user: User = sqlx::query_as("SELECT * FROM users WHERE name = ? AND password_hash = ?")
        .bind(&user.name)
        .bind(&user.password_hash)
        .fetch_one(&mut app.conn.acquire().await?)
        .await?;

    info!("user: {user:?}");

    auth.login(&user).await?;

    Ok(())
}

Inside the /login handler I can check auth.current_user and it is set correctly, but as soon as another route is called that is gone. For example on one run of the server:

Directly before calling auth.login(&user).await?

Session { 
    id: "jGomHmRqscfQGcLIpZPLzQvN8lDqlQ5qQJMA0GHlamQ=", 
    expiry: Some(2023-02-10T02:20:06.301437200Z), 
    data: RwLock { 
        data: {}, 
        poisoned: false, 
        .. 
    }, 
    cookie_value: Some("A/MJXP4FMV+rWiRmZMg0X5frpm9Gzt8dqSBCDW/YdVwroQTlmyocXT+X5FnMM5te4Y4PMnKmUwJT5kpBCXa+oQ=="), 
    data_changed: false, 
    destroy: false 
}

Directly after:

Session { 
    id: "jGomHmRqscfQGcLIpZPLzQvN8lDqlQ5qQJMA0GHlamQ=", 
    expiry: Some(2023-02-10T02:20:06.301437200Z), 
    data: RwLock { 
        data: {
            "_user_id": "\"3\"", 
            "_auth_id": "\"0qL/ZztGgEixRWFrMorDFNUsauceo4vmDcHcohY18PLWUUxTVc0/0SSSYMYeAaut45ZgEXbPJa1xeQBpyKP86w==\""
        }, 
        poisoned: false, 
        .. 
    }, 
    cookie_value: Some("A/MJXP4FMV+rWiRmZMg0X5frpm9Gzt8dqSBCDW/YdVwroQTlmyocXT+X5FnMM5te4Y4PMnKmUwJT5kpBCXa+oQ=="), 
    data_changed: true, 
    destroy: false 
}

You can see that the user_id has been added to the session. But, on the next request to /lists (or any route, it's all the same) we have:

Session { 
    id: "txK5yTK9y5Hyu6wxcSF6SwK079pmdSI7n/YV7RPg/HQ=", 
    expiry: Some(2023-02-10T02:20:13.404553800Z), 
    data: RwLock { 
        data: {}, 
        poisoned: false,
        ..
    }, 
    cookie_value: Some("29tPyCptmKMlBSJ8D08lYs1zFdz7NXaHouUiSt6u1o9JZvCI/OzmvZntzUSB6iMqFfI37YCkmKqZaKjQ8DsXjg=="), 
    data_changed: false, 
    destroy: false 
}

All of a sudden we have a brand new session with the old data gone. I've been struggling for hours trying to find out what I'm doing wrong, and at this point I'm pretty sure it's just some tiny thing I'm missing. Or of course, I'm just misunderstanding how I should be using the library. If so, please let me know. I'd really appreciate if anyone could take a look and see if they notice anything out of order. Also, if you want more information just let me know. Thanks in advance

Building sqlite/sqlx features implicitly enables runtimes

Is there a way to disable this? I'm trying to use axum-login in an existing tokio/axum project and enabling either of the sqlite/sqlx features produces an error:

06:17:48 โฏ cargo build
   Compiling sqlx-rt v0.6.2
error: only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', 'runtime-tokio-rustls'] can be enabled
  --> /Users/yaleman/.cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-rt-0.6.2/src/lib.rs:23:1
   |
23 | / compile_error!(
24 | |     "only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', \
25 | |      'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', \
26 | |      'runtime-tokio-rustls'] can be enabled"
27 | | );
   | |_^

error: could not compile `sqlx-rt` due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `sqlx-rt` due to previous error

examples/sqlite database in wrong folder and useless files

I propose fixing this issue by moving the user_store.dbfile into a sqlite folder and removing the user_store.db-shm and user_store.db-wal files.
I already made these changes on a fork. I you allow I will create a Pull Request.

Default query for SqlxStore does not work with MySQL

SqlX docs:

Bind parameters in the SQL string are specific to the database backend:
    Postgres: $N where N is the 1-based positional argument index
    MySQL: ? which matches arguments in order that it appears in the query

AuthContext operations may deadlock

Hello, thanks for this great crate.

I was using axum-sessions to store some stuff, and added axum-login afterwards. I added the auth context argument in the request handlers and tried to use auth.login(&..).await etc. But because I had the session as an argument, there was already an owned RwLock guard (either readable or writable)

pub async fn login_post(
    session: ReadableSession,
    mut auth: AuthContext,
    Form(payload): Form<_>,
) {
    let some_session_stuff = session.get::<_>(key);

    let user = /* authenticate user */;

    /* this will deadlock because `session` is still alive: */
    // auth.login(&user).await.unwrap();

    /* this will not deadlock: */
    drop(session);
    auth.login(&user).await.unwrap();
}

, so in AuthContext::login() in extractors.rs, the write() call will deadlock:

pub async fn login(&mut self, user: &User) -> crate::Result<()> {
let auth_id = self.get_session_auth_id(user.get_password_hash().expose_secret());
let user_id = user.get_id();
let mut session = self.session_handle.write().await;
session.insert(SESSION_AUTH_ID_KEY, auth_id)?;
session.insert(SESSION_USER_ID_KEY, user_id)?;

This can be fixed by dropping the requests session handle before calling any auth context method that uses the session. This should probably be documented. Would it be possible to introduce multiple session buckets/slots in axum-sessions and separate the user's session from axum-login's?

support tower-http 0.4

there were breaking changes in tower-http release 0.4: tower-rs/tower-http#290

it would be great if we could update Axum-login based on these so that updating Axum in app projects using Axum-login becomes possible again.

How to redirect with the auth headers/cookie?

Firstly, thank you for creating this library!

I'm implementing an OAuth flow where the provider redirects back to our server, we exchange the code for a token, login the user, then redirect to another internal route e.g.:

async fn oauth_callback(mut auth: AuthContext) -> impl IntoResponse {
     // I've omitted the oauth details such as code exchange for the token
     let user = find_user_somehow();

    // login the user
    auth.login(&user).await.unwrap();
    
    // This redirect causes the session to be lost, I think we need to pass along the headers that contain the cookie?
    Redirect::to("/")
}

// in main
let app = Router::new()
   .route("/auth/google/callback", get(oauth_callback))

Axum has an example of the OAuth flow, which looks like this:

    // Create a new session filled with user data
    let mut session = Session::new();
    session.insert("user", &user).unwrap();

    // Store session and get corresponding cookie
    let cookie = store.store_session(session).await.unwrap().unwrap();

    // Build the cookie
    let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie);

    // Set cookie
    let mut headers = HeaderMap::new();
    headers.insert(SET_COOKIE, cookie.parse().unwrap());

    // Redirect with headers
    (headers, Redirect::to("/"))

How can we redirect and preserve the session? Can we get the headers after the auth.login(...) call?

Constantly changing Session IDs.

I'm pretty sure this is the right place to put this, but if not then feel free to tell me.

Like the examples, I randomly generate a secret every time my server restarts:

let secret = {
        let mut rng = thread_rng();
        let mut v = Vec::with_capacity(64);
        v.append(&mut rng.gen::<[u8; 32]>().to_vec()); //can't get rand to do a [u8; 64] so fun times ensue
        v.append(&mut rng.gen::<[u8; 32]>().to_vec());
        v
    };
    ```
    
    However, because my server restarts often, that key changes often and logs everybody out when I use a `MemoryStore`. 
    I'm now using a persistent postgres-based store, but every time I restart, the secret regenerates and the auth layer picks a new ID for each user and everyone is logged out.
    
    Is there an intended way to fix this - storing the secret in the db doesn't seem intuitive, so I was wondering if I was doing something wrong.
    
    My Postgres solution is almost identical to [aysnc-sqlx-session](https://lib.rs/crates/async-sqlx-session)'s postgres one.

Thanks!

Implement a role-based `RequireAuthorizationLayer`

The layer should not only check the login and password, but also if the user has the right role.

@maxcountryman I understand that your idea is a RequireAuthorizarionLayer separate from the normal login one - the difference being it would also check the role of the user?

Incorrect use of the `tower_http::auth::RequireAuthorizarionLayer`

Hello @maxcountryman,
While reading the documentation for the RequireAuthorizarionLayer I noticed this crate does not use it according to its assumed use-case.
The documentation for it states that it's a:
Middleware that authorizes all requests using the Authorization header.
Correct me if I'm wrong, but neither this crate, nor the underlying axum_sessions uses the standard Authorization header to keep track of the user, but uses cookie-stored sessions instead.
Since they introduced a breaking change in the 0.4.0 version, making the layer async, maybe it's a good time to migrate away from it?

Improve usage of tracing

Currently tracing is provided but only sparsely. Furthermore it might be something that folks should opt into rather than be opted into.

Roadmap for different store support

Hello @maxcountryman,
In connection with our discussion under #73 I'd like to propose a roadmap for changes required to this crate to support multiple different database/store backends in a way that they won't conflict with one another.

Steps

  • Migrate all the core (non-store related) code to a separate axum-login-core sub-crate in this repository. The MemoryStore can either be left in the axum-login-core sub-crate or extracted to a separate sub-crate (e.g. axum-login-memory) to serve as an example for further store implementations. I'd like your decision for this point @maxcountryman.
  • Migrate all the sqlx related code to a axum-login-sqlx sub-crate.
  • The axum-login-tests sub-crates will be divided per store sub-crate (currently most of them will go to axum-login-sqlx).
  • The examples sub-crate shall be left as-is without any major structural changes (except imports).
  • Publish the axum-login-core and axum-login-sqlx crates separately on crates.io.
  • The current axum-login crate should probably be yanked from crates.io with information how to migrate to the new crates.
  • Document how to implement new stores (with examples).

In the future all the new store implementations shall be created as sub-crates in the axum-login workspace.

Additional info: The crate naming I proposed follows the same structure that axum has.

Can't use `self.pool` in impl of UserStore<CustomRole> for PostgresStore<CustomUser>

use axum::async_trait;
use axum_login::{secrecy::SecretVec, AuthUser};

#[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type)]
pub enum UserRole {
    Standard,
    Admin,
}

#[derive(Clone, Debug, sqlx::FromRow, sqlx::Decode)]
pub struct User {
    id: i32,
    username: String,
    password_hash: String,
    role: UserRole,
}

impl AuthUser<UserRole> for User {
    fn get_id(&self) -> String {
        format!("{}", self.id)
    }

    fn get_password_hash(&self) -> SecretVec<u8> {
        SecretVec::new(self.password_hash.clone().into())
    }

    fn get_role(&self) -> Option<UserRole> {
        Some(self.role.clone())
    }
}

#[async_trait]
impl axum_login::UserStore<UserRole> for axum_login::PostgresStore<User> {
    type User = User;

    async fn load_user(&self, user_id: &str) -> Result<Option<Self::User>, eyre::Report> {
        let mut connection = self.pool.acquire() ...
    }
}

When I try to use self.pool.acquire in the load_user, I get error that self.pool is private. How can I implement load_user without access to the database pool

Upgrading to latest broke my auth

I upgraded to latest but it's not working:

error[E0599]: the method `login` exists for struct `AuthContext<Uuid, User, SqlxStore<Pool<Postgres>, User>>`, but its trait bounds were not satisfied
   --> src/lib/routes/account_api.rs:116:22
    |
116 |                 auth.login(&user).await?;
    |                      ^^^^^ method cannot be called due to unsatisfied trait bounds
    |
   ::: /Users/kings/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-login-0.6.0/src/sqlx_store.rs:43:1
    |
43  | pub struct SqlxStore<Pool, User, Role = (), QueryProvider: DefaultQueryProvider = SqlxQueryProvider>
    | ----------------------------------------------------------------------------------------------------
    | |
    | doesn't satisfy `<_ as UserStore<Uuid, ()>>::User = User`
    | doesn't satisfy `_: UserStore<Uuid, ()>`
    |
    = note: the following trait bounds were not satisfied:
            `<SqlxStore<sqlx_core::pool::Pool<sqlx_core::postgres::database::Postgres>, User> as UserStore<uuid::Uuid, ()>>::User = User`
            `SqlxStore<sqlx_core::pool::Pool<sqlx_core::postgres::database::Postgres>, User>: UserStore<uuid::Uuid, ()>````

User Extension causes 500 Internal Server Error

I'm seeing this trace while trying to use the User extension as described in the docs: tower_http::trace::on_failure: response failed classification=Status code: 500 Internal Server Error

I believe that auth_cx.current_user should only be inserted if it exists. I can create a pull request if this solution makes sense.

`AuthUser` should not require the `Debug` trait

Currently the AuthUser requires the std::fmt::Debug trait to be implemented. I believe it's a leftover from some debugging implementation and should not be required. In reality it can even be considered a security issue due to the fact it forces the structs implementing this trait to also implement Debug and possibly print the password hash to an insecure channel like the console, logs or API error response.

Calling AuthContext::login does not compare password hashes until next request

Calling the AuthContext::login function doesn't seem to be checking whether the supplied user has the correct password.
This is fine in lots of cases since the next request checks the password hash, sees that they don't match and logs the user out. I found this slightly misleading, as I then tried to use the current_user field assuming that the password hashes had been validated.

I think the current behaviour is fine, but it might be worth documenting somewhere pretty obviously that the AuthContext::login function does not actually validate that the supplied user details are correct and just adds them to the user's session and current_user field, and that validating the supplied user is left up to the developer.

Support LDAP as a Store

Hi, Im looking to use the crate with an Ldap backend, however, there is mismatch with the current design.

As I understand it a UserStore expects to do a lookup using the user_id which provides the User struct with the password hash, which then gets compared by the login function.

Ldap works differently , the username and password are sent to the server and tested against the stored password server side and a response is returned.

Im not an LDAP expert but if I remember correctly there is a way to configure the Ldap server to return passwords, however, I believe it is not a recommended practice, also this means you have to connect to the server as a privileged user, not a good idea.

changing UserStore to have the function something like
async fn load_user(&self, user_id: &str, password: Option<&str>)

Would there be a way to support this different way of authentication?

Code sample using the ldap3 crate

ldap.simple_bind(&dn, &password).await

this then returns a result code (int) and a possibly empty result string.

let ldap_url=std::env::var("LDAP")
        .unwrap_or("ldap://localhost:3389".to_string());
    //ldaps 3636
    let connect= LdapConnAsync::new(&ldap_url).await;
    match connect {
        Ok((conn, mut ldap)) => {
            ldap3::drive!(conn);
            let dn = format!("uid={},ou=people,dc=example,dc=com", username);
            match ldap.simple_bind(&dn, &password).await {
                Ok(res) => {
                    if res.rc==0 {
                        println!("Login Success");
                        println!("Code {} Message {}", res.rc, res.text.len());
                    } else {
                        //Code 49 bad user or password
                        //Code 53 account inactivated
                        println!("Code {} Message {}", res.rc, res.text);
                    }
                }
                Err(err) => {
                    info!("Ldap Search Error {:?}",err);
                }
            }
        },
        Err(err) => {
            info!("Ldap Connect Error {:?}",err);
        }
    }

Auth extension missing when using `merge`

I am trying to use axum-login in my project.
The structure is below

โ”œโ”€โ”€ routers
โ”‚   โ”œโ”€โ”€ auth.rs (use axum-login here)
โ”‚   โ”œโ”€โ”€ mod.rs
โ”‚   โ”œโ”€โ”€ patient.rs
โ”‚   โ”œโ”€โ”€ router.rs
โ”‚   โ””โ”€โ”€ sign_up.rs

In router.rs, I tried to merge all router together,

let route = Router::new()
    .route("/admin", get(admin_handler))
    .route_layer(RequireAuthorizationLayer::<User, Role>::login_with_role(
        Role::Admin..,
    ))
    .route("/", get(protected_handler))
    .route_layer(RequireAuthorizationLayer::<User, Role>::login());

route
    .merge(sign_up::create_router().await)
    .merge(patient::create_router().await)
    .merge(auth::create_router().await)

In auth.rs

Router::new()
    .route("/auth/login", post(login_handler))
    .route("/auth/logout", get(logout_handler))
    .layer(auth_layer)
    .layer(session_layer)

When I ran the program, the server could not find extention. And showed

thread 'tokio-runtime-worker' panicked at 'Auth extension missing. Is the auth layer installed?', 

Should I use other functions, instead of merge? Does anyone give me some advice? Thanks.

Additional unit tests?

Currently there's a solitary test which shows that the library functions at some basic level. It would be nice to extend test coverage to ensure other parts of the library behave as we'd like. For example, testing the new role functionality might be good and no doubt there are other places we could introduce testing to help increase confidence in the code.

Proposal to Implement NoSQL Stores such as MongoDB and Redis

I trust this message finds you well. I wanted to share an idea that I believe could significantly enrich our product.

At present, axum-login relies on SQL database systems to store and manage user information. While these systems are powerful and have been useful so far, I propose that we also include support for NoSQL databases, specifically MongoDB and Redis. Incorporating these could provide significant benefits in terms of scalability, performance, and flexibility.

NoSQL databases are known for their high scalability and flexibility, allowing for a more efficient handling of large data sets and complex data structures. MongoDB, for example, uses a document-oriented model that can store vast amounts of data in varied formats, and its distributed architecture ensures high availability and scalability. On the other hand, Redis, an in-memory data structure store, can serve as a database, cache, and message broker, providing exceptional performance for real-time applications.

By supporting NoSQL databases, we can make our system more versatile, catering to a wider range of use cases and potential clients. This could also make our system more robust and future-proof, as NoSQL databases are increasingly being adopted in the industry.

I look forward to hearing your thoughts on this matter.

How do I create my own store

I'm using Sea-ORM for a project of mine and I wanted use axum-login to handle authentication. I'm using postgres as the database so I know I can use the postgres feature. But I was wondering if I can create my own store using Sea-ORM. If so, how would I go about doing that?

I tried implementing it on my own using memory_store.rs and sql_store.rs as examples but the compiler keeps complaining about AuthUser not being satisfied for my User struct (even though I believe I've implemented it). Can you give me some pointers?
Thanks.

Cross-origin WebSocket requests are rejected with 401

Hi, thanks for all your efforts making axum-login, it's extremely useful!

I have found an issue when creating an API using CORS, where any attempt to put a WebSocket endpoint behind RequireAuthorizationLayer::login() causes it to unconditionally return 401 Unauthorized, whether or not the user is logged in. I have traced this issue back as far as I'm able, in authorize() the user is None. Changing CORS or Cookie settings do not appear to have any effect.

Steps to Reproduce:

  • Clone and run https://github.com/bclarke123/auth-ws-mre
  • Navigate browser to http://localhost:3001/ and open the developer console - this will make a login request to port 3000 (making it cross origin), the login request should succeed, then the websocket request should fail. Enabling tracing on the server will show it quietly returning a 401.
  • Close the browser window, open a new one, and navigate to http://localhost:3000/ - you should observe in the console that the websocket connection succeeds, as the request is no longer cross-origin.
  • Interestingly, in Chrome, if you now change the URL in the address bar back to port 3001, the cross-origin websocket connection seems to begin working, after making a request from the same origin. I'm not sure if this is a quirk of connection pooling in Chrome or something, but if the steps are not followed in this order it's possible to miss the issue.

Thanks again, if I can do anything to help track this down I'm happy to help!

Redirect unauthenticated user to login page.

I have been hoping to replace my custom/fragile middleware I built around axum-sessions with axum-login. One of the things I cannot figure out with this library is how to redirect an unauthenticated user to the login page.

  • user tries to go to "example.com/requires_auth"
  • user is redirected to "example.com/login?next_url=/requires_auth"
  • user logs in
  • user is redirected back to "example.com/requries_auth"

Right now, because of how "requries_auth" attempts to extract a user, there is no way to redirect when there is no user available.

Do you have a recommendation or am I going about this all wrong?

Problem with session when frontend is on a different port than backend

Hello @maxcountryman,
As mentioned in the title, my local development frontend server is on port localhost:9000, my backend is on localhost:8080 and the session id changes constantly.

When using the same port localhost:8080 both for the frontend as well as the backend everything works as expected, the session id remains the same. Do you have any idea what the issue may be?

I tried setting the with_same_site_policy(SameSite::None) flag on the SessionLayer but it doesn't change anything.

Trait `UserStore` depends on eyre

I've tried to implement custom UserStore, but the return type of method load_user depends on eyre's Report which I'm not using in my application. It'd probably make more sense to give implementors more freedom and require std::error::Error instead.

I wrote a prototype axum/tower authorization layer for combining auth logic using AND/OR, thoughts on similiar features in axum-login?

Hi, sorry for long title.
I switched my graphql api on my personal project to a rest api and wrote this for describing authorization using AND/OR logic like graphql. It's not very ergonomic, also I use BoxFuture because I couldn't quite figure out how to write the future by hand. But, I was wondering if you were interested in a similar feature for axum-login or if one already exists. I think it would be good for the ecosystem to have one broad axum auth toolset, that let users describe authorization in a varied way.
https://github.com/sjud/axum_guard_logic
Warm regards, thank you for your FOSS contribution

Idea: RBAC support

As mentioned in #8 either an example or additional support traits or methods for RBAC would benefit the project.

@maxcountryman do you have some ideas on how this would look like?

What I imagine is a get_role() -> Option<R> method on AuthUser where R is some enum defining the possible roles for a user.

Additionally some (generic?) extractor (or maybe a macro to generate such an extractor) to allow only users with at least one of the roles specified in the extractor parameters, would be perfect.

This can possibly be implemented in a backwards-compatible way by providing a default implementation for the new method, which would return None.

Issues with PgPool type

Running this code I get a type error on the last line. The issue seems to be that axum-login is using sqlx_core's implementation of PgPool.

use sqlx::{Pool, Postgres, PgPool};

pub async fn new_app() -> Option<Router> {
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect("postgres://moose:test@localhost/test")
        .await
        .unwrap();
        
    let pg_pool: PgPool = pool.clone();

    let mut secret = [0u8; 64];
    thread_rng().fill(&mut secret);

    let session_store = UserSessionStore::new(&pool);
    let session_layer = SessionLayer::new(session_store, &secret);

    let user_store = PostgresStore::<User, Role>::new(pg_pool);
mismatched types [E0308] expected `sqlx_core::postgres::PgPool`, found `PgPool` 

Problem with async_sqlx_session

There seems to be a problem when using async_sqlx_session.
When using jbr/async_sqlx_session, the sqlx version is up to date but one can only use async_std whereas axum uses tokio.
When using CyrusAlbright/async_sqlx_session, it's possible to use tokio but the sqlx version is not up to date and for instance the PostgresSessionStore cannot be instanciated through the from_client() function.
Do you know if there is a way to solve this problem ?
Thanks a lot for your answer and for your awesome work.

Make `AuthUser` trait more flexible

Hi! I was evaluating this crate for use in my project and found it a little bit opinionated. I will describe my issue and would love to hear your feedback!

AuthUser trait is synchronous

In my project, User struct has an id, role and optional foreign key profile_id. There are 3 roles: guest, normal and admin. If user is a guest, then there's no related profile and profile_id is set to None. Profile struct has an email (which is the primary key), password_hash and some other data.

So, now I'm implementing the AuthUser trait. Getting a role is easy. But getting password_hash is not: for a guest user I can just return an empty Vec, but for normal and admin user I need to fetch related Profile, which would be an async operation inside a sync function. Surely, I can try to implement UserStore and fetch the profile eagerly. But this will complicate my type signature. Or I can move email and password_hash from Profile to User, but it won't make sense for a guest user.

Making the trait async would be a far better option. Surely, it is a breaking change, but the migration is very straightforward: just add #[async_trait] to the trait impl and async modifier before methods.

AuthUser expects password hash

But what if I want to use a passwordless authentication? For example, a magic-link. In this case, for every signed user I would store some kind of token, and use it instead of the password hash. Maybe this particular example is not ideal, but you probably get the idea.

How to deal with it? Just rename get_password_hash to get_secret and specify password hash as an example for such a secret in the docs.

Idea: Add an example for a simple `is_admin` extractor

A new example in the examples directory, which would show how to require not only a logged in user, but a user with a given "permission" (like a field is_admin set to true) would be very helpful for beginners.

Maybe creating a generic extractor trait to check some permission (which later can be specified to a RequireAdmin extractor or some other permission) by the end-user would also be helpful.

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.