Skip to content

Commit

Permalink
feat(api): deleteUser full support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Nov 25, 2021
1 parent a6fc4c8 commit 0a753b2
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| DeleteGroup ||
| DeleteIdentityProvider ||
| DeleteResourceServer ||
| DeleteUser | |
| DeleteUser | |
| DeleteUserAttributes ||
| DeleteUserPool ||
| DeleteUserPoolClient ||
Expand Down
79 changes: 79 additions & 0 deletions integration-tests/aws-sdk/deleteUser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { UUID } from "../../src/__tests__/patterns";
import { UserNotFoundError } from "../../src/errors";
import { attributeValue } from "../../src/services/userPoolClient";
import { withCognitoSdk } from "./setup";

describe(
"CognitoIdentityServiceProvider.deleteUser",
withCognitoSdk((Cognito) => {
it("deletes the current user", async () => {
const client = Cognito();

// create the user pool client
const upc = await client
.createUserPoolClient({
UserPoolId: "test",
ClientName: "test",
})
.promise();

// create a user
const createUserResponse = await client
.adminCreateUser({
TemporaryPassword: "def",
UserAttributes: [{ Name: "email", Value: "[email protected]" }],
Username: "abc",
UserPoolId: "test",
})
.promise();
const userSub = attributeValue(
"sub",
createUserResponse.User?.Attributes
);

// attempt to login
const initAuthResponse = await client
.initiateAuth({
ClientId: upc.UserPoolClient?.ClientId!,
AuthFlow: "USER_PASSWORD_AUTH",
AuthParameters: {
USERNAME: "abc",
PASSWORD: "def",
},
})
.promise();

// change their password on first login
// TODO: replace this with adminSetPassword when it's supported
const respondToAuthChallengeResult = await client
.respondToAuthChallenge({
ChallengeName: "NEW_PASSWORD_REQUIRED",
Session: initAuthResponse.Session,
ChallengeResponses: {
USERNAME: "abc",
NEW_PASSWORD: "new_password",
},
ClientId: upc.UserPoolClient?.ClientId!,
})
.promise();

// delete the user with their token
await client
.deleteUser({
AccessToken: respondToAuthChallengeResult.AuthenticationResult
?.AccessToken!,
})
.promise();

// verify they don't exist anymore
await expect(
client
.adminGetUser({
Username: "abc",
UserPoolId: "test",
})
.promise()
).rejects.toEqual(new UserNotFoundError("User does not exist"));
});
})
);
92 changes: 92 additions & 0 deletions src/targets/deleteUser.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 { newMockCognitoClient } from "../__tests__/mockCognitoClient";
import { MockLogger } from "../__tests__/mockLogger";
import { MockUserPoolClient } from "../__tests__/mockUserPoolClient";
import { InvalidParameterError, NotAuthorizedError } from "../errors";
import PrivateKey from "../keys/cognitoLocal.private.json";
import { CognitoClient } from "../services";
import { DeleteUser, DeleteUserTarget } from "./deleteUser";
import * as TDB from "../__tests__/testDataBuilder";

describe("DeleteUser target", () => {
let deleteUser: DeleteUserTarget;
let mockCognitoClient: jest.Mocked<CognitoClient>;

beforeEach(() => {
mockCognitoClient = newMockCognitoClient();

deleteUser = DeleteUser(
{
cognitoClient: mockCognitoClient,
},
MockLogger
);
});

it("parses token get user by sub", async () => {
const user = TDB.user();

MockUserPoolClient.getUserByUsername.mockResolvedValue(user);

await deleteUser({
AccessToken: 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",
}
),
});

expect(MockUserPoolClient.deleteUser).toHaveBeenCalledWith(user);
});

it("throws if token isn't valid", async () => {
await expect(
deleteUser({
AccessToken: "blah",
})
).rejects.toBeInstanceOf(InvalidParameterError);
});

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

await expect(
deleteUser({
AccessToken: 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",
}
),
})
).rejects.toEqual(new NotAuthorizedError());
});
});
33 changes: 33 additions & 0 deletions src/targets/deleteUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DeleteUserRequest } from "aws-sdk/clients/cognitoidentityserviceprovider";
import jwt from "jsonwebtoken";
import { InvalidParameterError, NotAuthorizedError } from "../errors";
import { Logger } from "../log";
import { Services } from "../services";
import { Token } from "../services/tokens";

export type DeleteUserTarget = (req: DeleteUserRequest) => Promise<{}>;

type DeleteUserServices = Pick<Services, "cognitoClient">;

export const DeleteUser = (
{ cognitoClient }: DeleteUserServices,
logger: Logger
): DeleteUserTarget => async (req) => {
const decodedToken = jwt.decode(req.AccessToken) as Token | null;
if (!decodedToken) {
logger.info("Unable to decode token");
throw new InvalidParameterError();
}

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

await userPool.deleteUser(user);

return {};
};
2 changes: 2 additions & 0 deletions src/targets/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfirmForgotPassword } from "./confirmForgotPassword";
import { ConfirmSignUp } from "./confirmSignUp";
import { CreateGroup } from "./createGroup";
import { CreateUserPoolClient } from "./createUserPoolClient";
import { DeleteUser } from "./deleteUser";
import { DescribeUserPoolClient } from "./describeUserPoolClient";
import { ForgotPassword } from "./forgotPassword";
import { ChangePassword } from "./changePassword";
Expand All @@ -31,6 +32,7 @@ export const Targets = {
ConfirmSignUp,
CreateGroup,
CreateUserPoolClient,
DeleteUser,
DescribeUserPoolClient,
ForgotPassword,
GetUser,
Expand Down

0 comments on commit 0a753b2

Please sign in to comment.