Skip to content

Commit cc8048b

Browse files
jagregorymaitojepoy
andcommitted
feat: support for GetUser api
Co-authored-by: Jeff Go <j3p0ygo@gmail.com>
1 parent b1fdf98 commit cc8048b

File tree

6 files changed

+196
-0
lines changed

6 files changed

+196
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ perfect, because it won't be.
1616
- [ConfirmSignUp](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmSignUp.html)
1717
- [CreateUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateUserPoolClient.html)
1818
- [ForgotPassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ForgotPassword.html)
19+
- [GetUser](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_GetUser.html)
1920
- [InitiateAuth](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html)
2021
- [ListUsers](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUsers.html)
2122
- [RespondToAuthChallenge](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_RespondToAuthChallenge.html)

src/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export class UnexpectedLambdaExceptionError extends CognitoError {
6262
}
6363
}
6464

65+
export class InvalidParameterError extends CognitoError {
66+
public constructor() {
67+
super("InvalidParameterException", "Invalid parameter");
68+
}
69+
}
70+
6571
export const unsupported = (message: string, res: Response) => {
6672
console.error(`Cognito Local unsupported feature: ${message}`);
6773
return res.status(500).json({

src/services/tokens.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ import * as uuid from "uuid";
33
import PrivateKey from "../keys/cognitoLocal.private.json";
44
import { User } from "./userPoolClient";
55

6+
export interface Token {
7+
client_id: string;
8+
iss: string;
9+
sub: string;
10+
token_use: string;
11+
username: string;
12+
event_id: string;
13+
scope: string;
14+
auth_time: Date;
15+
jti: string;
16+
}
17+
618
export function generateTokens(
719
user: User,
820
clientId: string,

src/targets/getUser.test.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { advanceTo } from "jest-date-mock";
2+
import jwt from "jsonwebtoken";
3+
import * as uuid from "uuid";
4+
import { InvalidParameterError } from "../errors";
5+
import PrivateKey from "../keys/cognitoLocal.private.json";
6+
import { CognitoClient, UserPoolClient } from "../services";
7+
import { Triggers } from "../services/triggers";
8+
import { GetUser, GetUserTarget } from "./getUser";
9+
10+
describe("GetUser target", () => {
11+
let getUser: GetUserTarget;
12+
let mockCognitoClient: jest.Mocked<CognitoClient>;
13+
let mockUserPoolClient: jest.Mocked<UserPoolClient>;
14+
let mockCodeDelivery: jest.Mock;
15+
let mockTriggers: jest.Mocked<Triggers>;
16+
let now: Date;
17+
18+
beforeEach(() => {
19+
now = new Date(2020, 1, 2, 3, 4, 5);
20+
advanceTo(now);
21+
22+
mockUserPoolClient = {
23+
config: {
24+
Id: "test",
25+
},
26+
createAppClient: jest.fn(),
27+
getUserByUsername: jest.fn(),
28+
listUsers: jest.fn(),
29+
saveUser: jest.fn(),
30+
};
31+
mockCognitoClient = {
32+
getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient),
33+
getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient),
34+
};
35+
mockCodeDelivery = jest.fn();
36+
mockTriggers = {
37+
enabled: jest.fn(),
38+
postConfirmation: jest.fn(),
39+
userMigration: jest.fn(),
40+
};
41+
42+
getUser = GetUser({
43+
cognitoClient: mockCognitoClient,
44+
codeDelivery: mockCodeDelivery,
45+
triggers: mockTriggers,
46+
});
47+
});
48+
49+
it("parses token get user by sub", async () => {
50+
mockUserPoolClient.getUserByUsername.mockResolvedValue({
51+
Attributes: [],
52+
UserStatus: "CONFIRMED",
53+
Password: "hunter2",
54+
Username: "0000-0000",
55+
Enabled: true,
56+
UserCreateDate: new Date().getTime(),
57+
UserLastModifiedDate: new Date().getTime(),
58+
ConfirmationCode: "1234",
59+
});
60+
61+
const output = await getUser({
62+
AccessToken: jwt.sign(
63+
{
64+
sub: "0000-0000",
65+
event_id: "0",
66+
token_use: "access",
67+
scope: "aws.cognito.signin.user.admin",
68+
auth_time: new Date(),
69+
jti: uuid.v4(),
70+
client_id: "test",
71+
username: "0000-0000",
72+
},
73+
PrivateKey.pem,
74+
{
75+
algorithm: "RS256",
76+
issuer: `http://localhost:9229/test`,
77+
expiresIn: "24h",
78+
keyid: "CognitoLocal",
79+
}
80+
),
81+
});
82+
83+
expect(output).toBeDefined();
84+
expect(output).toEqual({
85+
UserAttributes: [],
86+
Username: "0000-0000",
87+
});
88+
});
89+
90+
it("throws if token isn't valid", async () => {
91+
await expect(
92+
getUser({
93+
AccessToken: "blah",
94+
})
95+
).rejects.toBeInstanceOf(InvalidParameterError);
96+
});
97+
98+
it("returns null if user doesn't exist", async () => {
99+
mockUserPoolClient.getUserByUsername.mockResolvedValue(null);
100+
101+
const output = await getUser({
102+
AccessToken: jwt.sign(
103+
{
104+
sub: "0000-0000",
105+
event_id: "0",
106+
token_use: "access",
107+
scope: "aws.cognito.signin.user.admin",
108+
auth_time: new Date(),
109+
jti: uuid.v4(),
110+
client_id: "test",
111+
username: "0000-0000",
112+
},
113+
PrivateKey.pem,
114+
{
115+
algorithm: "RS256",
116+
issuer: `http://localhost:9229/test`,
117+
expiresIn: "24h",
118+
keyid: "CognitoLocal",
119+
}
120+
),
121+
});
122+
123+
expect(output).toBeNull();
124+
});
125+
});

src/targets/getUser.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import jwt from "jsonwebtoken";
2+
import { InvalidParameterError } from "../errors";
3+
import log from "../log";
4+
import { Services } from "../services";
5+
import { Token } from "../services/tokens";
6+
import { MFAOption, UserAttribute } from "../services/userPoolClient";
7+
8+
interface Input {
9+
AccessToken: string;
10+
}
11+
12+
interface Output {
13+
Username: string;
14+
UserAttributes: readonly UserAttribute[];
15+
MFAOptions?: readonly MFAOption[];
16+
}
17+
18+
export type GetUserTarget = (body: Input) => Promise<Output | null>;
19+
20+
export const GetUser = ({ cognitoClient }: Services): GetUserTarget => async (
21+
body
22+
) => {
23+
const decodedToken = jwt.decode(body.AccessToken) as Token | null;
24+
if (!decodedToken) {
25+
log.info("Unable to decode token");
26+
throw new InvalidParameterError();
27+
}
28+
29+
const { sub, client_id } = decodedToken;
30+
if (!sub || !client_id) {
31+
return null;
32+
}
33+
34+
const userPool = await cognitoClient.getUserPoolForClientId(client_id);
35+
const user = await userPool.getUserByUsername(sub);
36+
if (!user) {
37+
return null;
38+
}
39+
40+
const output: Output = {
41+
Username: user.Username,
42+
UserAttributes: user.Attributes,
43+
};
44+
45+
if (user.MFAOptions) {
46+
output.MFAOptions = user.MFAOptions;
47+
}
48+
49+
return output;
50+
};

src/targets/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { InitiateAuth } from "./initiateAuth";
88
import { ListUsers } from "./listUsers";
99
import { RespondToAuthChallenge } from "./respondToAuthChallenge";
1010
import { SignUp } from "./signUp";
11+
import { GetUser } from "./getUser";
1112

1213
export const Targets = {
1314
ConfirmForgotPassword,
@@ -18,6 +19,7 @@ export const Targets = {
1819
ListUsers,
1920
RespondToAuthChallenge,
2021
SignUp,
22+
GetUser,
2123
};
2224

2325
type TargetName = keyof typeof Targets;

0 commit comments

Comments
 (0)