Skip to content

Instantly share code, notes, and snippets.

@RajChowdhury240
Created July 24, 2025 23:45
Show Gist options
  • Save RajChowdhury240/a6fab24d8013cb5477abc854792bfee7 to your computer and use it in GitHub Desktop.
Save RajChowdhury240/a6fab24d8013cb5477abc854792bfee7 to your computer and use it in GitHub Desktop.
import React, { useState, useEffect } from 'react';
import { ChevronDown, ChevronUp, Plus, Trash2, Check, Clock, AlertCircle, User, Shield, Globe } from 'lucide-react';
const IAMRoleApp = () => {
const [activeTab, setActiveTab] = useState('create');
const [formData, setFormData] = useState({
roleName: '',
description: '',
trustRelationship: {
type: 'service',
principals: [],
conditions: []
},
permissions: {
services: [],
actions: [],
resources: ['*']
},
tags: [],
permissionBoundary: '',
deploymentMethod: 'terraform',
crossAccount: {
enabled: false,
targetAccount: '',
externalId: ''
},
oktaIntegration: {
enabled: false,
groups: []
},
approval: {
urgency: 'normal',
justification: '',
businessNeed: '',
approvalSystem: 'servicenow'
}
});
const [expandedSections, setExpandedSections] = useState({
basic: true,
trust: false,
permissions: false,
crossAccount: false,
okta: false,
approval: false
});
const [requests, setRequests] = useState([
{
id: 'REQ-001',
roleName: 'DataAnalyst-CrossAccount',
status: 'pending_security',
submittedBy: '[email protected]',
submittedAt: '2025-01-20T10:30:00Z',
approvers: ['security-team', 'manager'],
ticketId: 'INC0012345',
ticketSystem: 'servicenow'
},
{
id: 'REQ-002',
roleName: 'LambdaExecutionRole',
status: 'approved',
submittedBy: '[email protected]',
submittedAt: '2025-01-19T14:15:00Z',
approvers: ['manager'],
ticketId: 'PROJ-456',
ticketSystem: 'jira'
}
]);
const awsServices = [
{ name: 'ec2', label: 'Amazon EC2' },
{ name: 'lambda', label: 'AWS Lambda' },
{ name: 's3', label: 'Amazon S3' },
{ name: 'rds', label: 'Amazon RDS' },
{ name: 'dynamodb', label: 'Amazon DynamoDB' },
{ name: 'iam', label: 'AWS IAM' },
{ name: 'cloudwatch', label: 'Amazon CloudWatch' }
];
const principalTypes = [
{ value: 'service', label: 'AWS Service' },
{ value: 'account', label: 'AWS Account' },
{ value: 'federated', label: 'Federated (Okta)' },
{ value: 'user', label: 'IAM User' }
];
const oktaGroups = [
'DevOps-Team',
'Data-Engineers',
'Security-Analysts',
'Platform-Engineering',
'ML-Engineers'
];
const toggleSection = (section) => {
setExpandedSections(prev => ({
...prev,
[section]: !prev[section]
}));
};
const addPrincipal = () => {
setFormData(prev => ({
...prev,
trustRelationship: {
...prev.trustRelationship,
principals: [...prev.trustRelationship.principals, { type: 'service', value: '' }]
}
}));
};
const removePrincipal = (index) => {
setFormData(prev => ({
...prev,
trustRelationship: {
...prev.trustRelationship,
principals: prev.trustRelationship.principals.filter((_, i) => i !== index)
}
}));
};
const addTag = () => {
setFormData(prev => ({
...prev,
tags: [...prev.tags, { key: '', value: '' }]
}));
};
const removeTag = (index) => {
setFormData(prev => ({
...prev,
tags: prev.tags.filter((_, i) => i !== index)
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitting IAM role request:', formData);
// Here you would send the data to your backend
alert('IAM Role request submitted for approval!');
};
const getStatusIcon = (status) => {
switch (status) {
case 'pending_security':
case 'pending_manager':
return <Clock className="w-4 h-4 text-yellow-500" />;
case 'approved':
return <Check className="w-4 h-4 text-green-500" />;
case 'rejected':
return <AlertCircle className="w-4 h-4 text-red-500" />;
default:
return <Clock className="w-4 h-4 text-gray-500" />;
}
};
const SectionHeader = ({ title, section, icon: Icon }) => (
<div
className="flex items-center justify-between p-4 bg-gray-50 border-b cursor-pointer hover:bg-gray-100"
onClick={() => toggleSection(section)}
>
<div className="flex items-center space-x-2">
<Icon className="w-5 h-5 text-blue-600" />
<h3 className="font-medium text-gray-900">{title}</h3>
</div>
{expandedSections[section] ?
<ChevronUp className="w-5 h-5 text-gray-400" /> :
<ChevronDown className="w-5 h-5 text-gray-400" />
}
</div>
);
return (
<div className="min-h-screen bg-gray-100">
<div className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<h1 className="text-xl font-semibold text-gray-900">IAM Role Self-Service Portal</h1>
<div className="flex space-x-4">
<button
onClick={() => setActiveTab('create')}
className={`px-4 py-2 rounded-md ${activeTab === 'create' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:text-gray-900'}`}
>
Create Role
</button>
<button
onClick={() => setActiveTab('requests')}
className={`px-4 py-2 rounded-md ${activeTab === 'requests' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:text-gray-900'}`}
>
My Requests
</button>
<button
onClick={() => setActiveTab('approvals')}
className={`px-4 py-2 rounded-md ${activeTab === 'approvals' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:text-gray-900'}`}
>
Pending Approvals
</button>
</div>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{activeTab === 'create' && (
<div className="bg-white rounded-lg shadow">
<div className="p-6 border-b">
<h2 className="text-lg font-medium text-gray-900">Create New IAM Role</h2>
<p className="mt-1 text-sm text-gray-600">Fill out the form below to request a new IAM role. All requests require approval.</p>
</div>
<div>
{/* Basic Information */}
<div className="border-b">
<SectionHeader title="Basic Information" section="basic" icon={Shield} />
{expandedSections.basic && (
<div className="p-6 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Role Name *</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.roleName}
onChange={(e) => setFormData(prev => ({ ...prev, roleName: e.target.value }))}
placeholder="e.g., DataAnalyst-ReadOnly"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Deployment Method</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.deploymentMethod}
onChange={(e) => setFormData(prev => ({ ...prev, deploymentMethod: e.target.value }))}
>
<option value="terraform">Terraform</option>
<option value="cloudformation">CloudFormation</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Description</label>
<textarea
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
placeholder="Describe the purpose of this role..."
/>
</div>
</div>
)}
</div>
{/* Cross-Account Configuration */}
<div className="border-b">
<SectionHeader title="Cross-Account Configuration" section="crossAccount" icon={Globe} />
{expandedSections.crossAccount && (
<div className="p-6 space-y-4">
<div className="flex items-center">
<input
type="checkbox"
id="crossAccount"
checked={formData.crossAccount.enabled}
onChange={(e) => setFormData(prev => ({
...prev,
crossAccount: { ...prev.crossAccount, enabled: e.target.checked }
}))}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="crossAccount" className="ml-2 text-sm text-gray-700">
Enable cross-account role access
</label>
</div>
{formData.crossAccount.enabled && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Target Account ID</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.crossAccount.targetAccount}
onChange={(e) => setFormData(prev => ({
...prev,
crossAccount: { ...prev.crossAccount, targetAccount: e.target.value }
}))}
placeholder="123456789012"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">External ID (Optional)</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.crossAccount.externalId}
onChange={(e) => setFormData(prev => ({
...prev,
crossAccount: { ...prev.crossAccount, externalId: e.target.value }
}))}
placeholder="unique-external-id"
/>
</div>
</div>
)}
</div>
)}
</div>
{/* Trust Relationship */}
<div className="border-b">
<SectionHeader title="Trust Relationship" section="trust" icon={User} />
{expandedSections.trust && (
<div className="p-6 space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium text-gray-700">Principals</h4>
<button
type="button"
onClick={addPrincipal}
className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200"
>
<Plus className="w-4 h-4 mr-1" />
Add Principal
</button>
</div>
{formData.trustRelationship.principals.map((principal, index) => (
<div key={index} className="grid grid-cols-1 md:grid-cols-3 gap-4 items-end">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Type</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={principal.type}
onChange={(e) => {
const newPrincipals = [...formData.trustRelationship.principals];
newPrincipals[index].type = e.target.value;
setFormData(prev => ({
...prev,
trustRelationship: { ...prev.trustRelationship, principals: newPrincipals }
}));
}}
>
{principalTypes.map(type => (
<option key={type.value} value={type.value}>{type.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Value</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={principal.value}
onChange={(e) => {
const newPrincipals = [...formData.trustRelationship.principals];
newPrincipals[index].value = e.target.value;
setFormData(prev => ({
...prev,
trustRelationship: { ...prev.trustRelationship, principals: newPrincipals }
}));
}}
placeholder={principal.type === 'service' ? 'ec2.amazonaws.com' : 'arn:aws:iam::account:root'}
/>
</div>
<div>
<button
type="button"
onClick={() => removePrincipal(index)}
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
))}
</div>
)}
</div>
{/* Okta Integration */}
<div className="border-b">
<SectionHeader title="Okta Integration" section="okta" icon={User} />
{expandedSections.okta && (
<div className="p-6 space-y-4">
<div className="flex items-center">
<input
type="checkbox"
id="oktaIntegration"
checked={formData.oktaIntegration.enabled}
onChange={(e) => setFormData(prev => ({
...prev,
oktaIntegration: { ...prev.oktaIntegration, enabled: e.target.checked }
}))}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="oktaIntegration" className="ml-2 text-sm text-gray-700">
Enable Okta group-based access
</label>
</div>
{formData.oktaIntegration.enabled && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Authorized Okta Groups</label>
<div className="space-y-2">
{oktaGroups.map(group => (
<label key={group} className="inline-flex items-center mr-4">
<input
type="checkbox"
checked={formData.oktaIntegration.groups.includes(group)}
onChange={(e) => {
const newGroups = e.target.checked
? [...formData.oktaIntegration.groups, group]
: formData.oktaIntegration.groups.filter(g => g !== group);
setFormData(prev => ({
...prev,
oktaIntegration: { ...prev.oktaIntegration, groups: newGroups }
}));
}}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">{group}</span>
</label>
))}
</div>
</div>
)}
</div>
)}
</div>
{/* Permissions */}
<div className="border-b">
<SectionHeader title="Permissions" section="permissions" icon={Shield} />
{expandedSections.permissions && (
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">AWS Services</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
{awsServices.map(service => (
<label key={service.name} className="inline-flex items-center">
<input
type="checkbox"
checked={formData.permissions.services.includes(service.name)}
onChange={(e) => {
const newServices = e.target.checked
? [...formData.permissions.services, service.name]
: formData.permissions.services.filter(s => s !== service.name);
setFormData(prev => ({
...prev,
permissions: { ...prev.permissions, services: newServices }
}));
}}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">{service.label}</span>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Permission Boundary (Optional)</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.permissionBoundary}
onChange={(e) => setFormData(prev => ({ ...prev, permissionBoundary: e.target.value }))}
placeholder="arn:aws:iam::account:policy/BoundaryPolicy"
/>
</div>
</div>
)}
</div>
{/* Approval & Justification */}
<div className="border-b">
<SectionHeader title="Approval & Justification" section="approval" icon={Check} />
{expandedSections.approval && (
<div className="p-6 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Approval System</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.approval.approvalSystem}
onChange={(e) => setFormData(prev => ({
...prev,
approval: { ...prev.approval, approvalSystem: e.target.value }
}))}
>
<option value="servicenow">ServiceNow</option>
<option value="jira">Jira</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Urgency</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={formData.approval.urgency}
onChange={(e) => setFormData(prev => ({
...prev,
approval: { ...prev.approval, urgency: e.target.value }
}))}
>
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="emergency">Emergency</option>
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Business Justification *</label>
<textarea
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
value={formData.approval.businessNeed}
onChange={(e) => setFormData(prev => ({
...prev,
approval: { ...prev.approval, businessNeed: e.target.value }
}))}
placeholder="Explain the business need for this role..."
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Technical Justification</label>
<textarea
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
value={formData.approval.justification}
onChange={(e) => setFormData(prev => ({
...prev,
approval: { ...prev.approval, justification: e.target.value }
}))}
placeholder="Provide technical details about how this role will be used..."
/>
</div>
</div>
)}
</div>
<div className="p-6">
<button
type="button"
onClick={handleSubmit}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Submit for Approval
</button>
</div>
</div>
</div>
)}
{activeTab === 'requests' && (
<div className="bg-white rounded-lg shadow">
<div className="p-6 border-b">
<h2 className="text-lg font-medium text-gray-900">My Role Requests</h2>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Request ID</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role Name</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ticket</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Submitted</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{requests.map((request) => (
<tr key={request.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{request.id}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{request.roleName}</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
{getStatusIcon(request.status)}
<span className="ml-2 text-sm text-gray-900 capitalize">{request.status.replace('_', ' ')}</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{request.ticketSystem.toUpperCase()}: {request.ticketId}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(request.submittedAt).toLocaleDateString()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{activeTab === 'approvals' && (
<div className="bg-white rounded-lg shadow">
<div className="p-6 border-b">
<h2 className="text-lg font-medium text-gray-900">Pending Approvals</h2>
<p className="mt-1 text-sm text-gray-600">IAM role requests awaiting your approval</p>
</div>
<div className="p-6">
<div className="text-center py-8">
<Clock className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">No pending approvals</h3>
<p className="mt-1 text-sm text-gray-500">All requests have been processed.</p>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default IAMRoleApp;
@RajChowdhury240
Copy link
Author

// API endpoints you'll need
POST /api/roles/request          // Submit new role request
GET  /api/roles/requests         // Get user's requests
POST /api/roles/{id}/approve     // Approve a request
GET  /api/roles/pending-approvals // Get pending approvals
POST /api/servicenow/ticket      // Create ServiceNow ticket
POST /api/jira/ticket           // Create Jira ticket

@RajChowdhury240
Copy link
Author

# Create a new React app
npx create-react-app iam-role-webapp
cd iam-role-webapp

# Install additional dependencies (Lucide React for icons)
npm install lucide-react

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment