Skip to content

Commit

Permalink
fix: adminCreateUser correct response
Browse files Browse the repository at this point in the history
Also start refactoring to use the aws-sdk types to avoid these issues in the future.

Fixes #38
  • Loading branch information
jagregory committed Nov 24, 2021
1 parent d45384a commit 26aa5a7
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 74 deletions.
49 changes: 49 additions & 0 deletions integration-tests/aws-sdk/adminCreateUser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ClockFake } from "../../src/__tests__/clockFake";
import { UUID } from "../../src/__tests__/patterns";
import { withCognitoSdk } from "./setup";

const currentDate = new Date();
const roundedDate = new Date(currentDate.getTime());
roundedDate.setMilliseconds(0);

const clock = new ClockFake(currentDate);

describe(
"CognitoIdentityServiceProvider.adminCreateUser",
withCognitoSdk(
(Cognito) => {
it("creates a user with only the required parameters", async () => {
const client = Cognito();

const createUserResult = await client
.adminCreateUser({
Username: "abc",
UserPoolId: "test",

// TODO: shouldn't need to supply this
TemporaryPassword: "TemporaryPassword",
})
.promise();

expect(createUserResult).toEqual({
User: {
Attributes: [
{
Name: "sub",
Value: expect.stringMatching(UUID),
},
],
Enabled: true,
UserCreateDate: roundedDate,
UserLastModifiedDate: roundedDate,
UserStatus: "CONFIRMED",
Username: "abc",
},
});
});
},
{
clock,
}
)
);
3 changes: 2 additions & 1 deletion src/services/triggers/customMessage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { Logger } from "../../log";
import { CognitoClient } from "../index";
import { Lambda } from "../lambda";
Expand All @@ -23,7 +24,7 @@ export type CustomMessageTrigger = (params: {
clientId: string;
username: string;
code: string;
userAttributes: readonly { Name: string; Value: string }[];
userAttributes: AttributeListType;
}) => Promise<CustomMessageResponse | null>;

