Skip to content

Commit

Permalink
feat(api): deleteUserAttributes full support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Dec 11, 2021
1 parent 1a47086 commit 3d0e9a0
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| DeleteIdentityProvider ||
| DeleteResourceServer ||
| DeleteUser ||
| DeleteUserAttributes | |
| DeleteUserAttributes | |
| DeleteUserPool ||
| DeleteUserPoolClient ||
| DeleteUserPoolDomain ||
Expand Down
91 changes: 91 additions & 0 deletions integration-tests/aws-sdk/deleteUserAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { UUID } from "../../src/__tests__/patterns";
import { withCognitoSdk } from "./setup";

describe(
"CognitoIdentityServiceProvider.deleteUserAttributes",
withCognitoSdk((Cognito) => {
it("updates a user's attributes", async () => {
const client = Cognito();

const pool = await client
.createUserPool({
PoolName: "test",
AutoVerifiedAttributes: ["email"],
})
.promise();
const userPoolId = pool.UserPool?.Id as string;

const upc = await client
.createUserPoolClient({
UserPoolId: userPoolId,
ClientName: "test",
})
.promise();

await client
.adminCreateUser({
UserAttributes: [
{ Name: "email", Value: "[email protected]" },
{ Name: "custom:example", Value: "1" },
],
Username: "abc",
UserPoolId: userPoolId,
TemporaryPassword: "def",
DesiredDeliveryMediums: ["EMAIL"],
})
.promise();

await client
.adminConfirmSignUp({
UserPoolId: userPoolId,
Username: "abc",
})
.promise();

// login as the user
const initiateAuthResponse = await client
.initiateAuth({
AuthFlow: "USER_PASSWORD_AUTH",
AuthParameters: {
USERNAME: "abc",
PASSWORD: "def",
},
ClientId: upc.UserPoolClient?.ClientId as string,
})
.promise();

let user = await client
.adminGetUser({
UserPoolId: userPoolId,
Username: "abc",
})
.promise();

expect(user.UserAttributes).toEqual([
{ Name: "sub", Value: expect.stringMatching(UUID) },
{ Name: "email", Value: "[email protected]" },
{ Name: "custom:example", Value: "1" },
]);

await client
.deleteUserAttributes({
AccessToken: initiateAuthResponse.AuthenticationResult
?.AccessToken as string,
UserAttributeNames: ["custom:example"],
})
.promise();

user = await client
.adminGetUser({
UserPoolId: userPoolId,
Username: "abc",
})
.promise();

expect(user.UserAttributes).toEqual([
{ Name: "sub", Value: expect.stringMatching(UUID) },
{ Name: "email", Value: "[email protected]" },
]);
});
})
);
1 change: 0 additions & 1 deletion integration-tests/aws-sdk/updateUserAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Pino from "pino";
import { UUID } from "../../src/__tests__/patterns";
import { withCognitoSdk } from "./setup";

Expand Down
92 changes: 92 additions & 0 deletions src/targets/deleteUserAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import jwt from "jsonwebtoken";
import * as uuid from "uuid";
import { ClockFake } from "../__tests__/clockFake";
import { newMockCognitoService } from "../__tests__/mockCognitoService";
import { newMockUserPoolService } from "../__tests__/mockUserPoolService";
import { TestContext } from "../__tests__/testContext";
import { InvalidParameterError, NotAuthorizedError } from "../errors";
import PrivateKey from "../keys/cognitoLocal.private.json";
import { UserPoolService } from "../services";
import { attribute } from "../services/userPoolService";
import {
DeleteUserAttributes,
DeleteUserAttributesTarget,
} from "./deleteUserAttributes";
import * as TDB from "../__tests__/testDataBuilder";

const clock = new ClockFake(new Date());

const validToken = jwt.sign(
{
sub: "0000-0000",
event_id: "0",
token_use: "access",
scope: "aws.cognito.signin.user.admin",
auth_time: new Date(),
jti: uuid.v4(),
client_id: "test",
username: "0000-0000",
},
PrivateKey.pem,
{
algorithm: "RS256",
issuer: `http://localhost:9229/test`,
expiresIn: "24h",
keyid: "CognitoLocal",
}
);

