Skip to content

Commit

Permalink
changed passphrase algorithm and vault lock method
Browse files Browse the repository at this point in the history
  • Loading branch information
riipandi committed Aug 31, 2022
1 parent 1c44283 commit 6c47c0a
Show file tree
Hide file tree
Showing 13 changed files with 107 additions and 104 deletions.
13 changes: 2 additions & 11 deletions apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.5", features = ["api-all", "macos-private-api", "system-tray", "updater"] }
# uuid = { version = "1.1", features = ["v4", "fast-rng", "serde", "js"] }
libreauth = { version = "^0.15", features = ["key", "oath", "pass"] }
sha2 = { version = "0.10", default-features = false }
cocoa = "0.24"
magic-crypt = "^3.1"
bcrypt = "^0.13"
md-5 = "0.10"
sysinfo = "0.26"
machine-uid = "0.2"

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn main() {
security::decrypt_str,
security::create_hash,
security::verify_hash,
security::md5_hash,
security::generate_passphrase,
exit_app,
])
.build(tauri::generate_context!())
Expand Down
18 changes: 8 additions & 10 deletions apps/desktop/src-tauri/src/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use tauri::command;

use bcrypt::{hash, verify, DEFAULT_COST};
use magic_crypt::{new_magic_crypt, MagicCryptTrait};
use md5::{Digest, Md5};
use sha2::{Digest, Sha256};

#[command]
pub(crate) async fn encrypt_str(plain_str: String, passphrase: String) -> String {
let mc = new_magic_crypt!(passphrase, 256);
let encrypted = mc.encrypt_str_to_base64(plain_str);
// println!("Encrypted string: {:?}", encrypted);
encrypted.into()
}

Expand All @@ -34,15 +35,12 @@ pub(crate) async fn verify_hash(plaintext: String, hashed_str: String) -> bool {
}

