caps and delete
This commit is contained in:
@@ -9,8 +9,11 @@ use rand::RngCore;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::capability_sets::{
|
||||
ensure_capability_set, load_capabilities_for_set, normalize_capabilities,
|
||||
},
|
||||
error::AppError,
|
||||
models::{ApiToken, ApiTokenCapability, NewApiToken},
|
||||
models::{ApiCapability, ApiToken, NewApiToken},
|
||||
schema::api_tokens,
|
||||
state::PgPooledConnection,
|
||||
tenants::{apply_api_token_prefix, clear_api_token_prefix},
|
||||
@@ -34,9 +37,10 @@ pub fn create_api_token(
|
||||
tenant_id: Uuid,
|
||||
label: Option<String>,
|
||||
expires_at: Option<NaiveDateTime>,
|
||||
capabilities: Vec<ApiTokenCapability>,
|
||||
capabilities: Vec<ApiCapability>,
|
||||
) -> Result<IssuedApiToken, AppError> {
|
||||
let capabilities = normalize_capabilities(capabilities)?;
|
||||
let capability_set = ensure_capability_set(conn, tenant_id, &capabilities)?;
|
||||
|
||||
let raw_secret = generate_secret()?;
|
||||
let token_prefix = raw_secret[..TOKEN_PREFIX_LENGTH].to_string();
|
||||
@@ -49,7 +53,7 @@ pub fn create_api_token(
|
||||
token_hash,
|
||||
label,
|
||||
expires_at,
|
||||
capabilities,
|
||||
capability_set_id: capability_set.id,
|
||||
};
|
||||
|
||||
let record = diesel::insert_into(api_tokens::table)
|
||||
@@ -122,7 +126,7 @@ pub fn update_api_token_capabilities(
|
||||
token_id: Uuid,
|
||||
user_id: Uuid,
|
||||
tenant_id: Option<Uuid>,
|
||||
capabilities: Vec<ApiTokenCapability>,
|
||||
capabilities: Vec<ApiCapability>,
|
||||
) -> Result<ApiToken, AppError> {
|
||||
let capabilities = normalize_capabilities(capabilities)?;
|
||||
|
||||
@@ -134,8 +138,10 @@ pub fn update_api_token_capabilities(
|
||||
));
|
||||
}
|
||||
|
||||
let capability_set = ensure_capability_set(conn, token.tenant_id, &capabilities)?;
|
||||
|
||||
let updated = diesel::update(api_tokens::table.find(token.id))
|
||||
.set(api_tokens::capabilities.eq(capabilities))
|
||||
.set(api_tokens::capability_set_id.eq(capability_set.id))
|
||||
.get_result::<ApiToken>(conn)?;
|
||||
|
||||
Ok(updated)
|
||||
@@ -147,7 +153,7 @@ pub fn find_active_token_by_secret(
|
||||
conn: &mut PgPooledConnection,
|
||||
tenant_id: Option<Uuid>,
|
||||
secret: &str,
|
||||
required_capability: ApiTokenCapability,
|
||||
required_capability: ApiCapability,
|
||||
) -> Result<Option<ApiToken>, AppError> {
|
||||
if secret.len() < TOKEN_PREFIX_LENGTH {
|
||||
return Ok(None);
|
||||
@@ -175,7 +181,8 @@ pub fn find_active_token_by_secret(
|
||||
})?;
|
||||
|
||||
for token in candidates {
|
||||
if !token.capabilities.contains(&required_capability) {
|
||||
let capabilities = load_capabilities_for_set(conn, token.capability_set_id)?;
|
||||
if !capabilities.contains(&required_capability) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -218,23 +225,6 @@ pub fn verify_token_secret(secret: &str, token_hash: &str) -> Result<bool, AppEr
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_capabilities(
|
||||
capabilities: Vec<ApiTokenCapability>,
|
||||
) -> Result<Vec<ApiTokenCapability>, AppError> {
|
||||
if capabilities.is_empty() {
|
||||
return Err(AppError::bad_request("at least one capability is required"));
|
||||
}
|
||||
|
||||
let mut unique = Vec::new();
|
||||
for capability in capabilities {
|
||||
if !unique.contains(&capability) {
|
||||
unique.push(capability);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(unique)
|
||||
}
|
||||
|
||||
fn find_user_token(
|
||||
conn: &mut PgPooledConnection,
|
||||
token_id: Uuid,
|
||||
@@ -299,6 +289,9 @@ fn hash_secret(secret: &str) -> Result<String, AppError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::auth::capability_sets::{
|
||||
compute_slug, normalize_capabilities, owner_capabilities, webdav_capabilities,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn generated_secret_has_expected_length() {
|
||||
@@ -316,15 +309,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn normalize_capabilities_deduplicates() {
|
||||
let caps = normalize_capabilities(vec![
|
||||
ApiTokenCapability::Api,
|
||||
ApiTokenCapability::Webdav,
|
||||
ApiTokenCapability::Api,
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(caps.len(), 2);
|
||||
assert!(caps.contains(&ApiTokenCapability::Api));
|
||||
assert!(caps.contains(&ApiTokenCapability::Webdav));
|
||||
let mut caps = owner_capabilities().to_vec();
|
||||
caps.push(ApiCapability::DocumentsRead);
|
||||
let normalized = normalize_capabilities(caps).unwrap();
|
||||
assert_eq!(normalized.len(), owner_capabilities().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -332,6 +320,15 @@ mod tests {
|
||||
assert!(normalize_capabilities(Vec::new()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_slug_matches_system_sets() {
|
||||
let owner_slug = compute_slug(owner_capabilities());
|
||||
assert_eq!(owner_slug, "owner");
|
||||
|
||||
let webdav_slug = compute_slug(webdav_capabilities());
|
||||
assert_eq!(webdav_slug, "webdav");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_length_is_less_than_secret_length() {
|
||||
assert!(TOKEN_PREFIX_LENGTH < TOKEN_SECRET_LENGTH * 2);
|
||||
|
||||
Reference in New Issue
Block a user