So I am building a backend with AWS + ExpressJs using a single lambda setup. In the AuthorizerWrapper, a helper class I created to help me with initializing user pools, I have to use the scope coming from the class constructor in a method as below :
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { CfnOutput } from 'aws-cdk-lib';
import {
UserPool,
UserPoolClient,
CfnUserPoolGroup,
UserPoolEmail,
} from 'aws-cdk-lib/aws-cognito';
import { IdentityPoolWrapper } from './IdentityPoolWrapper';
import { HttpApi, HttpAuthorizer } from '@aws-cdk/aws-apigatewayv2';
export class AuthorizerWrapper {
private scope: Construct;
private api: HttpApi;
private userPool: UserPool;
private userPoolClient: UserPoolClient;
public authorizer: HttpAuthorizer;
private identityPoolWrapper: IdentityPoolWrapper;
constructor(scope: Construct, api: HttpApi) {
this.scope = scope;
this.api = api;
this.initalize();
this.addUserPoolClient();
this.createAuthorizer();
this.initializeIdentityPoolWrapper();
this.createAdminsGroup();
}
private initalize() {
this.userPool = new UserPool(this.scope, 'JobifyUserPool', {
userPoolName: 'JobifyUserPool',
selfSignUpEnabled: true,
signInAliases: {
email: true,
username: true,
},
passwordPolicy: {
minLength: 6,
requireLowercase: false,
requireDigits: false,
requireSymbols: false,
requireUppercase: false,
},
userVerification: {
emailSubject: 'Verify your email for Jobify',
emailBody:
'Thanks for signing up to Jobify! Your verification code is {####}',
},
email: UserPoolEmail.withCognito('[email protected]'),
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
new CfnOutput(this.scope, 'UserPoolId', {
value: this.userPool.userPoolId,
});
}
private addUserPoolClient() {
this.userPoolClient = this.userPool.addClient('JobifyUserPool-client', {
userPoolClientName: 'JobifyUserPool-client',
authFlows: {
userPassword: true,
adminUserPassword: true,
custom: true,
userSrp: true,
},
generateSecret: false,
});
new CfnOutput(this.scope, 'UserPoolClientId', {
value: this.userPoolClient.userPoolClientId,
});
}
** private createAuthorizer() {
this.authorizer = new HttpAuthorizer(this, 'JobifyUserAuthorizer', {
identitySource: ['$request.header.Authorization'],
jwtAudience: [this.userPoolClient.userPoolClientId],
jwtIssuer: this.userPool.userPoolProviderUrl,
});
}**
private initializeIdentityPoolWrapper() {
this.identityPoolWrapper = new IdentityPoolWrapper(
this.scope,
this.userPool,
this.userPoolClient
);
}
private createAdminsGroup() {
new CfnUserPoolGroup(this.scope, 'AdminsGroup', {
groupName: 'Admins',
userPoolId: this.userPool.userPoolId,
roleArn: this.identityPoolWrapper.adminRole.roleArn,
});
}
}
When trying to instantiate a new HttpAuthorizer with this as a Construct I get the error: Argument of type 'this' is not assignable to parameter of type 'Construct'. Type 'AuthorizerWrapper' is missing the following properties from type 'Construct': onValidate, onPrepare, onSynthesize
If I try to use this.scope I get:
Argument of type 'import("PATH_TO_PROJECT/cdk/node_modules/constructs/lib/construct").Construct' is not assignable to parameter of type 'import("PATH_TO_PROJECT/cdk/node_modules/@aws/cdk/core/node_modules/constructs/lib/construct").Construct'.
This is my package.json:
{
"name": "cdk",
"version": "0.1.0",
"bin": {
"cdk": "bin/cdk.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"devDependencies": {
"@types/aws-serverless-express": "^3.3.5",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jest": "^29.4.0",
"aws-cdk-lib": "^2.73.0",
"http-status-codes": "^2.2.0",
"jest": "^29.5.0",
"ts-jest": "^29.0.5",
"typescript": "~4.9.5"
},
"dependencies": {
"@aws-cdk/aws-apigatewayv2": "^1.198.1",
"@aws-cdk/aws-apigatewayv2-integrations": "^1.198.1",
"@aws-cdk/aws-lambda": "^1.198.1",
"aws-sdk": "^2.1343.0",
"aws-serverless-express": "^3.4.0",
"constructs": "^10.1.307",
"cors": "^2.8.5",
"express": "^4.18.2",
"source-map-support": "^0.5.21"
}
}```
The idiomatic CDK approach would be to make AuthorizerWrapper
a Construct subclass. See Writing your own constructs in the docs. Also idiomatic is a constructor that accepts three arguments like the CDK-defined constructs do: scope, id and props.
In your private methods, this
will refer to an AuthorizerWrapper instance, which is a Construct.
interface AuthorizerWrapperProps {
api: HttpApi
}
export class AuthorizerWrapper extends Construct {
private api: HttpApi;
constructor(scope: Construct, id: string, props: AuthorizerWrapperProps) {
super(scope, id);
this.api = props.api;
this.initalize();
// ...
}
private initalize() {
this.userPool = new UserPool(this, 'JobifyUserPool', {
// ...
});
}
N.B. You are getting the error because as written, this
is a plain JS object class, not a Construct. Your use of this.scope
everywhere will work, but is not idiomatic CDK. As the docs say:
Technically, it's possible to pass some scope other than
this
when instantiating a construct. The practical difficulty here is that you can't easily ensure that the IDs you choose for your constructs are unique within someone else's scope. The practice also makes your code more difficult to understand, maintain, and reuse.
You must also fix the HttpApi
import, which is currently referencing the v1 version. In CDK v2, experimental constructs are in separate alpha packages:
import { HttpApi } from "@aws-cdk/aws-apigatewayv2-alpha";