use std::fmt;
use crate::api;
#[derive(Clone, Copy, Serialize)]
pub enum Credentials<S> {
BasicAuth {
username: S,
password: S,
},
#[doc(hidden)]
#[serde(skip)]
_NonExhaustive,
}
impl<S> Credentials<S> {
pub const fn basic_auth(username: S, password: S) -> Self {
Credentials::BasicAuth { username, password }
}
}
impl<S: fmt::Debug> fmt::Debug for Credentials<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Credentials::BasicAuth { .. } => {
f.debug_struct("BasicAuth")
.field("username", &"[pii]")
.field("password", &"[private]")
.finish()
},
Credentials::_NonExhaustive => unreachable!(),
}
}
}
pub fn request_login_token<S: fmt::Display>(
credentials: &Credentials<S>,
) -> Result<String, LoginError> {
let url = api::url()?;
request_login_token_at(&url, credentials)
}
pub fn request_login_token_at<S: fmt::Display>(
api_url: &url::Url,
credentials: &Credentials<S>,
) -> Result<String, LoginError> {
let url = api_url.join("/v1/login")?;
request_login_token_at_specific(url.as_str(), credentials)
}
pub fn request_login_token_at_specific<U, S>(
url: U,
credentials: &Credentials<S>,
) -> Result<String, LoginError>
where
U: reqwest::IntoUrl + fmt::Debug,
S: fmt::Display,
{
fn request_token(
builder: reqwest::RequestBuilder,
) -> Result<String, LoginError> {
let response = builder.send()?;
let status = response.status();
if !status.is_success() {
return Err(LoginError::from(status));
}
for cookie in response.cookies() {
if cookie.name() == "token" {
return Ok(cookie.value().to_owned());
}
}
Err(LoginError::MissingToken)
}
let mut builder = reqwest::Client::new().post(url);
match credentials {
Credentials::BasicAuth { username, password } => {
builder = builder.basic_auth(username, Some(password));
},
Credentials::_NonExhaustive => unreachable!(),
}
request_token(builder)
}
#[derive(Debug)]
pub enum LoginError {
ParseUrl(url::ParseError),
Request(reqwest::Error),
Status(http::StatusCode),
Unauthorized,
MissingToken,
}
impl From<url::ParseError> for LoginError {
fn from(error: url::ParseError) -> Self {
Self::ParseUrl(error)
}
}
impl From<reqwest::Error> for LoginError {
fn from(error: reqwest::Error) -> Self {
Self::Request(error)
}
}
impl From<http::StatusCode> for LoginError {
fn from(error: http::StatusCode) -> Self {
if error == http::StatusCode::UNAUTHORIZED {
Self::Unauthorized
} else {
Self::Status(error)
}
}
}
impl std::fmt::Display for LoginError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use self::LoginError::*;
match self {
ParseUrl(error) => error.fmt(f),
Request(error) => error.fmt(f),
Status(code) => write!(f, "received response \"{}\"", code),
Unauthorized => write!(f, "incorrect username or password"),
MissingToken => write!(f, "login token was not received"),
}
}
}
impl std::error::Error for LoginError {}