jwt implementation and login, not working yet
This commit is contained in:
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -1024,8 +1024,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1541,6 +1543,21 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lasso"
|
name = "lasso"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -1928,6 +1945,16 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -2345,6 +2372,20 @@ dependencies = [
|
|||||||
"tstr",
|
"tstr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.15",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.7"
|
version = "0.9.7"
|
||||||
@@ -2554,6 +2595,7 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"jsonwebtoken",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"prost",
|
"prost",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -2641,6 +2683,18 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sized-chunks"
|
name = "sized-chunks"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
@@ -3521,6 +3575,12 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@@ -3551,6 +3611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "common.proto";
|
|||||||
|
|
||||||
service AuthService {
|
service AuthService {
|
||||||
rpc Register(RegisterRequest) returns (AuthResponse);
|
rpc Register(RegisterRequest) returns (AuthResponse);
|
||||||
|
rpc Login(LoginRequest) returns (LoginResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message RegisterRequest {
|
message RegisterRequest {
|
||||||
@@ -21,3 +22,16 @@ message AuthResponse {
|
|||||||
string email = 3; // Registered email (if provided)
|
string email = 3; // Registered email (if provided)
|
||||||
string role = 4; // Default role: 'accountant'
|
string role = 4; // Default role: 'accountant'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LoginRequest {
|
||||||
|
string identifier = 1; // Can be username or email
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginResponse {
|
||||||
|
string access_token = 1; // JWT token
|
||||||
|
string token_type = 2; // Usually "Bearer"
|
||||||
|
int32 expires_in = 3; // Expiration in seconds (86400 for 24 hours)
|
||||||
|
string user_id = 4; // User's UUID in string format
|
||||||
|
string role = 5; // User's role
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -25,6 +25,32 @@ pub struct AuthResponse {
|
|||||||
#[prost(string, tag = "4")]
|
#[prost(string, tag = "4")]
|
||||||
pub role: ::prost::alloc::string::String,
|
pub role: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct LoginRequest {
|
||||||
|
/// Can be username or email
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub identifier: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub password: ::prost::alloc::string::String,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct LoginResponse {
|
||||||
|
/// JWT token
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub access_token: ::prost::alloc::string::String,
|
||||||
|
/// Usually "Bearer"
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub token_type: ::prost::alloc::string::String,
|
||||||
|
/// Expiration in seconds (86400 for 24 hours)
|
||||||
|
#[prost(int32, tag = "3")]
|
||||||
|
pub expires_in: i32,
|
||||||
|
/// User's UUID in string format
|
||||||
|
#[prost(string, tag = "4")]
|
||||||
|
pub user_id: ::prost::alloc::string::String,
|
||||||
|
/// User's role
|
||||||
|
#[prost(string, tag = "5")]
|
||||||
|
pub role: ::prost::alloc::string::String,
|
||||||
|
}
|
||||||
/// Generated client implementations.
|
/// Generated client implementations.
|
||||||
pub mod auth_service_client {
|
pub mod auth_service_client {
|
||||||
#![allow(
|
#![allow(
|
||||||
@@ -137,6 +163,27 @@ pub mod auth_service_client {
|
|||||||
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Register"));
|
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Register"));
|
||||||
self.inner.unary(req, path, codec).await
|
self.inner.unary(req, path, codec).await
|
||||||
}
|
}
|
||||||
|
pub async fn login(
|
||||||
|
&mut self,
|
||||||
|
request: impl tonic::IntoRequest<super::LoginRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status> {
|
||||||
|
self.inner
|
||||||
|
.ready()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tonic::Status::unknown(
|
||||||
|
format!("Service was not ready: {}", e.into()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let codec = tonic::codec::ProstCodec::default();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(
|
||||||
|
"/multieko2.auth.AuthService/Login",
|
||||||
|
);
|
||||||
|
let mut req = request.into_request();
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert(GrpcMethod::new("multieko2.auth.AuthService", "Login"));
|
||||||
|
self.inner.unary(req, path, codec).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Generated server implementations.
|
/// Generated server implementations.
|
||||||
@@ -156,6 +203,10 @@ pub mod auth_service_server {
|
|||||||
&self,
|
&self,
|
||||||
request: tonic::Request<super::RegisterRequest>,
|
request: tonic::Request<super::RegisterRequest>,
|
||||||
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status>;
|
) -> std::result::Result<tonic::Response<super::AuthResponse>, tonic::Status>;
|
||||||
|
async fn login(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<super::LoginRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<super::LoginResponse>, tonic::Status>;
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthServiceServer<T> {
|
pub struct AuthServiceServer<T> {
|
||||||
@@ -278,6 +329,49 @@ pub mod auth_service_server {
|
|||||||
};
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
|
"/multieko2.auth.AuthService/Login" => {
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
struct LoginSvc<T: AuthService>(pub Arc<T>);
|
||||||
|
impl<T: AuthService> tonic::server::UnaryService<super::LoginRequest>
|
||||||
|
for LoginSvc<T> {
|
||||||
|
type Response = super::LoginResponse;
|
||||||
|
type Future = BoxFuture<
|
||||||
|
tonic::Response<Self::Response>,
|
||||||
|
tonic::Status,
|
||||||
|
>;
|
||||||
|
fn call(
|
||||||
|
&mut self,
|
||||||
|
request: tonic::Request<super::LoginRequest>,
|
||||||
|
) -> Self::Future {
|
||||||
|
let inner = Arc::clone(&self.0);
|
||||||
|
let fut = async move {
|
||||||
|
<T as AuthService>::login(&inner, request).await
|
||||||
|
};
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
|
let max_decoding_message_size = self.max_decoding_message_size;
|
||||||
|
let max_encoding_message_size = self.max_encoding_message_size;
|
||||||
|
let inner = self.inner.clone();
|
||||||
|
let fut = async move {
|
||||||
|
let method = LoginSvc(inner);
|
||||||
|
let codec = tonic::codec::ProstCodec::default();
|
||||||
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(
|
||||||
|
accept_compression_encodings,
|
||||||
|
send_compression_encodings,
|
||||||
|
)
|
||||||
|
.apply_max_message_size_config(
|
||||||
|
max_decoding_message_size,
|
||||||
|
max_encoding_message_size,
|
||||||
|
);
|
||||||
|
let res = grpc.unary(method, req).await;
|
||||||
|
Ok(res)
|
||||||
|
};
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut response = http::Response::new(empty_body());
|
let mut response = http::Response::new(empty_body());
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ lazy_static = "1.5.0"
|
|||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
bcrypt = "0.17.0"
|
bcrypt = "0.17.0"
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
uuid = { version = "1.16.0", features = ["v4"] }
|
uuid = { version = "1.16.0", features = ["serde", "v4"] }
|
||||||
|
jsonwebtoken = "9.3.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "server"
|
name = "server"
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// src/auth/handlers.rs
|
// src/auth/handlers.rs
|
||||||
|
|
||||||
pub mod register;
|
pub mod register;
|
||||||
|
pub mod login;
|
||||||
|
|
||||||
pub use register::*;
|
pub use register::*;
|
||||||
|
pub use login::*;
|
||||||
|
|||||||
46
server/src/auth/handlers/login.rs
Normal file
46
server/src/auth/handlers/login.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// src/auth/handlers/login.rs
|
||||||
|
use bcrypt::verify;
|
||||||
|
use tonic::{Request, Response, Status};
|
||||||
|
use crate::db::PgPool;
|
||||||
|
use crate::auth::{models::AuthError, logic::jwt}; // Fixed import path
|
||||||
|
use common::proto::multieko2::auth::{LoginRequest, LoginResponse};
|
||||||
|
|
||||||
|
pub async fn login(
|
||||||
|
pool: &PgPool,
|
||||||
|
request: LoginRequest,
|
||||||
|
) -> Result<Response<LoginResponse>, Status> {
|
||||||
|
let user = sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT id, password_hash, role
|
||||||
|
FROM users
|
||||||
|
WHERE username = $1 OR email = $1
|
||||||
|
"#,
|
||||||
|
request.identifier
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Status::internal(e.to_string()))?
|
||||||
|
.ok_or_else(|| Status::unauthenticated("Invalid credentials"))?;
|
||||||
|
|
||||||
|
// Handle the optional password_hash
|
||||||
|
let password_hash = user.password_hash
|
||||||
|
.ok_or_else(|| Status::internal("User account has no password set"))?;
|
||||||
|
|
||||||
|
// Verify the password
|
||||||
|
if !verify(&request.password, &password_hash)
|
||||||
|
.map_err(|e| Status::internal(e.to_string()))?
|
||||||
|
{
|
||||||
|
return Err(Status::unauthenticated("Invalid credentials"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = jwt::generate_token(user.id, &user.role)
|
||||||
|
.map_err(|e| Status::internal(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Response::new(LoginResponse {
|
||||||
|
access_token: token,
|
||||||
|
token_type: "Bearer".to_string(),
|
||||||
|
expires_in: 86400, // 24 hours
|
||||||
|
user_id: user.id.to_string(),
|
||||||
|
role: user.role,
|
||||||
|
}))
|
||||||
|
}
|
||||||
7
server/src/auth/logic.rs
Normal file
7
server/src/auth/logic.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// src/auth/logic.rs
|
||||||
|
|
||||||
|
pub mod jwt;
|
||||||
|
pub mod middleware;
|
||||||
|
|
||||||
|
pub use jwt::*;
|
||||||
|
pub use middleware::*;
|
||||||
55
server/src/auth/logic/jwt.rs
Normal file
55
server/src/auth/logic/jwt.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// src/auth/jwt.rs
|
||||||
|
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::{Duration, OffsetDateTime};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use crate::auth::models::AuthError;
|
||||||
|
|
||||||
|
static KEYS: OnceLock<Keys> = OnceLock::new();
|
||||||
|
|
||||||
|
struct Keys {
|
||||||
|
encoding: EncodingKey,
|
||||||
|
decoding: DecodingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub sub: Uuid, // User ID
|
||||||
|
pub exp: i64, // Expiration time
|
||||||
|
pub role: String, // User role
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_jwt() -> Result<(), AuthError> {
|
||||||
|
let secret = std::env::var("JWT_SECRET")
|
||||||
|
.map_err(|_| AuthError::ConfigError("JWT_SECRET must be set".to_string()))?;
|
||||||
|
|
||||||
|
KEYS.set(Keys {
|
||||||
|
encoding: EncodingKey::from_secret(secret.as_bytes()),
|
||||||
|
decoding: DecodingKey::from_secret(secret.as_bytes()),
|
||||||
|
}).map_err(|_| AuthError::ConfigError("Failed to initialize JWT keys".to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_token(user_id: Uuid, role: &str) -> Result<String, AuthError> {
|
||||||
|
let keys = KEYS.get().ok_or(AuthError::ConfigError("JWT not initialized".to_string()))?;
|
||||||
|
|
||||||
|
let exp = OffsetDateTime::now_utc() + Duration::hours(24);
|
||||||
|
let claims = Claims {
|
||||||
|
sub: user_id,
|
||||||
|
exp: exp.unix_timestamp(),
|
||||||
|
role: role.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
encode(&Header::default(), &claims, &keys.encoding)
|
||||||
|
.map_err(|e| AuthError::JwtError(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_token(token: &str) -> Result<Claims, AuthError> {
|
||||||
|
let keys = KEYS.get().ok_or(AuthError::ConfigError("JWT not initialized".to_string()))?;
|
||||||
|
|
||||||
|
decode::<Claims>(token, &keys.decoding, &Validation::default())
|
||||||
|
.map(|data| data.claims)
|
||||||
|
.map_err(|e| AuthError::JwtError(e.to_string()))
|
||||||
|
}
|
||||||
22
server/src/auth/logic/middleware.rs
Normal file
22
server/src/auth/logic/middleware.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// src/auth/middleware.rs
|
||||||
|
use tonic::{metadata::MetadataValue, service::Interceptor, Status};
|
||||||
|
use crate::auth::{logic::jwt, models::AuthError};
|
||||||
|
|
||||||
|
pub struct AuthInterceptor;
|
||||||
|
|
||||||
|
impl Interceptor for AuthInterceptor {
|
||||||
|
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
|
||||||
|
let metadata = request.metadata();
|
||||||
|
let token = metadata.get("authorization")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|s| s.strip_prefix("Bearer "))
|
||||||
|
.ok_or(Status::unauthenticated("Missing authorization header"))?;
|
||||||
|
|
||||||
|
let claims = jwt::validate_token(token)
|
||||||
|
.map_err(|e| Status::unauthenticated(e.to_string()))?;
|
||||||
|
|
||||||
|
// Store claims in request extensions
|
||||||
|
request.extensions_mut().insert(claims);
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// src/auth/mod.rs
|
// src/auth/mod.rs
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
pub mod logic;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ pub struct RegisterRequest {
|
|||||||
pub password_confirmation: String,
|
pub password_confirmation: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Validate, Deserialize)]
|
||||||
|
pub struct LoginRequest {
|
||||||
|
#[validate(length(min = 1))]
|
||||||
|
pub identifier: String,
|
||||||
|
#[validate(length(min = 1))]
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
#[error("Passwords do not match")]
|
#[error("Passwords do not match")]
|
||||||
@@ -24,4 +32,10 @@ pub enum AuthError {
|
|||||||
DatabaseError(String),
|
DatabaseError(String),
|
||||||
#[error("Hashing error: {0}")]
|
#[error("Hashing error: {0}")]
|
||||||
HashingError(String),
|
HashingError(String),
|
||||||
|
#[error("Invalid credentials")]
|
||||||
|
InvalidCredentials,
|
||||||
|
#[error("JWT error: {0}")]
|
||||||
|
JwtError(String),
|
||||||
|
#[error("Configuration error: {0}")]
|
||||||
|
ConfigError(String),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user