Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion createFeatureFlag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

// Use enhanced middleware with user verification (Week 2 migration)
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
Expand All @@ -80,6 +80,13 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return utils.ClientError(http.StatusUnauthorized, "User context not available")
}

// Check permission: CREATE_FEATURE_FLAG (Week 3 RBAC)
permResponse, err := utils.RequirePermission(userContext, utils.PermissionCreateFeatureFlag)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(req.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)

err = json.Unmarshal([]byte(req.Body), &createFeatureFlagRequest)
Expand Down
23 changes: 22 additions & 1 deletion createUserFeatureFlag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,34 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

jwtResponse, _, err := jwt.JWTMiddleware()(req)
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

if userContext == nil {
return utils.ClientError(http.StatusUnauthorized, "User context not available")
}

// Check permission: CREATE_USER_MAPPING (Week 3 RBAC)
permResponse, err := utils.RequirePermission(userContext, utils.PermissionCreateUserMapping)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(req.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)

// Check if user can access this resource (own resources or ADMIN)
if !utils.CanAccessUserResource(userContext, userId) {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "You can only manage your own feature flag mappings",
Headers: corsHeaders,
}, nil
}

var requestBody utils.CreateFeatureFlagUserMappingRequest
err = json.Unmarshal([]byte(req.Body), &requestBody)
if err != nil {
Expand Down
14 changes: 11 additions & 3 deletions getAllFeatureFlags/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,17 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
return corsResponse, err
}

