feat: add permission checker module
Checks: - User is logged in - Role is allowed for action - Verification status - Account status Includes unit tests.
This commit is contained in:
parent
eaec788b2f
commit
89e4809f0b
2 changed files with 182 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ mod error;
|
|||
mod forms;
|
||||
mod handlers;
|
||||
mod jobs;
|
||||
mod permissions;
|
||||
mod providers;
|
||||
mod retrieval;
|
||||
mod routes;
|
||||
|
|
|
|||
181
src/permissions/mod.rs
Normal file
181
src/permissions/mod.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::actions::ActionDefinition;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PermissionContext {
|
||||
pub user_id: Option<String>,
|
||||
pub role: Option<String>,
|
||||
pub is_verified: bool,
|
||||
pub account_status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PermissionResult {
|
||||
pub allowed: bool,
|
||||
pub reason_code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub struct PermissionChecker;
|
||||
|
||||
impl PermissionChecker {
|
||||
pub fn check(context: &PermissionContext, action: &ActionDefinition) -> PermissionResult {
|
||||
if !context.user_id.is_some() && action.requires_login {
|
||||
return PermissionResult {
|
||||
allowed: false,
|
||||
reason_code: "NOT_LOGGED_IN".to_string(),
|
||||
message: "You must be logged in to perform this action.".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(role) = &context.role {
|
||||
if !action.allowed_roles.contains(role) {
|
||||
return PermissionResult {
|
||||
allowed: false,
|
||||
reason_code: "ROLE_NOT_ALLOWED".to_string(),
|
||||
message: format!(
|
||||
"This action is not available for your role ({}).",
|
||||
role
|
||||
),
|
||||
};
|
||||
}
|
||||
} else if action.requires_login {
|
||||
return PermissionResult {
|
||||
allowed: false,
|
||||
reason_code: "NO_ROLE_SELECTED".to_string(),
|
||||
message: "Please select a role before performing this action.".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if action.requires_verification && !context.is_verified {
|
||||
return PermissionResult {
|
||||
allowed: false,
|
||||
reason_code: "VERIFICATION_REQUIRED".to_string(),
|
||||
message: "You need to complete verification before using this feature.".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if context.account_status != "ACTIVE" {
|
||||
return PermissionResult {
|
||||
allowed: false,
|
||||
reason_code: "ACCOUNT_NOT_ACTIVE".to_string(),
|
||||
message: format!(
|
||||
"Your account is {}. Please contact support.",
|
||||
context.account_status.to_lowercase()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
PermissionResult {
|
||||
allowed: true,
|
||||
reason_code: "ALLOWED".to_string(),
|
||||
message: "Permission granted.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_missing_fields(
|
||||
provided_fields: &[String],
|
||||
required_fields: &[String],
|
||||
) -> Vec<String> {
|
||||
required_fields
|
||||
.iter()
|
||||
.filter(|f| !provided_fields.contains(f))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn validate_fields(
|
||||
fields: &serde_json::Value,
|
||||
required_fields: &[String],
|
||||
optional_fields: &[String],
|
||||
) -> (bool, Vec<String>) {
|
||||
let mut missing = Vec::new();
|
||||
let obj = fields.as_object().cloned().unwrap_or_default();
|
||||
|
||||
for field in required_fields {
|
||||
if !obj.contains_key(field) || obj.get(field).map(|v| v.is_null()).unwrap_or(false) {
|
||||
missing.push(field.clone());
|
||||
}
|
||||
}
|
||||
|
||||
(missing.is_empty(), missing)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn get_test_action() -> ActionDefinition {
|
||||
ActionDefinition {
|
||||
action_code: "test_action".to_string(),
|
||||
intent: "test_action".to_string(),
|
||||
allowed_roles: vec!["COMPANY".to_string(), "JOB_SEEKER".to_string()],
|
||||
requires_login: true,
|
||||
requires_verification: true,
|
||||
requires_confirmation: true,
|
||||
required_fields: vec!["field1".to_string(), "field2".to_string()],
|
||||
optional_fields: vec!["opt1".to_string()],
|
||||
feature_code: "test".to_string(),
|
||||
ai_action_cost: 1,
|
||||
uses_llm: true,
|
||||
backend_handler: "test.handler".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_allows_verified_company_user() {
|
||||
let context = PermissionContext {
|
||||
user_id: Some("user123".to_string()),
|
||||
role: Some("COMPANY".to_string()),
|
||||
is_verified: true,
|
||||
account_status: "ACTIVE".to_string(),
|
||||
};
|
||||
let action = get_test_action();
|
||||
let result = PermissionChecker::check(&context, &action);
|
||||
assert!(result.allowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocks_unverified_user() {
|
||||
let context = PermissionContext {
|
||||
user_id: Some("user123".to_string()),
|
||||
role: Some("COMPANY".to_string()),
|
||||
is_verified: false,
|
||||
account_status: "ACTIVE".to_string(),
|
||||
};
|
||||
let action = get_test_action();
|
||||
let result = PermissionChecker::check(&context, &action);
|
||||
assert!(!result.allowed);
|
||||
assert_eq!(result.reason_code, "VERIFICATION_REQUIRED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blocks_wrong_role() {
|
||||
let context = PermissionContext {
|
||||
user_id: Some("user123".to_string()),
|
||||
role: Some("ADMIN".to_string()),
|
||||
is_verified: true,
|
||||
account_status: "ACTIVE".to_string(),
|
||||
};
|
||||
let action = get_test_action();
|
||||
let result = PermissionChecker::check(&context, &action);
|
||||
assert!(!result.allowed);
|
||||
assert_eq!(result.reason_code, "ROLE_NOT_ALLOWED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requires_login() {
|
||||
let context = PermissionContext {
|
||||
user_id: None,
|
||||
role: None,
|
||||
is_verified: false,
|
||||
account_status: "ACTIVE".to_string(),
|
||||
};
|
||||
let action = get_test_action();
|
||||
let result = PermissionChecker::check(&context, &action);
|
||||
assert!(!result.allowed);
|
||||
assert_eq!(result.reason_code, "NOT_LOGGED_IN");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue