I’m having trouble setting up an HttpLambdaAuthori...
# help
m
I’m having trouble setting up an HttpLambdaAuthorizer. Symptom: everything else in my stacks is being created successfully, including the authorization lambda itself, but the actual Authorizer that connects the API Gateway to the authorization lambda is not being created. If I manually create it in the AWS UI, everything works great, so I’m pretty sure it’s not some problem with the rest of the stacks. Any ideas what I might be doing wrong in my creation of the HttpLambdaAuthorizer?
r
Can you share your stack code?
m
Versions:
`````` “devDependencies”: { “@serverless-stack/aws-lambda-ric”: “^2.0.12”, }, “dependencies”: { “@aws-sdk/signature-v4-crt”: “3.47.0", “@serverless-stack/cli”: “0.69.6", “@serverless-stack/resources”: “0.69.6", “aws-cdk-lib”: “2.15.0", “constructs”: “10.0.114", “@aws-cdk/aws-apigatewayv2-authorizers-alpha”: “~2.15.0-alpha.0", “@aws-cdk/aws-apigatewayv2-alpha”: “2.15.0-alpha.0" }
r
Firstly, I’d strongly recommend you update to SST 1.x
m
Copy code
const singleUseAuthRequestTokenAuthorizerFunction = new Function(
      this,
      "SingleUseAuthRequestTokenAuthorizerFunction",
      {
        handler: '@hbo/hbomax-oyster-lambda/lib/SingleUseAuthRequestTokenAuthorizer.handler',
        environment: {
          SINGLE_USE_REQUEST_AUTH_TOKEN_TABLE_NAME: props.singleUseRequestAuthTokenTable.tableName
        },
        permissions: [props.singleUseRequestAuthTokenTable]
      }
    );

    const singleUseAuthRequestTokenAuthorizer: HttpLambdaAuthorizer = new HttpLambdaAuthorizer(
      'SingleUseAuthRequestTokenHttpLambdaAuthorizer',
      singleUseAuthRequestTokenAuthorizerFunction,
      {
        authorizerName: "SingleUseAuthRequestTokenAuthorizer",
        responseTypes: [HttpLambdaResponseType.IAM],
        identitySource: ['$request.header.Authorization'],
        resultsCacheTtl: Duration.seconds(0)
      }
    );


// Later, in the routes...
        'POST /CreateStbTesterNodeAuthToken': {
          handler: '@hbo/hbomax-oyster-lambda/lib/CreateStbTesterNodeAuthToken.handler',
          environment: {
            SINGLE_USE_REQUEST_AUTH_TOKEN_TABLE_NAME: props.singleUseRequestAuthTokenTable.tableName,
            STB_TESTER_NODE_AUTH_TOKEN_TABLE_NAME: props.stbTesterNodeAuthTokenTable.tableName
          },
          authorizationType: ApiAuthorizationType.CUSTOM,
          authorizer: singleUseAuthRequestTokenAuthorizer
        }
The authorizer lambda is created just fine, and the route is created just fine. The HttpLambdaAuthorizer is nowhere to be found.
r
I don’t have an example to hand of the 0.x way of doing things but it did change with v1.0 - definitely worth looking to upgrade
m
Unfortunately, that’s not an option for us right now, for Reasons. But I’ve looked really hard at the 0.x examples (in the “0.x Constructs” section of the old docs, under Api), and it sure looks to me like I’m doing the right thing.
@Frank, @Jay, any chance you can take a look at my code above, and see what I’m doing wrong?
In the Authorizers section of the 0.x -> 1.x migration guide, under the heading “Lambda authorizer”, I see the following 0.x code example:
Copy code
import { Duration } from "aws-cdk-lib";
import { HttpLambdaAuthorizer } from "@aws-cdk/aws-apigatewayv2-authorizers-alpha";

const authHandler = new Function(stack, "AuthHandler", {
  handler: "src/authorizer.main",
});

const authorizer = new HttpLambdaAuthorizer("MyAuthorizer", authHandler, {
  authorizerName: "LambdaAuthorizer",
  resultsCacheTtl: Duration.seconds(30),
});

new Api(stack, "Api", {
  defaultAuthorizationType: ApiAuthorizationType.CUSTOM,
  defaultAuthorizer: authorizer,
  routes: {
    "GET /notes": "src/list.main",
    "POST /notes": {
      function: "create.main",
      authorizer: authorizer
      authorizationType: ApiAuthorizationType.CUSTOM,
    }
  },
});
This appears to me to be substantially the same as my code. I do have one question about that code, though. I don’t understand the meaning/purpose/intent/effect of the three different names (“AuthHandler”, “MyAuthorizer”, and “LambdaAuthorizer”) for the two constructs (
authHandler
and
authorizer
). Why does the
HttpLambdaAuthorizer
take both a name as the first argument to its constructor, and also take an
authorizerName
as one of its props? And why does the
authorizerName
(“LambdaAuthorizer”) not appear anywhere else in the code? Is it a magic string, rather than a placeholder for a real name? And, if it is a real name, why is it needed when we have already named the
HttpLambdaAuthorizer
something else (“MyAuthorizer”)?
f
@mickey phoenix I think i spot the issue. Replace this:
Copy code
'POST /CreateStbTesterNodeAuthToken': {
  handler: '...',
  environment: {
    ...
  },
  authorizationType: ApiAuthorizationType.CUSTOM,
  authorizer: singleUseAuthRequestTokenAuthorizer
}
with
Copy code
'POST /CreateStbTesterNodeAuthToken': {
  function: {
    handler: '...',
    environment: {
      ...
    },
  },
  authorizationType: ApiAuthorizationType.CUSTOM,
  authorizer: singleUseAuthRequestTokenAuthorizer
}
m
…I’m using the v1 syntax for those. Aren’t I. /head /desk /headdesk
f
in v0, a route can take either this:
Copy code
'POST /route': {
  handler,
  environment,
}
Or this if authorizer is provided
Copy code
'POST /route': {
  function: {
    handler,
    environment,
  },
  authorizationType,
  authorizer
}
m
Okay, so…the 0.x types allow either the
function
version or the not-
function
version. So why does not-
function
version not work in 0.x?
f
If you did this, the authorzer and authorizationType are ignored:
Copy code
'POST /route': {
  handler,
  environment,
  authorizationType,
  authorizer
}
m
Oh, got it. but then…why is the typing allowing me to specify the mixed format?
f
B/c we were overloading the types that a route can take, it confused the TSC compiler.
In v1, we removed this format
Copy code
'POST /route': {
  handler,
  environment,
}
You always have to do this
Copy code
'POST /route': {
  function: {
    handler,
    environment,
  },
}
This made the constructs a lot more typesafe.
^ typesafety + ts doc was a big initiative of v1 🙂
m
Yeah. But I’m looking into the types, and I’m just not seeing why the typechecker is allowing that.
It should be looking at the types, and observing that none of them allow both “handler” and “authorizer”, and objecting that
Object literal may only specify known properties, and ''fred'' does not exist in type 'Function | FunctionProps | ApiFunctionRouteProps | ApiHttpRouteProps | ApiAlbRouteProps'
Unless the “Object literal may only specify known properties” is less smart than it should be, and just checks the union of all known properties of the types being combined. Which would be awful, but is TypeScript’s fault and not yours. 😄
Oh my god. It just looks at the union. #jawdrop #shockandhorror
f
@thdxr do u recall why the tsc was confused and wasn’t able to catch this in v0?
Copy code
'POST /route': {
  handler,
  environment,
  authorizationType,
  authorizer
}
m
Copy code
type Hobbit = { footHairs : number };
const frodo: Hobbit = { footHairs: 99134, palantirs: 13 }; // Object literal may only specify known properties, and 'palantirs' does not exist in type 'Hobbit'.ts(2322)

type Wizard = { palantirs : number };
const gandalf: Wizard = { footHairs: 13, palantirs: 1 }; // Object literal may only specify known properties, and 'footHairs' does not exist in type 'Wizard'.ts(2322)

type DenizenOfMiddleEarth = Hobbit | Wizard;
const HobbitsCannotBeMaiar: DenizenOfMiddleEarth = { footHairs: 99999, palantirs: 12345}; // TypeScript permits an abomination in the eyes of Eru Ilúvatar!!!
So, yeah. Just a TypeScript language weakness. So, I’m wondering — given that it sounds like 0.x is still supported, at least to some degree, could we possibly get a version of it that throws a runtime error when it detects an invalid specification like that? Or, arguably, a version that doesn’t ignore
authorizationType
and
authorization
in the
FunctionDefinition
branch of the routes? It kinda feels like a booby trap, the way it is right now… /wry grin
Also, would this syntax work?
Copy code
'POST /CreateStbTesterNodeAuthToken': {
          function: '@hbo/hbomax-oyster-lambda/lib/CreateStbTesterNodeAuthToken.handler',
          environment: {
            SINGLE_USE_REQUEST_AUTH_TOKEN_TABLE_NAME: props.singleUseRequestAuthTokenTable.tableName,
            STB_TESTER_NODE_AUTH_TOKEN_TABLE_NAME: props.stbTesterNodeAuthTokenTable.tableName
          },
          authorizer: singleUseAuthRequestTokenAuthorizer
        }
Seems to me that it would, given that it would still end up matching the definition of
ApiFunctionRouteProps
. But I want to make sure that the code that processes it is written such that it handles it correctly.
f
environment
needs to go into
function
, so this would work:
Copy code
'POST /CreateStbTesterNodeAuthToken': {
  function: {
    handler: '@hbo/hbomax-oyster-lambda/lib/CreateStbTesterNodeAuthToken.handler',
    environment: {
      SINGLE_USE_REQUEST_AUTH_TOKEN_TABLE_NAME: props.singleUseRequestAuthTokenTable.tableName,
      STB_TESTER_NODE_AUTH_TOKEN_TABLE_NAME: props.stbTesterNodeAuthTokenTable.tableName
    },
  },
  authorizer: singleUseAuthRequestTokenAuthorizer
}
We don’t release v0 updates any more, but you can easily add ur own checks, ie.
Copy code
function checkRoutes(routes) {
  if (routes.handler && routes.authorizer) {
    throw new Error("Move handler inside function");
  }
  if (routes.function && routes.environment) {
    throw new Error("Move environment inside function");
  }
  // add more checks
}
And you can run the checks like:
Copy code
const routes = {
  'POST /CreateStbTesterNodeAuthToken': {
    ...
   },
  'POST /anotherRoute': {
    ...
   },
};

checkRoutes(routes);

new Api(this, "MyAPI", {
  routes
});
Hope that helps a bit