export const CustomMessage = (
Expand Down
3 changes: 2 additions & 1 deletion src/services/triggers/postConfirmation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { Logger } from "../../log";
import { CognitoClient } from "../cognitoClient";
import { Lambda } from "../lambda";
Expand All @@ -11,7 +12,7 @@ export type PostConfirmationTrigger = (params: {
userPoolId: string;
clientId: string;
username: string;
userAttributes: readonly { Name: string; Value: string }[];
userAttributes: AttributeListType;
}) => Promise<void>;

export const PostConfirmation = (
Expand Down
4 changes: 2 additions & 2 deletions src/services/triggers/userMigration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import * as uuid from "uuid";
import { NotAuthorizedError, ResourceNotFoundError } from "../../errors";
import { Clock } from "../clock";
Expand All @@ -7,15 +8,14 @@ import {
attributesFromRecord,
attributesToRecord,
User,
UserAttribute,
} from "../userPoolClient";

export type UserMigrationTrigger = (params: {
userPoolId: string;
clientId: string;
username: string;
password: string;
userAttributes: readonly UserAttribute[];
userAttributes: AttributeListType;
}) => Promise<User>;

export const UserMigration = ({
Expand Down
4 changes: 2 additions & 2 deletions src/services/userPoolClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ClockFake } from "../__tests__/clockFake";
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { MockLogger } from "../__tests__/mockLogger";
import { DataStore } from "./dataStore";
import {
Expand All @@ -7,7 +8,6 @@ import {
attributesIncludeMatch,
attributesToRecord,
User,
UserAttribute,
UserPoolClient,
UserPoolClientService,
} from "./userPoolClient";
Expand Down Expand Up @@ -303,7 +303,7 @@ describe("User Pool Client", () => {
});

describe("attributes", () => {
const attributes: readonly UserAttribute[] = [
const attributes: AttributeListType = [
{ Name: "sub", Value: "uuid" },
{ Name: "email", Value: "example@example.com" },
];
Expand Down
26 changes: 11 additions & 15 deletions src/services/userPoolClient.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { Logger } from "../log";
import { AppClient, newId } from "./appClient";
import { Clock } from "./clock";
import { CreateDataStore, DataStore } from "./dataStore";

export interface UserAttribute {
Name: "sub" | "email" | "phone_number" | "preferred_username" | string;
Value: string;
}

export interface MFAOption {
DeliveryMedium: "SMS";
AttributeName: "phone_number";
Expand All @@ -16,29 +12,29 @@ export interface MFAOption {
export const attributesIncludeMatch = (
attributeName: string,
attributeValue: string,
attributes: readonly UserAttribute[]
attributes: AttributeListType | undefined
) =>
!!(attributes || []).find(
!!(attributes ?? []).find(
(x) => x.Name === attributeName && x.Value === attributeValue
);
export const attributesInclude = (
attributeName: string,
attributes: readonly UserAttribute[]
) => !!(attributes || []).find((x) => x.Name === attributeName);
attributes: AttributeListType | undefined
) => !!(attributes ?? []).find((x) => x.Name === attributeName);
export const attributeValue = (
attributeName: string,
attributes: readonly UserAttribute[]
) => (attributes || []).find((x) => x.Name === attributeName)?.Value;
attributes: AttributeListType | undefined
) => (attributes ?? []).find((x) => x.Name === attributeName)?.Value;
export const attributesToRecord = (
attributes: readonly UserAttribute[]
attributes: AttributeListType | undefined
): Record<string, string> =>
(attributes || []).reduce(
(attributes ?? []).reduce(
(acc, attr) => ({ ...acc, [attr.Name]: attr.Value }),
{}
);
export const attributesFromRecord = (
attributes: Record<string, string>
): readonly UserAttribute[] =>
): AttributeListType =>
Object.entries(attributes).map(([Name, Value]) => ({ Name, Value }));

export interface User {
Expand All @@ -47,7 +43,7 @@ export interface User {
UserLastModifiedDate: number;
Enabled: boolean;
UserStatus: "CONFIRMED" | "UNCONFIRMED" | "RESET_REQUIRED";
Attributes: readonly UserAttribute[];
Attributes: AttributeListType;
MFAOptions?: readonly MFAOption[];

// extra attributes for Cognito Local
Expand Down
44 changes: 19 additions & 25 deletions src/targets/adminCreateUser.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
import { AdminCreateUserRequest } from "aws-sdk/clients/cognitoidentityserviceprovider";
import uuid from "uuid";
import { UnsupportedError } from "../errors";
import { Services } from "../services";
import { attributesInclude, User } from "../services/userPoolClient";

interface Input {
UserPoolId: string;
Username: string;
TemporaryPassword: string;
MessageAction?: string;
UserAttributes?: any;
DesiredDeliveryMediums?: any;
}

interface Output {
User: User;
}

export type AdminCreateUserTarget = (body: Input) => Promise<User | null>;
export type AdminCreateUserTarget = (
body: AdminCreateUserRequest
) => Promise<{ User: User }>;

export const AdminCreateUser = ({
cognitoClient,
clock,
}: Services): AdminCreateUserTarget => async (body) => {
const { UserPoolId, Username, TemporaryPassword, UserAttributes } =
body || {};
const userPool = await cognitoClient.getUserPool(UserPoolId);
}: Services): AdminCreateUserTarget => async (req) => {
if (!req.TemporaryPassword) {
throw new UnsupportedError("AdminCreateUser without TemporaryPassword");
}

const attributes = attributesInclude("sub", UserAttributes)
? UserAttributes
: [{ Name: "sub", Value: uuid.v4() }, ...UserAttributes];
const userPool = await cognitoClient.getUserPool(req.UserPoolId);

const attributes = attributesInclude("sub", req.UserAttributes)
? req.UserAttributes ?? []
: [{ Name: "sub", Value: uuid.v4() }, ...(req.UserAttributes ?? [])];

const now = Math.floor(clock.get().getTime() / 1000);
const user: User = {
Username,
Password: TemporaryPassword,
Username: req.Username,
Password: req.TemporaryPassword,
Attributes: attributes,
Enabled: true,
UserStatus: "CONFIRMED",
Expand All @@ -42,6 +35,7 @@ export const AdminCreateUser = ({
};

await userPool.saveUser(user);
// TODO: Shuldn't return password.
return user;

// TODO: Shouldn't return password.
return { User: user };
};
10 changes: 0 additions & 10 deletions src/targets/describeUserPoolClient.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
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;
Expand Down
12 changes: 8 additions & 4 deletions src/targets/forgotPassword.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UserNotFoundError } from "../errors";
import { UnsupportedError, UserNotFoundError } from "../errors";
import { Services } from "../services";
import { DeliveryDetails } from "../services/messageDelivery/messageDelivery";
import { attributeValue } from "../services/userPoolClient";

interface Input {
ClientId: string;
Expand Down Expand Up @@ -29,12 +30,15 @@ export const ForgotPassword = ({
throw new UserNotFoundError();
}

const userEmail = attributeValue("email", user.Attributes);
if (!userEmail) {
throw new UnsupportedError("ForgotPassword without email on user");
}

const deliveryDetails: DeliveryDetails = {
AttributeName: "email",
DeliveryMedium: "EMAIL",
Destination: user.Attributes.filter((x) => x.Name === "email").map(
(x) => x.Value
)[0],
Destination: userEmail,
};

const code = otp();
Expand Down
5 changes: 3 additions & 2 deletions src/targets/getUser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import jwt from "jsonwebtoken";
import { InvalidParameterError } from "../errors";
import { Logger } from "../log";
import { Services } from "../services";
import { Token } from "../services/tokens";
import { MFAOption, UserAttribute } from "../services/userPoolClient";
import { MFAOption } from "../services/userPoolClient";

interface Input {
AccessToken: string;
}

interface Output {
Username: string;
UserAttributes: readonly UserAttribute[];
UserAttributes: AttributeListType;
MFAOptions?: readonly MFAOption[];
}

Expand Down
4 changes: 2 additions & 2 deletions src/targets/listUsers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AttributeListType } from "aws-sdk/clients/cognitoidentityserviceprovider";
import { Services } from "../services";
import { UserAttribute } from "../services/userPoolClient";

interface Input {
UserPoolId: string;
Expand All @@ -15,7 +15,7 @@ export interface DynamoDBUserRecord {
UserLastModifiedDate: number;
Enabled: boolean;
UserStatus: "CONFIRMED" | "UNCONFIRMED" | "RESET_REQUIRED";
Attributes: readonly UserAttribute[];
Attributes: AttributeListType;
}

interface Output {
Expand Down
14 changes: 4 additions & 10 deletions src/targets/signUp.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SignUpRequest } from "aws-sdk/clients/cognitoidentityserviceprovider";
import uuid from "uuid";
import { InvalidParameterError, UsernameExistsError } from "../errors";
import { Logger } from "../log";
Expand All @@ -9,13 +10,6 @@ import {
User,
} from "../services/userPoolClient";

interface Input {
ClientId: string;
Username: string;
Password: string;
UserAttributes: readonly { Name: string; Value: string }[];
}

interface Output {
UserConfirmed: boolean;
UserSub: string;
Expand All @@ -26,7 +20,7 @@ interface Output {
};
}

export type SignUpTarget = (body: Input) => Promise<Output>;
export type SignUpTarget = (body: SignUpRequest) => Promise<Output>;

export const SignUp = (
{ cognitoClient, clock, messageDelivery, messages, otp }: Services,
Expand All @@ -42,8 +36,8 @@ export const SignUp = (
}

const attributes = attributesInclude("sub", body.UserAttributes)
? body.UserAttributes
: [{ Name: "sub", Value: uuid.v4() }, ...body.UserAttributes];
? body.UserAttributes ?? []
: [{ Name: "sub", Value: uuid.v4() }, ...(body.UserAttributes ?? [])];

const now = Math.floor(clock.get().getTime() / 1000);
const user: User = {
Expand Down

0 comments on commit 26aa5a7

Please sign in to comment.