Skip to content

Commit e45cd54

Browse files
authored
Merge pull request #101 from HackRU/user-exists
Add endpoint that allows hackers to look up other users
2 parents 8c3b88c + 819c3dc commit e45cd54

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed

serverless.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import getBuyIns from '@functions/get-buy-ins';
1818
import notifyByEmail from '@functions/notify-by-email';
1919
import verifyEmail from '@functions/verify-email';
2020
import deleteUser from '@functions/delete';
21+
import userExists from '@functions/user-exists';
2122

2223
import * as path from 'path';
2324
import * as dotenv from 'dotenv';
@@ -62,6 +63,7 @@ const serverlessConfiguration: AWS = {
6263
notifyByEmail,
6364
verifyEmail,
6465
deleteUser,
66+
userExists,
6567
},
6668
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
6769
custom: {

src/functions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export { default as getBuyIns } from './get-buy-ins';
1616
export { default as notifyByEmail } from './notify-by-email';
1717
export { default as verifyEmail } from './verify-email';
1818
export { default as delete } from './delete';
19+
export { default as userExists } from './user-exists';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
2+
import { middyfy } from '@libs/lambda';
3+
import schema from './schema';
4+
import { MongoDB, validateToken } from '../../util';
5+
import * as path from 'path';
6+
import * as dotenv from 'dotenv';
7+
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
8+
9+
const userExists: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
10+
try {
11+
//Check token validity
12+
const isValidToken = validateToken(event.body.auth_token, process.env.JWT_SECRET, event.body.auth_email);
13+
if (!isValidToken) {
14+
return {
15+
statusCode: 401,
16+
body: JSON.stringify({
17+
statuscode: 401,
18+
message: 'Unauthorized',
19+
}),
20+
};
21+
}
22+
23+
//Connect to DB
24+
const db = MongoDB.getInstance(process.env.MONGO_URI);
25+
await db.connect();
26+
const users = db.getCollection('users');
27+
28+
const authUser = await users.findOne({ email: event.body.auth_email });
29+
if (!authUser) {
30+
return {
31+
statusCode: 404,
32+
body: JSON.stringify({
33+
statuscode: 404,
34+
message: 'Auth user not found.',
35+
}),
36+
};
37+
}
38+
39+
//Check if user being looked up exists
40+
const lookupUser = await users.findOne(
41+
{ email: event.body.email.toLowerCase() },
42+
{ projection: { password: 0, _id: 0 } }
43+
);
44+
if (!lookupUser) {
45+
return {
46+
statusCode: 404,
47+
body: JSON.stringify({
48+
statusCode: 404,
49+
message: 'Look-up user was not found',
50+
}),
51+
};
52+
}
53+
//return that user exists
54+
return {
55+
statusCode: 200,
56+
body: JSON.stringify('User exists'),
57+
};
58+
} catch (error) {
59+
console.error('Error reading user:', error);
60+
return {
61+
statusCode: 500,
62+
body: JSON.stringify({
63+
statusCode: 500,
64+
message: 'Internal server error.',
65+
error,
66+
}),
67+
};
68+
}
69+
};
70+
export const main = middyfy(userExists);

src/functions/user-exists/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { handlerPath } from '@libs/handler-resolver';
2+
import schema from './schema';
3+
4+
export default {
5+
handler: `${handlerPath(__dirname)}/handler.main`,
6+
events: [
7+
{
8+
http: {
9+
method: 'post',
10+
path: 'user-exists',
11+
cors: true,
12+
request: {
13+
schemas: {
14+
'application/json': schema,
15+
},
16+
},
17+
},
18+
},
19+
],
20+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
type: 'object',
3+
properties: {
4+
auth_email: { type: 'string', format: 'email' },
5+
auth_token: { type: 'string' },
6+
email: { type: 'string', format: 'email' },
7+
},
8+
required: ['auth_email', 'auth_token', 'email'],
9+
} as const;

