fixed password setup not working; general improvements & bugfixes

This commit is contained in:
Myzel394 2022-12-17 23:14:01 +01:00
parent 4ab7303071
commit 934a071f81
8 changed files with 186 additions and 146 deletions

View File

@ -11,13 +11,13 @@ export enum EncryptionStatus {
interface AuthContextTypeBase { interface AuthContextTypeBase {
user: ServerUser | User | null user: ServerUser | User | null
isAuthenticated: boolean isAuthenticated: boolean
login: (user: ServerUser | User) => void login: (user: ServerUser | User, callback?: () => void) => void
logout: () => void logout: () => void
encryptionStatus: EncryptionStatus encryptionStatus: EncryptionStatus
_decryptUsingMasterPassword: (content: string) => string _decryptUsingMasterPassword: (content: string) => string
_encryptUsingMasterPassword: (content: string) => string _encryptUsingMasterPassword: (content: string) => string
_decryptUsingPrivateKey: (message: string) => Promise<string> _decryptUsingPrivateKey: (message: string) => Promise<string>
_setDecryptionPassword: (decryptionPassword: string) => boolean _setDecryptionPassword: (decryptionPassword: string, callback?: () => void) => boolean
_updateUser: (user: ServerUser | User) => void _updateUser: (user: ServerUser | User) => void
} }

View File

@ -1,6 +1,6 @@
import {ReactElement, ReactNode, useCallback} from "react" import {ReactElement, ReactNode, useCallback} from "react"
import {useLocalStorage} from "react-use" import {useLocalStorage} from "react-use"
import fastHashCode from "fast-hash-code"; import fastHashCode from "fast-hash-code"
import {ServerUser, User} from "~/server-types" import {ServerUser, User} from "~/server-types"
@ -9,7 +9,7 @@ import PasswordShareConfirmationDialog from "./PasswordShareConfirmationDialog"
import useContextValue from "./use-context-value" import useContextValue from "./use-context-value"
import useExtensionHandler from "./use-extension-handler" import useExtensionHandler from "./use-extension-handler"
import useMasterPassword from "./use-master-password" import useMasterPassword from "./use-master-password"
import useUser from "./use-user"; import useUser from "./use-user"
export interface AuthContextProviderProps { export interface AuthContextProviderProps {
children: ReactNode children: ReactNode
@ -26,9 +26,11 @@ export default function AuthContextProvider({children}: AuthContextProviderProps
decryptUsingMasterPassword, decryptUsingMasterPassword,
decryptUsingPrivateKey, decryptUsingPrivateKey,
setDecryptionPassword, setDecryptionPassword,
decryptionPasswordHash,
_masterPassword, _masterPassword,
logout: logoutMasterPassword, logout: logoutMasterPassword,
} = useMasterPassword(user || null) } = useMasterPassword(user || null)
const passwordHash = _masterPassword ? fastHashCode(_masterPassword).toString() : null
const {sharePassword, closeDialog, showDialog, dispatchPasswordStatus} = useExtensionHandler( const {sharePassword, closeDialog, showDialog, dispatchPasswordStatus} = useExtensionHandler(
_masterPassword!, _masterPassword!,
user as User, user as User,
@ -39,10 +41,11 @@ export default function AuthContextProvider({children}: AuthContextProviderProps
}, [logoutMasterPassword]) }, [logoutMasterPassword])
const contextValue = useContextValue({ const contextValue = useContextValue({
decryptUsingPrivateKey, _decryptUsingPrivateKey: decryptUsingPrivateKey,
encryptUsingMasterPassword, _encryptUsingMasterPassword: encryptUsingMasterPassword,
decryptUsingMasterPassword, _decryptUsingMasterPassword: decryptUsingMasterPassword,
setDecryptionPassword, _setDecryptionPassword: setDecryptionPassword,
decryptionPasswordHash,
logout, logout,
login: setUser, login: setUser,
user: user || null, user: user || null,
@ -53,7 +56,7 @@ export default function AuthContextProvider({children}: AuthContextProviderProps
decryptUsingMasterPassword, decryptUsingMasterPassword,
user: user || null, user: user || null,
updateUser: setUser, updateUser: setUser,
masterPasswordHash: _masterPassword ? fastHashCode(_masterPassword).toString() : null, masterPasswordHash: passwordHash,
}) })
return ( return (

View File

@ -1,28 +1,49 @@
import {useMemo, useRef} from "react"
import {useUpdateEffect} from "react-use"
import {AuthContextType, EncryptionStatus} from "~/AuthContext/AuthContext" import {AuthContextType, EncryptionStatus} from "~/AuthContext/AuthContext"
import {ServerUser, User} from "~/server-types" import {ServerUser, User} from "~/server-types"
export interface UseContextValueData { export type UseContextValueData = Pick<
user: User | ServerUser | null AuthContextType,
| "user"
| "logout"
| "_encryptUsingMasterPassword"
| "_decryptUsingMasterPassword"
| "_decryptUsingPrivateKey"
> & {
decryptionPasswordHash: string
_setDecryptionPassword: (password: string) => boolean
login: (user: User | ServerUser) => void login: (user: User | ServerUser) => void
logout: () => void
encryptUsingMasterPassword: (content: string) => string
decryptUsingMasterPassword: (content: string) => string
decryptUsingPrivateKey: (message: string) => Promise<string>
setDecryptionPassword: (password: string) => boolean
} }
export default function useContextValue({ export default function useContextValue({
user, user,
login, login,
logout, logout,
encryptUsingMasterPassword, _encryptUsingMasterPassword,
decryptUsingMasterPassword, _decryptUsingMasterPassword,
setDecryptionPassword, _setDecryptionPassword,
decryptUsingPrivateKey, _decryptUsingPrivateKey,
decryptionPasswordHash,
}: UseContextValueData): AuthContextType { }: UseContextValueData): AuthContextType {
return { const $decryptionPasswordChangeCallback = useRef<(() => void) | null>(null)
const $userChangeCallback = useRef<(() => void) | null>(null)
useUpdateEffect(() => {
$decryptionPasswordChangeCallback.current?.()
}, [decryptionPasswordHash, user])
return useMemo(
() => ({
user, user,
login, login: (user, callback) => {
if (callback) {
$userChangeCallback.current = callback
}
return login(user)
},
logout, logout,
isAuthenticated: Boolean(user), isAuthenticated: Boolean(user),
encryptionStatus: (() => { encryptionStatus: (() => {
@ -41,9 +62,25 @@ export default function useContextValue({
return EncryptionStatus.PasswordRequired return EncryptionStatus.PasswordRequired
})(), })(),
_updateUser: login, _updateUser: login,
_setDecryptionPassword: setDecryptionPassword, _setDecryptionPassword: (password, callback) => {
_encryptUsingMasterPassword: encryptUsingMasterPassword, if (callback) {
_decryptUsingMasterPassword: decryptUsingMasterPassword, $decryptionPasswordChangeCallback.current = callback
_decryptUsingPrivateKey: decryptUsingPrivateKey,
} }
return _setDecryptionPassword(password)
},
_encryptUsingMasterPassword,
_decryptUsingMasterPassword,
_decryptUsingPrivateKey,
}),
[
user,
login,
logout,
_setDecryptionPassword,
_encryptUsingMasterPassword,
_decryptUsingMasterPassword,
_decryptUsingPrivateKey,
],
)
} }

View File

@ -1,9 +1,10 @@
import {useLocalStorage} from "react-use" import {useLocalStorage} from "react-use"
import {useCallback, useMemo} from "react" import {useCallback, useMemo} from "react"
import {decrypt, readMessage, readPrivateKey} from "openpgp"; import {decrypt, readMessage, readPrivateKey} from "openpgp"
import fastHashCode from "fast-hash-code"
import {decryptString, encryptString} from "~/utils" import {decryptString, encryptString} from "~/utils"
import {ServerUser, User} from "~/server-types"; import {ServerUser, User} from "~/server-types"
export interface UseMasterPasswordResult { export interface UseMasterPasswordResult {
encryptUsingMasterPassword: (content: string) => string encryptUsingMasterPassword: (content: string) => string
@ -12,13 +13,12 @@ export interface UseMasterPasswordResult {
setDecryptionPassword: (password: string) => boolean setDecryptionPassword: (password: string) => boolean
logout: () => void logout: () => void
decryptionPasswordHash: string
// Use this cautiously // Use this cautiously
_masterPassword: string _masterPassword: string
} }
export default function useMasterPassword( export default function useMasterPassword(user: User | ServerUser | null): UseMasterPasswordResult {
user: User | ServerUser | null,
): UseMasterPasswordResult {
const [decryptionPassword, setDecryptionPassword] = useLocalStorage<string | null>( const [decryptionPassword, setDecryptionPassword] = useLocalStorage<string | null>(
"_global-context-auth-decryption-password", "_global-context-auth-decryption-password",
null, null,
@ -78,21 +78,23 @@ export default function useMasterPassword(
[user], [user],
) )
const updateDecryptionPassword = useCallback((password: string) => { const updateDecryptionPassword = useCallback(
if (!user || !user.encryptedPassword) { (password: string) => {
throw new Error("User not set.") if (user?.encryptedPassword) {
}
try { try {
const masterPassword = decryptString(user.encryptedPassword, password) const masterPassword = decryptString(user.encryptedPassword, password)
JSON.parse(decryptString((user as ServerUser).encryptedNotes, masterPassword)) JSON.parse(decryptString((user as ServerUser).encryptedNotes, masterPassword))
setDecryptionPassword(password) } catch (e) {
} catch { return false
return false; }
} }
return true; setDecryptionPassword(password)
}, [user, masterPassword])
return true
},
[user, masterPassword],
)
const logout = useCallback(() => { const logout = useCallback(() => {
setDecryptionPassword(null) setDecryptionPassword(null)
@ -105,5 +107,6 @@ export default function useMasterPassword(
logout, logout,
setDecryptionPassword: updateDecryptionPassword, setDecryptionPassword: updateDecryptionPassword,
_masterPassword: masterPassword!, _masterPassword: masterPassword!,
decryptionPasswordHash: fastHashCode(decryptionPassword || "").toString(),
} }
} }

View File

@ -1,11 +1,11 @@
import {Dispatch, SetStateAction, useEffect} from "react"; import {Dispatch, SetStateAction, useEffect} from "react"
import {AxiosError} from "axios"; import {AxiosError} from "axios"
import {useMutation, useQuery} from "@tanstack/react-query"; import {useMutation, useQuery} from "@tanstack/react-query"
import {REFRESH_TOKEN_URL, RefreshTokenResult, getMe, refreshToken} from "~/apis" import {REFRESH_TOKEN_URL, RefreshTokenResult, getMe, refreshToken} from "~/apis"
import {AuthenticationDetails, ServerUser, User} from "~/server-types"; import {AuthenticationDetails, ServerUser, User} from "~/server-types"
import {client} from "~/constants/axios-client"; import {client} from "~/constants/axios-client"
export interface UseAuthData { export interface UseAuthData {
logout: () => void logout: () => void
@ -38,14 +38,11 @@ export default function useUser({
if (user && !user.isDecrypted && user.encryptedPassword && masterPasswordHash) { if (user && !user.isDecrypted && user.encryptedPassword && masterPasswordHash) {
const note = JSON.parse(decryptUsingMasterPassword(user.encryptedNotes!)) const note = JSON.parse(decryptUsingMasterPassword(user.encryptedNotes!))
updateUser( updateUser({
prevUser => ...user,
({
...(prevUser || {}),
notes: note, notes: note,
isDecrypted: true, isDecrypted: true,
} as User), } as User)
)
} }
}, [user, decryptUsingMasterPassword, updateUser, masterPasswordHash]) }, [user, decryptUsingMasterPassword, updateUser, masterPasswordHash])

View File

@ -12,7 +12,7 @@ import passwordGenerator from "secure-random-password"
import {PasswordField, SimpleForm} from "~/components" import {PasswordField, SimpleForm} from "~/components"
import {buildEncryptionPassword, encryptString} from "~/utils" import {buildEncryptionPassword, encryptString} from "~/utils"
import {isDev} from "~/constants/development" import {isDev} from "~/constants/development"
import {useExtensionHandler, useSystemPreferredTheme, useUser} from "~/hooks" import {useExtensionHandler, useNavigateToNext, useSystemPreferredTheme, useUser} from "~/hooks"
import {MASTER_PASSWORD_LENGTH} from "~/constants/values" import {MASTER_PASSWORD_LENGTH} from "~/constants/values"
import {AuthenticationDetails, UserNote} from "~/server-types" import {AuthenticationDetails, UserNote} from "~/server-types"
import {UpdateAccountData, updateAccount} from "~/apis" import {UpdateAccountData, updateAccount} from "~/apis"
@ -34,6 +34,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
const user = useUser() const user = useUser()
const theme = useSystemPreferredTheme() const theme = useSystemPreferredTheme()
const navigateToNext = useNavigateToNext()
const $password = useRef<HTMLInputElement | null>(null) const $password = useRef<HTMLInputElement | null>(null)
const $passwordConfirmation = useRef<HTMLInputElement | null>(null) const $passwordConfirmation = useRef<HTMLInputElement | null>(null)
const schema = yup.object().shape({ const schema = yup.object().shape({
@ -65,12 +66,6 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
) )
const {mutateAsync} = useMutation<AuthenticationDetails, AxiosError, UpdateAccountData>( const {mutateAsync} = useMutation<AuthenticationDetails, AxiosError, UpdateAccountData>(
updateAccount, updateAccount,
{
onSuccess: ({user}) => {
login(user)
setTimeout(onDone, 0)
},
},
) )
const formik = useFormik<Form>({ const formik = useFormik<Form>({
validationSchema: schema, validationSchema: schema,
@ -97,9 +92,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
} }
const encryptedNotes = encryptUserNote(note, masterPassword) const encryptedNotes = encryptUserNote(note, masterPassword)
_setDecryptionPassword(encryptionPassword) await mutateAsync(
{
await mutateAsync({
encryptedPassword: encryptedMasterPassword, encryptedPassword: encryptedMasterPassword,
publicKey: ( publicKey: (
await readKey({ await readKey({
@ -109,7 +103,14 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
.toPublic() .toPublic()
.armor(), .armor(),
encryptedNotes, encryptedNotes,
}) },
{
onSuccess: ({user: newUser}) => {
login(newUser)
_setDecryptionPassword(encryptionPassword, navigateToNext)
},
},
)
} catch (error) { } catch (error) {
setErrors({detail: t("general.defaultError")}) setErrors({detail: t("general.defaultError")})
} }

View File

@ -57,7 +57,7 @@ export default function ConfirmCodeForm({
sameRequestToken, sameRequestToken,
}: ConfirmCodeFormProps): ReactElement { }: ConfirmCodeFormProps): ReactElement {
const settings = useLoaderData() as ServerSettings const settings = useLoaderData() as ServerSettings
const expirationTime = isDev ? 9 : settings.emailLoginExpirationInSeconds const expirationTime = isDev ? 70 : settings.emailLoginExpirationInSeconds
const {t} = useTranslation() const {t} = useTranslation()
const requestDate = useMemo(() => new Date(), []) const requestDate = useMemo(() => new Date(), [])
const [isExpiringSoon, setIsExpiringSoon] = useState<boolean>(false) const [isExpiringSoon, setIsExpiringSoon] = useState<boolean>(false)

View File

@ -36,15 +36,14 @@ export default function EnterDecryptionPassword(): ReactElement {
onSubmit: async ({password}, {setErrors}) => { onSubmit: async ({password}, {setErrors}) => {
const decryptionPassword = buildEncryptionPassword(password, user.email.address) const decryptionPassword = buildEncryptionPassword(password, user.email.address)
console.log("decryptionPassword", decryptionPassword) const isPasswordCorrect = _setDecryptionPassword(decryptionPassword, navigateToNext)
if (!_setDecryptionPassword(decryptionPassword)) {
if (!isPasswordCorrect) {
setErrors({ setErrors({
password: t( password: t(
"components.EnterDecryptionPassword.form.password.errors.invalidPassword", "components.EnterDecryptionPassword.form.password.errors.invalidPassword",
), ),
}) })
} else {
setTimeout(navigateToNext, 0)
} }
}, },
}) })