Fixes for CSRF
parent
9a8ccb817f
commit
cd3ba1bc60
|
@ -2508,6 +2508,16 @@
|
||||||
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
|
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"csrf": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
|
||||||
|
"requires": {
|
||||||
|
"rndm": "1.2.0",
|
||||||
|
"tsscmp": "1.0.6",
|
||||||
|
"uid-safe": "2.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||||
|
@ -2524,6 +2534,41 @@
|
||||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
|
||||||
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
|
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
|
||||||
},
|
},
|
||||||
|
"csurf": {
|
||||||
|
"version": "1.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz",
|
||||||
|
"integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.0",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"csrf": "3.1.0",
|
||||||
|
"http-errors": "1.7.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||||
|
},
|
||||||
|
"http-errors": {
|
||||||
|
"version": "1.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||||
|
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||||
|
"requires": {
|
||||||
|
"depd": "1.1.2",
|
||||||
|
"inherits": "2.0.4",
|
||||||
|
"setprototypeof": "1.1.1",
|
||||||
|
"statuses": "1.5.0",
|
||||||
|
"toidentifier": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"currently-unhandled": {
|
"currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -9667,6 +9712,11 @@
|
||||||
"glob": "7.1.5"
|
"glob": "7.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rndm": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
||||||
|
},
|
||||||
"run-async": {
|
"run-async": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
||||||
|
@ -10758,6 +10808,11 @@
|
||||||
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tsscmp": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
|
||||||
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"connect-mongodb-session": "^2.2.0",
|
"connect-mongodb-session": "^2.2.0",
|
||||||
"cookie-parser": "^1.4.4",
|
"cookie-parser": "^1.4.4",
|
||||||
"countries-list": "^2.5.0",
|
"countries-list": "^2.5.0",
|
||||||
|
"csurf": "^1.11.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-handlebars": "^3.1.0",
|
"express-handlebars": "^3.1.0",
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
/* eslint-disable prefer-arrow-callback, no-var, no-tabs */
|
/* eslint-disable prefer-arrow-callback, no-var, no-tabs */
|
||||||
/* globals showNotification, slugify, numeral, moment, feather */
|
/* globals showNotification, slugify, numeral, moment, feather */
|
||||||
$(document).ready(function (){
|
$(document).ready(function (){
|
||||||
|
$.ajaxSetup({
|
||||||
|
headers: {
|
||||||
|
'csrf-token': $('meta[name="csrfToken"]').attr('content')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on('click', '#btnGenerateAPIkey', function(e){
|
$(document).on('click', '#btnGenerateAPIkey', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|
|
@ -9,9 +9,11 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const mime = require('mime-type/with-db');
|
const mime = require('mime-type/with-db');
|
||||||
|
const csrf = require('csurf');
|
||||||
const { validateJson } = require('../lib/schema');
|
const { validateJson } = require('../lib/schema');
|
||||||
const ObjectId = require('mongodb').ObjectID;
|
const ObjectId = require('mongodb').ObjectID;
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const csrfProtection = csrf({ cookie: true });
|
||||||
|
|
||||||
// Regex
|
// Regex
|
||||||
const emailRegex = /\S+@\S+\.\S+/;
|
const emailRegex = /\S+@\S+\.\S+/;
|
||||||
|
@ -30,6 +32,15 @@ router.get('/admin/logout', (req, res) => {
|
||||||
res.redirect('/');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Used for tests only
|
||||||
|
if(process.env.NODE_ENV === 'test'){
|
||||||
|
router.get('/admin/csrf', csrfProtection, (req, res, next) => {
|
||||||
|
res.json({
|
||||||
|
csrf: req.csrfToken()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// login form
|
// login form
|
||||||
router.get('/admin/login', async (req, res) => {
|
router.get('/admin/login', async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
@ -134,7 +145,7 @@ router.post('/admin/setup_action', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// dashboard
|
// dashboard
|
||||||
router.get('/admin/dashboard', restrict, async (req, res) => {
|
router.get('/admin/dashboard', csrfProtection, restrict, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
// Collate data for dashboard
|
// Collate data for dashboard
|
||||||
|
@ -183,12 +194,13 @@ router.get('/admin/dashboard', restrict, async (req, res) => {
|
||||||
message: common.clearSessionValue(req.session, 'message'),
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config
|
config: req.app.config,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
router.get('/admin/settings', restrict, (req, res) => {
|
router.get('/admin/settings', csrfProtection, restrict, (req, res) => {
|
||||||
res.render('settings', {
|
res.render('settings', {
|
||||||
title: 'Cart settings',
|
title: 'Cart settings',
|
||||||
session: req.session,
|
session: req.session,
|
||||||
|
@ -199,7 +211,8 @@ router.get('/admin/settings', restrict, (req, res) => {
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
footerHtml: typeof req.app.config.footerHtml !== 'undefined' ? escape.decode(req.app.config.footerHtml) : null,
|
footerHtml: typeof req.app.config.footerHtml !== 'undefined' ? escape.decode(req.app.config.footerHtml) : null,
|
||||||
googleAnalytics: typeof req.app.config.googleAnalytics !== 'undefined' ? escape.decode(req.app.config.googleAnalytics) : null
|
googleAnalytics: typeof req.app.config.googleAnalytics !== 'undefined' ? escape.decode(req.app.config.googleAnalytics) : null,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -236,7 +249,7 @@ router.post('/admin/settings/update', restrict, checkAccess, (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// settings menu
|
// settings menu
|
||||||
router.get('/admin/settings/menu', restrict, async (req, res) => {
|
router.get('/admin/settings/menu', csrfProtection, restrict, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
res.render('settings-menu', {
|
res.render('settings-menu', {
|
||||||
title: 'Cart menu',
|
title: 'Cart menu',
|
||||||
|
@ -246,12 +259,13 @@ router.get('/admin/settings/menu', restrict, async (req, res) => {
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
menu: common.sortMenu(await common.getMenu(db))
|
menu: common.sortMenu(await common.getMenu(db)),
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// page list
|
// page list
|
||||||
router.get('/admin/settings/pages', restrict, async (req, res) => {
|
router.get('/admin/settings/pages', csrfProtection, restrict, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
const pages = await db.pages.find({}).toArray();
|
const pages = await db.pages.find({}).toArray();
|
||||||
|
|
||||||
|
@ -264,12 +278,13 @@ router.get('/admin/settings/pages', restrict, async (req, res) => {
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
menu: common.sortMenu(await common.getMenu(db))
|
menu: common.sortMenu(await common.getMenu(db)),
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// pages new
|
// pages new
|
||||||
router.get('/admin/settings/pages/new', restrict, checkAccess, async (req, res) => {
|
router.get('/admin/settings/pages/new', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
res.render('settings-page', {
|
res.render('settings-page', {
|
||||||
|
@ -281,12 +296,13 @@ router.get('/admin/settings/pages/new', restrict, checkAccess, async (req, res)
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
menu: common.sortMenu(await common.getMenu(db))
|
menu: common.sortMenu(await common.getMenu(db)),
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// pages editor
|
// pages editor
|
||||||
router.get('/admin/settings/pages/edit/:page', restrict, checkAccess, async (req, res) => {
|
router.get('/admin/settings/pages/edit/:page', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
const page = await db.pages.findOne({ _id: common.getId(req.params.page) });
|
const page = await db.pages.findOne({ _id: common.getId(req.params.page) });
|
||||||
const menu = common.sortMenu(await common.getMenu(db));
|
const menu = common.sortMenu(await common.getMenu(db));
|
||||||
|
@ -312,7 +328,8 @@ router.get('/admin/settings/pages/edit/:page', restrict, checkAccess, async (req
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
menu
|
menu,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -434,7 +451,7 @@ router.post('/admin/validatePermalink', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Discount codes
|
// Discount codes
|
||||||
router.get('/admin/settings/discounts', restrict, checkAccess, async (req, res) => {
|
router.get('/admin/settings/discounts', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
const discounts = await db.discounts.find({}).toArray();
|
const discounts = await db.discounts.find({}).toArray();
|
||||||
|
@ -447,12 +464,13 @@ router.get('/admin/settings/discounts', restrict, checkAccess, async (req, res)
|
||||||
admin: true,
|
admin: true,
|
||||||
message: common.clearSessionValue(req.session, 'message'),
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers
|
helpers: req.handlebars.helpers,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edit a discount code
|
// Edit a discount code
|
||||||
router.get('/admin/settings/discount/edit/:id', restrict, checkAccess, async (req, res) => {
|
router.get('/admin/settings/discount/edit/:id', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
const discount = await db.discounts.findOne({ _id: common.getId(req.params.id) });
|
const discount = await db.discounts.findOne({ _id: common.getId(req.params.id) });
|
||||||
|
@ -465,7 +483,8 @@ router.get('/admin/settings/discount/edit/:id', restrict, checkAccess, async (re
|
||||||
message: common.clearSessionValue(req.session, 'message'),
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config
|
config: req.app.config,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -524,7 +543,7 @@ router.post('/admin/settings/discount/update', restrict, checkAccess, async (req
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a discount code
|
// Create a discount code
|
||||||
router.get('/admin/settings/discount/new', restrict, checkAccess, async (req, res) => {
|
router.get('/admin/settings/discount/new', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
res.render('settings-discount-new', {
|
res.render('settings-discount-new', {
|
||||||
title: 'Discount code create',
|
title: 'Discount code create',
|
||||||
session: req.session,
|
session: req.session,
|
||||||
|
@ -532,12 +551,13 @@ router.get('/admin/settings/discount/new', restrict, checkAccess, async (req, re
|
||||||
message: common.clearSessionValue(req.session, 'message'),
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
messageType: common.clearSessionValue(req.session, 'messageType'),
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
helpers: req.handlebars.helpers,
|
helpers: req.handlebars.helpers,
|
||||||
config: req.app.config
|
config: req.app.config,
|
||||||
|
csrfToken: req.csrfToken()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a discount code
|
// Create a discount code
|
||||||
router.post('/admin/settings/discount/create', restrict, checkAccess, async (req, res) => {
|
router.post('/admin/settings/discount/create', csrfProtection, restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
// Doc to insert
|
// Doc to insert
|
||||||
|
|
|
@ -76,6 +76,11 @@ const runBefore = async () => {
|
||||||
await g.db.orders.insertOne(order);
|
await g.db.orders.insertOne(order);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get csrf token
|
||||||
|
const csrf = await g.request
|
||||||
|
.get('/admin/csrf');
|
||||||
|
g.csrf = csrf.body.csrf;
|
||||||
|
|
||||||
// Index everything
|
// Index everything
|
||||||
await runIndexing(app);
|
await runIndexing(app);
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,7 @@ test('[Fail] Add a bogus code', async t => {
|
||||||
.send({
|
.send({
|
||||||
discountCode: 'some_bogus_code'
|
discountCode: 'some_bogus_code'
|
||||||
})
|
})
|
||||||
|
.set('csrf-token', g.csrf)
|
||||||
.expect(400);
|
.expect(400);
|
||||||
|
|
||||||
t.deepEqual(res.body.message, 'Discount code is invalid or expired');
|
t.deepEqual(res.body.message, 'Discount code is invalid or expired');
|
||||||
|
@ -156,6 +157,7 @@ test('[Success] Create a new discount code', async t => {
|
||||||
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
})
|
})
|
||||||
.set('apiKey', g.users[0].apiKey)
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.set('csrf-token', g.csrf)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
t.deepEqual(res.body.message, 'Discount code created successfully');
|
t.deepEqual(res.body.message, 'Discount code created successfully');
|
||||||
|
@ -173,6 +175,7 @@ test('[Fail] Create a new discount code with invalid type', async t => {
|
||||||
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
})
|
})
|
||||||
.set('apiKey', g.users[0].apiKey)
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.set('csrf-token', g.csrf)
|
||||||
.expect(400);
|
.expect(400);
|
||||||
|
|
||||||
t.deepEqual(res.body[0].message, 'should be equal to one of the allowed values');
|
t.deepEqual(res.body[0].message, 'should be equal to one of the allowed values');
|
||||||
|
@ -190,6 +193,7 @@ test('[Fail] Create a new discount code with existing code', async t => {
|
||||||
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
})
|
})
|
||||||
.set('apiKey', g.users[0].apiKey)
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.set('csrf-token', g.csrf)
|
||||||
.expect(400);
|
.expect(400);
|
||||||
|
|
||||||
t.deepEqual(res.body.message, 'Discount code already exists');
|
t.deepEqual(res.body.message, 'Discount code already exists');
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<meta name="description" content="{{snip config.cartDescription}}">
|
<meta name="description" content="{{snip config.cartDescription}}">
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<meta name="csrfToken" content="{{csrfToken}}">
|
||||||
<meta name="keywords" content="{{config.cartTitle}}">
|
<meta name="keywords" content="{{config.cartTitle}}">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha256-L/W5Wfqfa0sdBNIKN9cG6QA5F2qx4qICmU2VgLruv9Y=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha256-L/W5Wfqfa0sdBNIKN9cG6QA5F2qx4qICmU2VgLruv9Y=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="/stylesheets/pushy{{config.env}}.css">
|
<link rel="stylesheet" href="/stylesheets/pushy{{config.env}}.css">
|
||||||
|
|
Loading…
Reference in New Issue