diff --git a/src/constants/values.ts b/src/constants/values.ts index f54e72d..430804c 100644 --- a/src/constants/values.ts +++ b/src/constants/values.ts @@ -1,13 +1,12 @@ import {AliasNote} from "~/server-types" -export const MASTER_PASSWORD_LENGTH = 1024 export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~-]{1,64}$/g export const URL_REGEX = /((http[s]*:\/\/)?[a-z0-9-%\/\&=?\.]+\.[a-z]{2,4}\/?([^\s<>\#%"\,\{\}\\|\\\^\[\]`]+)?)/gi export const DEFAULT_ALIAS_NOTE: AliasNote = { version: "1.0", data: { - createdOn: "instance", + createdOn: "/aliases", createdAt: null, creationContext: "web", personalNotes: "", diff --git a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx index 9643bdf..4423ae3 100644 --- a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx +++ b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx @@ -37,6 +37,9 @@ export function CreateAliasButton(): ReactElement { JSON.stringify( update(DEFAULT_ALIAS_NOTE, { data: { + createdOn: { + $set: window.location.origin + window.location.pathname, + }, createdAt: { $set: new Date(), }, diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index 2f5093e..e8dcf11 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -5,19 +5,16 @@ import {readKey} from "openpgp" import {AxiosError} from "axios" import {useTranslation} from "react-i18next" import {useLoaderData} from "react-router-dom" -import React, {ReactElement, useCallback, useContext, useMemo, useRef} from "react" -import passwordGenerator from "secure-random-password" +import React, {ReactElement, useCallback, useContext, useRef} from "react" import {Box, InputAdornment} from "@mui/material" import {useMutation} from "@tanstack/react-query" import {AuthContext, PasswordField, SimpleForm} from "~/components" -import {encryptString, generateKeys, getEncryptionPassword, getUserSalt} from "~/utils" +import {setupEncryptionForUser} from "~/utils" import {useExtensionHandler, useNavigateToNext, useSystemPreferredTheme, useUser} from "~/hooks" -import {MASTER_PASSWORD_LENGTH} from "~/constants/values" -import {AuthenticationDetails, ServerSettings, UserNote} from "~/server-types" +import {AuthenticationDetails, ServerSettings} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" -import {encryptUserNote} from "~/utils/encrypt-user-note" export interface PasswordFormProps { onDone: () => void @@ -54,7 +51,6 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement const {_setEncryptionPassword, login} = useContext(AuthContext) - const waitForKeyGeneration = useMemo(generateKeys, []) const {mutateAsync} = useMutation( updateAccount, ) @@ -66,32 +62,20 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement }, onSubmit: async ({password}, {setErrors}) => { try { - const keyPair = await waitForKeyGeneration - - const masterPassword = passwordGenerator.randomPassword({ - length: MASTER_PASSWORD_LENGTH, - }) - - const salt = getUserSalt(user, serverSettings) - const encryptionKey = await getEncryptionPassword( - user.email.address, - password, - salt, - ) - const encryptedMasterPassword = encryptString(masterPassword, encryptionKey) - - const note: UserNote = { - theme, - privateKey: keyPair.privateKey, - } - const encryptedNotes = encryptUserNote(note, masterPassword) + const {encryptionPassword, encryptedPassword, encryptedNotes, publicKey} = + await setupEncryptionForUser({ + password, + user, + serverSettings, + theme, + }) await mutateAsync( { - encryptedPassword: encryptedMasterPassword, + encryptedPassword, publicKey: ( await readKey({ - armoredKey: keyPair.publicKey, + armoredKey: publicKey, }) ) .toPublic() @@ -101,7 +85,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement { onSuccess: ({user: newUser}) => { login(newUser) - _setEncryptionPassword(masterPassword) + _setEncryptionPassword(encryptionPassword) navigateToNext() }, }, diff --git a/src/utils/crypto/generate-encryption-password.ts b/src/utils/crypto/generate-encryption-password.ts new file mode 100644 index 0000000..2c81420 --- /dev/null +++ b/src/utils/crypto/generate-encryption-password.ts @@ -0,0 +1,9 @@ +import passwordGenerator from "secure-random-password" + +export default function generateEncryptionPassword(): string { + // 16 characters ^ 64 combinations results in an entropy of 256 bits + return passwordGenerator.randomPassword({ + length: 64, + characters: "0123456789ABCDEF", + }) +} diff --git a/src/utils/crypto/index.ts b/src/utils/crypto/index.ts index 066a3dd..36d5fab 100644 --- a/src/utils/crypto/index.ts +++ b/src/utils/crypto/index.ts @@ -12,3 +12,7 @@ export * from "./generate-keys" export {default as generateKeys} from "./generate-keys" export * from "./extract-cleartext-from-signed-message" export {default as extractCleartextFromSignedMessage} from "./extract-cleartext-from-signed-message" +export * from "./generate-encryption-password" +export {default as generateEncryptionPassword} from "./generate-encryption-password" +export * from "./setup-encryption-for-user" +export {default as setupEncryptionForUser} from "./setup-encryption-for-user" diff --git a/src/utils/crypto/setup-encryption-for-user.ts b/src/utils/crypto/setup-encryption-for-user.ts new file mode 100644 index 0000000..a7140bc --- /dev/null +++ b/src/utils/crypto/setup-encryption-for-user.ts @@ -0,0 +1,50 @@ +import {ServerSettings, ServerUser, Theme, User, UserNote} from "~/server-types" +import {encryptUserNote} from "~/utils/encrypt-user-note" + +import encryptString from "./encrypt-string" +import generateEncryptionPassword from "./generate-encryption-password" +import generateKeys from "./generate-keys" +import getEncryptionPassword from "./get-encryption-password" +import getUserSalt from "./get-user-salt" + +export interface SetupEncryptionForUserData { + password: string + user: ServerUser | User + serverSettings: ServerSettings + theme: Theme +} + +export interface SetupEncryptionForUserResult { + encryptedPassword: string + encryptedNotes: string + encryptionPassword: string + publicKey: string +} + +export default async function setupEncryptionForUser({ + user, + serverSettings, + password, + theme, +}: SetupEncryptionForUserData): Promise { + const keyPair = await generateKeys() + const masterPassword = generateEncryptionPassword() + + const salt = getUserSalt(user, serverSettings) + const encryptionKey = await getEncryptionPassword(user.email.address, password, salt) + + const encryptedMasterPassword = encryptString(masterPassword, encryptionKey) + + const note: UserNote = { + theme, + privateKey: keyPair.privateKey, + } + const encryptedNotes = encryptUserNote(note, masterPassword) + + return { + encryptedPassword: encryptedMasterPassword, + encryptedNotes, + encryptionPassword: masterPassword, + publicKey: keyPair.publicKey, + } +}