[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: jaredhanson\n"
  },
  {
    "path": ".gitignore",
    "content": ".env\nvar\n\n# Node.js\nnode_modules/\nnpm-debug.log*\n\n# Mac OS X\n.DS_Store\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>"
  },
  {
    "path": "README.md",
    "content": "This example demonstrates how to use [Express](https://expressjs.com) 4.x and\n[Passport](https://www.passportjs.org) to log users in with [Facebook](https://www.facebook.com).\nUse this example as a starting point for your own web applications.\n\n## Quick Start\n\nTo get started with this example, clone the repository and install the\ndependencies.\n\n```bash\n$ git clone git@github.com:passport/express-4.x-facebook-example.git\n$ cd express-4.x-facebook-example\n$ npm install\n```\n\nThis example requires credentials from Facebook, which can be obtained by\n[creating](https://developers.facebook.com/docs/development/create-an-app) an\napp in the [App Dashboard](https://developers.facebook.com/apps).\nThe OAuth redirect URI of the app should be set to: `http://localhost:3000/oauth2/redirect/www.facebook.com`\n\nOnce credentials have been obtained, create a `.env` file and add the following\nenvironment variables:\n\n```\nFACEBOOK_CLIENT_ID={{INSERT_APP_ID_HERE}}\nFACEBOOK_CLIENT_SECRET={{INSERT_APP_SECRET_HERE}}\n```\n\nStart the server.\n\n```bash\n$ npm start\n```\n\nNavigate to [`http://localhost:3000`](http://localhost:3000).\n\n## Overview\n\nThis example illustrates how to use [Passport](https://www.passportjs.org) and\nthe [`passport-facebook`](https://www.passportjs.org/packages/passport-facebook/)\nstrategy within an [Express](https://expressjs.com) application to log users in\nwith [Facebook](https://www.facebook.com).\n\nThe example builds upon the scaffolding created by [Express generator](https://expressjs.com/en/starter/generator.html),\nand uses [EJS](https://ejs.co) as a view engine and plain CSS for styling.  This\nscaffolding was generated by executing:\n\n```\n$ express --view ejs express-4.x-facebook-example\n```\n\nThe example uses [SQLite](https://www.sqlite.org) for storing user accounts.\nSQLite is a lightweight database that works well for development, including this\nexample.\n\nAdded to the scaffolding are files which add authentication to the application.\n\n* [`boot/db.js`](boot/db.js)\n\n  This file initializes the database by creating the tables used to store user\n  accounts and credentials.\n\n* [`boot/auth.js`](boot/auth.js)\n\n  This file initializes Passport.  It configures the Facebook strategy and\n  supplies the serialization functions used for session management.\n\n* [`routes/auth.js`](routes/auth.js)\n\n  This file defines the routes used for authentication.  In particular, there\n  are three routes used to authenticate with Facebook:\n  \n  - `GET /login`\n  \n    This route renders a page that prompts the user to login with Facebook.\n  \n  - `GET /login/federated/www.facebook.com`\n  \n    This route begins the authentication sequence by redirecting the user to\n    Facebook.\n  \n  - `POST /oauth2/redirect/www.facebook.com`\n  \n    This route completes the authentication sequence when Facebook redirects the\n    user back to the application.  When a new user logs in, a user account is\n    automatically created and their Facebook account is linked.  When an\n    existing user returns, they are logged in to their linked account.\n\n## License\n\n[The Unlicense](https://opensource.org/licenses/unlicense)\n"
  },
  {
    "path": "app.js",
    "content": "require('dotenv').config();\n\nvar createError = require('http-errors');\nvar express = require('express');\nvar path = require('path');\nvar cookieParser = require('cookie-parser');\nvar session = require('express-session');\nvar csrf = require('csurf');\nvar passport = require('passport');\nvar logger = require('morgan');\n\n// pass the session to the connect sqlite3 module\n// allowing it to inherit from session.Store\nvar SQLiteStore = require('connect-sqlite3')(session);\n\nvar indexRouter = require('./routes/index');\nvar authRouter = require('./routes/auth');\n\nvar app = express();\n\n// view engine setup\napp.set('views', path.join(__dirname, 'views'));\napp.set('view engine', 'ejs');\n\napp.locals.pluralize = require('pluralize');\n\napp.use(logger('dev'));\napp.use(express.json());\napp.use(express.urlencoded({ extended: false }));\napp.use(cookieParser());\napp.use(express.static(path.join(__dirname, 'public')));\napp.use(session({\n  secret: 'keyboard cat',\n  resave: false, // don't save session if unmodified\n  saveUninitialized: false, // don't create session until something stored\n  store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })\n}));\napp.use(csrf());\napp.use(passport.authenticate('session'));\napp.use(function(req, res, next) {\n  var msgs = req.session.messages || [];\n  res.locals.messages = msgs;\n  res.locals.hasMessages = !! msgs.length;\n  req.session.messages = [];\n  next();\n});\napp.use(function(req, res, next) {\n  res.locals.csrfToken = req.csrfToken();\n  next();\n});\n\napp.use('/', indexRouter);\napp.use('/', authRouter);\n\n// catch 404 and forward to error handler\napp.use(function(req, res, next) {\n  next(createError(404));\n});\n\n// error handler\napp.use(function(err, req, res, next) {\n  // set locals, only providing error in development\n  res.locals.message = err.message;\n  res.locals.error = req.app.get('env') === 'development' ? err : {};\n\n  // render the error page\n  res.status(err.status || 500);\n  res.render('error');\n});\n\nmodule.exports = app;\n"
  },
  {
    "path": "bin/www",
    "content": "#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar app = require('../app');\nvar debug = require('debug')('todos:server');\nvar http = require('http');\n\n/**\n * Get port from environment and store in Express.\n */\n\nvar port = normalizePort(process.env.PORT || '3000');\napp.set('port', port);\n\n/**\n * Create HTTP server.\n */\n\nvar server = http.createServer(app);\n\n/**\n * Listen on provided port, on all network interfaces.\n */\n\nserver.listen(port);\nserver.on('error', onError);\nserver.on('listening', onListening);\n\n/**\n * Normalize a port into a number, string, or false.\n */\n\nfunction normalizePort(val) {\n  var port = parseInt(val, 10);\n\n  if (isNaN(port)) {\n    // named pipe\n    return val;\n  }\n\n  if (port >= 0) {\n    // port number\n    return port;\n  }\n\n  return false;\n}\n\n/**\n * Event listener for HTTP server \"error\" event.\n */\n\nfunction onError(error) {\n  if (error.syscall !== 'listen') {\n    throw error;\n  }\n\n  var bind = typeof port === 'string'\n    ? 'Pipe ' + port\n    : 'Port ' + port;\n\n  // handle specific listen errors with friendly messages\n  switch (error.code) {\n    case 'EACCES':\n      console.error(bind + ' requires elevated privileges');\n      process.exit(1);\n      break;\n    case 'EADDRINUSE':\n      console.error(bind + ' is already in use');\n      process.exit(1);\n      break;\n    default:\n      throw error;\n  }\n}\n\n/**\n * Event listener for HTTP server \"listening\" event.\n */\n\nfunction onListening() {\n  var addr = server.address();\n  var bind = typeof addr === 'string'\n    ? 'pipe ' + addr\n    : 'port ' + addr.port;\n  debug('Listening on ' + bind);\n}\n"
  },
  {
    "path": "db.js",
    "content": "var sqlite3 = require('sqlite3');\nvar mkdirp = require('mkdirp');\n\nmkdirp.sync('var/db');\n\nvar db = new sqlite3.Database('var/db/todos.db');\n\ndb.serialize(function() {\n  db.run(\"CREATE TABLE IF NOT EXISTS users ( \\\n    id INTEGER PRIMARY KEY, \\\n    username TEXT UNIQUE, \\\n    hashed_password BLOB, \\\n    salt BLOB, \\\n    name TEXT, \\\n    email TEXT UNIQUE, \\\n    email_verified INTEGER \\\n  )\");\n  \n  db.run(\"CREATE TABLE IF NOT EXISTS federated_credentials ( \\\n    id INTEGER PRIMARY KEY, \\\n    user_id INTEGER NOT NULL, \\\n    provider TEXT NOT NULL, \\\n    subject TEXT NOT NULL, \\\n    UNIQUE (provider, subject) \\\n  )\");\n  \n  db.run(\"CREATE TABLE IF NOT EXISTS todos ( \\\n    id INTEGER PRIMARY KEY, \\\n    owner_id INTEGER NOT NULL, \\\n    title TEXT NOT NULL, \\\n    completed INTEGER \\\n  )\");\n});\n\nmodule.exports = db;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"todos-express-facebook\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"description\": \"Todo app using Express and Passport for log in with Facebook.\",\n  \"keywords\": [\n    \"example\",\n    \"express\",\n    \"passport\",\n    \"sqlite\"\n  ],\n  \"author\": {\n    \"name\": \"Jared Hanson\",\n    \"email\": \"jaredhanson@gmail.com\",\n    \"url\": \"https://www.jaredhanson.me/\"\n  },\n  \"homepage\": \"https://github.com/passport/todos-express-facebook\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/passport/todos-express-facebook.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/passport/todos-express-facebook/issues\"\n  },\n  \"funding\": {\n    \"type\": \"github\",\n    \"url\": \"https://github.com/sponsors/jaredhanson\"\n  },\n  \"license\": \"Unlicense\",\n  \"scripts\": {\n    \"start\": \"node ./bin/www\"\n  },\n  \"dependencies\": {\n    \"connect-ensure-login\": \"^0.1.1\",\n    \"connect-sqlite3\": \"^0.9.13\",\n    \"cookie-parser\": \"~1.4.4\",\n    \"csurf\": \"^1.11.0\",\n    \"debug\": \"~2.6.9\",\n    \"dotenv\": \"^8.6.0\",\n    \"ejs\": \"~2.6.1\",\n    \"express\": \"~4.16.1\",\n    \"express-session\": \"^1.17.2\",\n    \"http-errors\": \"~1.6.3\",\n    \"mkdirp\": \"^1.0.4\",\n    \"morgan\": \"~1.9.1\",\n    \"passport\": \"^0.6.0\",\n    \"passport-facebook\": \"^3.0.0\",\n    \"pluralize\": \"^8.0.0\",\n    \"sqlite3\": \"^5.0.2\"\n  }\n}\n"
  },
  {
    "path": "public/css/app.css",
    "content": ".nav {\n\tposition: absolute;\n\ttop: -130px;\n\tright: 0;\n}\n\n.nav ul {\n\tmargin: 0;\n\tlist-style: none;\n\ttext-align: center;\n}\n\n.nav li {\n\tdisplay: inline-block;\n\theight: 40px;\n\tmargin-left: 12px;\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tline-height: 40px;\n}\n\n.nav a {\n\tdisplay: block;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\n.nav a:hover {\n\tborder-bottom: 1px solid #DB7676;\n}\n\n.nav button {\n\theight: 40px;\n}\n\n.nav button:hover {\n\tborder-bottom: 1px solid #DB7676;\n\tcursor: pointer;\n}\n\n/* background image by Cole Bemis <https://feathericons.com> */\n.nav .user {\n\tpadding-left: 20px;\n\tbackground-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E\");\n\tbackground-repeat: no-repeat;\n\tbackground-position: center left;\n}\n\n/* background image by Cole Bemis <https://feathericons.com> */\n.nav .logout {\n\tpadding-left: 20px;\n\tbackground-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-log-out'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'%3E%3C/path%3E%3Cpolyline points='16 17 21 12 16 7'%3E%3C/polyline%3E%3Cline x1='21' y1='12' x2='9' y2='12'%3E%3C/line%3E%3C/svg%3E%0A\");\n\tbackground-repeat: no-repeat;\n\tbackground-position: center left;\n}\n"
  },
  {
    "path": "public/css/base.css",
    "content": "hr {\n\tmargin: 20px 0;\n\tborder: 0;\n\tborder-top: 1px dashed #c5c5c5;\n\tborder-bottom: 1px dashed #f7f7f7;\n}\n\n.learn a {\n\tfont-weight: normal;\n\ttext-decoration: none;\n\tcolor: #b83f45;\n}\n\n.learn a:hover {\n\ttext-decoration: underline;\n\tcolor: #787e7e;\n}\n\n.learn h3,\n.learn h4,\n.learn h5 {\n\tmargin: 10px 0;\n\tfont-weight: 500;\n\tline-height: 1.2;\n\tcolor: #000;\n}\n\n.learn h3 {\n\tfont-size: 24px;\n}\n\n.learn h4 {\n\tfont-size: 18px;\n}\n\n.learn h5 {\n\tmargin-bottom: 0;\n\tfont-size: 14px;\n}\n\n.learn ul {\n\tpadding: 0;\n\tmargin: 0 0 30px 25px;\n}\n\n.learn li {\n\tline-height: 20px;\n}\n\n.learn p {\n\tfont-size: 15px;\n\tfont-weight: 300;\n\tline-height: 1.3;\n\tmargin-top: 0;\n\tmargin-bottom: 0;\n}\n\n#issue-count {\n\tdisplay: none;\n}\n\n.quote {\n\tborder: none;\n\tmargin: 20px 0 60px 0;\n}\n\n.quote p {\n\tfont-style: italic;\n}\n\n.quote p:before {\n\tcontent: '“';\n\tfont-size: 50px;\n\topacity: .15;\n\tposition: absolute;\n\ttop: -20px;\n\tleft: 3px;\n}\n\n.quote p:after {\n\tcontent: '”';\n\tfont-size: 50px;\n\topacity: .15;\n\tposition: absolute;\n\tbottom: -42px;\n\tright: 3px;\n}\n\n.quote footer {\n\tposition: absolute;\n\tbottom: -40px;\n\tright: 0;\n}\n\n.quote footer img {\n\tborder-radius: 3px;\n}\n\n.quote footer a {\n\tmargin-left: 5px;\n\tvertical-align: middle;\n}\n\n.speech-bubble {\n\tposition: relative;\n\tpadding: 10px;\n\tbackground: rgba(0, 0, 0, .04);\n\tborder-radius: 5px;\n}\n\n.speech-bubble:after {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 100%;\n\tright: 30px;\n\tborder: 13px solid transparent;\n\tborder-top-color: rgba(0, 0, 0, .04);\n}\n\n.learn-bar > .learn {\n\tposition: absolute;\n\twidth: 272px;\n\ttop: 8px;\n\tleft: -300px;\n\tpadding: 10px;\n\tborder-radius: 5px;\n\tbackground-color: rgba(255, 255, 255, .6);\n\ttransition-property: left;\n\ttransition-duration: 500ms;\n}\n\n@media (min-width: 899px) {\n\t.learn-bar {\n\t\twidth: auto;\n\t\tpadding-left: 300px;\n\t}\n\n\t.learn-bar > .learn {\n\t\tleft: 8px;\n\t}\n}\n"
  },
  {
    "path": "public/css/home.css",
    "content": ".todohome {\n\tmargin: 130px 0 40px 0;\n\tposition: relative;\n}\n\n.todohome h1 {\n\tposition: absolute;\n\ttop: -140px;\n\twidth: 100%;\n\tfont-size: 80px;\n\tfont-weight: 200;\n\ttext-align: center;\n\tcolor: #b83f45;\n\t-webkit-text-rendering: optimizeLegibility;\n\t-moz-text-rendering: optimizeLegibility;\n\ttext-rendering: optimizeLegibility;\n}\n\n.todohome section {\n\tpadding-top: 1px;\n\ttext-align: center;\n}\n\n.todohome h2 {\n\tpadding-bottom: 48px;\n\tfont-size: 28px;\n\tfont-weight: 300;\n}\n\n.todohome .button {\n\tpadding: 13px 45px;\n\tfont-size: 16px;\n\tfont-weight: 500;\n\tcolor: white;\n\tborder-radius: 5px;\n\tbackground: #d83f45;\n}\n\n.todohome a.button {\n\ttext-decoration: none;\n}\n"
  },
  {
    "path": "public/css/index.css",
    "content": "html,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbutton {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tfont-size: 100%;\n\tvertical-align: baseline;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tcolor: inherit;\n\t-webkit-appearance: none;\n\tappearance: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n\tfont: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n\tline-height: 1.4em;\n\tbackground: #f5f5f5;\n\tcolor: #111111;\n\tmin-width: 230px;\n\tmax-width: 550px;\n\tmargin: 0 auto;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tfont-weight: 300;\n}\n\n.hidden {\n\tdisplay: none;\n}\n\n.todoapp {\n\tbackground: #fff;\n\tmargin: 130px 0 40px 0;\n\tposition: relative;\n\tbox-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\n\t            0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp input::-webkit-input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 400;\n\tcolor: rgba(0, 0, 0, 0.4);\n}\n\n.todoapp input::-moz-placeholder {\n\tfont-style: italic;\n\tfont-weight: 400;\n\tcolor: rgba(0, 0, 0, 0.4);\n}\n\n.todoapp input::input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 400;\n\tcolor: rgba(0, 0, 0, 0.4);\n}\n\n.todoapp h1 {\n\tposition: absolute;\n\ttop: -140px;\n\twidth: 100%;\n\tfont-size: 80px;\n\tfont-weight: 200;\n\ttext-align: center;\n\tcolor: #b83f45;\n\t-webkit-text-rendering: optimizeLegibility;\n\t-moz-text-rendering: optimizeLegibility;\n\ttext-rendering: optimizeLegibility;\n}\n\n.new-todo,\n.edit {\n\tposition: relative;\n\tmargin: 0;\n\twidth: 100%;\n\tfont-size: 24px;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tline-height: 1.4em;\n\tcolor: inherit;\n\tpadding: 6px;\n\tborder: 1px solid #999;\n\tbox-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n\tbox-sizing: border-box;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.new-todo {\n\tpadding: 16px 16px 16px 60px;\n\theight: 65px;\n\tborder: none;\n\tbackground: rgba(0, 0, 0, 0.003);\n\tbox-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\n}\n\n.main {\n\tposition: relative;\n\tz-index: 2;\n\tborder-top: 1px solid #e6e6e6;\n}\n\n.toggle-all {\n\twidth: 1px;\n\theight: 1px;\n\tborder: none; /* Mobile Safari */\n\topacity: 0;\n\tposition: absolute;\n\tright: 100%;\n\tbottom: 100%;\n}\n\n.toggle-all + label {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 45px;\n\theight: 65px;\n\tfont-size: 0;\n\tposition: absolute;\n\ttop: -65px;\n\tleft: -0;\n}\n\n.toggle-all + label:before {\n\tcontent: '❯';\n\tdisplay: inline-block;\n\tfont-size: 22px;\n\tcolor: #949494;\n\tpadding: 10px 27px 10px 27px;\n\t-webkit-transform: rotate(90deg);\n\ttransform: rotate(90deg);\n}\n\n.toggle-all:checked + label:before {\n\tcolor: #484848;\n}\n\n.todo-list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.todo-list li {\n\tposition: relative;\n\tfont-size: 24px;\n\tborder-bottom: 1px solid #ededed;\n}\n\n.todo-list li:last-child {\n\tborder-bottom: none;\n}\n\n.todo-list li.editing {\n\tborder-bottom: none;\n\tpadding: 0;\n}\n\n.todo-list li.editing .edit {\n\tdisplay: block;\n\twidth: calc(100% - 43px);\n\tpadding: 12px 16px;\n\tmargin: 0 0 0 43px;\n}\n\n.todo-list li.editing .view {\n\tdisplay: none;\n}\n\n.todo-list li .toggle {\n\ttext-align: center;\n\twidth: 40px;\n\t/* auto, since non-WebKit browsers doesn't support input styling */\n\theight: auto;\n\tposition: absolute;\n\ttop: 0;\n\tbottom: 0;\n\tmargin: auto 0;\n\tborder: none; /* Mobile Safari */\n\t-webkit-appearance: none;\n\tappearance: none;\n}\n\n.todo-list li .toggle {\n\topacity: 0;\n}\n\n.todo-list li .toggle + label {\n\t/*\n\t\tFirefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433\n\t\tIE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/\n\t*/\n\tbackground-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');\n\tbackground-repeat: no-repeat;\n\tbackground-position: center left;\n}\n\n.todo-list li .toggle:checked + label {\n\tbackground-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');\n}\n\n.todo-list li label {\n\tword-break: break-all;\n\tpadding: 15px 15px 15px 60px;\n\tdisplay: block;\n\tline-height: 1.2;\n\ttransition: color 0.4s;\n\tfont-weight: 400;\n\tcolor: #484848;\n}\n\n.todo-list li.completed label {\n\tcolor: #949494;\n\ttext-decoration: line-through;\n}\n\n.todo-list li .destroy {\n\tdisplay: none;\n\tposition: absolute;\n\ttop: 0;\n\tright: 10px;\n\tbottom: 0;\n\twidth: 40px;\n\theight: 40px;\n\tmargin: auto 0;\n\tfont-size: 30px;\n\tcolor: #949494;\n\ttransition: color 0.2s ease-out;\n}\n\n.todo-list li .destroy:hover,\n.todo-list li .destroy:focus {\n\tcolor: #C18585;\n}\n\n.todo-list li .destroy:after {\n\tcontent: '×';\n\tdisplay: block;\n\theight: 100%;\n\tline-height: 1.1;\n}\n\n.todo-list li:hover .destroy {\n\tdisplay: block;\n}\n\n.todo-list li .edit {\n\tdisplay: none;\n}\n\n.todo-list li.editing:last-child {\n\tmargin-bottom: -1px;\n}\n\n.footer {\n\tpadding: 10px 15px;\n\theight: 20px;\n\ttext-align: center;\n\tfont-size: 15px;\n\tborder-top: 1px solid #e6e6e6;\n}\n\n.footer:before {\n\tcontent: '';\n\tposition: absolute;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\theight: 50px;\n\toverflow: hidden;\n\tbox-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\n\t            0 8px 0 -3px #f6f6f6,\n\t            0 9px 1px -3px rgba(0, 0, 0, 0.2),\n\t            0 16px 0 -6px #f6f6f6,\n\t            0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todo-count {\n\tfloat: left;\n\ttext-align: left;\n}\n\n.todo-count strong {\n\tfont-weight: 300;\n}\n\n.filters {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n\tposition: absolute;\n\tright: 0;\n\tleft: 0;\n}\n\n.filters li {\n\tdisplay: inline;\n}\n\n.filters li a {\n\tcolor: inherit;\n\tmargin: 3px;\n\tpadding: 3px 7px;\n\ttext-decoration: none;\n\tborder: 1px solid transparent;\n\tborder-radius: 3px;\n}\n\n.filters li a:hover {\n\tborder-color: #DB7676;\n}\n\n.filters li a.selected {\n\tborder-color: #CE4646;\n}\n\n.clear-completed,\nhtml .clear-completed:active {\n\tfloat: right;\n\tposition: relative;\n\tline-height: 19px;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.clear-completed:hover {\n\ttext-decoration: underline;\n}\n\n.info {\n\tmargin: 65px auto 0;\n\tcolor: #4d4d4d;\n\tfont-size: 11px;\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\ttext-align: center;\n}\n\n.info p {\n\tline-height: 1;\n}\n\n.info a {\n\tcolor: inherit;\n\ttext-decoration: none;\n\tfont-weight: 400;\n}\n\n.info a:hover {\n\ttext-decoration: underline;\n}\n\n/*\n\tHack to remove background from Mobile Safari.\n\tCan't use it globally since it destroys checkboxes in Firefox\n*/\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n\t.toggle-all,\n\t.todo-list li .toggle {\n\t\tbackground: none;\n\t}\n\n\t.todo-list li .toggle {\n\t\theight: 40px;\n\t}\n}\n\n@media (max-width: 430px) {\n\t.footer {\n\t\theight: 50px;\n\t}\n\n\t.filters {\n\t\tbottom: 10px;\n\t}\n}\n\n:focus,\n.toggle:focus + label,\n.toggle-all:focus + label {\n\tbox-shadow: 0 0 2px 2px #CF7D7D;\n\toutline: 0;\n}\n"
  },
  {
    "path": "public/css/login.css",
    "content": ".prompt {\n\tmax-width: 400px;\n\tmargin: 50px auto;\n\tpadding: 25px;\n\tbackground: #fff;\n\tborder: 1px solid #e6e6e6;\n\tborder-radius: 8px;\n}\n\nbutton {\n\tdisplay: block;\n\tpadding: 10px;\n\twidth: 100%;\n\tborder-radius: 3px;\n\tbackground: #d83f45;\n\tfont-size: 14px;\n\tfont-weight: 700;\n\tcolor: white;\n\tcursor: pointer;\n}\n\na.button {\n\tbox-sizing: border-box;\n\tdisplay: block;\n\tpadding: 10px;\n\twidth: 100%;\n\tborder-radius: 3px;\n\tbackground: #000;\n\tfont-size: 14px;\n\tfont-weight: 700;\n\ttext-align: center;\n\ttext-decoration: none;\n\tcolor: white;\n}\n\na.facebook {\n\tbackground: #4267b2;\n}\n\nbutton:hover {\n\tbackground-color: #c83f45;\n}\n\nh1 {\n\tmargin: 0 0 20px 0;\n\tpadding: 0 0 5px 0;\n\tfont-size: 24px;\n\tfont-weight: 500;\n}\n\nh3 {\n\tmargin-top: 0;\n\tfont-size: 24px;\n\tfont-weight: 300;\n\ttext-align: center;\n\tcolor: #b83f45;\n}\n\nform section {\n\tmargin: 0 0 20px 0;\n\tposition: relative; /* for password toggle positioning */\n}\n\nlabel {\n\tdisplay: block;\n\tmargin: 0 0 3px 0;\n\tfont-size: 14px;\n\tfont-weight: 500;\n}\n\ninput {\n\tbox-sizing: border-box;\n\twidth: 100%;\n\tpadding: 10px;\n\tfont-size: 14px;\n\tborder: 1px solid #d9d9d9;\n\tborder-radius: 5px;\n}\n\ninput[type=email]:not(:focus):invalid,\ninput[type=password]:not(:focus):invalid {\n\tcolor: red;\n\toutline-color: red;\n}\n\nhr {\n\tborder-top: 1px solid #d9d9d9;\n\tborder-bottom: none;\n}\n\np.help {\n\ttext-align: center;\n\tfont-weight: 400;\n}\n\n/* background image by Cole Bemis <https://feathericons.com> */\n.messages p {\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tline-height: 1.3;\n\tcolor: #d83f45;\n\tpadding-left: 20px;\n\tbackground-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d83f45' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-alert-circle'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E\");\n\tbackground-repeat: no-repeat;\n\tbackground-position: center left;\n}\n"
  },
  {
    "path": "routes/auth.js",
    "content": "var express = require('express');\nvar passport = require('passport');\nvar FacebookStrategy = require('passport-facebook');\nvar db = require('../db');\n\n\npassport.use(new FacebookStrategy({\n  clientID: process.env['FACEBOOK_CLIENT_ID'],\n  clientSecret: process.env['FACEBOOK_CLIENT_SECRET'],\n  callbackURL: '/oauth2/redirect/facebook',\n  state: true\n}, function verify(accessToken, refreshToken, profile, cb) {\n  db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [\n    'https://www.facebook.com',\n    profile.id\n  ], function(err, row) {\n    if (err) { return cb(err); }\n    if (!row) {\n      db.run('INSERT INTO users (name) VALUES (?)', [\n        profile.displayName\n      ], function(err) {\n        if (err) { return cb(err); }\n        var id = this.lastID;\n        db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [\n          id,\n          'https://www.facebook.com',\n          profile.id\n        ], function(err) {\n          if (err) { return cb(err); }\n          var user = {\n            id: id,\n            name: profile.displayName\n          };\n          return cb(null, user);\n        });\n      });\n    } else {\n      db.get('SELECT * FROM users WHERE id = ?', [ row.user_id ], function(err, row) {\n        if (err) { return cb(err); }\n        if (!row) { return cb(null, false); }\n        return cb(null, row);\n      });\n    }\n  });\n}));\n\npassport.serializeUser(function(user, cb) {\n  process.nextTick(function() {\n    cb(null, { id: user.id, username: user.username, name: user.name });\n  });\n});\n\npassport.deserializeUser(function(user, cb) {\n  process.nextTick(function() {\n    return cb(null, user);\n  });\n});\n\n\nvar router = express.Router();\n\nrouter.get('/login', function(req, res, next) {\n  res.render('login');\n});\n\nrouter.get('/login/federated/facebook', passport.authenticate('facebook'));\n\nrouter.get('/oauth2/redirect/facebook', passport.authenticate('facebook', {\n  successReturnToOrRedirect: '/',\n  failureRedirect: '/login'\n}));\n\nrouter.post('/logout', function(req, res, next) {\n  req.logout(function(err) {\n    if (err) { return next(err); }\n    res.redirect('/');\n  });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/index.js",
    "content": "var express = require('express');\nvar ensureLogIn = require('connect-ensure-login').ensureLoggedIn;\nvar db = require('../db');\n\nvar ensureLoggedIn = ensureLogIn();\n\nfunction fetchTodos(req, res, next) {\n  db.all('SELECT * FROM todos WHERE owner_id = ?', [\n    req.user.id\n  ], function(err, rows) {\n    if (err) { return next(err); }\n    \n    var todos = rows.map(function(row) {\n      return {\n        id: row.id,\n        title: row.title,\n        completed: row.completed == 1 ? true : false,\n        url: '/' + row.id\n      }\n    });\n    res.locals.todos = todos;\n    res.locals.activeCount = todos.filter(function(todo) { return !todo.completed; }).length;\n    res.locals.completedCount = todos.length - res.locals.activeCount;\n    next();\n  });\n}\n\nvar router = express.Router();\n\n/* GET home page. */\nrouter.get('/', function(req, res, next) {\n  if (!req.user) { return res.render('home'); }\n  next();\n}, fetchTodos, function(req, res, next) {\n  res.locals.filter = null;\n  res.render('index', { user: req.user });\n});\n\nrouter.get('/active', ensureLoggedIn, fetchTodos, function(req, res, next) {\n  res.locals.todos = res.locals.todos.filter(function(todo) { return !todo.completed; });\n  res.locals.filter = 'active';\n  res.render('index', { user: req.user });\n});\n\nrouter.get('/completed', ensureLoggedIn, fetchTodos, function(req, res, next) {\n  res.locals.todos = res.locals.todos.filter(function(todo) { return todo.completed; });\n  res.locals.filter = 'completed';\n  res.render('index', { user: req.user });\n});\n\nrouter.post('/', ensureLoggedIn, function(req, res, next) {\n  req.body.title = req.body.title.trim();\n  next();\n}, function(req, res, next) {\n  if (req.body.title !== '') { return next(); }\n  return res.redirect('/' + (req.body.filter || ''));\n}, function(req, res, next) {\n  db.run('INSERT INTO todos (owner_id, title, completed) VALUES (?, ?, ?)', [\n    req.user.id,\n    req.body.title,\n    req.body.completed == true ? 1 : null\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n});\n\nrouter.post('/:id(\\\\d+)', ensureLoggedIn, function(req, res, next) {\n  req.body.title = req.body.title.trim();\n  next();\n}, function(req, res, next) {\n  if (req.body.title !== '') { return next(); }\n  db.run('DELETE FROM todos WHERE id = ? AND owner_id = ?', [\n    req.params.id,\n    req.user.id\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n}, function(req, res, next) {\n  db.run('UPDATE todos SET title = ?, completed = ? WHERE id = ? AND owner_id = ?', [\n    req.body.title,\n    req.body.completed !== undefined ? 1 : null,\n    req.params.id,\n    req.user.id\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n});\n\nrouter.post('/:id(\\\\d+)/delete', ensureLoggedIn, function(req, res, next) {\n  db.run('DELETE FROM todos WHERE id = ? AND owner_id = ?', [\n    req.params.id,\n    req.user.id\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n});\n\nrouter.post('/toggle-all', ensureLoggedIn, function(req, res, next) {\n  db.run('UPDATE todos SET completed = ? WHERE owner_id = ?', [\n    req.body.completed !== undefined ? 1 : null,\n    req.user.id\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n});\n\nrouter.post('/clear-completed', ensureLoggedIn, function(req, res, next) {\n  db.run('DELETE FROM todos WHERE owner_id = ? AND completed = ?', [\n    req.user.id,\n    1\n  ], function(err) {\n    if (err) { return next(err); }\n    return res.redirect('/' + (req.body.filter || ''));\n  });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "views/error.ejs",
    "content": "<h1><%= message %></h1>\n<h2><%= error.status %></h2>\n<pre><%= error.stack %></pre>\n"
  },
  {
    "path": "views/home.ejs",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t\t<title>Express • TodoMVC</title>\n\t\t<link rel=\"stylesheet\" href=\"/css/base.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/index.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/home.css\">\n\t</head>\n\t<body>\n\t\t<section class=\"todohome\">\n\t\t\t<header>\n\t\t\t\t<h1>todos</h1>\n\t\t\t</header>\n\t\t\t<section>\n\t\t\t\t<h2>todos helps you get things done</h2>\n\t\t\t\t<a class=\"button\" href=\"/login\">Sign in</a>\n\t\t\t</section>\n\t\t</section>\n\t\t<footer class=\"info\">\n\t\t\t<p>Created by <a href=\"https://www.jaredhanson.me\">Jared Hanson</a></p>\n\t\t\t<p>Part of <a href=\"https://todomvc.com\">TodoMVC</a></p>\n\t\t\t<p>Authentication powered by <a href=\"https://www.passportjs.org\">Passport</a></p>\n\t\t</footer>\n\t</body>\n</html>\n"
  },
  {
    "path": "views/index.ejs",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t\t<title>Express • TodoMVC</title>\n\t\t<link rel=\"stylesheet\" href=\"/css/base.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/index.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/app.css\">\n\t</head>\n\t<body>\n\t\t<section class=\"todoapp\">\n\t\t\t<nav class=\"nav\">\n\t\t\t\t<ul>\n\t\t\t\t\t<li class=\"user\"><%= user.name || user.username %></li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<form action=\"/logout\" method=\"post\">\n\t\t\t\t\t\t\t<button class=\"logout\" type=\"submit\">Sign out</button>\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t\t<header class=\"header\">\n\t\t\t\t<h1>todos</h1>\n\t\t\t\t<form action=\"/\" method=\"post\">\n\t\t\t\t\t<input class=\"new-todo\" name=\"title\" placeholder=\"What needs to be done?\" autofocus>\n\t\t\t\t\t<% if (filter) { %>\n\t\t\t\t\t<input type=\"hidden\" name=\"filter\" value=\"<%= filter %>\"/>\n\t\t\t\t\t<% } %>\n\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t</form>\n\t\t\t</header>\n\t\t\t<% if (activeCount + completedCount > 0) { %>\n\t\t\t<section class=\"main\">\n\t\t\t\t<form action=\"/toggle-all\" method=\"post\">\n\t\t\t\t\t<input id=\"toggle-all\" class=\"toggle-all\" type=\"checkbox\" name=\"completed\" <%- activeCount == 0 ? 'checked' : '' %> onchange=\"this.form.submit();\">\n\t\t\t\t\t<label for=\"toggle-all\">Mark all as complete</label>\n\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t</form>\n\t\t\t\t<ul class=\"todo-list\">\n\t\t\t\t\t<% todos.forEach(function(todo) { %>\n\t\t\t\t\t<li <%- todo.completed ? 'class=\"completed\"' : '' %>>\n\t\t\t\t\t\t<form action=\"<%= todo.url %>\" method=\"post\">\n\t\t\t\t\t\t\t<div class=\"view\">\n\t\t\t\t\t\t\t\t<input class=\"toggle\" type=\"checkbox\" name=\"completed\" <%- todo.completed ? 'checked' : '' %> onchange=\"this.form.submit();\">\n\t\t\t\t\t\t\t\t<label ondblclick=\"this.closest('li').className = this.closest('li').className + ' editing'; this.closest('li').querySelector('input.edit').focus(); this.closest('li').querySelector('input.edit').value = ''; this.closest('li').querySelector('input.edit').value = '<%= todo.title %>';\"><%= todo.title %></label>\n\t\t\t\t\t\t\t\t<button class=\"destroy\" form=\"delete-<%= todo.id %>\"></button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<input class=\"edit\" name=\"title\" value=\"<%= todo.title %>\" onkeyup=\"if (event.keyCode == 27) { this.setAttribute('data-esc', ''); this.closest('li').className = this.closest('li').className.replace('editing', ''); }\" onblur=\"if (this.getAttribute('data-esc') !== null) { return this.removeAttribute('data-esc'); } this.form.submit();\">\n\t\t\t\t\t\t\t<% if (filter) { %>\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"filter\" value=\"<%= filter %>\"/>\n\t\t\t\t\t\t\t<% } %>\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t\t\t</form>\n\t\t\t\t\t\t<form id=\"delete-<%= todo.id %>\" action=\"<%= todo.url %>/delete\" method=\"post\">\n\t\t\t\t\t\t\t<% if (filter) { %>\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"filter\" value=\"<%= filter %>\"/>\n\t\t\t\t\t\t\t<% } %>\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t\t\t</form>\n\t\t\t\t\t</li>\n\t\t\t\t\t<% }); %>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t\t<% } %>\n\t\t\t<% if (activeCount + completedCount > 0) { %>\n\t\t\t<footer class=\"footer\">\n\t\t\t\t<span class=\"todo-count\"><strong><%= activeCount %></strong> <%= pluralize('item', activeCount) %> left</span>\n\t\t\t\t<ul class=\"filters\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a <%- !filter ? 'class=\"selected\"' : '' %> href=\"/\">All</a>\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a <%- filter == 'active' ? 'class=\"selected\"' : '' %> href=\"/active\">Active</a>\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<a <%- filter == 'completed' ? 'class=\"selected\"' : '' %> href=\"/completed\">Completed</a>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<% if (completedCount > 0) { %>\n\t\t\t\t<form action=\"/clear-completed\" method=\"post\">\n\t\t\t\t\t<button class=\"clear-completed\">Clear completed</button>\n\t\t\t\t\t<% if (filter) { %>\n\t\t\t\t\t<input type=\"hidden\" name=\"filter\" value=\"<%= filter %>\"/>\n\t\t\t\t\t<% } %>\n\t\t\t\t\t<input type=\"hidden\" name=\"_csrf\" value=\"<%= csrfToken %>\">\n\t\t\t\t</form>\n\t\t\t\t<% } %>\n\t\t\t</footer>\n\t\t\t<% } %>\n\t\t</section>\n\t\t<footer class=\"info\">\n\t\t\t<p>Double-click to edit a todo</p>\n\t\t\t<p>Created by <a href=\"https://www.jaredhanson.me\">Jared Hanson</a></p>\n\t\t\t<p>Part of <a href=\"https://todomvc.com\">TodoMVC</a></p>\n\t\t\t<p>Authentication powered by <a href=\"https://www.passportjs.org\">Passport</a></p>\n\t\t</footer>\n\t</body>\n</html>\n"
  },
  {
    "path": "views/login.ejs",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t\t<title>Express • TodoMVC</title>\n\t\t<link rel=\"stylesheet\" href=\"/css/base.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/index.css\">\n\t\t<link rel=\"stylesheet\" href=\"/css/login.css\">\n\t</head>\n\t<body>\n\t\t<section class=\"prompt\">\n\t\t\t<h3>todos</h3>\n\t\t\t<h1>Sign in</h1>\n\t\t\t<% if (hasMessages) { %>\n\t\t\t<section class=\"messages\">\n\t\t\t\t<% messages.forEach(function(message) { %>\n\t\t\t\t<p><%= message %></p>\n\t\t\t\t<% }); %>\n\t\t\t</section>\n\t\t\t<% } %>\n\t\t\t<a class=\"button facebook\" href=\"/login/federated/facebook\">Sign in with Facebook</a>\n\t\t</section>\n\t\t<footer class=\"info\">\n\t\t\t<p>Created by <a href=\"https://www.jaredhanson.me\">Jared Hanson</a></p>\n\t\t\t<p>Part of <a href=\"https://todomvc.com\">TodoMVC</a></p>\n\t\t\t<p>Authentication powered by <a href=\"https://www.passportjs.org\">Passport</a></p>\n\t\t</footer>\n\t</body>\n</html>\n"
  }
]