capsets
This commit is contained in:
@@ -9,11 +9,9 @@ use rand::RngCore;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::capability_sets::{
|
||||
ensure_capability_set, load_capabilities_for_set, normalize_capabilities,
|
||||
},
|
||||
auth::capability_sets::{load_capabilities_for_set, load_capability_set},
|
||||
error::AppError,
|
||||
models::{ApiCapability, ApiToken, NewApiToken},
|
||||
models::{ApiCapability, ApiToken, CapabilitySet, NewApiToken},
|
||||
schema::api_tokens,
|
||||
state::PgPooledConnection,
|
||||
tenants::{apply_api_token_prefix, clear_api_token_prefix},
|
||||
@@ -37,10 +35,9 @@ pub fn create_api_token(
|
||||
tenant_id: Uuid,
|
||||
label: Option<String>,
|
||||
expires_at: Option<NaiveDateTime>,
|
||||
capabilities: Vec<ApiCapability>,
|
||||
capability_set_id: Uuid,
|
||||
) -> Result<IssuedApiToken, AppError> {
|
||||
let capabilities = normalize_capabilities(capabilities)?;
|
||||
let capability_set = ensure_capability_set(conn, tenant_id, &capabilities)?;
|
||||
let capability_set = validate_capability_set_belongs_to_tenant(conn, capability_set_id, tenant_id)?;
|
||||
|
||||
let raw_secret = generate_secret()?;
|
||||
let token_prefix = raw_secret[..TOKEN_PREFIX_LENGTH].to_string();
|
||||
@@ -120,40 +117,13 @@ pub fn regenerate_api_token(
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the set of capabilities associated with an API token.
|
||||
pub fn update_api_token_capabilities(
|
||||
conn: &mut PgPooledConnection,
|
||||
token_id: Uuid,
|
||||
user_id: Uuid,
|
||||
tenant_id: Option<Uuid>,
|
||||
capabilities: Vec<ApiCapability>,
|
||||
) -> Result<ApiToken, AppError> {
|
||||
let capabilities = normalize_capabilities(capabilities)?;
|
||||
|
||||
let token = find_user_token(conn, token_id, user_id, tenant_id)?;
|
||||
|
||||
if token.revoked_at.is_some() {
|
||||
return Err(AppError::bad_request(
|
||||
"cannot modify capabilities of a revoked API token",
|
||||
));
|
||||
}
|
||||
|
||||
let capability_set = ensure_capability_set(conn, token.tenant_id, &capabilities)?;
|
||||
|
||||
let updated = diesel::update(api_tokens::table.find(token.id))
|
||||
.set(api_tokens::capability_set_id.eq(capability_set.id))
|
||||
.get_result::<ApiToken>(conn)?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
/// Attempts to resolve an API token by its secret value while ensuring it provides the
|
||||
/// requested capability.
|
||||
pub fn find_active_token_by_secret(
|
||||
conn: &mut PgPooledConnection,
|
||||
tenant_id: Option<Uuid>,
|
||||
secret: &str,
|
||||
required_capability: ApiCapability,
|
||||
required_capability: Option<ApiCapability>,
|
||||
) -> Result<Option<ApiToken>, AppError> {
|
||||
if secret.len() < TOKEN_PREFIX_LENGTH {
|
||||
return Ok(None);
|
||||
@@ -181,9 +151,11 @@ pub fn find_active_token_by_secret(
|
||||
})?;
|
||||
|
||||
for token in candidates {
|
||||
let capabilities = load_capabilities_for_set(conn, token.capability_set_id)?;
|
||||
if !capabilities.contains(&required_capability) {
|
||||
continue;
|
||||
if let Some(required) = required_capability {
|
||||
let capabilities = load_capabilities_for_set(conn, token.capability_set_id)?;
|
||||
if !capabilities.contains(&required) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if verify_token_secret(secret, &token.token_hash)? {
|
||||
@@ -247,6 +219,21 @@ fn find_user_token(
|
||||
.ok_or_else(AppError::not_found)
|
||||
}
|
||||
|
||||
fn validate_capability_set_belongs_to_tenant(
|
||||
conn: &mut PgPooledConnection,
|
||||
capability_set_id: Uuid,
|
||||
tenant_id: Uuid,
|
||||
) -> Result<CapabilitySet, AppError> {
|
||||
let capability_set = load_capability_set(conn, capability_set_id)?;
|
||||
if capability_set.tenant_id != tenant_id {
|
||||
return Err(AppError::bad_request(
|
||||
"capability set does not belong to the tenant",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(capability_set)
|
||||
}
|
||||
|
||||
fn with_api_token_prefix<T, F>(
|
||||
conn: &mut PgPooledConnection,
|
||||
prefix: &str,
|
||||
|
||||
Reference in New Issue
Block a user