305 lines
8.9 KiB
Rust
305 lines
8.9 KiB
Rust
use anyhow::{Context, Result};
|
|
use common::proto::komp_ac::auth::{
|
|
AddRoleRequest, AssignUserRoleRequest, AuthResponse, AuthorizationSnapshot,
|
|
ChangePasswordRequest, GetAuthorizationRequest, GrantPermissionRequest,
|
|
GrantableObject, ListGrantableObjectsRequest, ListRolePermissionsRequest, ListRolesRequest,
|
|
ListUsersRequest, LoginRequest, LoginResponse, LogoutRequest, PasswordOperationResponse,
|
|
RegisterRequest, RemoveRoleRequest, ResetUserPasswordRequest, RevokePermissionRequest,
|
|
RevokeUserSessionsRequest, Role, RolePermissions, UserSummary,
|
|
auth_service_client::AuthServiceClient,
|
|
};
|
|
use tonic::transport::Channel;
|
|
use tonic::Request;
|
|
|
|
use crate::transport::{DEFAULT_GRPC_ENDPOINT, authenticated_request, connect_channel};
|
|
|
|
pub fn validate_password_change(password: &str, confirmation: &str) -> Result<()> {
|
|
if password.is_empty() {
|
|
anyhow::bail!("Password is required");
|
|
}
|
|
if password != confirmation {
|
|
anyhow::bail!("Passwords do not match");
|
|
}
|
|
if password.len() < 8 {
|
|
anyhow::bail!("Password must be at least 8 characters");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn validate_optional_password(password: Option<&str>, confirmation: Option<&str>) -> Result<()> {
|
|
let password = password.unwrap_or_default();
|
|
let confirmation = confirmation.unwrap_or_default();
|
|
if password.is_empty() && confirmation.is_empty() {
|
|
return Ok(());
|
|
}
|
|
validate_password_change(password, confirmation)
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct AuthClient {
|
|
client: AuthServiceClient<Channel>,
|
|
}
|
|
|
|
impl AuthClient {
|
|
pub async fn new() -> Result<Self> {
|
|
let endpoint = std::env::var("GRPC_ENDPOINT")
|
|
.unwrap_or_else(|_| DEFAULT_GRPC_ENDPOINT.to_string());
|
|
Self::connect(&endpoint).await
|
|
}
|
|
|
|
pub async fn connect(endpoint: &str) -> Result<Self> {
|
|
Self::with_channel(connect_channel(endpoint).await?).await
|
|
}
|
|
|
|
pub async fn with_channel(channel: Channel) -> Result<Self> {
|
|
Ok(Self {
|
|
client: AuthServiceClient::new(channel),
|
|
})
|
|
}
|
|
|
|
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse> {
|
|
Ok(self
|
|
.client
|
|
.login(Request::new(LoginRequest {
|
|
identifier,
|
|
password,
|
|
}))
|
|
.await?
|
|
.into_inner())
|
|
}
|
|
|
|
pub async fn register(
|
|
&mut self,
|
|
username: String,
|
|
email: String,
|
|
password: Option<String>,
|
|
password_confirmation: Option<String>,
|
|
timezone: String,
|
|
phone_country: String,
|
|
) -> Result<AuthResponse> {
|
|
validate_optional_password(password.as_deref(), password_confirmation.as_deref())?;
|
|
Ok(self
|
|
.client
|
|
.register(Request::new(RegisterRequest {
|
|
username,
|
|
email,
|
|
password: password.unwrap_or_default(),
|
|
password_confirmation: password_confirmation.unwrap_or_default(),
|
|
timezone,
|
|
phone_country,
|
|
}))
|
|
.await?
|
|
.into_inner())
|
|
}
|
|
|
|
pub async fn change_password(
|
|
&mut self,
|
|
token: &str,
|
|
current_password: String,
|
|
new_password: String,
|
|
new_password_confirmation: String,
|
|
) -> Result<PasswordOperationResponse> {
|
|
validate_password_change(&new_password, &new_password_confirmation)?;
|
|
let request = authenticated_request(
|
|
Some(token),
|
|
ChangePasswordRequest {
|
|
current_password,
|
|
new_password,
|
|
new_password_confirmation,
|
|
},
|
|
)?;
|
|
Ok(self.client.change_password(request).await?.into_inner())
|
|
}
|
|
|
|
pub async fn logout(&mut self, token: &str) -> Result<()> {
|
|
self.client
|
|
.logout(authenticated_request(Some(token), LogoutRequest {})?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_authorization(&mut self, token: &str) -> Result<AuthorizationSnapshot> {
|
|
Ok(self
|
|
.client
|
|
.get_authorization(authenticated_request(Some(token), GetAuthorizationRequest {})?)
|
|
.await?
|
|
.into_inner())
|
|
}
|
|
|
|
pub async fn list_roles(&mut self, token: &str) -> Result<Vec<Role>> {
|
|
Ok(self
|
|
.client
|
|
.list_roles(authenticated_request(Some(token), ListRolesRequest {})?)
|
|
.await?
|
|
.into_inner()
|
|
.roles)
|
|
}
|
|
|
|
pub async fn add_role(&mut self, token: &str, name: String, parent: String) -> Result<()> {
|
|
self.client
|
|
.add_role(authenticated_request(
|
|
Some(token),
|
|
AddRoleRequest { name, parent },
|
|
)?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn remove_role(&mut self, token: &str, name: String) -> Result<()> {
|
|
self.client
|
|
.remove_role(authenticated_request(Some(token), RemoveRoleRequest { name })?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn grant_permission(
|
|
&mut self,
|
|
token: &str,
|
|
role: String,
|
|
object: String,
|
|
action: String,
|
|
) -> Result<()> {
|
|
self.client
|
|
.grant_permission(authenticated_request(
|
|
Some(token),
|
|
GrantPermissionRequest {
|
|
role,
|
|
object,
|
|
action,
|
|
},
|
|
)?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn revoke_permission(
|
|
&mut self,
|
|
token: &str,
|
|
role: String,
|
|
object: String,
|
|
action: String,
|
|
) -> Result<()> {
|
|
self.client
|
|
.revoke_permission(authenticated_request(
|
|
Some(token),
|
|
RevokePermissionRequest {
|
|
role,
|
|
object,
|
|
action,
|
|
},
|
|
)?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn try_revoke_permission(
|
|
&mut self,
|
|
token: &str,
|
|
role: String,
|
|
object: String,
|
|
action: String,
|
|
) -> Result<bool> {
|
|
let request = authenticated_request(
|
|
Some(token),
|
|
RevokePermissionRequest {
|
|
role,
|
|
object,
|
|
action,
|
|
},
|
|
)?;
|
|
match self.client.revoke_permission(request).await {
|
|
Ok(_) => Ok(true),
|
|
Err(status) if status.code() == tonic::Code::NotFound => Ok(false),
|
|
Err(status) => Err(status.into()),
|
|
}
|
|
}
|
|
|
|
pub async fn list_role_permissions(
|
|
&mut self,
|
|
token: &str,
|
|
role: String,
|
|
) -> Result<RolePermissions> {
|
|
Ok(self
|
|
.client
|
|
.list_role_permissions(authenticated_request(
|
|
Some(token),
|
|
ListRolePermissionsRequest { role },
|
|
)?)
|
|
.await?
|
|
.into_inner())
|
|
}
|
|
|
|
pub async fn list_grantable_objects(
|
|
&mut self,
|
|
token: &str,
|
|
target_role: String,
|
|
) -> Result<Vec<GrantableObject>> {
|
|
Ok(self
|
|
.client
|
|
.list_grantable_objects(authenticated_request(
|
|
Some(token),
|
|
ListGrantableObjectsRequest { target_role },
|
|
)?)
|
|
.await?
|
|
.into_inner()
|
|
.objects)
|
|
}
|
|
|
|
pub async fn assign_user_role(
|
|
&mut self,
|
|
token: &str,
|
|
username: String,
|
|
role: String,
|
|
) -> Result<()> {
|
|
self.client
|
|
.assign_user_role(authenticated_request(
|
|
Some(token),
|
|
AssignUserRoleRequest { username, role },
|
|
)?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn list_users(&mut self, token: &str) -> Result<Vec<UserSummary>> {
|
|
Ok(self
|
|
.client
|
|
.list_users(authenticated_request(Some(token), ListUsersRequest {})?)
|
|
.await?
|
|
.into_inner()
|
|
.users)
|
|
}
|
|
|
|
pub async fn revoke_user_sessions(&mut self, token: &str, username: String) -> Result<()> {
|
|
self.client
|
|
.revoke_user_sessions(authenticated_request(
|
|
Some(token),
|
|
RevokeUserSessionsRequest { username },
|
|
)?)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn reset_user_password(
|
|
&mut self,
|
|
token: &str,
|
|
username: String,
|
|
new_password: String,
|
|
new_password_confirmation: String,
|
|
) -> Result<PasswordOperationResponse> {
|
|
validate_password_change(&new_password, &new_password_confirmation)?;
|
|
Ok(self
|
|
.client
|
|
.reset_user_password(authenticated_request(
|
|
Some(token),
|
|
ResetUserPasswordRequest {
|
|
username,
|
|
new_password,
|
|
new_password_confirmation,
|
|
},
|
|
)?)
|
|
.await
|
|
.context("Failed to reset user password")?
|
|
.into_inner())
|
|
}
|
|
}
|