describe("DeleteUserAttributes target", () => {
let deleteUserAttributes: DeleteUserAttributesTarget;
let mockUserPoolService: jest.Mocked<UserPoolService>;

beforeEach(() => {
mockUserPoolService = newMockUserPoolService();
deleteUserAttributes = DeleteUserAttributes({
clock,
cognito: newMockCognitoService(mockUserPoolService),
});
});

it("throws if the user doesn't exist", async () => {
mockUserPoolService.getUserByUsername.mockResolvedValue(null);

await expect(
deleteUserAttributes(TestContext, {
AccessToken: validToken,
UserAttributeNames: ["custom:example"],
})
).rejects.toEqual(new NotAuthorizedError());
});

it("throws if the token is invalid", async () => {
await expect(
deleteUserAttributes(TestContext, {
AccessToken: "invalid token",
UserAttributeNames: ["custom:example"],
})
).rejects.toEqual(new InvalidParameterError());
});

it("saves the updated attributes on the user", async () => {
const user = TDB.user({
Attributes: [
attribute("email", "[email protected]"),
attribute("custom:example", "1"),
],
});

mockUserPoolService.getUserByUsername.mockResolvedValue(user);

await deleteUserAttributes(TestContext, {
AccessToken: validToken,
UserAttributeNames: ["custom:example"],
});

expect(mockUserPoolService.saveUser).toHaveBeenCalledWith(TestContext, {
...user,
Attributes: [attribute("email", "[email protected]")],
UserLastModifiedDate: clock.get(),
});
});
});
49 changes: 49 additions & 0 deletions src/targets/deleteUserAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
DeleteUserAttributesRequest,
DeleteUserAttributesResponse,
} from "aws-sdk/clients/cognitoidentityserviceprovider";
import jwt from "jsonwebtoken";
import { InvalidParameterError, NotAuthorizedError } from "../errors";
import { Services } from "../services";
import { Token } from "../services/tokenGenerator";
import { attributesRemove } from "../services/userPoolService";
import { Target } from "./router";

export type DeleteUserAttributesTarget = Target<
DeleteUserAttributesRequest,
DeleteUserAttributesResponse
>;

type DeleteUserAttributesServices = Pick<Services, "clock" | "cognito">;

export const DeleteUserAttributes =
({
clock,
cognito,
}: DeleteUserAttributesServices): DeleteUserAttributesTarget =>
async (ctx, req) => {
const decodedToken = jwt.decode(req.AccessToken) as Token | null;
if (!decodedToken) {
ctx.logger.info("Unable to decode token");
throw new InvalidParameterError();
}

const userPool = await cognito.getUserPoolForClientId(
ctx,
decodedToken.client_id
);
const user = await userPool.getUserByUsername(ctx, decodedToken.sub);
if (!user) {
throw new NotAuthorizedError();
}

const updatedUser = {
...user,
Attributes: attributesRemove(user.Attributes, ...req.UserAttributeNames),
UserLastModifiedDate: clock.get(),
};

await userPool.saveUser(ctx, updatedUser);

return {};
};
2 changes: 2 additions & 0 deletions src/targets/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CreateGroup } from "./createGroup";
import { CreateUserPool } from "./createUserPool";
import { CreateUserPoolClient } from "./createUserPoolClient";
import { DeleteUser } from "./deleteUser";
import { DeleteUserAttributes } from "./deleteUserAttributes";
import { DescribeUserPoolClient } from "./describeUserPoolClient";
import { ForgotPassword } from "./forgotPassword";
import { ChangePassword } from "./changePassword";
Expand Down Expand Up @@ -46,6 +47,7 @@ export const Targets = {
CreateUserPool,
CreateUserPoolClient,
DeleteUser,
DeleteUserAttributes,
DescribeUserPoolClient,
ForgotPassword,
GetUser,
Expand Down

0 comments on commit 3d0e9a0

Please sign in to comment.