mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Fix clippy warnings
This commit is contained in:
		
							parent
							
								
									eec0903052
								
							
						
					
					
						commit
						2f7019433d
					
				@ -46,7 +46,7 @@ where
 | 
				
			|||||||
    R: serde::ser::Serialize,
 | 
					    R: serde::ser::Serialize,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    fn from(request: &'a R) -> Self {
 | 
					    fn from(request: &'a R) -> Self {
 | 
				
			||||||
        Self(Json(&request))
 | 
					        Self(Json(request))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,7 +92,7 @@ impl HostService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pub fn login_start(
 | 
					    pub fn login_start(
 | 
				
			||||||
        request: login::ClientLoginStartRequest,
 | 
					        request: login::ClientLoginStartRequest,
 | 
				
			||||||
        callback: Callback<Result<login::ServerLoginStartResponse>>,
 | 
					        callback: Callback<Result<Box<login::ServerLoginStartResponse>>>,
 | 
				
			||||||
    ) -> Result<FetchTask> {
 | 
					    ) -> Result<FetchTask> {
 | 
				
			||||||
        call_server(
 | 
					        call_server(
 | 
				
			||||||
            "/auth/opaque/login/start",
 | 
					            "/auth/opaque/login/start",
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ pub struct Props {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub enum Msg {
 | 
					pub enum Msg {
 | 
				
			||||||
    Submit,
 | 
					    Submit,
 | 
				
			||||||
    AuthenticationStartResponse(Result<login::ServerLoginStartResponse>),
 | 
					    AuthenticationStartResponse(Result<Box<login::ServerLoginStartResponse>>),
 | 
				
			||||||
    AuthenticationFinishResponse(Result<String>),
 | 
					    AuthenticationFinishResponse(Result<String>),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,9 +57,9 @@ impl LoginForm {
 | 
				
			|||||||
        match msg {
 | 
					        match msg {
 | 
				
			||||||
            Msg::Submit => {
 | 
					            Msg::Submit => {
 | 
				
			||||||
                let username = get_form_field("username")
 | 
					                let username = get_form_field("username")
 | 
				
			||||||
                    .ok_or(anyhow!("Could not get username from form"))?;
 | 
					                    .ok_or_else(|| anyhow!("Could not get username from form"))?;
 | 
				
			||||||
                let password = get_form_field("password")
 | 
					                let password = get_form_field("password")
 | 
				
			||||||
                    .ok_or(anyhow!("Could not get password from form"))?;
 | 
					                    .ok_or_else(|| anyhow!("Could not get password from form"))?;
 | 
				
			||||||
                let mut rng = rand::rngs::OsRng;
 | 
					                let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
                let login_start_request =
 | 
					                let login_start_request =
 | 
				
			||||||
                    opaque::client::login::start_login(&password, &mut rng)
 | 
					                    opaque::client::login::start_login(&password, &mut rng)
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ pub enum AuthenticationError {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub type AuthenticationResult<T> = std::result::Result<T, AuthenticationError>;
 | 
					pub type AuthenticationResult<T> = std::result::Result<T, AuthenticationError>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use opaque_ke::keypair::{PublicKey, PrivateKey};
 | 
					pub use opaque_ke::keypair::{PrivateKey, PublicKey};
 | 
				
			||||||
pub type KeyPair = opaque_ke::keypair::KeyPair<<DefaultSuite as CipherSuite>::Group>;
 | 
					pub type KeyPair = opaque_ke::keypair::KeyPair<<DefaultSuite as CipherSuite>::Group>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A wrapper around argon2 to provide the [`opaque_ke::slow_hash::SlowHash`] trait.
 | 
					/// A wrapper around argon2 to provide the [`opaque_ke::slow_hash::SlowHash`] trait.
 | 
				
			||||||
@ -64,8 +64,10 @@ pub mod client {
 | 
				
			|||||||
    pub mod registration {
 | 
					    pub mod registration {
 | 
				
			||||||
        pub use super::*;
 | 
					        pub use super::*;
 | 
				
			||||||
        pub type ClientRegistration = opaque_ke::ClientRegistration<DefaultSuite>;
 | 
					        pub type ClientRegistration = opaque_ke::ClientRegistration<DefaultSuite>;
 | 
				
			||||||
        pub type ClientRegistrationStartResult = opaque_ke::ClientRegistrationStartResult<DefaultSuite>;
 | 
					        pub type ClientRegistrationStartResult =
 | 
				
			||||||
        pub type ClientRegistrationFinishResult = opaque_ke::ClientRegistrationFinishResult<DefaultSuite>;
 | 
					            opaque_ke::ClientRegistrationStartResult<DefaultSuite>;
 | 
				
			||||||
 | 
					        pub type ClientRegistrationFinishResult =
 | 
				
			||||||
 | 
					            opaque_ke::ClientRegistrationFinishResult<DefaultSuite>;
 | 
				
			||||||
        pub type RegistrationResponse = opaque_ke::RegistrationResponse<DefaultSuite>;
 | 
					        pub type RegistrationResponse = opaque_ke::RegistrationResponse<DefaultSuite>;
 | 
				
			||||||
        pub use opaque_ke::ClientRegistrationFinishParameters;
 | 
					        pub use opaque_ke::ClientRegistrationFinishParameters;
 | 
				
			||||||
        /// Initiate the registration negotiation.
 | 
					        /// Initiate the registration negotiation.
 | 
				
			||||||
@ -73,10 +75,7 @@ pub mod client {
 | 
				
			|||||||
            password: &str,
 | 
					            password: &str,
 | 
				
			||||||
            rng: &mut R,
 | 
					            rng: &mut R,
 | 
				
			||||||
        ) -> AuthenticationResult<ClientRegistrationStartResult> {
 | 
					        ) -> AuthenticationResult<ClientRegistrationStartResult> {
 | 
				
			||||||
            Ok(ClientRegistration::start(
 | 
					            Ok(ClientRegistration::start(rng, password.as_bytes())?)
 | 
				
			||||||
                rng,
 | 
					 | 
				
			||||||
                password.as_bytes(),
 | 
					 | 
				
			||||||
            )?)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Finalize the registration negotiation.
 | 
					        /// Finalize the registration negotiation.
 | 
				
			||||||
@ -101,10 +100,7 @@ pub mod client {
 | 
				
			|||||||
        pub type ClientLoginStartResult = opaque_ke::ClientLoginStartResult<DefaultSuite>;
 | 
					        pub type ClientLoginStartResult = opaque_ke::ClientLoginStartResult<DefaultSuite>;
 | 
				
			||||||
        pub type CredentialResponse = opaque_ke::CredentialResponse<DefaultSuite>;
 | 
					        pub type CredentialResponse = opaque_ke::CredentialResponse<DefaultSuite>;
 | 
				
			||||||
        pub type CredentialFinalization = opaque_ke::CredentialFinalization<DefaultSuite>;
 | 
					        pub type CredentialFinalization = opaque_ke::CredentialFinalization<DefaultSuite>;
 | 
				
			||||||
        pub use opaque_ke::{
 | 
					        pub use opaque_ke::{ClientLoginFinishParameters, ClientLoginStartParameters};
 | 
				
			||||||
            ClientLoginFinishParameters,
 | 
					 | 
				
			||||||
            ClientLoginStartParameters,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Initiate the login negotiation.
 | 
					        /// Initiate the login negotiation.
 | 
				
			||||||
        pub fn start_login<R: RngCore + CryptoRng>(
 | 
					        pub fn start_login<R: RngCore + CryptoRng>(
 | 
				
			||||||
@ -139,7 +135,8 @@ pub mod server {
 | 
				
			|||||||
        pub use super::*;
 | 
					        pub use super::*;
 | 
				
			||||||
        pub type RegistrationRequest = opaque_ke::RegistrationRequest<DefaultSuite>;
 | 
					        pub type RegistrationRequest = opaque_ke::RegistrationRequest<DefaultSuite>;
 | 
				
			||||||
        pub type RegistrationUpload = opaque_ke::RegistrationUpload<DefaultSuite>;
 | 
					        pub type RegistrationUpload = opaque_ke::RegistrationUpload<DefaultSuite>;
 | 
				
			||||||
        pub type ServerRegistrationStartResult = opaque_ke::ServerRegistrationStartResult<DefaultSuite>;
 | 
					        pub type ServerRegistrationStartResult =
 | 
				
			||||||
 | 
					            opaque_ke::ServerRegistrationStartResult<DefaultSuite>;
 | 
				
			||||||
        /// Start a registration process, from a request sent by the client.
 | 
					        /// Start a registration process, from a request sent by the client.
 | 
				
			||||||
        ///
 | 
					        ///
 | 
				
			||||||
        /// The result must be kept for the next step.
 | 
					        /// The result must be kept for the next step.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
use thiserror::Error;
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(clippy::enum_variant_names)]
 | 
				
			||||||
#[derive(Error, Debug)]
 | 
					#[derive(Error, Debug)]
 | 
				
			||||||
pub enum Error {
 | 
					pub enum DomainError {
 | 
				
			||||||
    #[error("Authentication error for `{0}`")]
 | 
					    #[error("Authentication error for `{0}`")]
 | 
				
			||||||
    AuthenticationError(String),
 | 
					    AuthenticationError(String),
 | 
				
			||||||
    #[error("Database error: `{0}`")]
 | 
					    #[error("Database error: `{0}`")]
 | 
				
			||||||
@ -12,4 +13,4 @@ pub enum Error {
 | 
				
			|||||||
    InternalError(String),
 | 
					    InternalError(String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type Result<T> = std::result::Result<T, Error>;
 | 
					pub type Result<T> = std::result::Result<T, DomainError>;
 | 
				
			||||||
 | 
				
			|||||||
@ -173,8 +173,8 @@ impl BackendHandler for SqlBackendHandler {
 | 
				
			|||||||
            // Transform it into a single result (the first error if any), and group the group_ids
 | 
					            // Transform it into a single result (the first error if any), and group the group_ids
 | 
				
			||||||
            // into a HashSet.
 | 
					            // into a HashSet.
 | 
				
			||||||
            .collect::<sqlx::Result<HashSet<_>>>()
 | 
					            .collect::<sqlx::Result<HashSet<_>>>()
 | 
				
			||||||
            // Map the sqlx::Error into a domain::Error.
 | 
					            // Map the sqlx::Error into a DomainError.
 | 
				
			||||||
            .map_err(Error::DatabaseError)
 | 
					            .map_err(DomainError::DatabaseError)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
 | 
					    async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,7 @@ impl LoginHandler for SqlBackendHandler {
 | 
				
			|||||||
                return Ok(());
 | 
					                return Ok(());
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                debug!(r#"Invalid password for LDAP bind user"#);
 | 
					                debug!(r#"Invalid password for LDAP bind user"#);
 | 
				
			||||||
                return Err(Error::AuthenticationError(request.name));
 | 
					                return Err(DomainError::AuthenticationError(request.name));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let query = Query::select()
 | 
					        let query = Query::select()
 | 
				
			||||||
@ -65,7 +65,7 @@ impl LoginHandler for SqlBackendHandler {
 | 
				
			|||||||
                row.get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
					                row.get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if let Err(e) = passwords_match(
 | 
					                if let Err(e) = passwords_match(
 | 
				
			||||||
                    &&password_hash,
 | 
					                    &password_hash,
 | 
				
			||||||
                    &request.password,
 | 
					                    &request.password,
 | 
				
			||||||
                    self.config.get_server_keys().private(),
 | 
					                    self.config.get_server_keys().private(),
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
@ -79,7 +79,7 @@ impl LoginHandler for SqlBackendHandler {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            debug!(r#"No user found for "{}""#, request.name);
 | 
					            debug!(r#"No user found for "{}""#, request.name);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Err(Error::AuthenticationError(request.name))
 | 
					        Err(DomainError::AuthenticationError(request.name))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,11 +101,11 @@ impl OpaqueHandler for SqlOpaqueHandler {
 | 
				
			|||||||
                .await?
 | 
					                .await?
 | 
				
			||||||
                .get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
					                .get::<Option<Vec<u8>>, _>(&*Users::PasswordHash.to_string())
 | 
				
			||||||
                // If no password, always fail.
 | 
					                // If no password, always fail.
 | 
				
			||||||
                .ok_or_else(|| Error::AuthenticationError(request.username.clone()))?
 | 
					                .ok_or_else(|| DomainError::AuthenticationError(request.username.clone()))?
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let password_file = opaque::server::ServerRegistration::deserialize(&password_file_bytes)
 | 
					        let password_file = opaque::server::ServerRegistration::deserialize(&password_file_bytes)
 | 
				
			||||||
            .map_err(|_| {
 | 
					            .map_err(|_| {
 | 
				
			||||||
            Error::InternalError(format!("Corrupted password file for {}", request.username))
 | 
					            DomainError::InternalError(format!("Corrupted password file for {}", request.username))
 | 
				
			||||||
        })?;
 | 
					        })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut rng = rand::rngs::OsRng;
 | 
					        let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
@ -163,7 +163,7 @@ impl OpaqueHandler for SqlOpaqueHandler {
 | 
				
			|||||||
            &row.get::<Vec<u8>, _>(&*LoginAttempts::ServerLoginData.to_string()),
 | 
					            &row.get::<Vec<u8>, _>(&*LoginAttempts::ServerLoginData.to_string()),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .map_err(|_| {
 | 
					        .map_err(|_| {
 | 
				
			||||||
            Error::InternalError(format!(
 | 
					            DomainError::InternalError(format!(
 | 
				
			||||||
                "Corrupted login data for user `{}` [id `{}`]",
 | 
					                "Corrupted login data for user `{}` [id `{}`]",
 | 
				
			||||||
                username, request.login_key
 | 
					                username, request.login_key
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
@ -248,7 +248,7 @@ impl OpaqueHandler for SqlOpaqueHandler {
 | 
				
			|||||||
            &row.get::<Vec<u8>, _>(&*RegistrationAttempts::ServerRegistrationData.to_string()),
 | 
					            &row.get::<Vec<u8>, _>(&*RegistrationAttempts::ServerRegistrationData.to_string()),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .map_err(|_| {
 | 
					        .map_err(|_| {
 | 
				
			||||||
            Error::InternalError(format!(
 | 
					            DomainError::InternalError(format!(
 | 
				
			||||||
                "Corrupted registration data for user `{}` [id `{}`]",
 | 
					                "Corrupted registration data for user `{}` [id `{}`]",
 | 
				
			||||||
                username, request.registration_key
 | 
					                username, request.registration_key
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::{
 | 
					    domain::{
 | 
				
			||||||
 | 
					        error::DomainError,
 | 
				
			||||||
        handler::{BackendHandler, LoginHandler},
 | 
					        handler::{BackendHandler, LoginHandler},
 | 
				
			||||||
        opaque_handler::OpaqueHandler,
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -191,7 +192,7 @@ where
 | 
				
			|||||||
    // token.
 | 
					    // token.
 | 
				
			||||||
    data.backend_handler
 | 
					    data.backend_handler
 | 
				
			||||||
        .get_user_groups(name.to_string())
 | 
					        .get_user_groups(name.to_string())
 | 
				
			||||||
        .and_then(|g| async { Ok((g, data.backend_handler.create_refresh_token(&name).await?)) })
 | 
					        .and_then(|g| async { Ok((g, data.backend_handler.create_refresh_token(name).await?)) })
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .map(|(groups, (refresh_token, max_age))| {
 | 
					        .map(|(groups, (refresh_token, max_age))| {
 | 
				
			||||||
            let token = create_jwt(&data.jwt_key, name.to_string(), groups);
 | 
					            let token = create_jwt(&data.jwt_key, name.to_string(), groups);
 | 
				
			||||||
@ -205,7 +206,7 @@ where
 | 
				
			|||||||
                        .finish(),
 | 
					                        .finish(),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .cookie(
 | 
					                .cookie(
 | 
				
			||||||
                    Cookie::build("refresh_token", refresh_token + "+" + &name)
 | 
					                    Cookie::build("refresh_token", refresh_token + "+" + name)
 | 
				
			||||||
                        .max_age(max_age.num_days().days())
 | 
					                        .max_age(max_age.num_days().days())
 | 
				
			||||||
                        .path("/auth")
 | 
					                        .path("/auth")
 | 
				
			||||||
                        .http_only(true)
 | 
					                        .http_only(true)
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,7 @@ pub struct Configuration {
 | 
				
			|||||||
impl ConfigurationBuilder {
 | 
					impl ConfigurationBuilder {
 | 
				
			||||||
    #[cfg(test)]
 | 
					    #[cfg(test)]
 | 
				
			||||||
    pub fn build(self) -> Result<Configuration> {
 | 
					    pub fn build(self) -> Result<Configuration> {
 | 
				
			||||||
        let server_keys = get_server_keys(&self.key_file.as_deref().unwrap_or("server_key"))?;
 | 
					        let server_keys = get_server_keys(self.key_file.as_deref().unwrap_or("server_key"))?;
 | 
				
			||||||
        Ok(self.server_keys(server_keys).private_build()?)
 | 
					        Ok(self.server_keys(server_keys).private_build()?)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::handler::*,
 | 
					    domain::{error::DomainError, handler::*},
 | 
				
			||||||
    infra::{
 | 
					    infra::{
 | 
				
			||||||
        tcp_backend_handler::*,
 | 
					        tcp_backend_handler::*,
 | 
				
			||||||
        tcp_server::{error_to_http_response, AppState},
 | 
					        tcp_server::{error_to_http_response, AppState},
 | 
				
			||||||
@ -54,7 +54,7 @@ where
 | 
				
			|||||||
            let msg = err.to_string();
 | 
					            let msg = err.to_string();
 | 
				
			||||||
            actix_web::error::InternalError::from_response(
 | 
					            actix_web::error::InternalError::from_response(
 | 
				
			||||||
                err,
 | 
					                err,
 | 
				
			||||||
                HttpResponse::BadRequest().body(msg).into(),
 | 
					                HttpResponse::BadRequest().body(msg),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .into()
 | 
					            .into()
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
use async_trait::async_trait;
 | 
					use async_trait::async_trait;
 | 
				
			||||||
use std::collections::HashSet;
 | 
					use std::collections::HashSet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type DomainError = crate::domain::error::Error;
 | 
					 | 
				
			||||||
pub type DomainResult<T> = crate::domain::error::Result<T>;
 | 
					pub type DomainResult<T> = crate::domain::error::Result<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    domain::{
 | 
					    domain::{
 | 
				
			||||||
 | 
					        error::DomainError,
 | 
				
			||||||
        handler::{BackendHandler, LoginHandler},
 | 
					        handler::{BackendHandler, LoginHandler},
 | 
				
			||||||
        opaque_handler::OpaqueHandler,
 | 
					        opaque_handler::OpaqueHandler,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -48,7 +49,7 @@ fn http_config<Backend>(
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    cfg.data(AppState::<Backend> {
 | 
					    cfg.data(AppState::<Backend> {
 | 
				
			||||||
        backend_handler,
 | 
					        backend_handler,
 | 
				
			||||||
        jwt_key: Hmac::new_varkey(&jwt_secret.as_bytes()).unwrap(),
 | 
					        jwt_key: Hmac::new_varkey(jwt_secret.as_bytes()).unwrap(),
 | 
				
			||||||
        jwt_blacklist: RwLock::new(jwt_blacklist),
 | 
					        jwt_blacklist: RwLock::new(jwt_blacklist),
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    // Serve index.html and main.js, and default to index.html.
 | 
					    // Serve index.html and main.js, and default to index.html.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user