|
package main |
|
|
|
import ( |
|
"context" |
|
"errors" |
|
"regexp" |
|
"strings" |
|
|
|
"github.com/aws/aws-lambda-go/events" |
|
"github.com/aws/aws-lambda-go/lambda" |
|
) |
|
|
|
const ( |
|
UserIDHeader = "x-user-id" |
|
UserTokenHeader = "x-user-token" |
|
) |
|
|
|
var ( |
|
// allowedResourceEndpoints is a list of the API Gateway ARN suffix parts that |
|
// this authorizer allows. See `allowedResource` below. |
|
allowedResourceEndpoints = []string{ |
|
"/POST/things", |
|
"/GET/things", |
|
"/GET/someotherthing", |
|
"/PUT/users", |
|
"/DELETE/users", |
|
} |
|
) |
|
|
|
func validUserToken(userID, userToken string) bool { |
|
user, err := repo.GetUser(userID) |
|
if err != nil || user == nil { |
|
return false |
|
} |
|
|
|
return user.AccessToken == userToken |
|
} |
|
|
|
// allowedResources returns the list of allowed resources for the policy document |
|
// so that we authorize the user for all (user authorized) API's in this |
|
// system with a single call. We use wildcards so that we don't have to do any |
|
// environment (i.e. dev vs. prod) handling. We list all these resources, because |
|
// API Gateway caches this policy, so if we use the exact methodArn of the HTTP |
|
// call, it winds up then blocking that same user from calling other APIs because |
|
// the policy is cached with just the first API call's methodArn. |
|
// See: |
|
// https://forum.serverless.com/t/help-really-weird-access-denied-issue/12676/6 |
|
// This function strips off everything after the first encountered slash, where |
|
// the beginning part is the API Gateway API ID, and then adds the list of |
|
// endpoints that this particular authorizer is used for, with a wildcard for |
|
// the account/environment (dev vs. prod) part. e.g. a methodArn of: |
|
// arn:aws:execute-api:us-east-1:123456789000:jol83sbu0b/dev/POST/things |
|
// will be converted to: |
|
// arn:aws:execute-api:us-east-1:123456789000:jol83sbu0b/*/POST/things |
|
// (as well as the other API endpoints this authorizer pertains to). |
|
func allowedResources(methodArn string) (resources []string) { |
|
parts := strings.Split(methodArn, "/") |
|
prefix := parts[0] + "/*" |
|
|
|
for _, endpoint := range allowedResourceEndpoints { |
|
resources = append(resources, prefix+endpoint) |
|
} |
|
|
|
return |
|
} |
|
|
|
func generatePolicy(principalID, effect string, resources []string) events.APIGatewayCustomAuthorizerResponse { |
|
authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalID} |
|
|
|
if effect != "" && len(resources) != 0 { |
|
authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{ |
|
Version: "2012-10-17", |
|
Statement: []events.IAMPolicyStatement{ |
|
{ |
|
Action: []string{"execute-api:Invoke"}, |
|
Effect: effect, |
|
Resource: resources, |
|
}, |
|
}, |
|
} |
|
} |
|
return authResponse |
|
} |
|
|
|
func handler(ctx context.Context, request events.APIGatewayCustomAuthorizerRequestTypeRequest) (events.APIGatewayCustomAuthorizerResponse, error) { |
|
if validUserToken(request.Headers[UserIDHeader], request.Headers[UserTokenHeader]) { |
|
return generatePolicy("user", "Allow", allowedResources(request.MethodArn)), nil |
|
} |
|
|
|
return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized") |
|
} |
|
|
|
func main() { |
|
lambda.Start(handler) |
|
} |