diff --git a/packages/encryption/src/Domain/Username/PrivateUsername.ts b/packages/encryption/src/Domain/Username/PrivateUsername.ts new file mode 100644 index 000000000..e0dce5688 --- /dev/null +++ b/packages/encryption/src/Domain/Username/PrivateUsername.ts @@ -0,0 +1,19 @@ +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' + +const PrivateUserNameV1 = 'StandardNotes-PrivateUsername-V1' + +export async function ComputePrivateUsername( + crypto: PureCryptoInterface, + usernameInput: string, +): Promise { + const result = await crypto.hmac256( + await crypto.sha256(PrivateUserNameV1), + await crypto.sha256(usernameInput.trim().toLowerCase()), + ) + + if (result == undefined) { + return undefined + } + + return result +} diff --git a/packages/encryption/src/Domain/Workspace/PrivateWorkspace.ts b/packages/encryption/src/Domain/Workspace/PrivateWorkspace.ts deleted file mode 100644 index 281b33496..000000000 --- a/packages/encryption/src/Domain/Workspace/PrivateWorkspace.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { PureCryptoInterface } from '@standardnotes/sncrypto-common' - -export async function ComputePrivateWorkspaceIdentifier( - crypto: PureCryptoInterface, - userphrase: string, - name: string, -): Promise { - const identifier = await crypto.hmac256( - await crypto.sha256(name.trim().toLowerCase()), - await crypto.sha256(userphrase.trim().toLowerCase()), - ) - - if (identifier == undefined) { - return undefined - } - - return identifier -} diff --git a/packages/encryption/src/Domain/index.ts b/packages/encryption/src/Domain/index.ts index a6e96cbd8..617a5f8f6 100644 --- a/packages/encryption/src/Domain/index.ts +++ b/packages/encryption/src/Domain/index.ts @@ -34,4 +34,4 @@ export * from './Types/EncryptedParameters' export * from './Types/ItemAuthenticatedData' export * from './Types/LegacyAttachedData' export * from './Types/RootKeyEncryptedAuthenticatedData' -export * from './Workspace/PrivateWorkspace' +export * from './Username/PrivateUsername' diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 8580d715c..42f1ad07f 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -53,7 +53,7 @@ import { WorkspaceManager, } from '@standardnotes/services' import { FilesClientInterface } from '@standardnotes/files' -import { ComputePrivateWorkspaceIdentifier } from '@standardnotes/encryption' +import { ComputePrivateUsername } from '@standardnotes/encryption' import { useBoolean } from '@standardnotes/utils' import { BackupFile, @@ -272,8 +272,8 @@ export class SNApplication return this.componentManagerService } - public computePrivateWorkspaceIdentifier(userphrase: string, name: string): Promise { - return ComputePrivateWorkspaceIdentifier(this.options.crypto, userphrase, name) + public computePrivateUsername(username: string): Promise { + return ComputePrivateUsername(this.options.crypto, username) } /** diff --git a/packages/snjs/mocha/test.html b/packages/snjs/mocha/test.html index f50a8ca8a..cf4718eb0 100644 --- a/packages/snjs/mocha/test.html +++ b/packages/snjs/mocha/test.html @@ -43,7 +43,7 @@ - + diff --git a/packages/snjs/mocha/username.test.js b/packages/snjs/mocha/username.test.js new file mode 100644 index 000000000..20ab192b3 --- /dev/null +++ b/packages/snjs/mocha/username.test.js @@ -0,0 +1,12 @@ +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('private username', () => { + it('generates private username', async () => { + const username = 'myusername' + + const result = await ComputePrivateUsername(new SNWebCrypto(), username) + + expect(result).to.equal('9aae57db8dbb233291a49cb7b8ab902336ec785e04f3be70157b8c1669014d0d') + }) +}) diff --git a/packages/snjs/mocha/workspaces.test.js b/packages/snjs/mocha/workspaces.test.js deleted file mode 100644 index cdb96c4cd..000000000 --- a/packages/snjs/mocha/workspaces.test.js +++ /dev/null @@ -1,25 +0,0 @@ -chai.use(chaiAsPromised) -const expect = chai.expect -import * as Factory from './lib/factory.js' - -describe('private workspaces', () => { - it('generates identifier', async () => { - const userphrase = 'myworkspaceuserphrase' - const name = 'myworkspacename' - - const result = await ComputePrivateWorkspaceIdentifier(new SNWebCrypto(), userphrase, name) - - expect(result).to.equal('5155c13a44f333790f6564fbcee0c35a16d26a8359dd77d67d8ecc6ad5d399bb') - }) - - it('application result matches direct function call', async () => { - const userphrase = 'myworkspaceuserphrase' - const name = 'myworkspacename' - - const application = (await Factory.createAppContextWithRealCrypto()).application - const appResult = await application.computePrivateWorkspaceIdentifier(userphrase, name) - const directResult = await ComputePrivateWorkspaceIdentifier(new SNWebCrypto(), userphrase, name) - - expect(appResult).to.equal(directResult) - }) -}) diff --git a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx index 721c560fe..360a116ae 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx @@ -10,7 +10,7 @@ type Props = { application: WebApplication viewControllerManager: ViewControllerManager disabled?: boolean - onPrivateWorkspaceChange?: (isPrivate: boolean, identifier?: string) => void + onPrivateUsernameModeChange?: (isPrivate: boolean, identifier?: string) => void onStrictSignInChange?: (isStrictSignIn: boolean) => void children?: ReactNode } @@ -19,54 +19,46 @@ const AdvancedOptions: FunctionComponent = ({ viewControllerManager, application, disabled = false, - onPrivateWorkspaceChange, + onPrivateUsernameModeChange, onStrictSignInChange, children, }) => { const { server, setServer, enableServerOption, setEnableServerOption } = viewControllerManager.accountMenuController const [showAdvanced, setShowAdvanced] = useState(false) - const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false) - const [privateWorkspaceName, setPrivateWorkspaceName] = useState('') - const [privateWorkspaceUserphrase, setPrivateWorkspaceUserphrase] = useState('') + const [isPrivateUsername, setIsPrivateUsername] = useState(false) + const [privateUsername, setPrivateUsername] = useState('') const [isStrictSignin, setIsStrictSignin] = useState(false) useEffect(() => { - const recomputePrivateWorkspaceIdentifier = async () => { - const identifier = await application.computePrivateWorkspaceIdentifier( - privateWorkspaceName, - privateWorkspaceUserphrase, - ) + const recomputePrivateUsername = async () => { + const identifier = await application.computePrivateUsername(privateUsername) if (!identifier) { - if (privateWorkspaceName?.length > 0 && privateWorkspaceUserphrase?.length > 0) { - application.alertService.alert('Unable to compute private workspace name.').catch(console.error) + if (privateUsername?.length > 0) { + application.alertService.alert('Unable to compute private username.').catch(console.error) } return } - onPrivateWorkspaceChange?.(true, identifier) + onPrivateUsernameModeChange?.(true, identifier) } - if (privateWorkspaceName && privateWorkspaceUserphrase) { - recomputePrivateWorkspaceIdentifier().catch(console.error) + if (privateUsername) { + recomputePrivateUsername().catch(console.error) } - }, [privateWorkspaceName, privateWorkspaceUserphrase, application, onPrivateWorkspaceChange]) + }, [privateUsername, application, onPrivateUsernameModeChange]) useEffect(() => { - onPrivateWorkspaceChange?.(isPrivateWorkspace) - }, [isPrivateWorkspace, onPrivateWorkspaceChange]) + onPrivateUsernameModeChange?.(isPrivateUsername) + }, [isPrivateUsername, onPrivateUsernameModeChange]) - const handleIsPrivateWorkspaceChange = useCallback(() => { - setIsPrivateWorkspace(!isPrivateWorkspace) - }, [isPrivateWorkspace]) + const handleIsPrivateUsernameChange = useCallback(() => { + setIsPrivateUsername(!isPrivateUsername) + }, [isPrivateUsername]) - const handlePrivateWorkspaceNameChange = useCallback((name: string) => { - setPrivateWorkspaceName(name) - }, []) - - const handlePrivateWorkspaceUserphraseChange = useCallback((userphrase: string) => { - setPrivateWorkspaceUserphrase(userphrase) + const handlePrivateUsernameNameChange = useCallback((name: string) => { + setPrivateUsername(name) }, []) const handleServerOptionChange: ChangeEventHandler = useCallback( @@ -114,35 +106,28 @@ const AdvancedOptions: FunctionComponent = ({ - {isPrivateWorkspace && ( + {isPrivateUsername && ( <> ]} + left={[]} type="text" - placeholder="Userphrase" - value={privateWorkspaceUserphrase} - onChange={handlePrivateWorkspaceUserphraseChange} - disabled={disabled} - /> - ]} - type="text" - placeholder="Name" - value={privateWorkspaceName} - onChange={handlePrivateWorkspaceNameChange} + placeholder="Username" + value={privateUsername} + onChange={handlePrivateUsernameNameChange} disabled={disabled} + spellcheck={false} + autocomplete={false} /> )} diff --git a/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx b/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx index dd1e46165..b913f53a1 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/CreateAccount.tsx @@ -40,7 +40,7 @@ const CreateAccount: FunctionComponent = ({ }) => { const emailInputRef = useRef(null) const passwordInputRef = useRef(null) - const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false) + const [isPrivateUsername, setIsPrivateUsername] = useState(false) useEffect(() => { if (emailInputRef.current) { @@ -98,11 +98,11 @@ const CreateAccount: FunctionComponent = ({ setPassword('') }, [setEmail, setMenuPane, setPassword]) - const onPrivateWorkspaceChange = useCallback( - (isPrivateWorkspace: boolean, privateWorkspaceIdentifier?: string) => { - setIsPrivateWorkspace(isPrivateWorkspace) - if (isPrivateWorkspace && privateWorkspaceIdentifier) { - setEmail(privateWorkspaceIdentifier) + const onPrivateUsernameChange = useCallback( + (isPrivateUsername: boolean, privateUsernameIdentifier?: string) => { + setIsPrivateUsername(isPrivateUsername) + if (isPrivateUsername && privateUsernameIdentifier) { + setEmail(privateUsernameIdentifier) } }, [setEmail], @@ -123,7 +123,7 @@ const CreateAccount: FunctionComponent = ({
]} onChange={handleEmailChange} onKeyDown={handleKeyDown} @@ -147,7 +147,7 @@ const CreateAccount: FunctionComponent = ({ ) diff --git a/packages/web/src/javascripts/Components/AccountMenu/SignIn.tsx b/packages/web/src/javascripts/Components/AccountMenu/SignIn.tsx index 240bfd3f4..5b8001d17 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/SignIn.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/SignIn.tsx @@ -29,7 +29,7 @@ const SignInPane: FunctionComponent = ({ application, viewControllerManag const [isStrictSignin, setIsStrictSignin] = useState(false) const [isSigningIn, setIsSigningIn] = useState(false) const [shouldMergeLocal, setShouldMergeLocal] = useState(true) - const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false) + const [isPrivateUsername, setIsPrivateUsername] = useState(false) const emailInputRef = useRef(null) const passwordInputRef = useRef(null) @@ -100,11 +100,11 @@ const SignInPane: FunctionComponent = ({ application, viewControllerManag }) }, [viewControllerManager, application, email, isEphemeral, isStrictSignin, password, shouldMergeLocal]) - const onPrivateWorkspaceChange = useCallback( - (newIsPrivateWorkspace: boolean, privateWorkspaceIdentifier?: string) => { - setIsPrivateWorkspace(newIsPrivateWorkspace) - if (newIsPrivateWorkspace && privateWorkspaceIdentifier) { - setEmail(privateWorkspaceIdentifier) + const onPrivateUsernameChange = useCallback( + (newisPrivateUsername: boolean, privateUsernameIdentifier?: string) => { + setIsPrivateUsername(newisPrivateUsername) + if (newisPrivateUsername && privateUsernameIdentifier) { + setEmail(privateUsernameIdentifier) } }, [setEmail], @@ -161,7 +161,7 @@ const SignInPane: FunctionComponent = ({ application, viewControllerManag onChange={handleEmailChange} onFocus={resetInvalid} onKeyDown={handleKeyDown} - disabled={isSigningIn || isPrivateWorkspace} + disabled={isSigningIn || isPrivateUsername} ref={emailInputRef} /> = ({ application, viewControllerManag viewControllerManager={viewControllerManager} application={application} disabled={isSigningIn} - onPrivateWorkspaceChange={onPrivateWorkspaceChange} + onPrivateUsernameModeChange={onPrivateUsernameChange} onStrictSignInChange={handleStrictSigninChange} /> diff --git a/packages/web/src/javascripts/Components/Input/DecoratedInput.tsx b/packages/web/src/javascripts/Components/Input/DecoratedInput.tsx index d9b76dbdd..e537dc9ad 100644 --- a/packages/web/src/javascripts/Components/Input/DecoratedInput.tsx +++ b/packages/web/src/javascripts/Components/Input/DecoratedInput.tsx @@ -20,6 +20,7 @@ const DecoratedInput = forwardRef( ( { autocomplete = false, + spellcheck = true, className, disabled = false, id, @@ -68,6 +69,7 @@ const DecoratedInput = forwardRef( title={title} type={type} value={value} + spellCheck={spellcheck} /> {right && ( diff --git a/packages/web/src/javascripts/Components/Input/DecoratedInputProps.ts b/packages/web/src/javascripts/Components/Input/DecoratedInputProps.ts index 74c0569d3..78a411390 100644 --- a/packages/web/src/javascripts/Components/Input/DecoratedInputProps.ts +++ b/packages/web/src/javascripts/Components/Input/DecoratedInputProps.ts @@ -2,6 +2,7 @@ import { FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react' export type DecoratedInputProps = { autocomplete?: boolean + spellcheck?: boolean className?: { container?: string input?: string