From 0280794b0c08c80fef74d94507ab391ea2feabd2 Mon Sep 17 00:00:00 2001 From: Ashelyn Dawn Date: Sun, 8 Mar 2020 17:12:29 -0600 Subject: [PATCH] Login / logout --- api/auth.js | 29 +++++++++++++++++----- api/index.js | 6 ++--- api/middleware/session.js | 20 +++++++++++++++ api/users.js | 10 +++++++- components/header/header.js | 2 +- components/hero/hero.js | 4 +-- db/index.js | 5 ++-- db/mappings/user.js | 3 ++- db/models/session.js | 30 ++++++++++++++++++++++- db/models/user.js | 4 +-- db/sql/3-functions.sql | 29 ++++++++++++++++------ package-lock.json | 49 +++++++++++++++++++++++++++++++++++++ package.json | 1 + pages/_app.js | 11 ++++++--- pages/login.js | 7 +++++- pages/register.js | 22 +++++++++++++++++ 16 files changed, 199 insertions(+), 33 deletions(-) create mode 100644 api/middleware/session.js create mode 100644 pages/register.js diff --git a/api/auth.js b/api/auth.js index e47c2bf..df84b87 100644 --- a/api/auth.js +++ b/api/auth.js @@ -17,18 +17,35 @@ router.post('/', parseJSON, loginValidation, async (req, res) => { req.body.password ) - // TODO: Create session + if(!user){ + const err = new Error("Invalid login") + err.status = 400 + throw err + } + + const session = await db.session.create( + user.uuid, + req.ip, + req.get('User Agent') || "", + req.get('Referrer') || "", + null + ) + + req.session.uuid = session.uuid - if(user) - res.json(user) - else - res.status(400).json({errors: [{param: 'password', msg: 'Invalid login'}]}) + res.json(user) }) // TODO: Login link stuff router.get('/', async (req, res) => { - // TODO: Get current user and session stuff + res.json(req.user) +}) + +// TODO: de-auth session +router.get('/logout', (req, res) => { + req.session = null + res.redirect('/') }) module.exports = router; diff --git a/api/index.js b/api/index.js index b294fe6..422b1e9 100644 --- a/api/index.js +++ b/api/index.js @@ -14,16 +14,14 @@ router.use((req, res, next)=>{ next() }) +router.use(require('cookie-session')({name: 'sos-session', secret: process.env.COOKIE_SECRET})) +router.use(require('./middleware/session')) router.use('/auth', require('./auth')) router.use('/users/', require('./users')) router.use('/items/', require('./items')) router.use('/images/', require('./images')) router.use('/categories/', require('./categories')) -router.get('/', (req, res)=>{ - res.json({test: true}) -}) - router.use((req, res, next)=>{ const err = new Error('Not found') err.status = 404 diff --git a/api/middleware/session.js b/api/middleware/session.js new file mode 100644 index 0000000..b28ec58 --- /dev/null +++ b/api/middleware/session.js @@ -0,0 +1,20 @@ +const db = require('../../db') + +const sessionMiddleware = (req, res, next)=>{ + (async ()=>{ + let session = await db.session.validate(req.session.uuid); + + if(!session) return; + + // Update last active + session = await db.session.update(req.session.uuid); + // Attach updated session object to request + req.sessionObj = session; + + if(session && session.user) + req.user = session.user; + })() + .then(next).catch(next); +} + +module.exports = sessionMiddleware; diff --git a/api/users.js b/api/users.js index 597b7a1..0d98625 100644 --- a/api/users.js +++ b/api/users.js @@ -17,7 +17,15 @@ router.post('/', parseJSON, registerValidation, async (req, res) => { req.body.password ) - // TODO: Create session + const session = await db.session.create( + user.uuid, + req.ip, + req.get('User Agent') || "", + req.get('Referrer') || "", + null + ) + + req.session.uuid = session.uuid res.json(user) }) diff --git a/components/header/header.js b/components/header/header.js index 1680b15..eaa67e3 100644 --- a/components/header/header.js +++ b/components/header/header.js @@ -26,7 +26,7 @@ const Header = ({user}) => ( ):( <>
  • Account
  • -
  • Log out
  • +
  • Log out
  • ) } diff --git a/components/hero/hero.js b/components/hero/hero.js index d6d964e..56c7636 100644 --- a/components/hero/hero.js +++ b/components/hero/hero.js @@ -8,8 +8,8 @@ export default function Hero(){ return (
    - -
    + +

    Your Socks. Your Style.

    Are you tired of plain (boring) white socks? We've got you covered.

    At Society of Socks we strive to give every design a unique personality that is sure to stand out from the crowd.

    diff --git a/db/index.js b/db/index.js index 0add5a9..3a3d544 100644 --- a/db/index.js +++ b/db/index.js @@ -2,5 +2,6 @@ module.exports = { user: require('./models/user'), item: require('./models/item'), category: require('./models/category'), - user: require('./models/user') -} \ No newline at end of file + user: require('./models/user'), + session: require('./models/session') +} diff --git a/db/mappings/user.js b/db/mappings/user.js index 9e788a4..1a5b014 100644 --- a/db/mappings/user.js +++ b/db/mappings/user.js @@ -4,7 +4,7 @@ module.exports = [ idProperty: 'uuid', properties: [ 'email', - 'password', + 'password_hash', 'email_confirmed', 'time_registered', 'time_email_confirmed' @@ -44,6 +44,7 @@ module.exports = [ ], associations: [ {name: 'originating_link', mapId: 'loginLinkMap', columnPrefix: 'login_link_'}, + {name: 'user', mapId: 'userMap', columnPrefix: 'session_user_'}, // {name: 'cart', mapId: 'cartMap', columnPrefix: 'cart_'} ] } diff --git a/db/models/session.js b/db/models/session.js index c8671d6..2bb5778 100644 --- a/db/models/session.js +++ b/db/models/session.js @@ -21,5 +21,33 @@ session.create = async (user_uuid, ip_address, user_agent, referer, origin_link_ debug(query); const {rows} = await pg.query(query) - return joinjs.map(rows, mappings, 'sessionMap', 'session_'); + return joinjs.map(rows, mappings, 'sessionMap', 'session_')[0]; +} + +session.validate = async (session_uuid) => { + const query = { + text: 'select * from validate_session($1)', + values: [ + session_uuid + ] + } + + debug(query); + + const {rows} = await pg.query(query) + return joinjs.map(rows, mappings, 'sessionMap', 'session_')[0]; +} + +session.update = async (session_uuid) => { + const query = { + text: 'select * from update_session($1)', + values: [ + session_uuid + ] + } + + debug(query); + + const {rows} = await pg.query(query) + return joinjs.map(rows, mappings, 'sessionMap', 'session_')[0]; } diff --git a/db/models/user.js b/db/models/user.js index 7243930..7c4f862 100644 --- a/db/models/user.js +++ b/db/models/user.js @@ -60,13 +60,13 @@ user.login = async (email, password) => { if(!_user){ // Avoid early exit timing difference await bcrypt.hash(password, saltRounds) - throw new Error("User not found") + return null } const passwordCorrect = await bcrypt.compare(password, _user.password_hash) if(!passwordCorrect) - throw new Error("Password incorrect") + return null return _user } diff --git a/db/sql/3-functions.sql b/db/sql/3-functions.sql index 0467861..2f900e0 100644 --- a/db/sql/3-functions.sql +++ b/db/sql/3-functions.sql @@ -23,7 +23,20 @@ as $function$ begin return query select * from v_session where session_uuid = _session_uuid - and session_time_last_active + session_timeout_length < now(); + and session_time_last_active + session_timeout_length > now(); +end; $function$; + +create or replace function public.update_session(_session_uuid uuid) + returns setof public.v_session + language plpgsql +as $function$ +begin + update "session" + set session_time_last_active = now() + where session_uuid = _session_uuid + and now() < (select session_time_last_active + session_timeout_length); + + return query select * from validate_session(_session_uuid); end; $function$; create or replace function public.login_user_session(_user_uuid uuid, _timeout_length interval, _ip_addr varchar(50), _user_agent varchar(500), _referer varchar(500), _link uuid) @@ -122,11 +135,11 @@ create or replace function public.get_image_large(_image_uuid uuid) language plpgsql as $function$ begin - return query select + return query select "image".image_uuid, "image".image_mime_type, - "image".image_large_file as image_file - from "image" + "image".image_large_file as image_file + from "image" where "image".image_uuid = _image_uuid; end; $function$; @@ -135,11 +148,11 @@ create or replace function public.get_image_thumb(_image_uuid uuid) language plpgsql as $function$ begin - return query select + return query select "image".image_uuid, "image".image_mime_type, - "image".image_thumb_file as image_file - from "image" + "image".image_thumb_file as image_file + from "image" where "image".image_uuid = _image_uuid; end; $function$; @@ -161,4 +174,4 @@ begin ) returning category_uuid into _category_uuid; return query select * from v_category where category_uuid = _category_uuid; -end; $function$; \ No newline at end of file +end; $function$; diff --git a/package-lock.json b/package-lock.json index 4d3a997..320d5e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2367,11 +2367,47 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, + "cookie-session": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-1.4.0.tgz", + "integrity": "sha512-0hhwD+BUIwMXQraiZP/J7VP2YFzqo6g4WqZlWHtEHQ22t0MeZZrNBSCxC1zcaLAs8ApT3BzAKizx9gW/AP9vNA==", + "requires": { + "cookies": "0.8.0", + "debug": "2.6.9", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -4050,6 +4086,14 @@ "minimist": "^1.2.0" } }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "requires": { + "tsscmp": "1.0.6" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7326,6 +7370,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", diff --git a/package.json b/package.json index 6a0ca51..3ab87c5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "base64-async": "^2.1.3", "bcrypt": "^3.0.8", "body-parser": "^1.19.0", + "cookie-session": "^1.4.0", "cross-env": "^7.0.0", "debug": "^4.1.1", "dotenv": "^8.2.0", diff --git a/pages/_app.js b/pages/_app.js index 3b24183..5c38e14 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,17 +1,20 @@ import React from "react" import PropTypes from "prop-types" +import axios from 'axios' import Header from '~/components/header' import Footer from '~/components/footer' import "../styles/layout.css" -const Layout = ({ Component, pageProps }) => { - // Retrieve user information - // const user = useUserSession() +Layout.getInitialProps = async ({ctx}) => { + const {data: user} = await axios.get(`/api/auth`, { headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined }) + return {user} +} +function Layout({ Component, pageProps, user }){ return ( <> -
    +