//! Client authentication mechanisms. pub mod backend; pub use backend::Backend; mod credentials; pub(crate) use credentials::{ ComputeUserInfoMaybeEndpoint, ComputeUserInfoParseError, IpPattern, check_peer_addr_is_in_list, endpoint_sni, }; mod password_hack; use password_hack::PasswordHackPayload; pub(crate) use password_hack::parse_endpoint_param; mod flow; use std::io; use std::net::IpAddr; pub(crate) use flow::*; use thiserror::Error; use tokio::time::error::Elapsed; use crate::auth::backend::jwt::JwtError; use crate::control_plane; use crate::error::{ReportableError, UserFacingError}; /// Convenience wrapper for the authentication error. pub(crate) type Result = std::result::Result; /// Common authentication error. #[derive(Debug, Error)] pub(crate) enum AuthError { #[error(transparent)] ConsoleRedirect(#[from] backend::ConsoleRedirectError), #[error(transparent)] GetAuthInfo(#[from] control_plane::errors::GetAuthInfoError), /// SASL protocol errors (includes [SCRAM](crate::scram)). #[error(transparent)] Sasl(#[from] crate::sasl::Error), #[error("Unsupported authentication method: {0}")] BadAuthMethod(Box), #[error("Malformed password message: {0}")] MalformedPassword(&'static str), #[error( "Endpoint ID is not specified. \ Either please upgrade the postgres client library (libpq) for SNI support \ or pass the endpoint ID (first part of the domain name) as a parameter: '?options=endpoint%3D'. \ See more at https://neon.tech/sni" )] MissingEndpointName, #[error( "VPC endpoint ID is not specified. \ This endpoint requires a VPC endpoint ID to connect." )] MissingVPCEndpointId, #[error("password authentication failed for user '{0}'")] PasswordFailed(Box), /// Errors produced by e.g. [`crate::stream::PqStream`]. #[error(transparent)] Io(#[from] io::Error), #[error( "This IP address {0} is not allowed to connect to this endpoint. \ Please add it to the allowed list in the Neon console. \ Make sure to check for IPv4 or IPv6 addresses." )] IpAddressNotAllowed(IpAddr), #[error("This connection is trying to access this endpoint from a blocked network.")] NetworkNotAllowed, #[error( "This VPC endpoint id {0} is not allowed to connect to this endpoint. \ Please add it to the allowed list in the Neon console." )] VpcEndpointIdNotAllowed(String), #[error("Too many connections to this endpoint. Please try again later.")] TooManyConnections, #[error("Authentication timed out")] UserTimeout(Elapsed), #[error("Disconnected due to inactivity after {0}.")] ConfirmationTimeout(humantime::Duration), #[error(transparent)] Jwt(#[from] JwtError), } impl AuthError { pub(crate) fn bad_auth_method(name: impl Into>) -> Self { AuthError::BadAuthMethod(name.into()) } pub(crate) fn password_failed(user: impl Into>) -> Self { AuthError::PasswordFailed(user.into()) } pub(crate) fn ip_address_not_allowed(ip: IpAddr) -> Self { AuthError::IpAddressNotAllowed(ip) } pub(crate) fn vpc_endpoint_id_not_allowed(id: String) -> Self { AuthError::VpcEndpointIdNotAllowed(id) } pub(crate) fn too_many_connections() -> Self { AuthError::TooManyConnections } pub(crate) fn is_password_failed(&self) -> bool { matches!(self, AuthError::PasswordFailed(_)) } pub(crate) fn user_timeout(elapsed: Elapsed) -> Self { AuthError::UserTimeout(elapsed) } pub(crate) fn confirmation_timeout(timeout: humantime::Duration) -> Self { AuthError::ConfirmationTimeout(timeout) } } impl UserFacingError for AuthError { fn to_string_client(&self) -> String { match self { Self::ConsoleRedirect(e) => e.to_string_client(), Self::GetAuthInfo(e) => e.to_string_client(), Self::Sasl(e) => e.to_string_client(), Self::PasswordFailed(_) => self.to_string(), Self::BadAuthMethod(_) => self.to_string(), Self::MalformedPassword(_) => self.to_string(), Self::MissingEndpointName => self.to_string(), Self::MissingVPCEndpointId => self.to_string(), Self::Io(_) => "Internal error".to_string(), Self::IpAddressNotAllowed(_) => self.to_string(), Self::NetworkNotAllowed => self.to_string(), Self::VpcEndpointIdNotAllowed(_) => self.to_string(), Self::TooManyConnections => self.to_string(), Self::UserTimeout(_) => self.to_string(), Self::ConfirmationTimeout(_) => self.to_string(), Self::Jwt(_) => self.to_string(), } } } impl ReportableError for AuthError { fn get_error_kind(&self) -> crate::error::ErrorKind { match self { Self::ConsoleRedirect(e) => e.get_error_kind(), Self::GetAuthInfo(e) => e.get_error_kind(), Self::Sasl(e) => e.get_error_kind(), Self::PasswordFailed(_) => crate::error::ErrorKind::User, Self::BadAuthMethod(_) => crate::error::ErrorKind::User, Self::MalformedPassword(_) => crate::error::ErrorKind::User, Self::MissingEndpointName => crate::error::ErrorKind::User, Self::MissingVPCEndpointId => crate::error::ErrorKind::User, Self::Io(_) => crate::error::ErrorKind::ClientDisconnect, Self::IpAddressNotAllowed(_) => crate::error::ErrorKind::User, Self::NetworkNotAllowed => crate::error::ErrorKind::User, Self::VpcEndpointIdNotAllowed(_) => crate::error::ErrorKind::User, Self::TooManyConnections => crate::error::ErrorKind::RateLimit, Self::UserTimeout(_) => crate::error::ErrorKind::User, Self::ConfirmationTimeout(_) => crate::error::ErrorKind::User, Self::Jwt(_) => crate::error::ErrorKind::User, } } }