use std::borrow::ToOwned;
use std::boxed::Box;
use std::cell::RefCell;
use std::net::{IpAddr, Ipv4Addr};
use std::string::{String, ToString};
use crate::core::{Caller, SessionCookie};
use crate::types::{
AuthError, AuthRequest, AuthResponse, LoginRequest, NewUser, PasswordChange, Role, UserChange,
};
use crate::yew023::{AuthService, Error, Transport, TransportFuture};
use crate::{Auth, MemoryStore, generate_session_key};
const PASSWORD: &str = "password1";
struct Dispatch {
auth: Auth,
session: RefCell<Option<String>>,
}
impl Transport for Dispatch {
fn send(&self, request: AuthRequest) -> TransportFuture<'_> {
Box::pin(async move {
let session = self.session.borrow().clone();
let mut caller = Caller::new().address(IpAddr::V4(Ipv4Addr::LOCALHOST));
if let Some(session) = &session {
caller = caller.session(session);
}
let dispatched = match self.auth.dispatch(&caller, request).await {
Ok(dispatched) => dispatched,
Err(error) => {
return Err(match error.auth_error() {
Some(error) => Error::Auth(error.clone()),
None => Error::Network(error.to_string()),
});
}
};
match dispatched.cookie {
Some(SessionCookie::Set(session)) => *self.session.borrow_mut() = Some(session.id),
Some(SessionCookie::Clear) => *self.session.borrow_mut() = None,
None => {}
}
Ok(dispatched.response)
})
}
}
async fn setup() -> AuthService {
let auth = Auth::builder(MemoryStore::new(), generate_session_key())
.impersonation(true)
.build();
for (login, role) in [("root", Role::Admin), ("bob", Role::Regular)] {
let new = NewUser {
login: Some(login.to_owned()),
email: None,
role,
};
auth.test_user(&new, PASSWORD).await.unwrap();
}
AuthService::new(Dispatch {
auth,
session: RefCell::new(None),
})
}
fn login(login: &str) -> LoginRequest {
LoginRequest {
login: login.to_owned(),
password: PASSWORD.to_owned(),
}
}
#[tokio::test]
async fn signs_in_and_out() {
let service = setup().await;
assert!(service.me().await.unwrap_err().is_unauthorized());
assert!(service.config().await.unwrap().password);
let bob = service.login(login("bob")).await.unwrap();
assert_eq!(bob.login.as_deref(), Some("bob"));
assert_eq!(service.me().await.unwrap().id, bob.id);
service.logout().await.unwrap();
assert!(service.me().await.unwrap_err().is_unauthorized());
}
#[tokio::test]
async fn account_operations() {
let service = setup().await;
service.login(login("bob")).await.unwrap();
let change = UserChange {
login: Some(Some("robert".to_owned())),
..UserChange::default()
};
let user = service.update_account(change).await.unwrap();
assert_eq!(user.login.as_deref(), Some("robert"));
let change = PasswordChange {
old_password: Some("wrong-password".to_owned()),
new_password: "password2".to_owned(),
};
assert!(matches!(
service.change_password(change).await,
Err(Error::Auth(
AuthError::Unauthorized | AuthError::Invalid(..)
))
));
assert_eq!(
service.users().await.unwrap_err(),
Error::Auth(AuthError::Forbidden)
);
}
#[tokio::test]
async fn admin_operations_and_impersonation() {
let service = setup().await;
let root = service.login(login("root")).await.unwrap();
let new = NewUser {
login: Some("carol".to_owned()),
email: None,
role: Role::Regular,
};
let created = service.create_user(new).await.unwrap();
let carol = created.entry.user.id;
assert_eq!(service.users().await.unwrap().len(), 3);
let link = service.issue_link(carol).await.unwrap();
assert_ne!(link.token, created.link.token);
service.revoke_link(carol).await.unwrap();
let bob = service
.users()
.await
.unwrap()
.into_iter()
.find(|entry| entry.user.login.as_deref() == Some("bob"))
.unwrap()
.user;
let change = UserChange {
role: Some(Role::Admin),
..UserChange::default()
};
assert_eq!(
service.update_user(bob.id, change).await.unwrap().role,
Role::Admin
);
service.sign_out_user(bob.id).await.unwrap();
let acting = service.impersonate(bob.id).await.unwrap();
assert_eq!(acting.id, bob.id);
let me = service.me().await.unwrap();
assert_eq!(me.id, bob.id);
assert!(me.impersonated_by.is_some());
assert_eq!(service.stop_impersonating().await.unwrap().id, root.id);
assert_eq!(service.me().await.unwrap().id, root.id);
service.delete_user(carol).await.unwrap();
assert_eq!(service.users().await.unwrap().len(), 2);
}
#[tokio::test]
async fn unexpected_response() {
struct Wrong;
impl Transport for Wrong {
fn send(&self, _: AuthRequest) -> TransportFuture<'_> {
Box::pin(async { Ok(AuthResponse::Done) })
}
}
let service = AuthService::new(Wrong);
assert_eq!(service.me().await.unwrap_err(), Error::UnexpectedResponse);
service.logout().await.unwrap();
}