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 = { 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 { try { const response = await axios.get(`${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 { 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 { 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 { try { const response = await axios.get(`${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 { 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 { 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;