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 {
user: ServerUser | User | null
isAuthenticated: boolean
login: (user: ServerUser | User) => void
login: (user: ServerUser | User, callback?: () => void) => void
logout: () => void
encryptionStatus: EncryptionStatus
_decryptUsingMasterPassword: (content: string) => string
_encryptUsingMasterPassword: (content: string) => string
_decryptUsingPrivateKey: (message: string) => Promise<string>
_setDecryptionPassword: (decryptionPassword: string) => boolean
_setDecryptionPassword: (decryptionPassword: string, callback?: () => void) => boolean
_updateUser: (user: ServerUser | User) => void
}

View File

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

View File

@ -1,28 +1,49 @@
import {useMemo, useRef} from "react"
import {useUpdateEffect} from "react-use"
import {AuthContextType, EncryptionStatus} from "~/AuthContext/AuthContext"
import {ServerUser, User} from "~/server-types"
export interface UseContextValueData {
user: User | ServerUser | null
export type UseContextValueData = Pick<
AuthContextType,
| "user"
| "logout"
| "_encryptUsingMasterPassword"
| "_decryptUsingMasterPassword"
| "_decryptUsingPrivateKey"
> & {
decryptionPasswordHash: string
_setDecryptionPassword: (password: string) => boolean
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({
user,
login,
logout,
encryptUsingMasterPassword,
decryptUsingMasterPassword,
setDecryptionPassword,
decryptUsingPrivateKey,
_encryptUsingMasterPassword,
_decryptUsingMasterPassword,
_setDecryptionPassword,
_decryptUsingPrivateKey,
decryptionPasswordHash,
}: UseContextValueData): AuthContextType {
return {
const $decryptionPasswordChangeCallback = useRef<(() => void) | null>(null)
const $userChangeCallback = useRef<(() => void) | null>(null)
useUpdateEffect(() => {
$decryptionPasswordChangeCallback.current?.()
}, [decryptionPasswordHash, user])
return useMemo(
() => ({
user,
login,
login: (user, callback) => {
if (callback) {
$userChangeCallback.current = callback
}
return login(user)
},
logout,
isAuthenticated: Boolean(user),
encryptionStatus: (() => {
@ -41,9 +62,25 @@ export default function useContextValue({
return EncryptionStatus.PasswordRequired
})(),
_updateUser: login,
_setDecryptionPassword: setDecryptionPassword,
_encryptUsingMasterPassword: encryptUsingMasterPassword,
_decryptUsingMasterPassword: decryptUsingMasterPassword,
_decryptUsingPrivateKey: decryptUsingPrivateKey,
_setDecryptionPassword: (password, callback) => {
if (callback) {
$decryptionPasswordChangeCallback.current = callback
}
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 {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 {ServerUser, User} from "~/server-types";
import {ServerUser, User} from "~/server-types"
export interface UseMasterPasswordResult {
encryptUsingMasterPassword: (content: string) => string
@ -12,13 +13,12 @@ export interface UseMasterPasswordResult {
setDecryptionPassword: (password: string) => boolean
logout: () => void
decryptionPasswordHash: string
// Use this cautiously
_masterPassword: string
}
export default function useMasterPassword(
user: User | ServerUser | null,
): UseMasterPasswordResult {
export default function useMasterPassword(user: User | ServerUser | null): UseMasterPasswordResult {
const [decryptionPassword, setDecryptionPassword] = useLocalStorage<string | null>(
"_global-context-auth-decryption-password",
null,
@ -78,21 +78,23 @@ export default function useMasterPassword(
[user],
)
const updateDecryptionPassword = useCallback((password: string) => {
if (!user || !user.encryptedPassword) {
throw new Error("User not set.")
}
const updateDecryptionPassword = useCallback(
(password: string) => {
if (user?.encryptedPassword) {
try {
const masterPassword = decryptString(user.encryptedPassword, password)
JSON.parse(decryptString((user as ServerUser).encryptedNotes, masterPassword))
setDecryptionPassword(password)
} catch {
return false;
} catch (e) {
return false
}
}
return true;
}, [user, masterPassword])
setDecryptionPassword(password)
return true
},
[user, masterPassword],
)
const logout = useCallback(() => {
setDecryptionPassword(null)
@ -105,5 +107,6 @@ export default function useMasterPassword(
logout,
setDecryptionPassword: updateDecryptionPassword,
_masterPassword: masterPassword!,
decryptionPasswordHash: fastHashCode(decryptionPassword || "").toString(),
}
}

View File

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

View File

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

View File

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

View File

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