Skip to content

Commit d686a12

Browse files
committed
signup login logout with tests done
1 parent 697fd64 commit d686a12

11 files changed

Lines changed: 413 additions & 15 deletions

File tree

examples_new/microservices/auth/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples_new/microservices/auth/package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
"main": "index.js",
66
"scripts": {
77
"start": "ts-node-dev ./src/index.ts",
8-
"test": "echo \"Error: no test specified\" && exit 1"
8+
"test": "jest --watchAll --no-cache"
9+
},
10+
"jest": {
11+
"preset": "ts-jest",
12+
"testEnvironment": "node",
13+
"setupFilesAfterEnv": [
14+
"./src/test/setup.ts"
15+
]
916
},
1017
"keywords": [],
1118
"author": "",
@@ -14,7 +21,9 @@
1421
"@chronosrx/common": "^1.0.1",
1522
"bcryptjs": "^2.4.3",
1623
"cookie-parser": "^1.4.6",
24+
"dotenv": "^16.3.1",
1725
"express": "^4.18.2",
26+
"express-async-errors": "^3.1.1",
1827
"jsonwebtoken": "^9.0.2",
1928
"mongoose": "^8.0.3",
2029
"ts-node-dev": "^2.0.0"
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import request from 'supertest';
2+
import { app } from '../app';
3+
4+
// Mongo Memory Server - Users collection always starts out empty**
5+
6+
// 1) Fails with bad request error either username or password are not provided
7+
it('fails if either username or password are not provided', async () => {
8+
await request(app)
9+
.post('/api/auth/login')
10+
.send({
11+
username: 'AYYYYY',
12+
})
13+
.expect(400);
14+
15+
await request(app)
16+
.post('/api/auth/login')
17+
.send({
18+
password: 'CUH',
19+
})
20+
.expect(400);
21+
});
22+
23+
// 2) Fails with bad request error if user does not exist in the database
24+
it('user does not exist', async () => {
25+
await request(app)
26+
.post('/api/auth/login')
27+
.send({
28+
username: 'nonexistentuser',
29+
password: 'test',
30+
})
31+
.expect(400);
32+
});
33+
34+
// 3) Fails with BadRequest Error if passwords do not match
35+
it('fails if passwords do not match', async () => {
36+
await request(app)
37+
.post('/api/auth/signup')
38+
.send({
39+
username: 'test',
40+
password: 'rightTest',
41+
})
42+
.expect(201);
43+
44+
await request(app)
45+
.post('/api/auth/login')
46+
.send({
47+
username: 'test',
48+
password: 'wrongTest',
49+
})
50+
.expect(400);
51+
});
52+
53+
// 4) Succeeds if username and password match a user in the database
54+
it('Users has correct password', async () => {
55+
await request(app)
56+
.post('/api/auth/signup')
57+
.send({
58+
username: 'test',
59+
password: 'test123',
60+
})
61+
.expect(201);
62+
63+
const response = await request(app)
64+
.post('/api/auth/login')
65+
.send({
66+
username: 'test',
67+
password: 'test123',
68+
})
69+
.expect(200);
70+
71+
expect(response.get('Set-Cookie')).toBeDefined();
72+
expect(response.get('Set-Cookie')[0].split('=')[0]).toEqual('token');
73+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import request from 'supertest';
2+
import { app } from '../app';
3+
4+
// Mongo Memory Server - Users collection always starts out empty**
5+
6+
// 1) Clears the cookie on logout
7+
// Means that 'Set-Cookie' header is defined
8+
//200 means request is successful, 201 means request is successful and result generated
9+
it('Clears the cookie on logout', async () => {
10+
await request(app)
11+
.post('/api/auth/logout')
12+
.send({
13+
username: 'test',
14+
password: 'test',
15+
})
16+
.expect(200);
17+
18+
const response = await request(app).post('/api/auth/logout').send().expect(200);
19+
20+
console.log(response.get('Set-Cookie'));
21+
expect(response.get('Set-Cookie')).toBeDefined();
22+
// expect(response.get('Set-Cookie')[0].split('=')[0]).toEqual('token');
23+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import request from 'supertest';
2+
import { app } from '../app';
3+
import { User } from '../models/user';
4+
5+
it('fails with 400 if no username/password provided', async () => {
6+
await request(app)
7+
.post('/api/auth/signup')
8+
.send({
9+
password: 'wheremyname',
10+
})
11+
.expect(400);
12+
13+
await request(app)
14+
.post('/api/auth/signup')
15+
.send({
16+
username: 'wheremypassword',
17+
})
18+
.expect(400);
19+
});
20+
21+
it('fails with 400 with invalid password', async () => {
22+
await request(app)
23+
.post('/api/auth/signup')
24+
.send({
25+
username: 'validuser',
26+
password: 'b',
27+
})
28+
.expect(400);
29+
});
30+
31+
it('Does not allow duplicate username signup ', async () => {
32+
await request(app)
33+
.post('/api/auth/signup')
34+
.send({
35+
username: 'test',
36+
password: 'test',
37+
})
38+
.expect(201);
39+
40+
await request(app)
41+
.post('/api/auth/signup')
42+
.send({
43+
username: 'test',
44+
password: 'test',
45+
})
46+
.expect(400);
47+
});
48+
49+
it('creates a user with valid inputs', async () => {
50+
await request(app)
51+
.post('/api/auth/signup')
52+
.send({
53+
username: 'test',
54+
password: 'test',
55+
})
56+
.expect(201);
57+
58+
const users = await User.find({});
59+
expect(users[0].username).toEqual('test');
60+
});
61+
62+
it('Sets a cookie on successful signup', async () => {
63+
const response = await request(app)
64+
.post('/api/auth/signup')
65+
.send({
66+
username: 'test',
67+
password: 'test',
68+
})
69+
.expect(201);
70+
71+
expect(response.get('Set-Cookie')).toBeDefined();
72+
expect(response.get('Set-Cookie')[0].split('=')[0]).toEqual('token');
73+
});

examples_new/microservices/auth/src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import express from 'express';
2+
import 'express-async-errors';
3+
import dotenv from 'dotenv';
4+
dotenv.config();
25
import { NotFoundError, errorHandler } from '@chronosrx/common';
36
import authRouter from './routes/auth-router';
47

58
const app = express();
69

10+
app.use(express.json());
11+
712
// app.get('/', (req, res) => {
813
// console.log('💥 Test Route');
914
// res.status(200).send({ msg: '💥 Test route' });
Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,87 @@
1+
import { BadRequestError } from '@chronosrx/common';
12
import { Request, Response } from 'express';
3+
import { User } from '../models/user';
4+
import { attachCookie } from '../../util/attachCookie';
25

36
export const signup = async (req: Request, res: Response) => {
4-
console.log('💥 authController signup');
5-
res.send({ msg: '💥 authController signup' });
7+
// console.log('💥 authController signup');
8+
const { username, password } = req.body;
9+
10+
// Validate inputs
11+
if (!username || !password || password.length < 4) {
12+
throw new BadRequestError('Invalid inputs');
13+
}
14+
15+
// Check to see if user with supplied username already exists
16+
const existingUser = await User.findOne({ username });
17+
if (existingUser) {
18+
throw new BadRequestError('User with that username exists');
19+
}
20+
21+
// create the user document
22+
const newUser = User.build({
23+
username,
24+
password,
25+
});
26+
// save newly created user document to the database
27+
await newUser.save();
28+
29+
// create a JWT w/ userId store on it
30+
// note: createJwt method created on the userSchema
31+
const token = newUser.createJwt();
32+
// set cookie on response object with name 'token' and value of the jwt
33+
// attachCookie method - defined in util folder
34+
attachCookie(res, token);
35+
36+
//
37+
res.status(201).send(newUser);
638
};
739

840
export const login = async (req: Request, res: Response) => {
9-
console.log('💥 authController login');
10-
res.send({ msg: '💥 authController login' });
41+
// console.log('💥 authController login');
42+
// pull username and password off request body
43+
const { username, password } = req.body;
44+
// validate username and password - they exist
45+
if (!username || !password) {
46+
throw new BadRequestError('Must provide username and password');
47+
}
48+
// query database for user with that username
49+
const existingUser = await User.findOne({ username });
50+
// handle case where no user exists with that username
51+
if (!existingUser) {
52+
throw new BadRequestError('Invalid credentials');
53+
}
54+
55+
// if user does exist - compare provided password to user's password in DB
56+
// * we defined a comparePassword method on the userSchema -> accepts provided password as argument
57+
// handle case when passwords do not match
58+
const passwordsMatch = await existingUser.comparePassword(password);
59+
if (!passwordsMatch) {
60+
throw new BadRequestError('Invalid credentials');
61+
}
62+
63+
// if passwords do match - create a JWT
64+
// * we created a method createJwt on the userSchema that returns the jwt (aka token)
65+
const token = existingUser.createJwt();
66+
// attach the jwt to the cookie
67+
// * we defined an attachCookie helper function (in util folder) - is already imported for us
68+
// accepts the response object and jwt/token as arguments
69+
attachCookie(res, token);
70+
// send back the found user with status code 200
71+
res.status(200).send(existingUser);
1172
};
1273

1374
export const logout = async (req: Request, res: Response) => {
14-
console.log('💥 authController logout');
15-
res.send({ msg: '💥 authController logout' });
75+
// console.log('💥 authController logout');
76+
77+
// Set cookie on response object with name 'token' to null
78+
// make the cookie httpOnly
79+
// set cookie expiration to 500ms from now
80+
res.cookie('token', null, {
81+
httpOnly: true,
82+
secure: false,
83+
expires: new Date(Date.now() + 500),
84+
});
85+
86+
res.status(200).send({ message: 'success' });
1687
};
Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
import { DbConnectionError } from '@chronosrx/common';
12
import { app } from './app';
3+
import mongoose from 'mongoose';
24

35
const PORT = process.env.PORT || 3000;
46

57
const start = async () => {
6-
if (!process.env.MONGO_URI) throw new Error('MONGO_URI must be defined')
7-
if (!process.env.JWT_SECRET) throw new Error('JWT_SECRET must be defined')
8-
if (!process.env.JWT_LIFETIME) throw new Error('JWT_LIFETIME must be defined')
8+
if (!process.env.MONGO_URI) throw new Error('MONGO_URI must be defined');
9+
if (!process.env.JWT_KEY) throw new Error('JWT_KEY must be defined');
10+
if (!process.env.JWT_LIFETIME) throw new Error('JWT_LIFETIME must be defined');
911

10-
app.listen(PORT, () => {
12+
try {
13+
await mongoose.connect(process.env.MONGO_URI, {});
14+
console.log('🍃 Connected to MongoDB');
15+
} catch (err) {
16+
throw new DbConnectionError();
17+
}
18+
19+
app.listen(PORT, async () => {
1120
console.log(`💥 App listening on ${PORT}`);
1221
});
1322
};
1423

15-
start()
24+
start();

0 commit comments

Comments
 (0)