223 lines
7.0 KiB
Rust
223 lines
7.0 KiB
Rust
|
|
//! Error types for the authentication plugin.
|
||
|
|
//!
|
||
|
|
//! This module provides structured error handling with specific error kinds
|
||
|
|
//! for different failure scenarios in authentication operations.
|
||
|
|
|
||
|
|
use std::fmt;
|
||
|
|
|
||
|
|
/// Enum representing different kinds of authentication errors.
|
||
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
|
|
pub enum AuthErrorKind {
|
||
|
|
/// Failed to authenticate with invalid credentials
|
||
|
|
InvalidCredentials,
|
||
|
|
/// Token has expired
|
||
|
|
TokenExpired,
|
||
|
|
/// Token format is invalid
|
||
|
|
InvalidToken,
|
||
|
|
/// Failed to verify token signature
|
||
|
|
SignatureVerificationFailed,
|
||
|
|
/// Keyring operation failed
|
||
|
|
KeyringError,
|
||
|
|
/// Network or HTTP request failed
|
||
|
|
NetworkError,
|
||
|
|
/// Server returned an error response
|
||
|
|
ServerError,
|
||
|
|
/// MFA verification failed
|
||
|
|
MfaFailed,
|
||
|
|
/// User not found
|
||
|
|
UserNotFound,
|
||
|
|
/// Session not found or expired
|
||
|
|
SessionNotFound,
|
||
|
|
/// Configuration error
|
||
|
|
ConfigurationError,
|
||
|
|
/// Internal error
|
||
|
|
InternalError,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for AuthErrorKind {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
|
match self {
|
||
|
|
Self::InvalidCredentials => write!(f, "invalid credentials"),
|
||
|
|
Self::TokenExpired => write!(f, "token expired"),
|
||
|
|
Self::InvalidToken => write!(f, "invalid token format"),
|
||
|
|
Self::SignatureVerificationFailed => write!(f, "signature verification failed"),
|
||
|
|
Self::KeyringError => write!(f, "keyring operation failed"),
|
||
|
|
Self::NetworkError => write!(f, "network error"),
|
||
|
|
Self::ServerError => write!(f, "server error"),
|
||
|
|
Self::MfaFailed => write!(f, "MFA verification failed"),
|
||
|
|
Self::UserNotFound => write!(f, "user not found"),
|
||
|
|
Self::SessionNotFound => write!(f, "session not found"),
|
||
|
|
Self::ConfigurationError => write!(f, "configuration error"),
|
||
|
|
Self::InternalError => write!(f, "internal error"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Structured error type for authentication operations.
|
||
|
|
///
|
||
|
|
/// Provides detailed error information including:
|
||
|
|
/// - Error kind for programmatic handling
|
||
|
|
/// - Context message for additional details
|
||
|
|
/// - Optional source error for error chaining
|
||
|
|
#[derive(Debug)]
|
||
|
|
pub struct AuthError {
|
||
|
|
/// The kind of error that occurred
|
||
|
|
pub kind: AuthErrorKind,
|
||
|
|
/// Additional context about the error
|
||
|
|
pub context: String,
|
||
|
|
/// Optional underlying error
|
||
|
|
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl AuthError {
|
||
|
|
/// Creates a new AuthError with the specified kind and context.
|
||
|
|
///
|
||
|
|
/// # Arguments
|
||
|
|
///
|
||
|
|
/// * `kind` - The type of authentication error
|
||
|
|
/// * `context` - Additional context describing the error
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
///
|
||
|
|
/// ```
|
||
|
|
/// use nu_plugin_auth::error::{AuthError, AuthErrorKind};
|
||
|
|
///
|
||
|
|
/// let error = AuthError::new(
|
||
|
|
/// AuthErrorKind::InvalidCredentials,
|
||
|
|
/// "Username or password is incorrect"
|
||
|
|
/// );
|
||
|
|
/// ```
|
||
|
|
pub fn new(kind: AuthErrorKind, context: impl Into<String>) -> Self {
|
||
|
|
Self {
|
||
|
|
kind,
|
||
|
|
context: context.into(),
|
||
|
|
source: None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates an AuthError with an underlying source error.
|
||
|
|
///
|
||
|
|
/// # Arguments
|
||
|
|
///
|
||
|
|
/// * `kind` - The type of authentication error
|
||
|
|
/// * `context` - Additional context describing the error
|
||
|
|
/// * `source` - The underlying error that caused this error
|
||
|
|
pub fn with_source(
|
||
|
|
kind: AuthErrorKind,
|
||
|
|
context: impl Into<String>,
|
||
|
|
source: impl std::error::Error + Send + Sync + 'static,
|
||
|
|
) -> Self {
|
||
|
|
Self {
|
||
|
|
kind,
|
||
|
|
context: context.into(),
|
||
|
|
source: Some(Box::new(source)),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates an invalid credentials error.
|
||
|
|
pub fn invalid_credentials(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::InvalidCredentials, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a token expired error.
|
||
|
|
pub fn token_expired(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::TokenExpired, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates an invalid token error.
|
||
|
|
pub fn invalid_token(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::InvalidToken, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a keyring error.
|
||
|
|
pub fn keyring_error(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::KeyringError, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a network error.
|
||
|
|
pub fn network_error(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::NetworkError, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a server error.
|
||
|
|
pub fn server_error(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::ServerError, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates an MFA failed error.
|
||
|
|
pub fn mfa_failed(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::MfaFailed, context)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a configuration error.
|
||
|
|
pub fn configuration_error(context: impl Into<String>) -> Self {
|
||
|
|
Self::new(AuthErrorKind::ConfigurationError, context)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl fmt::Display for AuthError {
|
||
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
|
|
write!(f, "{}: {}", self.kind, self.context)?;
|
||
|
|
if let Some(ref source) = self.source {
|
||
|
|
write!(f, " (caused by: {})", source)?;
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl std::error::Error for AuthError {
|
||
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||
|
|
self.source
|
||
|
|
.as_ref()
|
||
|
|
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl From<AuthError> for nu_protocol::LabeledError {
|
||
|
|
fn from(err: AuthError) -> Self {
|
||
|
|
nu_protocol::LabeledError::new(err.to_string())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_error_display() {
|
||
|
|
let error = AuthError::new(
|
||
|
|
AuthErrorKind::InvalidCredentials,
|
||
|
|
"username admin not found",
|
||
|
|
);
|
||
|
|
assert!(error.to_string().contains("invalid credentials"));
|
||
|
|
assert!(error.to_string().contains("username admin not found"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_error_with_source() {
|
||
|
|
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
|
||
|
|
let error = AuthError::with_source(
|
||
|
|
AuthErrorKind::KeyringError,
|
||
|
|
"failed to read keyring",
|
||
|
|
io_error,
|
||
|
|
);
|
||
|
|
assert!(error.to_string().contains("caused by"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_error_kind_display() {
|
||
|
|
assert_eq!(AuthErrorKind::InvalidCredentials.to_string(), "invalid credentials");
|
||
|
|
assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired");
|
||
|
|
assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_convenience_constructors() {
|
||
|
|
let error = AuthError::invalid_credentials("bad password");
|
||
|
|
assert_eq!(error.kind, AuthErrorKind::InvalidCredentials);
|
||
|
|
|
||
|
|
let error = AuthError::network_error("connection refused");
|
||
|
|
assert_eq!(error.kind, AuthErrorKind::NetworkError);
|
||
|
|
}
|
||
|
|
}
|