feat: implement hardware key auth
This commit is contained in:
parent
0035684040
commit
f987a07e0b
17 changed files with 1302 additions and 36 deletions
|
|
@ -1,11 +1,14 @@
|
|||
import { useMutation } from '@apollo/react-hooks'
|
||||
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { startAssertion } from '@simplewebauthn/browser'
|
||||
import base64 from 'base-64'
|
||||
import { Field, Form, Formik } from 'formik'
|
||||
import gql from 'graphql-tag'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import AppContext from 'src/AppContext'
|
||||
import { Button } from 'src/components/buttons'
|
||||
import { Checkbox, SecretInput, TextInput } from 'src/components/inputs/formik'
|
||||
import { Label3, P } from 'src/components/typography'
|
||||
|
|
@ -21,6 +24,28 @@ const LOGIN = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const GENERATE_ASSERTION = gql`
|
||||
query generateAssertionOptions {
|
||||
generateAssertionOptions
|
||||
}
|
||||
`
|
||||
|
||||
const VALIDATE_ASSERTION = gql`
|
||||
mutation validateAssertion($assertionResponse: JSONObject!) {
|
||||
validateAssertion(assertionResponse: $assertionResponse)
|
||||
}
|
||||
`
|
||||
|
||||
const GET_USER_DATA = gql`
|
||||
{
|
||||
userData {
|
||||
id
|
||||
username
|
||||
role
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
client: Yup.string()
|
||||
.required('Client field is required!')
|
||||
|
|
@ -44,10 +69,12 @@ const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
|
|||
return null
|
||||
}
|
||||
|
||||
const LoginState = ({ state, dispatch }) => {
|
||||
const LoginState = ({ state, dispatch, strategy }) => {
|
||||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const { setUserData } = useContext(AppContext)
|
||||
|
||||
const [login, { error: mutationError }] = useMutation(LOGIN)
|
||||
const [login, { error: loginMutationError }] = useMutation(LOGIN)
|
||||
|
||||
const submitLogin = async (username, password, rememberMe) => {
|
||||
const options = {
|
||||
|
|
@ -65,8 +92,7 @@ const LoginState = ({ state, dispatch }) => {
|
|||
|
||||
if (!loginResponse.login) return
|
||||
|
||||
const stateVar =
|
||||
loginResponse.login === 'INPUT2FA' ? STATES.INPUT_2FA : STATES.SETUP_2FA
|
||||
const stateVar = STATES[loginResponse.login]
|
||||
|
||||
return dispatch({
|
||||
type: stateVar,
|
||||
|
|
@ -78,6 +104,43 @@ const LoginState = ({ state, dispatch }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const [validateAssertion, { error: FIDOMutationError }] = useMutation(
|
||||
VALIDATE_ASSERTION,
|
||||
{
|
||||
onCompleted: ({ validateAssertion: success }) => success && getUserData()
|
||||
}
|
||||
)
|
||||
|
||||
const [assertionOptions, { error: assertionQueryError }] = useLazyQuery(
|
||||
GENERATE_ASSERTION,
|
||||
{
|
||||
onCompleted: ({ generateAssertionOptions: options }) => {
|
||||
console.log(options)
|
||||
startAssertion(options)
|
||||
.then(res => {
|
||||
validateAssertion({
|
||||
variables: {
|
||||
assertionResponse: res
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const [getUserData, { error: userDataQueryError }] = useLazyQuery(
|
||||
GET_USER_DATA,
|
||||
{
|
||||
onCompleted: ({ userData }) => {
|
||||
setUserData(userData)
|
||||
history.push('/')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
|
|
@ -95,7 +158,14 @@ const LoginState = ({ state, dispatch }) => {
|
|||
fullWidth
|
||||
autoFocus
|
||||
className={classes.input}
|
||||
error={getErrorMsg(errors, touched, mutationError)}
|
||||
error={getErrorMsg(
|
||||
errors,
|
||||
touched,
|
||||
loginMutationError ||
|
||||
FIDOMutationError ||
|
||||
assertionQueryError ||
|
||||
userDataQueryError
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
name="password"
|
||||
|
|
@ -103,7 +173,14 @@ const LoginState = ({ state, dispatch }) => {
|
|||
component={SecretInput}
|
||||
label="Password"
|
||||
fullWidth
|
||||
error={getErrorMsg(errors, touched, mutationError)}
|
||||
error={getErrorMsg(
|
||||
errors,
|
||||
touched,
|
||||
loginMutationError ||
|
||||
FIDOMutationError ||
|
||||
assertionQueryError ||
|
||||
userDataQueryError
|
||||
)}
|
||||
/>
|
||||
<div className={classes.rememberMeWrapper}>
|
||||
<Field
|
||||
|
|
@ -114,11 +191,41 @@ const LoginState = ({ state, dispatch }) => {
|
|||
<Label3>Keep me logged in</Label3>
|
||||
</div>
|
||||
<div className={classes.footer}>
|
||||
{getErrorMsg(errors, touched, mutationError) && (
|
||||
{getErrorMsg(
|
||||
errors,
|
||||
touched,
|
||||
loginMutationError ||
|
||||
FIDOMutationError ||
|
||||
assertionQueryError ||
|
||||
userDataQueryError
|
||||
) && (
|
||||
<P className={classes.errorMessage}>
|
||||
{getErrorMsg(errors, touched, mutationError)}
|
||||
{getErrorMsg(
|
||||
errors,
|
||||
touched,
|
||||
loginMutationError ||
|
||||
FIDOMutationError ||
|
||||
assertionQueryError ||
|
||||
userDataQueryError
|
||||
)}
|
||||
</P>
|
||||
)}
|
||||
{strategy !== 'FIDO2FA' && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
return strategy === 'FIDOUsernameless'
|
||||
? assertionOptions()
|
||||
: dispatch({
|
||||
type: 'FIDO',
|
||||
payload: {}
|
||||
})
|
||||
}}
|
||||
buttonClassName={classes.loginButton}
|
||||
className={classes.fidoLoginButtonWrapper}>
|
||||
I have a YubiKey
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
form="login-form"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue