mirror of
https://github.com/usatiuk/ustk-todolist.git
synced 2025-10-28 07:37:49 +01:00
create, update, remove users
This commit is contained in:
28
app.js
28
app.js
@@ -1,14 +1,28 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const morgan = require('morgan');
|
||||
const cors = require('cors');
|
||||
const config = require('./config');
|
||||
const db = require('./config/db');
|
||||
const app = require('./config/app');
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(cors());
|
||||
app.use(morgan('dev'));
|
||||
|
||||
require('./models/User');
|
||||
require('./models/TodoList');
|
||||
require('./models/Todo');
|
||||
|
||||
const passport = require('./config/passport');
|
||||
|
||||
app.use(passport.initialize());
|
||||
|
||||
app.use('/lists', require('./routes/lists'));
|
||||
app.use('/todos', require('./routes/todos'));
|
||||
app.use('/users', require('./routes/users'));
|
||||
|
||||
// 404 route
|
||||
app.use((req, res) => {
|
||||
@@ -31,17 +45,19 @@ app.use((req, res) => {
|
||||
app.use((error, req, res, next) => {
|
||||
switch (error.name) {
|
||||
case 'ValidationError':
|
||||
case 'MissingPasswordError':
|
||||
case 'BadRequestError':
|
||||
res.status(400);
|
||||
res.json({ success: false, error });
|
||||
break;
|
||||
case 'UnauthorizedError':
|
||||
res.status(401);
|
||||
res.json({ success: false, error });
|
||||
break;
|
||||
case 'NotFound':
|
||||
res.status(404);
|
||||
res.json({ success: false, error });
|
||||
break;
|
||||
case 'BadRequestError':
|
||||
res.status(400);
|
||||
res.json({ success: false, error });
|
||||
break;
|
||||
default:
|
||||
res.status(500);
|
||||
res.json({ success: false, error });
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const methodOverride = require('method-override');
|
||||
const morgan = require('morgan');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(cors());
|
||||
|
||||
app.use(morgan('dev'));
|
||||
|
||||
app.use(methodOverride('_method'));
|
||||
|
||||
module.exports = app;
|
||||
@@ -9,6 +9,7 @@ const dev = {
|
||||
port: process.env.DEV_DB_PORT || 27017,
|
||||
name: process.env.DEV_DB_NAME || 'todolist',
|
||||
},
|
||||
secret: process.env.DEV_SECRET || 'devsecret',
|
||||
};
|
||||
const test = {
|
||||
app: {
|
||||
@@ -19,6 +20,7 @@ const test = {
|
||||
port: process.env.TEST_DB_PORT || 27017,
|
||||
name: process.env.TEST_DB_NAME || 'todolistTest',
|
||||
},
|
||||
secret: process.env.TEST_SECRET || 'testsecret',
|
||||
};
|
||||
|
||||
const config = {
|
||||
|
||||
8
config/passport.js
Normal file
8
config/passport.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const passport = require('passport');
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const User = mongoose.model('User');
|
||||
|
||||
passport.use(User.createStrategy());
|
||||
|
||||
module.exports = passport;
|
||||
25
models/User.js
Normal file
25
models/User.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const mongoose = require('mongoose');
|
||||
const passportLocalMongoose = require('passport-local-mongoose');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const { secret } = require('../config');
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const UserSchema = Schema({ username: { type: String, required: true } });
|
||||
|
||||
UserSchema.plugin(passportLocalMongoose);
|
||||
|
||||
UserSchema.methods.generateJwt = function () {
|
||||
return jwt.sign({ id: this._id, username: this.username }, secret, { expiresIn: '1y' });
|
||||
};
|
||||
|
||||
UserSchema.methods.toAuthJson = function () {
|
||||
return {
|
||||
id: this._id,
|
||||
username: this.username,
|
||||
jwt: this.generateJwt(),
|
||||
};
|
||||
};
|
||||
|
||||
mongoose.model('User', UserSchema);
|
||||
2938
package-lock.json
generated
2938
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,13 @@
|
||||
"cors": "^2.8.4",
|
||||
"dotenv": "^5.0.1",
|
||||
"express": "^4.16.3",
|
||||
"method-override": "^2.3.10",
|
||||
"express-jwt": "^5.3.1",
|
||||
"jsonwebtoken": "^8.2.1",
|
||||
"mongoose": "^5.1.1",
|
||||
"morgan": "^1.9.0"
|
||||
"morgan": "^1.9.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-local-mongoose": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^4.19.1",
|
||||
|
||||
7
routes/auth.js
Normal file
7
routes/auth.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const jwt = require('express-jwt');
|
||||
const { secret } = require('../config');
|
||||
|
||||
module.exports = {
|
||||
required: jwt({ secret }),
|
||||
optional: jwt({ secret, credentialsRequired: false }),
|
||||
};
|
||||
@@ -43,11 +43,7 @@ router.patch(
|
||||
if (completed !== undefined) {
|
||||
patch.completed = completed;
|
||||
}
|
||||
const todo = await Todo.findByIdAndUpdate(
|
||||
{ _id: todoId },
|
||||
{ $set: patch },
|
||||
{ new: true },
|
||||
).exec();
|
||||
const todo = await Todo.findByIdAndUpdate(todoId, { $set: patch }, { new: true }).exec();
|
||||
if (!todo) {
|
||||
throw new NotFoundError(`can't find todo with id ${todoId}`);
|
||||
}
|
||||
|
||||
63
routes/users.js
Normal file
63
routes/users.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const express = require('express');
|
||||
const mongoose = require('mongoose');
|
||||
const passport = require('passport');
|
||||
|
||||
const User = mongoose.model('User');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const asyncHelper = require('../asyncHelper');
|
||||
const auth = require('./auth');
|
||||
|
||||
const { NotFoundError } = require('../errors');
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHelper(async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = new User({ username });
|
||||
await user.setPassword(password);
|
||||
await user.save();
|
||||
res.json({ success: true, data: user.toAuthJson() });
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/user',
|
||||
auth.required,
|
||||
asyncHelper(async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const patch = {};
|
||||
if (username !== undefined) {
|
||||
patch.username = username;
|
||||
}
|
||||
const user = await User.findByIdAndUpdate(req.user.id, { $set: patch }, { new: true }).exec();
|
||||
if (!user) {
|
||||
throw new NotFoundError(`can't find user with username ${req.user.username}`);
|
||||
}
|
||||
if (password !== undefined) {
|
||||
await user.setPassword(password);
|
||||
await user.save();
|
||||
}
|
||||
res.json({ success: true, data: user.toAuthJson() });
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/user',
|
||||
auth.required,
|
||||
asyncHelper(async (req, res) => {
|
||||
await User.findByIdAndRemove(req.user.id).exec();
|
||||
res.json({ success: true });
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/login',
|
||||
passport.authenticate('local', { session: false }),
|
||||
asyncHelper(async (req, res) => {
|
||||
res.json({ success: true, data: req.user.toAuthJson() });
|
||||
}),
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
164
tests/integration/users.test.js
Normal file
164
tests/integration/users.test.js
Normal file
@@ -0,0 +1,164 @@
|
||||
const server = require('../../app.js');
|
||||
|
||||
const request = require('supertest');
|
||||
const mongoose = require('mongoose');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const db = require('../../config/db');
|
||||
const { secret } = require('../../config');
|
||||
|
||||
const User = mongoose.model('User');
|
||||
|
||||
let user;
|
||||
let token;
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.connect();
|
||||
|
||||
user = new User({ username: 'User' });
|
||||
await user.setPassword('password');
|
||||
await user.save();
|
||||
token = user.generateJwt();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await User.remove({}).exec();
|
||||
await db.disconnect();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
describe('test users', () => {
|
||||
test('should create user', async () => {
|
||||
const response = await request(server)
|
||||
.post('/users')
|
||||
.send({
|
||||
username: 'User 2',
|
||||
password: 'password 2',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeTruthy();
|
||||
const tokenDecoded = jwt.verify(response.body.data.jwt, secret);
|
||||
expect(tokenDecoded.username).toEqual('User 2');
|
||||
const userAuth = await User.authenticate()('User 2', 'password 2');
|
||||
expect(userAuth.user).toBeTruthy();
|
||||
});
|
||||
test('should not create user with no username', async () => {
|
||||
const response = await request(server)
|
||||
.post('/users')
|
||||
.send({
|
||||
username: '',
|
||||
password: 'password 2',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeFalsy();
|
||||
});
|
||||
test('should not create user with no password', async () => {
|
||||
const response = await request(server)
|
||||
.post('/users')
|
||||
.send({
|
||||
username: 'User',
|
||||
password: '',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeFalsy();
|
||||
});
|
||||
test('should login user', async () => {
|
||||
const response = await request(server)
|
||||
.post('/users/login')
|
||||
.send({
|
||||
username: 'User',
|
||||
password: 'password',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeTruthy();
|
||||
const tokenDecoded = jwt.verify(response.body.data.jwt, secret);
|
||||
expect(tokenDecoded.username).toEqual('User');
|
||||
});
|
||||
test('should not login user with no name', async () => {
|
||||
await request(server)
|
||||
.post('/users/login')
|
||||
.send({
|
||||
username: '',
|
||||
password: 'notpassword',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
test('should not login user with wrong password', async () => {
|
||||
await request(server)
|
||||
.post('/users/login')
|
||||
.send({
|
||||
username: 'User',
|
||||
password: 'notpassword',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(401);
|
||||
});
|
||||
test('should update user', async () => {
|
||||
const response = await request(server)
|
||||
.patch('/users/user')
|
||||
.send({
|
||||
username: 'User 2',
|
||||
password: 'password2',
|
||||
})
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeTruthy();
|
||||
const tokenDecoded = jwt.verify(response.body.data.jwt, secret);
|
||||
expect(tokenDecoded.username).toEqual('User 2');
|
||||
const userAuth = await User.authenticate()('User 2', 'password2');
|
||||
expect(userAuth.user).toBeTruthy();
|
||||
});
|
||||
test('should not update user without authentication', async () => {
|
||||
const response = await request(server)
|
||||
.patch('/users/user')
|
||||
.send({
|
||||
username: 'User 2',
|
||||
password: 'password2',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(401);
|
||||
expect(response.body.success).toBeFalsy();
|
||||
});
|
||||
test('should not delete user without authentication', async () => {
|
||||
const response = await request(server)
|
||||
.delete('/users/user')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(401);
|
||||
expect(response.body.success).toBeFalsy();
|
||||
expect(await User.findOne({ username: 'User' }).exec()).toBeTruthy();
|
||||
});
|
||||
test('should delete user', async () => {
|
||||
const response = await request(server)
|
||||
.delete('/users/user')
|
||||
.set('Authorization', `Bearer ${token}`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8');
|
||||
expect(response.body.success).toBeTruthy();
|
||||
expect(await User.findOne({ username: 'User' }).exec()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user