Files
minio-webui/frontend/src/services/policyService.ts
T
paul 55983d0088 feat: Implement user and policy management UI
- Add comprehensive user management with CRUD operations
- Implement policy management with templates and custom JSON
- Create reusable components (ConfirmDialog, LoadingState, EmptyState)
- Add user creation with password validation and credential copying
- Implement policy creation with built-in templates
- Add policy attachment to users functionality
- Include search, filtering, and status management
- Add proper error handling and validation
- Implement Material-UI based responsive design

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-23 16:45:39 +02:00

229 lines
6.2 KiB
TypeScript

import axios from 'axios';
import { getAuthHeaders } from '../utils/auth';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080';
export interface Policy {
name: string;
policy: string;
type?: 'built-in' | 'custom';
createdAt?: string;
}
export interface CreatePolicyRequest {
name: string;
policy: string;
}
export interface AttachPolicyRequest {
policyName: string;
userName: string;
}
export interface PolicyListResponse {
policies: Policy[];
}
export interface PolicyTemplate {
name: string;
description: string;
generatePolicy: (bucketName: string) => object;
}
export const policyTemplates: Record<string, PolicyTemplate> = {
bucketFullAccess: {
name: 'Bucket Full Access',
description: 'Grants full access to a specific bucket',
generatePolicy: (bucketName: string) => ({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: ['s3:*'],
Resource: [
`arn:aws:s3:::${bucketName}`,
`arn:aws:s3:::${bucketName}/*`
],
},
],
}),
},
bucketReadOnly: {
name: 'Bucket Read Only',
description: 'Grants read-only access to a specific bucket',
generatePolicy: (bucketName: string) => ({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
's3:GetObject',
's3:ListBucket',
's3:GetBucketLocation',
],
Resource: [
`arn:aws:s3:::${bucketName}`,
`arn:aws:s3:::${bucketName}/*`
],
},
],
}),
},
bucketWriteOnly: {
name: 'Bucket Write Only',
description: 'Grants write-only access to a specific bucket',
generatePolicy: (bucketName: string) => ({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
's3:PutObject',
's3:DeleteObject',
's3:ListBucket',
],
Resource: [
`arn:aws:s3:::${bucketName}`,
`arn:aws:s3:::${bucketName}/*`
],
},
],
}),
},
};
class PolicyService {
private baseURL = `${API_BASE_URL}/api`;
async listPolicies(): Promise<Policy[]> {
try {
const response = await axios.get<PolicyListResponse>(`${this.baseURL}/policies`, {
headers: getAuthHeaders(),
});
return response.data.policies || [];
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to fetch policies');
}
throw error;
}
}
async createPolicy(policyData: CreatePolicyRequest): Promise<void> {
try {
await axios.post(`${this.baseURL}/policies`, policyData, {
headers: getAuthHeaders(),
});
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to create policy');
}
throw error;
}
}
async deletePolicy(policyName: string): Promise<void> {
try {
await axios.delete(`${this.baseURL}/policies/${policyName}`, {
headers: getAuthHeaders(),
});
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to delete policy');
}
throw error;
}
}
async getPolicy(policyName: string): Promise<Policy> {
try {
const response = await axios.get<Policy>(`${this.baseURL}/policies/${policyName}`, {
headers: getAuthHeaders(),
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to fetch policy');
}
throw error;
}
}
async attachPolicy(data: AttachPolicyRequest): Promise<void> {
try {
await axios.post(`${this.baseURL}/policies/attach`, data, {
headers: getAuthHeaders(),
});
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to attach policy');
}
throw error;
}
}
async detachPolicy(policyName: string, userName: string): Promise<void> {
try {
await axios.post(`${this.baseURL}/policies/detach`,
{ policyName, userName },
{
headers: getAuthHeaders(),
}
);
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || 'Failed to detach policy');
}
throw error;
}
}
// Policy validation helper
validatePolicyJSON(policyString: string): { valid: boolean; error?: string } {
try {
const policy = JSON.parse(policyString);
// Basic validation
if (!policy.Version) {
return { valid: false, error: 'Policy must have a Version field' };
}
if (!policy.Statement || !Array.isArray(policy.Statement)) {
return { valid: false, error: 'Policy must have a Statement array' };
}
if (policy.Statement.length === 0) {
return { valid: false, error: 'Policy must have at least one statement' };
}
// Validate each statement
for (let i = 0; i < policy.Statement.length; i++) {
const stmt = policy.Statement[i];
if (!stmt.Effect || !['Allow', 'Deny'].includes(stmt.Effect)) {
return { valid: false, error: `Statement ${i + 1}: Effect must be 'Allow' or 'Deny'` };
}
if (!stmt.Action) {
return { valid: false, error: `Statement ${i + 1}: Action is required` };
}
if (!stmt.Resource) {
return { valid: false, error: `Statement ${i + 1}: Resource is required` };
}
}
return { valid: true };
} catch (e) {
return { valid: false, error: 'Invalid JSON format' };
}
}
// Generate policy from template
generatePolicyFromTemplate(templateKey: string, bucketName: string): string {
const template = policyTemplates[templateKey];
if (!template) {
throw new Error('Invalid template');
}
const policy = template.generatePolicy(bucketName);
return JSON.stringify(policy, null, 2);
}
}
const policyService = new PolicyService();
export default policyService;