response, _, err := jwt.JWTMiddleware()(request)
if err != nil || response.StatusCode != http.StatusOK {
return response, err
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(request)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

// Check permission: READ_FEATURE_FLAG
permResponse, err := utils.RequirePermission(userContext, utils.PermissionReadFeatureFlag)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(request.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(request.Headers)
Expand Down
16 changes: 12 additions & 4 deletions getFeatureFlagById/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

response, _, err := jwt.JWTMiddleware()(req)
if err != nil || response.StatusCode != http.StatusOK {
return response, err
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

// Check permission: READ_FEATURE_FLAG
permResponse, err := utils.RequirePermission(userContext, utils.PermissionReadFeatureFlag)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(req.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)
Expand Down Expand Up @@ -56,7 +64,7 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return serverErrorResponse, nil
}

response = events.APIGatewayProxyResponse{
response := events.APIGatewayProxyResponse{
StatusCode: http.StatusOK,
Headers: corsHeaders,
Body: string(jsonResponse),
Expand Down
30 changes: 24 additions & 6 deletions getUserById/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

response, _, err := jwt.JWTMiddleware()(req)
if err != nil || response.StatusCode != http.StatusOK {
return response, err
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

if userContext == nil {
return utils.ClientError(http.StatusUnauthorized, "User context not available")
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)
Expand All @@ -70,8 +75,21 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return clientErrorResponse, nil
}

// TODO: Add role-based access control in Week 3
// Users can only view their own profile unless they're ADMIN
// Check permission: READ_USER (Week 3 RBAC)
permResponse, err := utils.RequirePermission(userContext, utils.PermissionReadUser)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = corsHeaders
return permResponse, err
}

// Check if user can access this resource (own resources or ADMIN)
if !utils.CanAccessUserResource(userContext, userId) {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "You can only view your own profile",
Headers: corsHeaders,
}, nil
}

user, err := getUserById(ctx, db, userId)
if err != nil {
Expand Down Expand Up @@ -104,7 +122,7 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return serverErrorResponse, nil
}

response = events.APIGatewayProxyResponse{
response := events.APIGatewayProxyResponse{
StatusCode: http.StatusOK,
Headers: corsHeaders,
Body: string(jsonResponse),
Expand Down
23 changes: 20 additions & 3 deletions getUserFeatureFlag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,31 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

response, _, err := jwt.JWTMiddleware()(req)
if err != nil || response.StatusCode != http.StatusOK {
return response, err
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

// Check permission: READ_USER_MAPPING (Week 3 RBAC)
permResponse, err := utils.RequirePermission(userContext, utils.PermissionReadUserMapping)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(req.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)

userId := req.PathParameters["userId"]

// Check if user can access this resource (own resources or ADMIN)
if !utils.CanAccessUserResource(userContext, userId) {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "You can only access your own feature flag mappings",
Headers: corsHeaders,
}, nil
}

flagId := req.PathParameters["flagId"]

Expand Down
23 changes: 20 additions & 3 deletions getUserFeatureFlags/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,31 @@ func handler(ctx context.Context, req events.APIGatewayProxyRequest) (events.API
return corsResponse, err
}

response, _, err := jwt.JWTMiddleware()(req)
if err != nil || response.StatusCode != http.StatusOK {
return response, err
// Use enhanced middleware with user verification and RBAC (Week 3)
jwtResponse, userContext, err := jwt.JWTMiddlewareWithUserVerification()(req)
if err != nil || jwtResponse.StatusCode != http.StatusOK {
return jwtResponse, err
}

// Check permission: READ_USER_MAPPING (Week 3 RBAC)
permResponse, err := utils.RequirePermission(userContext, utils.PermissionReadUserMapping)
if err != nil || permResponse.StatusCode != http.StatusOK {
permResponse.Headers = middleware.GetCORSHeadersV1(req.Headers)
return permResponse, err
}

corsHeaders := middleware.GetCORSHeadersV1(req.Headers)

userId := req.PathParameters["userId"]

// Check if user can access this resource (own resources or ADMIN)
if !utils.CanAccessUserResource(userContext, userId) {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "You can only access your own feature flag mappings",
Headers: corsHeaders,
}, nil
}
result, err := processGetById(ctx, userId)
if err != nil {
return utils.ServerError(err)
Expand Down
148 changes: 148 additions & 0 deletions layer/utils/RBAC.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package utils

import (
"log"
"net/http"

"github.com/aws/aws-lambda-go/events"
)

// Permission represents an action that can be performed
type Permission string

const (
// Feature Flag Permissions
PermissionCreateFeatureFlag Permission = "CREATE_FEATURE_FLAG"
PermissionUpdateFeatureFlag Permission = "UPDATE_FEATURE_FLAG"
PermissionReadFeatureFlag Permission = "READ_FEATURE_FLAG"
PermissionDeleteFeatureFlag Permission = "DELETE_FEATURE_FLAG"

// User Feature Flag Mapping Permissions
PermissionCreateUserMapping Permission = "CREATE_USER_MAPPING"
PermissionUpdateUserMapping Permission = "UPDATE_USER_MAPPING"
PermissionReadUserMapping Permission = "READ_USER_MAPPING"

// User Management Permissions
PermissionReadUser Permission = "READ_USER"
PermissionUpdateUser Permission = "UPDATE_USER"
PermissionDeleteUser Permission = "DELETE_USER"
)

// RolePermissions maps roles to their allowed permissions
var RolePermissions = map[string][]Permission{
ROLE_ADMIN: {
// Feature Flags - Full Access
PermissionCreateFeatureFlag,
PermissionUpdateFeatureFlag,
PermissionReadFeatureFlag,
PermissionDeleteFeatureFlag,
// User Mappings - Full Access
PermissionCreateUserMapping,
PermissionUpdateUserMapping,
PermissionReadUserMapping,
// User Management - Full Access
PermissionReadUser,
PermissionUpdateUser,
PermissionDeleteUser,
},
ROLE_DEVELOPER: {
// Feature Flags - Create and Update
PermissionCreateFeatureFlag,
PermissionUpdateFeatureFlag,
PermissionReadFeatureFlag,
// User Mappings - Full Access
PermissionCreateUserMapping,
PermissionUpdateUserMapping,
PermissionReadUserMapping,
// User Management - Read Only
PermissionReadUser,
},
ROLE_VIEWER: {
// Feature Flags - Read Only
PermissionReadFeatureFlag,
// User Mappings - Read Only (own mappings)
PermissionReadUserMapping,
// User Management - Read Own Profile
PermissionReadUser,
},
}

// HasPermission checks if a role has a specific permission
func HasPermission(role string, permission Permission) bool {
permissions, exists := RolePermissions[role]
if !exists {
log.Printf("Unknown role: %s", role)
return false
}

for _, p := range permissions {
if p == permission {
return true
}
}

return false
}

// RequirePermission is a middleware helper that checks if user has required permission
func RequirePermission(userContext *UserContext, permission Permission) (events.APIGatewayProxyResponse, error) {
if userContext == nil {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusUnauthorized,
Body: "User context not available",
}, nil
}

if !HasPermission(userContext.Role, permission) {
log.Printf("User %s with role %s does not have permission %s", userContext.UserId, userContext.Role, permission)
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "Insufficient permissions",
}, nil
}

return events.APIGatewayProxyResponse{
StatusCode: http.StatusOK,
}, nil
}

// RequireAnyPermission checks if user has any of the provided permissions
func RequireAnyPermission(userContext *UserContext, permissions ...Permission) (events.APIGatewayProxyResponse, error) {
if userContext == nil {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusUnauthorized,
Body: "User context not available",
}, nil
}

for _, permission := range permissions {
if HasPermission(userContext.Role, permission) {
return events.APIGatewayProxyResponse{
StatusCode: http.StatusOK,
}, nil
}
}

log.Printf("User %s with role %s does not have any of the required permissions", userContext.UserId, userContext.Role)
return events.APIGatewayProxyResponse{
StatusCode: http.StatusForbidden,
Body: "Insufficient permissions",
}, nil
}

// CanAccessUserResource checks if user can access a resource belonging to another user
// Users can access their own resources, or if they're ADMIN
func CanAccessUserResource(userContext *UserContext, resourceUserId string) bool {
if userContext == nil {
return false
}

// Users can always access their own resources
if userContext.UserId == resourceUserId {
return true
}

// Only ADMIN can access other users' resources
return userContext.Role == ROLE_ADMIN
}

Loading