From d0a25572de19b84e8228c1fea9d2ebbe9d40d160 Mon Sep 17 00:00:00 2001 From: Rory Mulligan Date: Tue, 29 Sep 2020 13:00:27 -0400 Subject: [PATCH 1/3] fix: key setting in the datastore to work with arrays --- src/services/dataStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/dataStore.ts b/src/services/dataStore.ts index 0c49e2de..5bf37207 100644 --- a/src/services/dataStore.ts +++ b/src/services/dataStore.ts @@ -47,7 +47,7 @@ export const createDataStore: CreateDataStore = async ( }, async set(key, value) { - await db.set(key, value).save(); + await db.set(key instanceof Array ? key.join(".") : key, value).save(); }, }; }; From 26cf370d38d83bc922431c68202a25d268402eb7 Mon Sep 17 00:00:00 2001 From: James Gregory Date: Thu, 22 Jul 2021 12:39:28 +1000 Subject: [PATCH 2/3] feat(api): describeUserPoolClient support --- README.md | 1 + src/services/cognitoClient.ts | 7 +- src/services/triggers/customMessage.test.ts | 1 + .../triggers/postConfirmation.test.ts | 1 + src/services/triggers/userMigration.test.ts | 1 + src/targets/confirmForgotPassword.test.ts | 1 + src/targets/confirmSignUp.test.ts | 1 + src/targets/createUserPoolClient.test.ts | 1 + src/targets/describeUserPoolClient.test.ts | 70 ++++++++++++++++++ src/targets/describeUserPoolClient.ts | 72 +++++++++++++++++++ src/targets/forgotPassword.test.ts | 1 + src/targets/getUser.test.ts | 1 + src/targets/initiateAuth.test.ts | 1 + src/targets/listUsers.test.ts | 1 + src/targets/respondToAuthChallenge.test.ts | 1 + src/targets/router.ts | 4 +- src/targets/signUp.test.ts | 1 + 17 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/targets/describeUserPoolClient.test.ts create mode 100644 src/targets/describeUserPoolClient.ts diff --git a/README.md b/README.md index 8c7b373d..5abd211f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ perfect, because it won't be. - [ConfirmForgotPassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmForgotPassword.html) - [ConfirmSignUp](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmSignUp.html) - [CreateUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateUserPoolClient.html) +- [DescribeUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_DescribeUserPoolClient.html) - [ForgotPassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ForgotPassword.html) - [GetUser](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_GetUser.html) - [InitiateAuth](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html) diff --git a/src/services/cognitoClient.ts b/src/services/cognitoClient.ts index d4eb7918..b49f6e10 100644 --- a/src/services/cognitoClient.ts +++ b/src/services/cognitoClient.ts @@ -9,6 +9,7 @@ import { } from "./userPoolClient"; export interface CognitoClient { + getAppClient(clientId: string): Promise; getUserPool(userPoolId: string): Promise; getUserPoolForClientId(clientId: string): Promise; } @@ -63,7 +64,7 @@ export class CognitoClientService implements CognitoClient { public async getUserPoolForClientId( clientId: string ): Promise { - const appClient = await this.clients.get(["Clients", clientId]); + const appClient = await this.getAppClient(clientId); if (!appClient) { throw new ResourceNotFoundError(); } @@ -75,4 +76,8 @@ export class CognitoClientService implements CognitoClient { this.logger ); } + + public async getAppClient(clientId: string): Promise { + return this.clients.get(["Clients", clientId]); + } } diff --git a/src/services/triggers/customMessage.test.ts b/src/services/triggers/customMessage.test.ts index 0e954ce8..ca073dc1 100644 --- a/src/services/triggers/customMessage.test.ts +++ b/src/services/triggers/customMessage.test.ts @@ -25,6 +25,7 @@ describe("CustomMessage trigger", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/services/triggers/postConfirmation.test.ts b/src/services/triggers/postConfirmation.test.ts index dbd2f2e3..80d34738 100644 --- a/src/services/triggers/postConfirmation.test.ts +++ b/src/services/triggers/postConfirmation.test.ts @@ -25,6 +25,7 @@ describe("PostConfirmation trigger", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/services/triggers/userMigration.test.ts b/src/services/triggers/userMigration.test.ts index 60b3c405..765b9e07 100644 --- a/src/services/triggers/userMigration.test.ts +++ b/src/services/triggers/userMigration.test.ts @@ -27,6 +27,7 @@ describe("UserMigration trigger", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/confirmForgotPassword.test.ts b/src/targets/confirmForgotPassword.test.ts index 73bdf421..85ce4dbd 100644 --- a/src/targets/confirmForgotPassword.test.ts +++ b/src/targets/confirmForgotPassword.test.ts @@ -27,6 +27,7 @@ describe("ConfirmForgotPassword target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/confirmSignUp.test.ts b/src/targets/confirmSignUp.test.ts index ac9a8fc0..5f85bbf5 100644 --- a/src/targets/confirmSignUp.test.ts +++ b/src/targets/confirmSignUp.test.ts @@ -24,6 +24,7 @@ describe("ConfirmSignUp target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/createUserPoolClient.test.ts b/src/targets/createUserPoolClient.test.ts index aa0c2ab9..0747875e 100644 --- a/src/targets/createUserPoolClient.test.ts +++ b/src/targets/createUserPoolClient.test.ts @@ -26,6 +26,7 @@ describe("CreateUserPoolClient target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/describeUserPoolClient.test.ts b/src/targets/describeUserPoolClient.test.ts new file mode 100644 index 00000000..eab56e1c --- /dev/null +++ b/src/targets/describeUserPoolClient.test.ts @@ -0,0 +1,70 @@ +import { advanceTo } from "jest-date-mock"; +import { ResourceNotFoundError } from "../errors"; +import { CognitoClient, UserPoolClient } from "../services"; +import { AppClient } from "../services/appClient"; +import { + DescribeUserPoolClient, + DescribeUserPoolClientTarget, +} from "./describeUserPoolClient"; + +describe("DescribeUserPoolClient target", () => { + let describeUserPoolClient: DescribeUserPoolClientTarget; + let mockCognitoClient: jest.Mocked; + let mockUserPoolClient: jest.Mocked; + let now: Date; + + beforeEach(() => { + now = new Date(2020, 1, 2, 3, 4, 5); + advanceTo(now); + + mockUserPoolClient = { + config: { + Id: "test", + }, + createAppClient: jest.fn(), + getUserByUsername: jest.fn(), + listUsers: jest.fn(), + saveUser: jest.fn(), + }; + mockCognitoClient = { + getAppClient: jest.fn(), + getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), + getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), + }; + + describeUserPoolClient = DescribeUserPoolClient({ + cognitoClient: mockCognitoClient, + }); + }); + + it("returns an existing app client", async () => { + const existingAppClient: AppClient = { + RefreshTokenValidity: 30, + AllowedOAuthFlowsUserPoolClient: false, + LastModifiedDate: new Date().getTime(), + CreationDate: new Date().getTime(), + UserPoolId: "userPoolId", + ClientId: "abc", + ClientName: "clientName", + }; + mockCognitoClient.getAppClient.mockResolvedValue(existingAppClient); + + const result = await describeUserPoolClient({ + ClientId: "abc", + UserPoolId: "userPoolId", + }); + + expect(result).toEqual({ UserPoolClient: existingAppClient }); + }); + + it("throws resource not found for an invalid app client", async () => { + mockCognitoClient.getAppClient.mockResolvedValue(null); + + await expect( + describeUserPoolClient({ + ClientId: "abc", + UserPoolId: "userPoolId", + }) + ).rejects.toEqual(new ResourceNotFoundError()); + }); +}); diff --git a/src/targets/describeUserPoolClient.ts b/src/targets/describeUserPoolClient.ts new file mode 100644 index 00000000..efb22b51 --- /dev/null +++ b/src/targets/describeUserPoolClient.ts @@ -0,0 +1,72 @@ +import { ResourceNotFoundError } from "../errors"; +import { Services } from "../services"; +import { UserAttribute } from "../services/userPoolClient"; + +interface Input { + ClientId: string; + UserPoolId: string; +} + +export interface DynamoDBUserRecord { + Username: string; + UserCreateDate: number; + UserLastModifiedDate: number; + Enabled: boolean; + UserStatus: "CONFIRMED" | "UNCONFIRMED" | "RESET_REQUIRED"; + Attributes: readonly UserAttribute[]; +} + +interface Output { + UserPoolClient: { + AccessTokenValidity?: number; + AllowedOAuthFlows?: ("code" | "implicit" | "client_credentials")[]; + AllowedOAuthFlowsUserPoolClient?: boolean; + AllowedOAuthScopes?: string[]; + AnalyticsConfiguration?: { + ApplicationArn?: string; + ApplicationId?: string; + ExternalId?: string; + RoleArn?: string; + UserDataShared?: boolean; + }; + CallbackURLs?: string[]; + ClientId?: string; + ClientName?: string; + ClientSecret?: string; + CreationDate?: number; + DefaultRedirectURI?: string; + EnableTokenRevocation?: boolean; + ExplicitAuthFlows?: string[]; + IdTokenValidity?: number; + LastModifiedDate?: number; + LogoutURLs?: string[]; + PreventUserExistenceErrors?: "LEGACY" | "ENABLED"; + ReadAttributes?: string[]; + RefreshTokenValidity?: number; + SupportedIdentityProviders?: string[]; + TokenValidityUnits?: { + AccessToken?: "seconds" | "minutes" | "hours" | "days"; + IdToken?: "seconds" | "minutes" | "hours" | "days"; + RefreshToken?: "seconds" | "minutes" | "hours" | "days"; + }; + UserPoolId?: string; + WriteAttributes?: string[]; + }; +} + +export type DescribeUserPoolClientTarget = (body: Input) => Promise; + +export const DescribeUserPoolClient = ({ + cognitoClient, +}: Pick): DescribeUserPoolClientTarget => async ( + body +) => { + const client = await cognitoClient.getAppClient(body.ClientId); + if (client?.UserPoolId !== body.UserPoolId) { + throw new ResourceNotFoundError(); + } + + return { + UserPoolClient: client, + }; +}; diff --git a/src/targets/forgotPassword.test.ts b/src/targets/forgotPassword.test.ts index 9f1a65af..a5791dc3 100644 --- a/src/targets/forgotPassword.test.ts +++ b/src/targets/forgotPassword.test.ts @@ -31,6 +31,7 @@ describe("ForgotPassword target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/getUser.test.ts b/src/targets/getUser.test.ts index 24569476..6697bfd1 100644 --- a/src/targets/getUser.test.ts +++ b/src/targets/getUser.test.ts @@ -27,6 +27,7 @@ describe("GetUser target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/initiateAuth.test.ts b/src/targets/initiateAuth.test.ts index 8771eabb..a6218ed5 100644 --- a/src/targets/initiateAuth.test.ts +++ b/src/targets/initiateAuth.test.ts @@ -47,6 +47,7 @@ describe("InitiateAuth target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/listUsers.test.ts b/src/targets/listUsers.test.ts index 1d99ee8c..b457d017 100644 --- a/src/targets/listUsers.test.ts +++ b/src/targets/listUsers.test.ts @@ -22,6 +22,7 @@ describe("ListUsers target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/respondToAuthChallenge.test.ts b/src/targets/respondToAuthChallenge.test.ts index 902f8d7e..f85ed2cf 100644 --- a/src/targets/respondToAuthChallenge.test.ts +++ b/src/targets/respondToAuthChallenge.test.ts @@ -30,6 +30,7 @@ describe("RespondToAuthChallenge target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; diff --git a/src/targets/router.ts b/src/targets/router.ts index 192bd48d..9a2f515c 100644 --- a/src/targets/router.ts +++ b/src/targets/router.ts @@ -4,6 +4,7 @@ import { UnsupportedError } from "../errors"; import { ConfirmForgotPassword } from "./confirmForgotPassword"; import { ConfirmSignUp } from "./confirmSignUp"; import { CreateUserPoolClient } from "./createUserPoolClient"; +import { DescribeUserPoolClient } from "./describeUserPoolClient"; import { ForgotPassword } from "./forgotPassword"; import { InitiateAuth } from "./initiateAuth"; import { ListUsers } from "./listUsers"; @@ -15,12 +16,13 @@ export const Targets = { ConfirmForgotPassword, ConfirmSignUp, CreateUserPoolClient, + DescribeUserPoolClient, ForgotPassword, + GetUser, InitiateAuth, ListUsers, RespondToAuthChallenge, SignUp, - GetUser, }; type TargetName = keyof typeof Targets; diff --git a/src/targets/signUp.test.ts b/src/targets/signUp.test.ts index b0229eba..005fbfb8 100644 --- a/src/targets/signUp.test.ts +++ b/src/targets/signUp.test.ts @@ -35,6 +35,7 @@ describe("SignUp target", () => { saveUser: jest.fn(), }; mockCognitoClient = { + getAppClient: jest.fn(), getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient), getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient), }; From ecb582914ea6dc4b8b87f29b836468c65beee287 Mon Sep 17 00:00:00 2001 From: James Gregory Date: Thu, 22 Jul 2021 12:42:53 +1000 Subject: [PATCH 3/3] test(userpoolclient): verify UserPoolClient is persisted properly --- .../aws-sdk/createUserPoolClient.test.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/integration-tests/aws-sdk/createUserPoolClient.test.ts b/integration-tests/aws-sdk/createUserPoolClient.test.ts index 106d372d..b809f4c3 100644 --- a/integration-tests/aws-sdk/createUserPoolClient.test.ts +++ b/integration-tests/aws-sdk/createUserPoolClient.test.ts @@ -4,7 +4,9 @@ describe( "CognitoIdentityServiceProvider.createUserPoolClient", withCognitoSdk((Cognito) => { it("can create a new app client", async () => { - const result = await Cognito() + const client = Cognito(); + + const result = await client .createUserPoolClient({ ClientName: "test", UserPoolId: "test", @@ -22,6 +24,17 @@ describe( UserPoolId: "test", }, }); + + const createdClient = await client + .describeUserPoolClient({ + ClientId: result.UserPoolClient?.ClientId!, + UserPoolId: "test", + }) + .promise(); + + expect(createdClient).toEqual({ + UserPoolClient: result.UserPoolClient, + }); }); }) );