diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js
index 236ab444..42c3c89f 100644
--- a/lib/new-admin/admin-server.js
+++ b/lib/new-admin/admin-server.js
@@ -21,7 +21,9 @@ const options = require('../options')
const db = require('../db')
const users = require('../users')
-const { typeDefs, resolvers, AuthDirective, SuperuserDirective } = require('./graphql/schema')
+const authRouter = require('./routes/auth')
+const { AuthDirective } = require('./graphql/directives')
+const { typeDefs, resolvers } = require('./graphql/schema')
const devMode = require('minimist')(process.argv.slice(2)).dev
const idPhotoCardBasedir = _.get('idPhotoCardDir', options)
@@ -64,8 +66,7 @@ const apolloServer = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
- auth: AuthDirective,
- superuser: SuperuserDirective
+ auth: AuthDirective
},
playground: false,
introspection: false,
@@ -74,7 +75,8 @@ const apolloServer = new ApolloServer({
return error
},
context: async ({ req }) => {
- if (!req.session.user) throw new AuthenticationError('Authentication failed')
+ if (!req.session.user) return { req }
+
const user = await users.verifyAndUpdateUser(
req.session.user.id,
req.headers['user-agent'] || 'Unknown',
@@ -87,7 +89,8 @@ const apolloServer = new ApolloServer({
req.session.lastUsed = new Date(Date.now()).toISOString()
req.session.user.id = user.id
req.session.user.role = user.role
- return { req: { ...req } }
+
+ return { req }
}
})
@@ -104,9 +107,7 @@ app.use(cors({ credentials: true, origin: devMode && 'https://localhost:3001' })
app.use('/id-card-photo', serveStatic(idPhotoCardBasedir, { index: false }))
app.use('/front-camera-photo', serveStatic(frontCameraBasedir, { index: false }))
-app.use('/api', register)
-
-require('./routes/auth')(app)
+app.use(authRouter)
// Everything not on graphql or api/register is redirected to the front-end
app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')))
diff --git a/lib/new-admin/graphql/directives/auth.js b/lib/new-admin/graphql/directives/auth.js
new file mode 100644
index 00000000..d4bbacf9
--- /dev/null
+++ b/lib/new-admin/graphql/directives/auth.js
@@ -0,0 +1,40 @@
+const _ = require('lodash/fp')
+
+const { SchemaDirectiveVisitor, AuthenticationError } = require('apollo-server-express')
+const { defaultFieldResolver } = require('graphql')
+
+class AuthDirective extends SchemaDirectiveVisitor {
+ visitObject (type) {
+ this.ensureFieldsWrapped(type)
+ type._requiredAuthRole = this.args.requires
+ }
+
+ visitFieldDefinition (field, details) {
+ this.ensureFieldsWrapped(details.objectType)
+ field._requiredAuthRole = this.args.requires
+ }
+
+ ensureFieldsWrapped (objectType) {
+ if (objectType._authFieldsWrapped) return
+ objectType._authFieldsWrapped = true
+
+ const fields = objectType.getFields()
+
+ _.forEach(fieldName => {
+ const field = fields[fieldName]
+ const { resolve = defaultFieldResolver } = field
+
+ field.resolve = function (root, args, context, info) {
+ const requiredRoles = field._requiredAuthRole ? field._requiredAuthRole : objectType._requiredAuthRole
+ if (!requiredRoles) return resolve.apply(this, [root, args, context, info])
+
+ const user = context.req.session.user
+ if (!user || !_.includes(_.upperCase(user.role), requiredRoles)) throw new AuthenticationError('You do not have permission to access this resource!')
+
+ return resolve.apply(this, [root, args, context, info])
+ }
+ }, _.keys(fields))
+ }
+}
+
+module.exports = AuthDirective
diff --git a/lib/new-admin/graphql/directives/index.js b/lib/new-admin/graphql/directives/index.js
new file mode 100644
index 00000000..f2bbcf42
--- /dev/null
+++ b/lib/new-admin/graphql/directives/index.js
@@ -0,0 +1,3 @@
+const AuthDirective = require('./auth')
+
+module.exports = { AuthDirective }
diff --git a/lib/new-admin/graphql/modules/authentication.js b/lib/new-admin/graphql/modules/authentication.js
new file mode 100644
index 00000000..1b68ee36
--- /dev/null
+++ b/lib/new-admin/graphql/modules/authentication.js
@@ -0,0 +1,220 @@
+const otplib = require('otplib')
+const bcrypt = require('bcrypt')
+
+const loginHelper = require('../../services/login')
+const T = require('../../../time')
+const users = require('../../../users')
+const sessionManager = require('../../../session-manager')
+
+const REMEMBER_ME_AGE = 90 * T.day
+
+async function authenticateUser (username, password) {
+ const hashedPassword = await loginHelper.checkUser(username)
+ if (!hashedPassword) return null
+
+ const isMatch = await bcrypt.compare(password, hashedPassword)
+ if (!isMatch) return null
+
+ const user = await loginHelper.validateUser(username, hashedPassword)
+ if (!user) return null
+ return user
+}
+
+const getUserData = context => {
+ const lidCookie = context.req.cookies && context.req.cookies.lid
+ if (!lidCookie) return null
+
+ const user = context.req.session.user
+ return user
+}
+
+const get2FASecret = (username, password) => {
+ return authenticateUser(username, password).then(user => {
+ if (!user) return null
+
+ const secret = otplib.authenticator.generateSecret()
+ const otpauth = otplib.authenticator.keyuri(username, 'Lamassu Industries', secret)
+ return { secret, otpauth }
+ })
+}
+
+const confirm2FA = (codeArg, context) => {
+ const code = codeArg
+ const requestingUser = context.req.session.user
+
+ if (!requestingUser) return false
+
+ return users.get2FASecret(requestingUser.id).then(user => {
+ const secret = user.twofa_code
+ const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
+
+ if (!isCodeValid) return false
+ return true
+ })
+}
+
+const validateRegisterLink = token => {
+ if (!token) return null
+ return users.validateUserRegistrationToken(token)
+ .then(r => {
+ if (!r.success) return null
+ return { username: r.username, role: r.role }
+ })
+ .catch(err => console.error(err))
+}
+
+const validateResetPasswordLink = token => {
+ if (!token) return null
+ return users.validatePasswordResetToken(token)
+ .then(r => {
+ if (!r.success) return null
+ return { id: r.userID }
+ })
+ .catch(err => console.error(err))
+}
+
+const validateReset2FALink = token => {
+ if (!token) return null
+ return users.validate2FAResetToken(token)
+ .then(r => {
+ if (!r.success) return null
+ return users.findById(r.userID)
+ })
+ .then(user => {
+ const secret = otplib.authenticator.generateSecret()
+ const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu Industries', secret)
+ return { user_id: user.id, secret, otpauth }
+ })
+ .catch(err => console.error(err))
+}
+
+const deleteSession = (sessionID, context) => {
+ if (sessionID === context.req.session.id) {
+ context.req.session.destroy()
+ }
+ return sessionManager.deleteSession(sessionID)
+}
+
+const login = (username, password) => {
+ return authenticateUser(username, password).then(user => {
+ if (!user) return 'FAILED'
+ return users.get2FASecret(user.id).then(user => {
+ const twoFASecret = user.twofa_code
+ return twoFASecret ? 'INPUT2FA' : 'SETUP2FA'
+ })
+ })
+}
+
+const input2FA = (username, password, rememberMe, code, context) => {
+ return authenticateUser(username, password).then(user => {
+ if (!user) return false
+
+ return users.get2FASecret(user.id).then(user => {
+ const secret = user.twofa_code
+ const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
+ if (!isCodeValid) return false
+
+ const finalUser = { id: user.id, username: user.username, role: user.role }
+ context.req.session.user = finalUser
+ if (rememberMe) context.req.session.cookie.maxAge = REMEMBER_ME_AGE
+
+ return true
+ })
+ })
+}
+
+const setup2FA = (username, password, secret, codeConfirmation) => {
+ return authenticateUser(username, password).then(user => {
+ if (!user || !secret) return false
+
+ const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: secret })
+ if (!isCodeValid) return false
+
+ users.save2FASecret(user.id, secret)
+ return true
+ })
+}
+
+const createResetPasswordToken = userID => {
+ return users.findById(userID)
+ .then(user => {
+ if (!user) return null
+ return users.createResetPasswordToken(user.id)
+ })
+ .then(token => {
+ return token
+ })
+ .catch(err => console.error(err))
+}
+
+const createReset2FAToken = userID => {
+ return users.findById(userID)
+ .then(user => {
+ if (!user) return null
+ return users.createReset2FAToken(user.id)
+ })
+ .then(token => {
+ return token
+ })
+ .catch(err => console.error(err))
+}
+
+const createRegisterToken = (username, role) => {
+ return users.getByName(username)
+ .then(user => {
+ if (user) return null
+
+ return users.createUserRegistrationToken(username, role).then(token => {
+ return token
+ })
+ })
+ .catch(err => console.error(err))
+}
+
+const register = (username, password, role) => {
+ return users.getByName(username)
+ .then(user => {
+ if (user) return false
+
+ users.createUser(username, password, role)
+ return true
+ })
+ .catch(err => console.error(err))
+}
+
+const resetPassword = (userID, newPassword, context) => {
+ return users.findById(userID).then(user => {
+ if (!user) return false
+ if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
+ return users.updatePassword(user.id, newPassword)
+ }).then(() => { return true }).catch(err => console.error(err))
+}
+
+const reset2FA = (userID, code, secret, context) => {
+ return users.findById(userID).then(user => {
+ const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
+ if (!isCodeValid) return false
+
+ 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 })
+ }).catch(err => console.error(err))
+}
+
+module.exports = {
+ getUserData,
+ get2FASecret,
+ confirm2FA,
+ validateRegisterLink,
+ validateResetPasswordLink,
+ validateReset2FALink,
+ deleteSession,
+ login,
+ input2FA,
+ setup2FA,
+ createResetPasswordToken,
+ createReset2FAToken,
+ createRegisterToken,
+ register,
+ resetPassword,
+ reset2FA
+}
diff --git a/lib/new-admin/graphql/resolvers/index.js b/lib/new-admin/graphql/resolvers/index.js
index b54c25b9..60ed22c0 100644
--- a/lib/new-admin/graphql/resolvers/index.js
+++ b/lib/new-admin/graphql/resolvers/index.js
@@ -16,6 +16,7 @@ const scalar = require('./scalar.resolver')
const settings = require('./settings.resolver')
const status = require('./status.resolver')
const transaction = require('./transaction.resolver')
+const user = require('./users.resolver')
const version = require('./version.resolver')
const resolvers = [
@@ -35,6 +36,7 @@ const resolvers = [
settings,
status,
transaction,
+ user,
version
]
diff --git a/lib/new-admin/graphql/resolvers/users.resolver.js b/lib/new-admin/graphql/resolvers/users.resolver.js
new file mode 100644
index 00000000..846cab57
--- /dev/null
+++ b/lib/new-admin/graphql/resolvers/users.resolver.js
@@ -0,0 +1,35 @@
+const authentication = require('../modules/authentication')
+const users = require('../../../users')
+const sessionManager = require('../../../session-manager')
+
+const resolver = {
+ Query: {
+ users: () => users.getUsers(),
+ sessions: () => sessionManager.getSessionList(),
+ userSessions: (...[, { username }]) => sessionManager.getUserSessions(username),
+ userData: (root, args, context, info) => authentication.getUserData(context),
+ get2FASecret: (...[, { username, password }]) => authentication.get2FASecret(username, password),
+ confirm2FA: (root, args, context, info) => authentication.confirm2FA(args.code, context),
+ validateRegisterLink: (...[, { token }]) => authentication.validateRegisterLink(token),
+ validateResetPasswordLink: (...[, { token }]) => authentication.validateResetPasswordLink(token),
+ validateReset2FALink: (...[, { token }]) => authentication.validateReset2FALink(token)
+ },
+ Mutation: {
+ deleteUser: (...[, { id }]) => users.deleteUser(id),
+ deleteSession: (root, args, context, info) => authentication.deleteSession(args.sid, context),
+ deleteUserSessions: (...[, { username }]) => sessionManager.deleteUserSessions(username),
+ changeUserRole: (...[, { id, newRole }]) => users.changeUserRole(id, newRole),
+ toggleUserEnable: (...[, { id }]) => users.toggleUserEnable(id),
+ login: (...[, { username, password }]) => authentication.login(username, password),
+ input2FA: (root, args, context, info) => authentication.input2FA(args.username, args.password, args.rememberMe, args.code, context),
+ setup2FA: (...[, { username, password, secret, codeConfirmation }]) => authentication.setup2FA(username, password, secret, codeConfirmation),
+ createResetPasswordToken: (...[, { userID }]) => authentication.createResetPasswordToken(userID),
+ createReset2FAToken: (...[, { userID }]) => authentication.createReset2FAToken(userID),
+ createRegisterToken: (...[, { username, role }]) => authentication.createRegisterToken(username, role),
+ register: (...[, { username, password, role }]) => authentication.register(username, password, role),
+ resetPassword: (root, args, context, info) => authentication.resetPassword(args.userID, args.newPassword, context),
+ reset2FA: (root, args, context, info) => authentication.reset2FA(args.userID, args.code, args.secret, context)
+ }
+}
+
+module.exports = resolver
diff --git a/lib/new-admin/graphql/types/index.js b/lib/new-admin/graphql/types/index.js
index 3e4531af..390e5924 100644
--- a/lib/new-admin/graphql/types/index.js
+++ b/lib/new-admin/graphql/types/index.js
@@ -16,6 +16,7 @@ const scalar = require('./scalar.type')
const settings = require('./settings.type')
const status = require('./status.type')
const transaction = require('./transaction.type')
+const user = require('./users.type')
const version = require('./version.type')
const types = [
@@ -35,6 +36,7 @@ const types = [
settings,
status,
transaction,
+ user,
version
]
diff --git a/lib/new-admin/graphql/types/users.type.js b/lib/new-admin/graphql/types/users.type.js
new file mode 100644
index 00000000..ee99b5fa
--- /dev/null
+++ b/lib/new-admin/graphql/types/users.type.js
@@ -0,0 +1,77 @@
+const typeDef = `
+ directive @auth(
+ requires: [Role] = [USER, SUPERUSER]
+ ) on OBJECT | FIELD_DEFINITION
+
+ enum Role {
+ SUPERUSER
+ USER
+ }
+
+ type UserSession {
+ sid: String!
+ sess: JSONObject!
+ expire: Date!
+ }
+
+ type User {
+ id: ID
+ username: String
+ role: String
+ enabled: Boolean
+ created: Date
+ last_accessed: Date
+ last_accessed_from: String
+ last_accessed_address: String
+ }
+
+ type TwoFactorSecret {
+ user_id: ID
+ secret: String!
+ otpauth: String!
+ }
+
+ type ResetToken {
+ token: String
+ user_id: ID
+ expire: Date
+ }
+
+ type RegistrationToken {
+ token: String
+ username: String
+ role: String
+ expire: Date
+ }
+
+ type Query {
+ users: [User] @auth(requires: [SUPERUSER])
+ sessions: [UserSession] @auth(requires: [SUPERUSER])
+ userSessions(username: String!): [UserSession] @auth(requires: [SUPERUSER])
+ userData: User
+ get2FASecret(username: String!, password: String!): TwoFactorSecret
+ confirm2FA(code: String!): Boolean @auth(requires: [SUPERUSER])
+ validateRegisterLink(token: String!): User
+ validateResetPasswordLink(token: String!): User
+ validateReset2FALink(token: String!): TwoFactorSecret
+ }
+
+ type Mutation {
+ deleteUser(id: ID!): User @auth(requires: [SUPERUSER])
+ deleteSession(sid: String!): UserSession @auth(requires: [SUPERUSER])
+ deleteUserSessions(username: String!): [UserSession] @auth(requires: [SUPERUSER])
+ changeUserRole(id: ID!, newRole: String!): User @auth(requires: [SUPERUSER])
+ toggleUserEnable(id: ID!): User @auth(requires: [SUPERUSER])
+ login(username: String!, password: String!): String
+ input2FA(username: String!, password: String!, code: String!, rememberMe: Boolean!): Boolean
+ setup2FA(username: String!, password: String!, secret: String!, codeConfirmation: String!): Boolean
+ createResetPasswordToken(userID: ID!): ResetToken @auth(requires: [SUPERUSER])
+ createReset2FAToken(userID: ID!): ResetToken @auth(requires: [SUPERUSER])
+ createRegisterToken(username: String!, role: String!): RegistrationToken @auth(requires: [SUPERUSER])
+ register(username: String!, password: String!, role: String!): Boolean
+ resetPassword(userID: ID!, newPassword: String!): Boolean
+ reset2FA(userID: ID!, secret: String!, code: String!): Boolean
+ }
+`
+
+module.exports = typeDef
diff --git a/lib/new-admin/routes/auth.js b/lib/new-admin/routes/auth.js
index 7ea9a870..1df1a983 100644
--- a/lib/new-admin/routes/auth.js
+++ b/lib/new-admin/routes/auth.js
@@ -1,259 +1,17 @@
-const otplib = require('otplib')
-const bcrypt = require('bcrypt')
+const express = require('express')
+const router = express.Router()
-const users = require('../../users')
-const login = require('../login')
+const getUserData = function (req, res, next) {
+ const lidCookie = req.cookies && req.cookies.lid
+ if (!lidCookie) {
+ res.sendStatus(403)
+ return
+ }
-async function isValidUser (username, password) {
- const hashedPassword = await login.checkUser(username)
- if (!hashedPassword) return false
-
- const isMatch = await bcrypt.compare(password, hashedPassword)
- if (!isMatch) return false
-
- const user = await login.validateUser(username, hashedPassword)
- if (!user) return false
- return user
+ const user = req.session.user
+ return res.status(200).json({ message: 'Success', user: user })
}
-module.exports = function (app) {
- app.post('/api/login', function (req, res, next) {
- const usernameInput = req.body.username
- const passwordInput = req.body.password
+router.get('/user-data', getUserData)
- isValidUser(usernameInput, passwordInput).then(user => {
- if (!user) return res.sendStatus(403)
- users.get2FASecret(user.id).then(user => {
- const twoFASecret = user.twofa_code
- if (twoFASecret) return res.status(200).json({ message: 'INPUT2FA' })
- if (!twoFASecret) return res.status(200).json({ message: 'SETUP2FA' })
- })
- })
- })
-
- app.post('/api/login/2fa', function (req, res, next) {
- const code = req.body.twoFACode
- const username = req.body.username
- const password = req.body.password
- const rememberMeInput = req.body.rememberMe
-
- isValidUser(username, password).then(user => {
- if (!user) return res.sendStatus(403)
-
- users.get2FASecret(user.id).then(user => {
- const secret = user.twofa_code
- const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
- if (!isCodeValid) return res.sendStatus(403)
-
- const finalUser = { id: user.id, username: user.username, role: user.role }
- req.session.user = finalUser
- if (rememberMeInput) req.session.cookie.maxAge = 90 * 24 * 60 * 60 * 1000 // 90 days
-
- return res.sendStatus(200)
- })
- })
- })
-
- app.post('/api/login/2fa/setup', function (req, res, next) {
- const username = req.body.username
- const password = req.body.password
-
- // TODO: maybe check if the user already has a 2fa secret
- isValidUser(username, password).then(user => {
- if (!user) return res.sendStatus(403)
-
- const secret = otplib.authenticator.generateSecret()
- const otpauth = otplib.authenticator.keyuri(username, 'Lamassu Industries', secret)
- return res.status(200).json({ secret, otpauth })
- })
- })
-
- app.post('/api/login/2fa/save', function (req, res, next) {
- const username = req.body.username
- const password = req.body.password
- const secret = req.body.secret
- const code = req.body.code
-
- isValidUser(username, password).then(user => {
- if (!user || !secret) return res.sendStatus(403)
-
- const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
- if (!isCodeValid) return res.sendStatus(403)
-
- users.save2FASecret(user.id, secret)
- return res.sendStatus(200)
- })
- })
-
- app.get('/user-data', function (req, res, next) {
- const lidCookie = req.cookies && req.cookies.lid
- if (!lidCookie) {
- res.sendStatus(403)
- return
- }
-
- const user = req.session.user
- return res.status(200).json({ message: 'Success', user: user })
- })
-
- app.post('/api/resetpassword', function (req, res, next) {
- const userID = req.body.userID
-
- users.findById(userID)
- .then(user => {
- if (!user) return res.sendStatus(403)
- return users.createResetPasswordToken(user.id)
- })
- .then(token => {
- return res.status(200).json({ token })
- })
- .catch(err => console.log(err))
- })
-
- app.get('/api/resetpassword', function (req, res, next) {
- const token = req.query.t
-
- if (!token) return res.sendStatus(400)
- return users.validatePasswordResetToken(token)
- .then(r => {
- if (!r.success) return res.status(200).send('The link has expired')
- return res.status(200).json({ userID: r.userID })
- })
- .catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.post('/api/updatepassword', function (req, res, next) {
- const userID = req.body.userID
- const newPassword = req.body.newPassword
-
- users.findById(userID).then(user => {
- if (req.session.user && user.id === req.session.user.id) req.session.destroy()
- return users.updatePassword(user.id, newPassword)
- }).then(() => {
- res.sendStatus(200)
- }).catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.post('/api/reset2fa', function (req, res, next) {
- const userID = req.body.userID
-
- users.findById(userID)
- .then(user => {
- if (!user) return res.sendStatus(403)
- return users.createReset2FAToken(user.id)
- })
- .then(token => {
- return res.status(200).json({ token })
- })
- .catch(err => console.log(err))
- })
-
- app.get('/api/reset2fa', function (req, res, next) {
- const token = req.query.t
-
- if (!token) return res.sendStatus(400)
- return users.validate2FAResetToken(token)
- .then(r => {
- if (!r.success) return res.status(200).send('The link has expired')
- return users.findById(r.userID)
- })
- .then(user => {
- const secret = otplib.authenticator.generateSecret()
- const otpauth = otplib.authenticator.keyuri(user.username, 'Lamassu Industries', secret)
- return res.status(200).json({ userID: user.id, secret, otpauth })
- })
- .catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.post('/api/update2fa', function (req, res, next) {
- const userID = req.body.userID
- const secret = req.body.secret
- const code = req.body.code
-
- users.findById(userID).then(user => {
- const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
- if (!isCodeValid) return res.sendStatus(401)
-
- if (req.session.user && user.id === req.session.user.id) req.session.destroy()
- users.save2FASecret(user.id, secret).then(() => { return res.sendStatus(200) })
- }).catch(err => {
- console.log(err)
- return res.sendStatus(400)
- })
- })
-
- app.post('/api/createuser', function (req, res, next) {
- const username = req.body.username
- const role = req.body.role
-
- users.getByName(username)
- .then(user => {
- if (user) return res.status(200).json({ message: 'User already exists!' })
-
- users.createUserRegistrationToken(username, role).then(token => {
- return res.status(200).json({ token })
- })
- })
- .catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.get('/api/register', function (req, res, next) {
- const token = req.query.t
-
- if (!token) return res.sendStatus(400)
- users.validateUserRegistrationToken(token)
- .then(r => {
- if (!r.success) return res.status(200).json({ message: 'The link has expired' })
- return res.status(200).json({ username: r.username, role: r.role })
- })
- .catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.post('/api/register', function (req, res, next) {
- const username = req.body.username
- const password = req.body.password
- const role = req.body.role
-
- users.getByName(username)
- .then(user => {
- if (user) return res.status(200).json({ message: 'User already exists!' })
-
- users.createUser(username, password, role)
- res.sendStatus(200)
- })
- .catch(err => {
- console.log(err)
- res.sendStatus(400)
- })
- })
-
- app.post('/api/confirm2fa', function (req, res, next) {
- const code = req.body.code
- const requestingUser = req.session.user
-
- if (!requestingUser) return res.status(403)
-
- users.get2FASecret(requestingUser.id).then(user => {
- const secret = user.twofa_code
- const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
- if (!isCodeValid) return res.sendStatus(401)
-
- return res.sendStatus(200)
- })
- })
-}
+module.exports = router
diff --git a/new-lamassu-admin/src/lamassu/App.js b/new-lamassu-admin/src/lamassu/App.js
index e4a6d3b1..3baed658 100644
--- a/new-lamassu-admin/src/lamassu/App.js
+++ b/new-lamassu-admin/src/lamassu/App.js
@@ -9,7 +9,7 @@ import {
import { axios } from '@use-hooks/axios'
import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
-import React, { createContext, useContext, useEffect, useState } from 'react'
+import React, { useContext, useEffect, useState } from 'react'
import {
useLocation,
useHistory,
diff --git a/new-lamassu-admin/src/pages/Authentication/Input2FAState.js b/new-lamassu-admin/src/pages/Authentication/Input2FAState.js
index 3e70d77c..63c4ad27 100644
--- a/new-lamassu-admin/src/pages/Authentication/Input2FAState.js
+++ b/new-lamassu-admin/src/pages/Authentication/Input2FAState.js
@@ -1,5 +1,6 @@
+import { useMutation, useLazyQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles'
-import axios from 'axios'
+import gql from 'graphql-tag'
import React, { useContext, useState } from 'react'
import { useHistory } from 'react-router-dom'
@@ -10,11 +11,34 @@ import { H2, P } from 'src/components/typography'
import styles from './Login.styles'
-const url =
- process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
-
const useStyles = makeStyles(styles)
+const INPUT_2FA = gql`
+ mutation input2FA(
+ $username: String!
+ $password: String!
+ $code: String!
+ $rememberMe: Boolean!
+ ) {
+ input2FA(
+ username: $username
+ password: $password
+ code: $code
+ rememberMe: $rememberMe
+ )
+ }
+`
+
+const GET_USER_DATA = gql`
+ {
+ userData {
+ id
+ username
+ role
+ }
+ }
+`
+
const Input2FAState = ({
twoFAField,
onTwoFAChange,
@@ -33,53 +57,25 @@ const Input2FAState = ({
setInvalidToken(false)
}
- const handle2FA = () => {
- axios({
- method: 'POST',
- url: `${url}/api/login/2fa`,
- data: {
- username: clientField,
- password: passwordField,
- rememberMe: rememberMeField,
- twoFACode: twoFAField
- },
- withCredentials: true,
- headers: {
- 'Content-Type': 'application/json'
- }
- })
- .then((res, err) => {
- if (err) return
- if (res) {
- const status = res.status
- if (status === 200) {
- getUserData()
- history.push('/')
- }
- }
- })
- .catch(err => {
- if (err.response && err.response.data) {
- if (err.response.status === 403) {
- onTwoFAChange('')
- setInvalidToken(true)
- }
- }
- })
- }
+ const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
+ onCompleted: ({ input2FA: success }) => {
+ success ? getUserData() : setInvalidToken(true)
+ }
+ })
- const getUserData = () => {
- axios({
- method: 'GET',
- url: `${url}/user-data`,
- withCredentials: true
- })
- .then(res => {
- if (res.status === 200) setUserData(res.data.user)
- })
- .catch(err => {
- if (err.status === 403) setUserData(null)
- })
+ const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, {
+ onCompleted: ({ userData }) => {
+ setUserData(userData)
+ history.push('/')
+ }
+ })
+
+ const getErrorMsg = () => {
+ if (mutationError || queryError) return 'Internal server error'
+ if (twoFAField.length !== 6 && invalidToken)
+ return 'The code should have 6 characters!'
+ if (invalidToken) return 'Code is invalid. Please try again.'
+ return null
}
return (
@@ -93,16 +89,26 @@ const Input2FAState = ({
onChange={handle2FAChange}
numInputs={6}
error={invalidToken}
+ shouldAutoFocus
/>
- {invalidToken && (
-
- Code is invalid. Please try again.
-
+ {getErrorMsg() && (
+
{getErrorMsg()}
)}