tests/user-exists.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { main } from '../src/functions/user-exists/handler';
2+
3+
import { createEvent, mockContext } from './helper';
4+
import * as util from '../src/util';
5+
6+
jest.mock('../src/util', () => ({
7+
// eslint-disable-next-line @typescript-eslint/naming-convention
8+
MongoDB: {
9+
getInstance: jest.fn().mockReturnValue({
10+
connect: jest.fn(),
11+
disconnect: jest.fn(),
12+
getCollection: jest.fn().mockReturnValue({
13+
findOne: jest.fn(),
14+
find: jest.fn().mockReturnValue({
15+
toArray: jest.fn(),
16+
}),
17+
}),
18+
}),
19+
},
20+
validateToken: jest.fn().mockReturnValueOnce(false).mockReturnValue(true),
21+
}));
22+
23+
describe('/user-exists endpoint', () => {
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
const path = '/user-exists';
28+
const httpMethod = 'POST';
29+
30+
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
31+
const mockCallback = jest.fn();
32+
33+
it('invalid token', async () => {
34+
const userData = {
35+
auth_email: 'hacker@hackru.org',
36+
auth_token: 'mockToken',
37+
email: 'hacker@hackru.org',
38+
};
39+
const mockEvent = createEvent(userData, path, httpMethod);
40+
41+
const res = await main(mockEvent, mockContext, mockCallback);
42+
expect(res.statusCode).toBe(401);
43+
expect(JSON.parse(res.body).message).toBe('Unauthorized');
44+
});
45+
46+
it('auth user not found', async () => {
47+
findOneMock.mockReturnValueOnce(null);
48+
const userData = {
49+
auth_email: 'non-existent-user@hackru.org',
50+
auth_token: 'mockToken',
51+
email: 'hacker@hackru.org',
52+
};
53+
const mockEvent = createEvent(userData, path, httpMethod);
54+
55+
const res = await main(mockEvent, mockContext, mockCallback);
56+
expect(res.statusCode).toBe(404);
57+
expect(JSON.parse(res.body).message).toBe('Auth user not found.');
58+
});
59+
60+
it('look-up user not found', async () => {
61+
findOneMock
62+
.mockReturnValueOnce({
63+
email: 'hackerCheck@hackru.org',
64+
})
65+
.mockReturnValueOnce(null);
66+
const userData = {
67+
auth_email: 'hackerCheck@hackru.org',
68+
auth_token: 'mockToken',
69+
email: 'non-existent-user@hackru.org',
70+
};
71+
const mockEvent = createEvent(userData, path, httpMethod);
72+
73+
const res = await main(mockEvent, mockContext, mockCallback);
74+
75+
expect(res.statusCode).toBe(404);
76+
expect(JSON.parse(res.body).message).toBe('Look-up user was not found');
77+
});
78+
79+
it('success case', async () => {
80+
//can check even if you don't have the admin role
81+
findOneMock
82+
.mockReturnValueOnce({
83+
email: 'hackerCheck@hackru.org',
84+
})
85+
.mockReturnValueOnce({});
86+
const userData = {
87+
auth_email: 'hackerCheck@hackru.org',
88+
auth_token: 'mockToken',
89+
email: 'hacker@hackru.org',
90+
};
91+
const mockEvent = createEvent(userData, path, httpMethod);
92+
93+
const res = await main(mockEvent, mockContext, mockCallback);
94+
95+
expect(res.statusCode).toBe(200);
96+
expect(res.body).toBeDefined();
97+
});
98+
99+
it('success case for all lookup', async () => {
100+
findOneMock
101+
.mockReturnValueOnce({
102+
email: 'hackerCheck@hackru.org',
103+
})
104+
.mockReturnValueOnce({ email: 'targetHacker@hackru.org' });
105+
const userData = {
106+
auth_email: 'hackerCheck@hackru.org',
107+
auth_token: 'mockToken',
108+
email: 'anyemail@hackru.org',
109+
};
110+
const mockEvent = createEvent(userData, path, httpMethod);
111+
const res = await main(mockEvent, mockContext, mockCallback);
112+
console.log(res.body);
113+
expect(res.statusCode).toBe(200);
114+
expect(JSON.parse(res.body)).toEqual('User exists');
115+
});
116+
});

0 commit comments

Comments
 (0)