#[command]
pub(crate) async fn md5_hash(str: String) -> String {
let mut hasher = Md5::new();
hasher.update(&str);

let encoded = hasher.finalize();
let result = format!("{:x}", encoded);
// println!("MD5-encoded hash: {:?}", result);

result.into()
pub(crate) async fn generate_passphrase(user_id: String, password: String) -> String {
let uid = user_id.replace("-", "").to_uppercase();
let hash = Sha256::new().chain_update(&uid).chain_update(&password).finalize();
let encoded = format!("{:x}", hash);
// println!("SHA2-encoded hash: {:?}", encoded);
encoded.into()
}

#[command]
Expand Down
16 changes: 13 additions & 3 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { QueryClientProvider } from '@tanstack/react-query'
import { sendNotification } from '@tauri-apps/api/notification'
import { Offline, Online } from 'react-detect-offline'
Expand Down Expand Up @@ -55,12 +55,22 @@ export default function App() {
<div className="mx-auto max-w-sm">
<TitleBar />

<Offline polling={offlineDetectConfig} onChange={(online) => handleOffline(online)}>
<Offline
polling={offlineDetectConfig}
onChange={(online) => {
handleOffline(online)
}}
>
<ExitButton />
<OfflineScreen />
</Offline>

<Online polling={offlineDetectConfig} onChange={(online) => handleOnline(online)}>
<Online
polling={offlineDetectConfig}
onChange={(online) => {
handleOnline(online)
}}
>
<QueryClientProvider client={queryClient}>
<MainScreen />
</QueryClientProvider>
Expand Down
104 changes: 51 additions & 53 deletions apps/desktop/src/components/AppMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Fragment, useEffect, useState } from 'react'
import { invoke } from '@tauri-apps/api/tauri'
import { ask, open, save } from '@tauri-apps/api/dialog'
Expand Down Expand Up @@ -34,59 +35,6 @@ export const AppMenu = () => {
const [menuPayload, setMenuPayload] = useState<EventName | undefined | unknown>('')
const [listenTauriEvent, setListenTauriEvent] = useState(false)

useEffect(() => {
// Listen tauri event
listen('app-event', (e) => {
setMenuPayload(e.payload)
setListenTauriEvent(true)
})
}, [setMenuPayload, setListenTauriEvent])

useEffect(() => {
if (listenTauriEvent) {
switch (menuPayload) {
case 'quit':
handleQuit()
break

case 'close':
handleQuit()
break

case 'export':
handleExport()
break

case 'import':
handleImport()
break

case 'lock_vault':
setLockStreenState(true)
break

case 'new_item':
setFormCreateOpen(true)
break

case 'signout':
handleSignOut()
break

case 'sync_vault':
setForceFetch(true)
break

case 'update_check':
break

default:
break
}
}
setListenTauriEvent(false)
}, [listenTauriEvent, menuPayload])

// Reset all states before quit.
const resetStates = () => {
setLockStreenState(true)
Expand Down Expand Up @@ -147,6 +95,56 @@ export const AppMenu = () => {
}
}

useEffect(() => {
// Listen tauri event
listen('app-event', (e) => {
setMenuPayload(e.payload)
setListenTauriEvent(true)
})
}, [setMenuPayload, setListenTauriEvent])

useEffect(() => {
if (listenTauriEvent) {
switch (menuPayload) {
case 'quit':
handleQuit()
break

case 'close':
handleQuit()
break

case 'export':
handleExport()
break

case 'import':
handleImport()
break

case 'lock_vault':
setLockStreenState(true)
break

case 'new_item':
setFormCreateOpen(true)
break

case 'signout':
handleSignOut()
break

case 'sync_vault':
setForceFetch(true)
break

default:
break
}
}
setListenTauriEvent(false)
}, [listenTauriEvent, menuPayload])

return (
<div className="absolute top-0 right-0 z-30 flex h-14 items-center px-4">
<Menu as="div" className="relative">
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useStores } from '../stores/stores'

export const EmptyState = () => {
const setFormCreateOpen = useStores((state) => state.setFormCreateOpen)
const handleAction = async () => setFormCreateOpen(true)

return (
<div className="relative z-10 -mt-6 flex h-full flex-col items-center justify-center text-center">
Expand All @@ -13,7 +14,7 @@ export const EmptyState = () => {
<button
type="button"
className="flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"
onClick={() => setFormCreateOpen(true)}
onClick={handleAction}
>
<PlusCircleIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
<span>Add New Item</span>
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/screens/AuthScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { toast } from 'react-hot-toast'
import { sbClient, storeDeviceInfo } from '../utils/supabase'
import { LoaderScreen } from '../components/LoaderScreen'
import { classNames } from '../utils/ui-helpers'
import { createHash, md5Hash } from '../utils/string-helpers'
import { createHash, generatePassphrase } from '../utils/string-helpers'
import { ExitButton } from '../components/ExitButton'
import { TitleBar } from '../components/TitleBar'
import { localData } from '../utils/storage'
Expand Down Expand Up @@ -36,7 +36,7 @@ export const AuthScreen = () => {

if (user) {
// If login success then store hashed passphrase in localStorage
const hashedPassphrase = await md5Hash(password)
const hashedPassphrase = await generatePassphrase(user.id, password)
await localData.set('passphrase', hashedPassphrase)
await storeDeviceInfo() // Store device information.
setLoading(false)
Expand Down
27 changes: 11 additions & 16 deletions apps/desktop/src/screens/LockScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@ import { Dialog } from '@headlessui/react'
import { EyeIcon, EyeSlashIcon, LockClosedIcon } from '@heroicons/react/24/outline'
import { ExclamationCircleIcon } from '@heroicons/react/24/solid'

import { useAuth } from '../hooks/useAuth'
import { useStores } from '../stores/stores'
import { classNames } from '../utils/ui-helpers'
import { DialogTransition } from '../components/DialogTransition'
import { md5Hash, verifyHash } from '../utils/string-helpers'
import { LoaderScreen } from '../components/LoaderScreen'
import { localData } from '../utils/storage'
import { updateDeviceInfo } from '../utils/supabase'
import { validatePassphrase } from '../utils/guards'

export const LockScreen = () => {
const session = useAuth()
const locked = useStores((state) => state.locked)
const setLockStreenState = useStores((state) => state.setLockStreenState)
const [error, setError] = useState<any>({ error: null, text: null })
const [passphrase, setPassphrase] = useState('')
const [password, setPassphrase] = useState('')
const [inputType, setInputType] = useState('password')
const [loading, setLoading] = useState(false)

Expand All @@ -28,23 +25,21 @@ export const LockScreen = () => {
const handleUnlockAction = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()

if (passphrase.length <= 1) {
if (password.length <= 1) {
return setError({ error: true, text: 'Your password required!' })
}

setLoading(true)
const passphraseHash = session?.user?.user_metadata?.passphrase
const validPassphrase = await verifyHash(passphrase, passphraseHash)
// Compare with hashed passphrase in localStorage
const validPassphrase = await validatePassphrase(password)
setLoading(false)

if (!validPassphrase) {
setLoading(false)
return setError({ error: true, text: 'Invalid password!' })
}

// Store hashed passphrase in localStorage
const hashedPassphrase = await md5Hash(passphrase)
await localData.set('passphrase', hashedPassphrase)
await updateDeviceInfo() // Update device information.
// Update device information.
await updateDeviceInfo()

setError(null)
setLoading(false)
Expand All @@ -70,14 +65,14 @@ export const LockScreen = () => {
</div>

<div className="mt-2">
<label htmlFor="passphrase" className="sr-only">
<label htmlFor="password" className="sr-only">
Passphrase
</label>
<div className="relative mt-1 rounded-md shadow-sm">
<input
type={inputType}
name="passphrase"
id="passphrase"
name="password"
id="password"
className={classNames(
error &&
'border-red-300 text-red-900 placeholder-red-300 focus:border-red-500 focus:ring-red-500 ',
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/screens/MainScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const MainScreen = (): JSX.Element => {
}, 1000)

return () => clearInterval(myInterval)
}, [startTimer, isFetching, seconds])
}, [startTimer, isFetching, seconds, data])

if (isLoading && !filter) return <LoaderScreen />
if (!locked && error) return <ErrorScreen message={error.message} />
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/utils/guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generatePassphrase } from './string-helpers'
import { localData } from '../utils/storage'
import { sbClient } from './supabase'

export const validatePassphrase = async (password: string): Promise<boolean> => {
const userId = sbClient.auth.session()?.user?.id || ''
const storedPassphrase = await localData.get('passphrase')
const hashedPassphrase = await generatePassphrase(userId, password)

return hashedPassphrase === storedPassphrase ? true : false
}
4 changes: 2 additions & 2 deletions apps/desktop/src/utils/string-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export const decryptStr = async (encryptedStr: string): Promise<any> => {
return await invoke('decrypt_str', { encryptedStr, passphrase })
}

export const md5Hash = async (str: string): Promise<any> => {
return await invoke('md5_hash', { str })
export const generatePassphrase = async (userId: string, password: string): Promise<any> => {
return await invoke('generate_passphrase', { userId, password })
}

export const createHash = async (plaintext: string): Promise<any> => {
Expand Down
Loading

0 comments on commit 6c47c0a

Please sign in to comment.