Prompt file imported from ShalomDeitch1/AgentGroup (
.github/prompts/roleManagementFix.prompt.md). Fill in{{userType}},{{error_message}},{{userIdToDelete}}before use. Copyright stays with the author.
There is a problem of how the app is managing AWS IAM permissions, so I can not deploy agents and maybe not even run them.
I got from Amazon Q some instructions of how to manage roles in my situation, a local app that has 2 different roles, based on authorization (isAdmin).
I need to have admin roles for both the CDK that sets up Multi-Agent Collaboration (if the user decides to do that) and if the user decides to set them up on app start and if an admin user wants to deploy or redeploy (with changes) either a "default" agent or a new one, and even remove a non-default agent. Deployment includes "prepare" the agent and giving an alias and becoming part of the Multi-Agent Collaberation (where the supervisor has a special job). Some agents can also get access to lambdas etc.
The current code (including setting up CDK) defines the permissions needed and what endpoints need what roles.
Amazon Q gave me example code (below the line) that shows the CONCEPTs, but not my particular use-case. I want you to implement changes so that I can run my app. I expect that there will be changes in the CDK code + assuming roles in my backend (i hope not front end)
I want the README.md to be updated so I know how set up as a new user + current instructions of what to do now, to be able to run MOST IMPORTANT fix the code so it does what it does now but does not get problems of permissions when running.
SOME OF WHAT IT WRITES IS NOT RELEVENAT, SO ONLY TAKE THE CONCEPTS AND HOW TO DO WHAT IS NEEDED IN MY SITUATION e.g. I am not using S3
remember to start with the existing code in the project and just change how we are accessing AWS with the correct permissions
Summary: Stateless Cached Credentials Pattern You're using two cached credential sets (admin + normal) that are stateless (no user session storage). Each API endpoint gets the appropriate permissions based on user role.
- CDK Infrastructure Code // lib/my-app-stack.ts import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; import { Construct } from 'constructs';
export class MyAppStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// Get current AWS account ID for trust policies
const account = cdk.Stack.of(this).account;
// ========================================
// NORMAL USER ROLE - Limited Permissions
// ========================================
const normalUserRole = new iam.Role(this, 'NormalUserRole', {
roleName: 'MyApp-NormalUser',
// Trust policy: Allow entire account + Lambda service to assume this role
assumedBy: new iam.CompositePrincipal(
new iam.AccountPrincipal(account), // Any user in this account can assume
new iam.ServicePrincipal('lambda.amazonaws.com') // For production Lambda deployment
),
description: 'Limited permissions for normal app users'
});
// Normal user permissions - READ ONLY operations
normalUserRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject', // Read files from S3
's3:ListBucket', // List bucket contents
'dynamodb:GetItem', // Read single items from DynamoDB
'dynamodb:Query', // Query DynamoDB tables
'dynamodb:Scan' // Scan DynamoDB tables (limited)
],
resources: [
'arn:aws:s3:::my-app-bucket/*', // S3 objects
'arn:aws:s3:::my-app-bucket', // S3 bucket itself
'arn:aws:dynamodb:*:*:table/my-app-users', // Users table
'arn:aws:dynamodb:*:*:table/my-app-data' // Data table
]
}));
// ========================================
// ADMIN USER ROLE - Full Permissions
// ========================================
const adminUserRole = new iam.Role(this, 'AdminUserRole', {
roleName: 'MyApp-Admin',
// Same trust policy as normal user
assumedBy: new iam.CompositePrincipal(
new iam.AccountPrincipal(account),
new iam.ServicePrincipal('lambda.amazonaws.com')
),
description: 'Full permissions for admin users'
});
// Admin permissions - FULL CRUD operations
adminUserRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:*', // All S3 operations
'dynamodb:*', // All DynamoDB operations
'lambda:InvokeFunction' // Can invoke Lambda functions
],
resources: [
'arn:aws:s3:::my-app-bucket/*',
'arn:aws:s3:::my-app-bucket',
'arn:aws:dynamodb:*:*:table/my-app-*', // All app tables
'arn:aws:lambda:*:*:function:my-app-*' // All app functions
]
}));
// ========================================
// OUTPUTS - For environment variables
// ========================================
// Export role ARNs so your app can use them
new cdk.CfnOutput(this, 'NormalUserRoleArn', {
value: normalUserRole.roleArn,
description: 'ARN for normal user role - put in NORMAL_USER_ROLE_ARN env var'
});
new cdk.CfnOutput(this, 'AdminUserRoleArn', {
value: adminUserRole.roleArn,
description: 'ARN for admin user role - put in ADMIN_USER_ROLE_ARN env var'
});
// ========================================
// OPTIONAL: Create S3 bucket and DynamoDB tables
// ========================================
// Uncomment if you want CDK to create these resources too
/*
const bucket = new s3.Bucket(this, 'MyAppBucket', {
bucketName: 'my-app-bucket',
removalPolicy: cdk.RemovalPolicy.DESTROY // CAREFUL: This deletes data on stack deletion
});
const usersTable = new dynamodb.Table(this, 'UsersTable', {
tableName: 'my-app-users',
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
removalPolicy: cdk.RemovalPolicy.DESTROY
});
*/
} }
- Server-Side Credential Manager // lib/aws-credential-manager.js import AWS from 'aws-sdk';
class AWSCredentialManager { constructor() { // Role ARNs from CDK outputs (set in environment variables) this.normalRoleArn = process.env.NORMAL_USER_ROLE_ARN; this.adminRoleArn = process.env.ADMIN_USER_ROLE_ARN;
// In-memory credential cache (stateless - no user-specific storage)
this.credentialCache = {
normal: null, // Cached normal user credentials
admin: null // Cached admin user credentials
};
// Create STS client for role assumption
this.stsClient = new AWS.STS();
}
// ======================================== // GET CREDENTIALS WITH CACHING // ======================================== async getCredentials(userType) { // Check if we have valid cached credentials for this user type const cached = this.credentialCache[userType];
if (cached && this.isCredentialValid(cached)) {
console.log(`Using cached {{userType}} credentials`);
return cached.credentials;
}
// Cache miss or expired - get fresh credentials
console.log(`Fetching fresh {{userType}} credentials`);
const freshCredentials = await this.assumeRole(userType);
// Cache the credentials with expiry time
this.credentialCache[userType] = {
credentials: freshCredentials,
expiresAt: Date.now() + (50 * 60 * 1000) // Cache for 50 minutes (credentials valid for 1 hour)
};
return freshCredentials;
}
// ======================================== // ASSUME ROLE FOR USER TYPE // ======================================== async assumeRole(userType) { // Determine which role to assume const roleArn = userType === 'admin' ? this.adminRoleArn : this.normalRoleArn;
if (!roleArn) {
throw new Error(`Role ARN not configured for user type: {{userType}}`);
}
try {
// Call STS to assume the role
const result = await this.stsClient.assumeRole({
RoleArn: roleArn,
RoleSessionName: `{{userType}}-session-${Date.now()}`, // Unique session name
DurationSeconds: 3600 // 1 hour session duration
}).promise();
// Return AWS SDK compatible credentials object
return new AWS.Credentials({
accessKeyId: result.Credentials.AccessKeyId,
secretAccessKey: result.Credentials.SecretAccessKey,
sessionToken: result.Credentials.SessionToken
});
} catch (error) {
console.error(`Failed to assume {{userType}} role:`, error);
throw new Error(`Failed to get {{userType}} credentials: {{error_message}}`);
}
}
// ======================================== // CHECK IF CACHED CREDENTIALS ARE STILL VALID // ======================================== isCredentialValid(cachedCredential) { // Check if credentials exist and haven't expired return cachedCredential && cachedCredential.expiresAt && cachedCredential.expiresAt > Date.now(); }
// ======================================== // EXECUTE AWS OPERATION WITH APPROPRIATE CREDENTIALS // ======================================== async executeWithCredentials(userType, awsOperation) { // Get appropriate credentials (cached or fresh) const credentials = await this.getCredentials(userType);
// Execute the AWS operation with these credentials
return await awsOperation(credentials);
} }
// Export singleton instance export const credentialManager = new AWSCredentialManager();
- API Endpoint Examples // pages/api/user-data.js - Normal user endpoint import { credentialManager } from '../../lib/aws-credential-manager'; import jwt from 'jsonwebtoken'; import AWS from 'aws-sdk';
export default async function handler(req, res) { try { // ======================================== // 1. AUTHENTICATE USER // ======================================== const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ error: 'No authorization token' }); }
const user = jwt.verify(token, process.env.JWT_SECRET);
// ========================================
// 2. GET USER DATA (Normal user permissions)
// ========================================
const userData = await credentialManager.executeWithCredentials('normal', async (credentials) => {
// Create DynamoDB client with normal user credentials
const dynamodb = new AWS.DynamoDB.DocumentClient({ credentials });
// Normal users can only read their own data
const result = await dynamodb.get({
TableName: 'my-app-users',
Key: { id: user.id }
}).promise();
return result.Item;
});
res.json({ userData });
} catch (error) { console.error('Error in user-data endpoint:', error); res.status(500).json({ error: 'Failed to fetch user data' }); } }
// pages/api/admin/delete-user.js - Admin only endpoint import { credentialManager } from '../../../lib/aws-credential-manager'; import jwt from 'jsonwebtoken'; import AWS from 'aws-sdk';
export default async function handler(req, res) { try { // ======================================== // 1. AUTHENTICATE USER // ======================================== const token = req.headers.authorization?.replace('Bearer ', ''); const user = jwt.verify(token, process.env.JWT_SECRET);
// ========================================
// 2. CHECK ADMIN PERMISSION
// ========================================
if (user.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
// ========================================
// 3. DELETE USER (Admin permissions required)
// ========================================
const { userIdToDelete } = req.body;
await credentialManager.executeWithCredentials('admin', async (credentials) => {
// Create AWS clients with admin credentials
const dynamodb = new AWS.DynamoDB.DocumentClient({ credentials });
const s3 = new AWS.S3({ credentials });
// Admin can delete any user's data
await Promise.all([
// Delete user record from database
dynamodb.delete({
TableName: 'my-app-users',
Key: { id: userIdToDelete }
}).promise(),
// Delete user files from S3
s3.deleteObject({
Bucket: 'my-app-bucket',
Key: `users/{{userIdToDelete}}/profile.json`
}).promise()
]);
});
res.json({ success: true, message: 'User deleted successfully' });
} catch (error) { console.error('Error in delete-user endpoint:', error); res.status(500).json({ error: 'Failed to delete user' }); } }
- Environment Variables (.env.local)
AWS Role ARNs (from CDK deployment output)
NORMAL_USER_ROLE_ARN=arn:aws:iam::YOUR-ACCOUNT:role/MyApp-NormalUser ADMIN_USER_ROLE_ARN=arn:aws:iam::YOUR-ACCOUNT:role/MyApp-Admin
Your app's authentication
JWT_SECRET=your-jwt-secret-key
AWS credentials for your server to assume roles
AWS_ACCESS_KEY_ID=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-key AWS_REGION=us-east-1
Run in CloudShell 5. Deployment Commands
Deploy CDK infrastructure
cdk deploy
Copy the role ARNs from CDK output to your .env.local file
Start your Next.js app
npm run dev
Run in CloudShell Key Benefits:
✅ Stateless: No user session storage ✅ Cached: Credentials reused for 50 minutes ✅ Secure: Each user type gets appropriate permissions ✅ Simple: Easy to understand and maintain ✅ Scalable: Works with serverless deployment