fix: naming and redundancy issues

This commit is contained in:
Sérgio Salgado 2021-04-06 00:39:52 +01:00 committed by Josh Harvey
parent fff9523988
commit 40974dd501
15 changed files with 194 additions and 143 deletions

View file

@ -11,19 +11,15 @@ const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const { ApolloServer, AuthenticationError } = require('apollo-server-express') const { ApolloServer, AuthenticationError } = require('apollo-server-express')
const _ = require('lodash/fp') const _ = require('lodash/fp')
const session = require('express-session')
const pgSession = require('connect-pg-simple')(session)
const hkdf = require('futoin-hkdf')
const pify = require('pify') const pify = require('pify')
const login = require('./services/login') const login = require('./services/login')
const register = require('./routes/authentication') const register = require('./routes/authentication')
const options = require('../options') const options = require('../options')
const db = require('../db')
const users = require('../users') const users = require('../users')
const mnemonicHelpers = require('../mnemonic-helpers')
const session = require('./middlewares/session')
const authRouter = require('./routes/auth') const authRouter = require('./routes/auth')
const { AuthDirective } = require('./graphql/directives') const { AuthDirective } = require('./graphql/directives')
const { typeDefs, resolvers } = require('./graphql/schema') const { typeDefs, resolvers } = require('./graphql/schema')
@ -46,33 +42,7 @@ app.use(cookieParser())
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true })) // support encoded bodies app.use(bodyParser.urlencoded({ extended: true })) // support encoded bodies
app.use(express.static(path.resolve(__dirname, '..', '..', 'public'))) app.use(express.static(path.resolve(__dirname, '..', '..', 'public')))
app.use(session)
const getSecret = () => {
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8')
return hkdf(
mnemonicHelpers.toEntropyBuffer(mnemonic),
16,
{ salt: 'lamassu-server-salt', info: 'operator-id' }
).toString('hex')
}
app.use('*', session({
store: new pgSession({
pgPromise: db,
tableName: 'user_sessions'
}),
name: 'lid',
secret: getSecret(),
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
domain: hostname,
sameSite: true,
maxAge: 60 * 10 * 1000 // 10 minutes
}
}))
const apolloServer = new ApolloServer({ const apolloServer = new ApolloServer({
typeDefs, typeDefs,

View file

@ -0,0 +1,36 @@
const { ApolloError } = require('apollo-server-express')
class InvalidCredentialsError extends ApolloError {
constructor(message) {
super(message, 'INVALID_CREDENTIALS')
Object.defineProperty(this, 'name', { value: 'InvalidCredentialsError' })
}
}
class UserAlreadyExistsError extends ApolloError {
constructor(message) {
super(message, 'USER_ALREADY_EXISTS')
Object.defineProperty(this, 'name', { value: 'UserAlreadyExistsError' })
}
}
class InvalidTwoFactorError extends ApolloError {
constructor(message) {
super(message, 'INVALID_TWO_FACTOR_CODE')
Object.defineProperty(this, 'name', { value: 'InvalidTwoFactorError' })
}
}
class InvalidUrlError extends ApolloError {
constructor(message) {
super(message, 'INVALID_URL_TOKEN')
Object.defineProperty(this, 'name', { value: 'InvalidUrlError' })
}
}
module.exports = {
InvalidCredentialsError,
UserAlreadyExistsError,
InvalidTwoFactorError,
InvalidUrlError
}

View file

@ -5,27 +5,28 @@ const loginHelper = require('../../services/login')
const T = require('../../../time') const T = require('../../../time')
const users = require('../../../users') const users = require('../../../users')
const sessionManager = require('../../../session-manager') const sessionManager = require('../../../session-manager')
const authErrors = require('../errors/authentication')
const REMEMBER_ME_AGE = 90 * T.day const REMEMBER_ME_AGE = 90 * T.day
function authenticateUser(username, password) { function authenticateUser(username, password) {
return loginHelper.checkUser(username).then(hashedPassword => { return loginHelper.checkUser(username)
if (!hashedPassword) return null .then(hashedPassword => {
if (!hashedPassword) throw new authErrors.InvalidCredentialsError()
return Promise.all([bcrypt.compare(password, hashedPassword), hashedPassword]) return Promise.all([bcrypt.compare(password, hashedPassword), hashedPassword])
}).then(([isMatch, hashedPassword]) => { })
if (!isMatch) return null .then(([isMatch, hashedPassword]) => {
if (!isMatch) throw new authErrors.InvalidCredentialsError()
return loginHelper.validateUser(username, hashedPassword) return loginHelper.validateUser(username, hashedPassword)
}).then(user => { })
if (!user) return null .catch(e => {
return user
}).catch(e => {
console.error(e) console.error(e)
}) })
} }
const getUserData = context => { const getUserData = context => {
const lidCookie = context.req.cookies && context.req.cookies.lid const lidCookie = context.req.cookies && context.req.cookies.lid
if (!lidCookie) return null if (!lidCookie) throw new authErrors.InvalidCredentialsError()
const user = context.req.session.user const user = context.req.session.user
return user return user
@ -33,7 +34,7 @@ const getUserData = context => {
const get2FASecret = (username, password) => { const get2FASecret = (username, password) => {
return authenticateUser(username, password).then(user => { return authenticateUser(username, password).then(user => {
if (!user) return null if (!user) throw new authErrors.InvalidCredentialsError()
const secret = otplib.authenticator.generateSecret() const secret = otplib.authenticator.generateSecret()
const otpauth = otplib.authenticator.keyuri(username, 'Lamassu Industries', secret) const otpauth = otplib.authenticator.keyuri(username, 'Lamassu Industries', secret)
@ -41,46 +42,45 @@ const get2FASecret = (username, password) => {
}) })
} }
const confirm2FA = (codeArg, context) => { const confirm2FA = (token, context) => {
const code = codeArg
const requestingUser = context.req.session.user const requestingUser = context.req.session.user
if (!requestingUser) return false if (!requestingUser) throw new authErrors.InvalidCredentialsError()
return users.get2FASecret(requestingUser.id).then(user => { return users.get2FASecret(requestingUser.id).then(user => {
const secret = user.twofa_code const secret = user.twofa_code
const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret }) const isCodeValid = otplib.authenticator.verify({ token, secret })
if (!isCodeValid) return false if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
return true return true
}) })
} }
const validateRegisterLink = token => { const validateRegisterLink = token => {
if (!token) return null if (!token) throw new authErrors.InvalidUrlError()
return users.validateUserRegistrationToken(token) return users.validateUserRegistrationToken(token)
.then(r => { .then(r => {
if (!r.success) return null if (!r.success) throw new authErrors.InvalidUrlError()
return { username: r.username, role: r.role } return { username: r.username, role: r.role }
}) })
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
const validateResetPasswordLink = token => { const validateResetPasswordLink = token => {
if (!token) return null if (!token) throw new authErrors.InvalidUrlError()
return users.validatePasswordResetToken(token) return users.validatePasswordResetToken(token)
.then(r => { .then(r => {
if (!r.success) return null if (!r.success) throw new authErrors.InvalidUrlError()
return { id: r.userID } return { id: r.userID }
}) })
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
const validateReset2FALink = token => { const validateReset2FALink = token => {
if (!token) return null if (!token) throw new authErrors.InvalidUrlError()
return users.validate2FAResetToken(token) return users.validate2FAResetToken(token)
.then(r => { .then(r => {
if (!r.success) return null if (!r.success) throw new authErrors.InvalidUrlError()
return users.findById(r.userID) return users.findById(r.userID)
}) })
.then(user => { .then(user => {
@ -95,12 +95,12 @@ const deleteSession = (sessionID, context) => {
if (sessionID === context.req.session.id) { if (sessionID === context.req.session.id) {
context.req.session.destroy() context.req.session.destroy()
} }
return sessionManager.deleteSession(sessionID) return sessionManager.deleteSessionById(sessionID)
} }
const login = (username, password) => { const login = (username, password) => {
return authenticateUser(username, password).then(user => { return authenticateUser(username, password).then(user => {
if (!user) return 'FAILED' if (!user) throw new authErrors.InvalidCredentialsError()
return users.get2FASecret(user.id).then(user => { return users.get2FASecret(user.id).then(user => {
const twoFASecret = user.twofa_code const twoFASecret = user.twofa_code
return twoFASecret ? 'INPUT2FA' : 'SETUP2FA' return twoFASecret ? 'INPUT2FA' : 'SETUP2FA'
@ -110,12 +110,12 @@ const login = (username, password) => {
const input2FA = (username, password, rememberMe, code, context) => { const input2FA = (username, password, rememberMe, code, context) => {
return authenticateUser(username, password).then(user => { return authenticateUser(username, password).then(user => {
if (!user) return false if (!user) throw new authErrors.InvalidCredentialsError()
return users.get2FASecret(user.id).then(user => { return users.get2FASecret(user.id).then(user => {
const secret = user.twofa_code const secret = user.twofa_code
const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret }) const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
if (!isCodeValid) return false if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
const finalUser = { id: user.id, username: user.username, role: user.role } const finalUser = { id: user.id, username: user.username, role: user.role }
context.req.session.user = finalUser context.req.session.user = finalUser
@ -128,48 +128,39 @@ const input2FA = (username, password, rememberMe, code, context) => {
const setup2FA = (username, password, secret, codeConfirmation) => { const setup2FA = (username, password, secret, codeConfirmation) => {
return authenticateUser(username, password).then(user => { return authenticateUser(username, password).then(user => {
if (!user || !secret) return false if (!user || !secret) throw new authErrors.InvalidCredentialsError()
const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: secret }) const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: secret })
if (!isCodeValid) return false if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
users.save2FASecret(user.id, secret) return users.save2FASecret(user.id, secret).then(() => true)
return true
}) })
} }
const createResetPasswordToken = userID => { const createResetPasswordToken = userID => {
return users.findById(userID) return users.findById(userID)
.then(user => { .then(user => {
if (!user) return null if (!user) throw new authErrors.InvalidCredentialsError()
return users.createResetPasswordToken(user.id) return users.createResetPasswordToken(user.id)
}) })
.then(token => {
return token
})
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
const createReset2FAToken = userID => { const createReset2FAToken = userID => {
return users.findById(userID) return users.findById(userID)
.then(user => { .then(user => {
if (!user) return null if (!user) throw new authErrors.InvalidCredentialsError()
return users.createReset2FAToken(user.id) return users.createReset2FAToken(user.id)
}) })
.then(token => {
return token
})
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
const createRegisterToken = (username, role) => { const createRegisterToken = (username, role) => {
return users.getByName(username) return users.getByName(username)
.then(user => { .then(user => {
if (user) return null if (user) throw new authErrors.UserAlreadyExistsError()
return users.createUserRegistrationToken(username, role).then(token => { return users.createUserRegistrationToken(username, role)
return token
})
}) })
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
@ -177,29 +168,26 @@ const createRegisterToken = (username, role) => {
const register = (username, password, role) => { const register = (username, password, role) => {
return users.getByName(username) return users.getByName(username)
.then(user => { .then(user => {
if (user) return false if (user) throw new authErrors.UserAlreadyExistsError()
return users.createUser(username, password, role).then(() => true)
users.createUser(username, password, role)
return true
}) })
.catch(err => console.error(err)) .catch(err => console.error(err))
} }
const resetPassword = (userID, newPassword, context) => { const resetPassword = (userID, newPassword, context) => {
return users.findById(userID).then(user => { return users.findById(userID).then(user => {
if (!user) return false if (!user) throw new authErrors.InvalidCredentialsError()
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy() if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
return users.updatePassword(user.id, newPassword) return users.updatePassword(user.id, newPassword)
}).then(() => { return true }).catch(err => console.error(err)) }).then(() => true).catch(err => console.error(err))
} }
const reset2FA = (userID, code, secret, context) => { const reset2FA = (userID, token, secret, context) => {
return users.findById(userID).then(user => { return users.findById(userID).then(user => {
const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret }) const isCodeValid = otplib.authenticator.verify({ token, secret })
if (!isCodeValid) return false if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy() if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
return users.save2FASecret(user.id, secret).then(() => { return true }) return users.save2FASecret(user.id, secret).then(() => true)
}).catch(err => console.error(err)) }).catch(err => console.error(err))
} }

View file

@ -5,8 +5,8 @@ const sessionManager = require('../../../session-manager')
const resolver = { const resolver = {
Query: { Query: {
users: () => users.getUsers(), users: () => users.getUsers(),
sessions: () => sessionManager.getSessionList(), sessions: () => sessionManager.getSessions(),
userSessions: (...[, { username }]) => sessionManager.getUserSessions(username), userSessions: (...[, { username }]) => sessionManager.getSessionsByUsername(username),
userData: (root, args, context, info) => authentication.getUserData(context), userData: (root, args, context, info) => authentication.getUserData(context),
get2FASecret: (...[, { username, password }]) => authentication.get2FASecret(username, password), get2FASecret: (...[, { username, password }]) => authentication.get2FASecret(username, password),
confirm2FA: (root, args, context, info) => authentication.confirm2FA(args.code, context), confirm2FA: (root, args, context, info) => authentication.confirm2FA(args.code, context),
@ -17,7 +17,7 @@ const resolver = {
Mutation: { Mutation: {
deleteUser: (...[, { id }]) => users.deleteUser(id), deleteUser: (...[, { id }]) => users.deleteUser(id),
deleteSession: (root, args, context, info) => authentication.deleteSession(args.sid, context), deleteSession: (root, args, context, info) => authentication.deleteSession(args.sid, context),
deleteUserSessions: (...[, { username }]) => sessionManager.deleteUserSessions(username), deleteUserSessions: (...[, { username }]) => sessionManager.deleteSessionsByUsername(username),
changeUserRole: (...[, { id, newRole }]) => users.changeUserRole(id, newRole), changeUserRole: (...[, { id, newRole }]) => users.changeUserRole(id, newRole),
toggleUserEnable: (...[, { id }]) => users.toggleUserEnable(id), toggleUserEnable: (...[, { id }]) => users.toggleUserEnable(id),
login: (...[, { username, password }]) => authentication.login(username, password), login: (...[, { username, password }]) => authentication.login(username, password),

View file

@ -0,0 +1,40 @@
const fs = require('fs')
const express = require('express')
const router = express.Router()
const hkdf = require('futoin-hkdf')
const session = require('express-session')
const pgSession = require('connect-pg-simple')(session)
const mnemonicHelpers = require('../../mnemonic-helpers')
const db = require('../../db')
const options = require('../../options')
const getSecret = () => {
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8')
return hkdf(
mnemonicHelpers.toEntropyBuffer(mnemonic),
16,
{ salt: 'lamassu-server-salt', info: 'operator-id' }
).toString('hex')
}
const hostname = options.hostname
router.use('*', session({
store: new pgSession({
pgPromise: db,
tableName: 'user_sessions'
}),
name: 'lid',
secret: getSecret(),
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
domain: hostname,
sameSite: true,
maxAge: 60 * 10 * 1000 // 10 minutes
}
}))
module.exports = router

View file

@ -10,7 +10,10 @@ function validateUser (username, password) {
const sqlUpdateLastAccessed = 'UPDATE users SET last_accessed = now() WHERE username=$1' const sqlUpdateLastAccessed = 'UPDATE users SET last_accessed = now() WHERE username=$1'
return db.oneOrNone(sql, [username, password]) return db.oneOrNone(sql, [username, password])
.then(user => { db.none(sqlUpdateLastAccessed, [user.username]); return user }) .then(user => {
return db.none(sqlUpdateLastAccessed, [user.username])
.then(() => user)
})
.catch(() => false) .catch(() => false)
} }

View file

@ -1,11 +1,11 @@
const db = require('./db') const db = require('./db')
function getSessionList () { function getSessions () {
const sql = `SELECT * FROM user_sessions ORDER BY sess -> 'user' ->> 'username'` const sql = `SELECT * FROM user_sessions ORDER BY sess -> 'user' ->> 'username'`
return db.any(sql) return db.any(sql)
} }
function getLastSessionByUser () { function getLastSessionPerUser () {
const sql = `SELECT b.username, a.user_agent, a.ip_address, a.last_used, b.role FROM ( const sql = `SELECT b.username, a.user_agent, a.ip_address, a.last_used, b.role FROM (
SELECT sess -> 'user' ->> 'username' AS username, SELECT sess -> 'user' ->> 'username' AS username,
sess ->> 'ua' AS user_agent, sess ->> 'ua' AS user_agent,
@ -19,24 +19,24 @@ function getLastSessionByUser () {
return db.any(sql) return db.any(sql)
} }
function getUserSessions (username) { function getSessionsByUsername (username) {
const sql = `SELECT * FROM user_sessions WHERE sess -> 'user' ->> 'username'=$1` const sql = `SELECT * FROM user_sessions WHERE sess -> 'user' ->> 'username'=$1`
return db.any(sql, [username]) return db.any(sql, [username])
} }
function getSession (sessionID) { function getSessionById (sessionID) {
const sql = `SELECT * FROM user_sessions WHERE sid=$1` const sql = `SELECT * FROM user_sessions WHERE sid=$1`
return db.any(sql, [sessionID]) return db.any(sql, [sessionID])
} }
function deleteUserSessions (username) { function deleteSessionsByUsername (username) {
const sql = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'username'=$1` const sql = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'username'=$1`
return db.none(sql, [username]) return db.none(sql, [username])
} }
function deleteSession (sessionID) { function deleteSessionById (sessionID) {
const sql = `DELETE FROM user_sessions WHERE sid=$1` const sql = `DELETE FROM user_sessions WHERE sid=$1`
return db.none(sql, [sessionID]) return db.none(sql, [sessionID])
} }
module.exports = { getSessionList, getLastSessionByUser, getUserSessions, getSession, deleteUserSessions, deleteSession } module.exports = { getSessions, getLastSessionPerUser, getSessionsByUsername, getSessionById, deleteSessionsByUsername, deleteSessionById }

View file

@ -61,11 +61,12 @@ function verifyAndUpdateUser (id, ua, ip) {
function createUser (username, password, role) { function createUser (username, password, role) {
const sql = `INSERT INTO users (id, username, password, role) VALUES ($1, $2, $3, $4)` const sql = `INSERT INTO users (id, username, password, role) VALUES ($1, $2, $3, $4)`
bcrypt.hash(password, 12).then(function (hash) { return bcrypt.hash(password, 12).then(function (hash) {
return db.none(sql, [uuid.v4(), username, hash, role]) return db.none(sql, [uuid.v4(), username, hash, role])
}) })
} }
// TO DELETE
function deleteUser (id) { function deleteUser (id) {
const sql = `DELETE FROM users WHERE id=$1` const sql = `DELETE FROM users WHERE id=$1`
const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1` const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`
@ -84,9 +85,14 @@ function get2FASecret (id) {
} }
function save2FASecret (id, secret) { function save2FASecret (id, secret) {
const sql = 'UPDATE users SET twofa_code=$1 WHERE id=$2' return db.tx(t => {
const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1` const q1 = t.none('UPDATE users SET twofa_code=$1 WHERE id=$2', [secret, id])
return db.none(sql, [secret, id]).then(() => db.none(sql2, [id])) const q2 = t.none(`DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`, [id])
return t.batch([q1, q2])
// const sql = 'UPDATE users SET twofa_code=$1 WHERE id=$2'
// const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`
// return db.none(sql, [secret, id]).then(() => db.none(sql2, [id]))
})
} }
function validate2FAResetToken (token) { function validate2FAResetToken (token) {
@ -107,9 +113,14 @@ function createReset2FAToken (userID) {
function updatePassword (id, password) { function updatePassword (id, password) {
bcrypt.hash(password, 12).then(function (hash) { bcrypt.hash(password, 12).then(function (hash) {
const sql = `UPDATE users SET password=$1 WHERE id=$2` return db.tx(t => {
const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1` const q1 = t.none(`UPDATE users SET password=$1 WHERE id=$2`, [hash, id])
return db.none(sql, [hash, id]).then(() => db.none(sql2, [id])) const q2 = t.none(`DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`, [id])
return t.batch([q1, q2])
})
// const sql = `UPDATE users SET password=$1 WHERE id=$2`
// const sql2 = `DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`
// return db.none(sql, [hash, id]).then(() => db.none(sql2, [id]))
}) })
} }

View file

@ -5,9 +5,9 @@ exports.up = function (next) {
`CREATE TYPE role AS ENUM('user', 'superuser')`, `CREATE TYPE role AS ENUM('user', 'superuser')`,
`CREATE TABLE users ( `CREATE TABLE users (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
username VARCHAR(50) UNIQUE, username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100), password VARCHAR(100),
role role DEFAULT 'user', role role NOT NULL DEFAULT 'user',
enabled BOOLEAN DEFAULT true, enabled BOOLEAN DEFAULT true,
twofa_code VARCHAR(100), twofa_code VARCHAR(100),
created TIMESTAMPTZ NOT NULL DEFAULT now(), created TIMESTAMPTZ NOT NULL DEFAULT now(),

View file

@ -71,10 +71,11 @@ const Input2FAState = ({
}) })
const getErrorMsg = () => { const getErrorMsg = () => {
if (mutationError || queryError) return 'Internal server error' if (queryError) return 'Internal server error'
if (twoFAField.length !== 6 && invalidToken) if (twoFAField.length !== 6 && invalidToken)
return 'The code should have 6 characters!' return 'The code should have 6 characters!'
if (invalidToken) return 'Code is invalid. Please try again.' if (mutationError || invalidToken)
return 'Code is invalid. Please try again.'
return null return null
} }

View file

@ -2,7 +2,7 @@ import { useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import React, { useState } from 'react' import React from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
@ -46,19 +46,15 @@ const LoginState = ({
onCompleted: ({ login }) => { onCompleted: ({ login }) => {
if (login === 'INPUT2FA') handleLoginState(STATES.INPUT_2FA) if (login === 'INPUT2FA') handleLoginState(STATES.INPUT_2FA)
if (login === 'SETUP2FA') handleLoginState(STATES.SETUP_2FA) if (login === 'SETUP2FA') handleLoginState(STATES.SETUP_2FA)
if (login === 'FAILED') setInvalidLogin(true)
} }
}) })
const [invalidLogin, setInvalidLogin] = useState(false)
const getErrorMsg = (formikErrors, formikTouched) => { const getErrorMsg = (formikErrors, formikTouched) => {
if (!formikErrors || !formikTouched) return null if (!formikErrors || !formikTouched) return null
if (mutationError) return 'Internal server error' if (mutationError) return 'Invalid login/password combination'
if (formikErrors.client && formikTouched.client) return formikErrors.client if (formikErrors.client && formikTouched.client) return formikErrors.client
if (formikErrors.password && formikTouched.password) if (formikErrors.password && formikTouched.password)
return formikErrors.password return formikErrors.password
if (invalidLogin) return 'Invalid login/password combination'
return null return null
} }
@ -67,7 +63,6 @@ const LoginState = ({
validationSchema={validationSchema} validationSchema={validationSchema}
initialValues={initialValues} initialValues={initialValues}
onSubmit={values => { onSubmit={values => {
setInvalidLogin(false)
onClientChange(values.client) onClientChange(values.client)
onPasswordChange(values.password) onPasswordChange(values.password)
onRememberMeChange(values.rememberMe) onRememberMeChange(values.rememberMe)
@ -89,9 +84,6 @@ const LoginState = ({
autoFocus autoFocus
className={classes.input} className={classes.input}
error={getErrorMsg(errors, touched)} error={getErrorMsg(errors, touched)}
onKeyUp={() => {
if (invalidLogin) setInvalidLogin(false)
}}
/> />
<Field <Field
name="password" name="password"
@ -100,9 +92,6 @@ const LoginState = ({
label="Password" label="Password"
fullWidth fullWidth
error={getErrorMsg(errors, touched)} error={getErrorMsg(errors, touched)}
onKeyUp={() => {
if (invalidLogin) setInvalidLogin(false)
}}
/> />
<div className={classes.rememberMeWrapper}> <div className={classes.rememberMeWrapper}>
<Field <Field

View file

@ -78,10 +78,11 @@ const Reset2FA = () => {
}) })
const getErrorMsg = () => { const getErrorMsg = () => {
if (mutationError || queryError) return 'Internal server error' if (queryError) return 'Internal server error'
if (twoFAConfirmation.length !== 6 && invalidToken) if (twoFAConfirmation.length !== 6 && invalidToken)
return 'The code should have 6 characters!' return 'The code should have 6 characters!'
if (invalidToken) return 'Code is invalid. Please try again.' if (mutationError || invalidToken)
return 'Code is invalid. Please try again.'
return null return null
} }

View file

@ -5,7 +5,6 @@ import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { import {
matchPath, matchPath,
Route,
Redirect, Redirect,
Switch, Switch,
useHistory, useHistory,
@ -398,11 +397,11 @@ const Routes = () => {
</PrivateRoute> </PrivateRoute>
<PrivateRoute path="/machines" component={Machines} /> <PrivateRoute path="/machines" component={Machines} />
<PrivateRoute path="/wizard" component={Wizard} /> <PrivateRoute path="/wizard" component={Wizard} />
<Route path="/register" component={Register} /> <PublicRoute path="/register" component={Register} />
{/* <Route path="/configmigration" component={ConfigMigration} /> */} {/* <PublicRoute path="/configmigration" component={ConfigMigration} /> */}
<PublicRoute path="/login" restricted component={Login} /> <PublicRoute path="/login" restricted component={Login} />
<Route path="/resetpassword" component={ResetPassword} /> <PublicRoute path="/resetpassword" component={ResetPassword} />
<Route path="/reset2fa" component={Reset2FA} /> <PublicRoute path="/reset2fa" component={Reset2FA} />
{getFilteredRoutes().map(({ route, component: Page, key }) => ( {getFilteredRoutes().map(({ route, component: Page, key }) => (
<PrivateRoute path={route} key={key}> <PrivateRoute path={route} key={key}>
<Transition <Transition
@ -421,10 +420,10 @@ const Routes = () => {
/> />
</PrivateRoute> </PrivateRoute>
))} ))}
<Route path="/404" /> <PublicRoute path="/404" />
<Route path="*"> <PublicRoute path="*">
<Redirect to={{ pathname: '/404' }} /> <Redirect to={{ pathname: '/404' }} />
</Route> </PublicRoute>
</Switch> </Switch>
) )
} }

View file

@ -1,8 +1,6 @@
export const isLoggedIn = userData => { export const isLoggedIn = userData => !!userData
return userData
}
export const ROLES = { export const ROLES = {
USER: { key: 'user', value: '0' }, USER: { key: 'user' },
SUPERUSER: { key: 'superuser', value: '1' } SUPERUSER: { key: 'superuser' }
} }

View file

@ -4,6 +4,7 @@ import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link' import { ApolloLink } from 'apollo-link'
import { onError } from 'apollo-link-error' import { onError } from 'apollo-link-error'
import { HttpLink } from 'apollo-link-http' import { HttpLink } from 'apollo-link-http'
import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { useHistory, useLocation } from 'react-router-dom' import { useHistory, useLocation } from 'react-router-dom'
@ -18,10 +19,7 @@ const getClient = (history, location, setUserData) =>
onError(({ graphQLErrors, networkError }) => { onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path, extensions }) => { graphQLErrors.forEach(({ message, locations, path, extensions }) => {
if (extensions?.code === 'UNAUTHENTICATED') { handle(extensions?.code, history, location, setUserData)
setUserData(null)
if (location.pathname !== '/login') history.push('/login')
}
console.log( console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
) )
@ -49,6 +47,23 @@ const getClient = (history, location, setUserData) =>
} }
}) })
const handle = (type, ...args) => {
const handler = {
UNAUTHENTICATED: ({ history, location, setUserData }) => {
setUserData(null)
if (location.pathname !== '/login') history.push('/login')
},
INVALID_CREDENTIALS: () => {},
INVALID_TWO_FACTOR_CODE: () => {},
INVALID_URL_TOKEN: () => {},
USER_ALREADY_EXISTS: () => {}
}
if (!R.has(type, handler)) throw new Error('Unknown error code.')
return handler[type](...args)
}
const Provider = ({ children }) => { const Provider = ({ children }) => {
const history = useHistory() const history = useHistory()
const location = useLocation() const location = useLocation()