diff --git a/components/labels.js b/components/labels.js index 7f1657b..7c38808 100644 --- a/components/labels.js +++ b/components/labels.js @@ -7,7 +7,8 @@ export const shared = { incorrectPassword: 'Password incorrect', incorrectPasswordMessage: 'That password is incorrect.', tryAgain: 'Try again', - ok: 'OK' + ok: 'OK', + unlock: 'Unlock' } export const settings = { @@ -58,7 +59,8 @@ export const settings = { explainerDisabled: "Encrypt the app's database with a password. You need to enter the password every time the app is started.", explainerEnabled: "Password protection and database encryption is currently enabled", setPassword: 'Set password', - deletePassword: "Delete password", + changePassword: 'Change password', + deletePassword: 'Delete password', enterCurrent: "Please enter your current password", enterNew: "Please enter a new password", backupReminderTitle: 'Read this before making changes to your password', diff --git a/components/password-prompt.js b/components/password-prompt.js index 6ff5904..8e4a719 100644 --- a/components/password-prompt.js +++ b/components/password-prompt.js @@ -15,7 +15,7 @@ export default class PasswordPrompt extends Component { } nodejs.channel.addListener( - 'message', + 'check-pw', this.passHashToDb, this ) @@ -27,6 +27,7 @@ export default class PasswordPrompt extends Component { try { await openDb({ persistConnection: true }) await saveEncryptionFlag(false) + this.props.showApp() } catch (err) { this.setState({ showPasswordPrompt: true }) await saveEncryptionFlag(true) @@ -34,18 +35,16 @@ export default class PasswordPrompt extends Component { } } - passHashToDb = async msg => { - msg = JSON.parse(msg) - if (msg.type != 'sha512') return + passHashToDb = async hash => { try { - await openDb({hash: msg.message, persistConnection: true }) + await openDb({ hash, persistConnection: true }) } catch (err) { Alert.alert( shared.incorrectPassword, shared.incorrectPasswordMessage, [{ text: shared.tryAgain, - onPress: () => this.setState({password: null}) + onPress: () => this.setState({ password: null }) }] ) return @@ -73,6 +72,7 @@ export default class PasswordPrompt extends Component { text: labels.reallyDeleteData, onPress: async () => { await deleteDbAndOpenNew() + await saveEncryptionFlag(false) this.props.showApp() } }] @@ -83,7 +83,7 @@ export default class PasswordPrompt extends Component { } componentWillUnmount() { - nodejs.channel.removeListener('message', this.passHashToDb) + nodejs.channel.removeListener('check-pw', this.passHashToDb) } render() { @@ -104,7 +104,7 @@ export default class PasswordPrompt extends Component { { - requestHash(this.state.password) + requestHash('check-pw', this.state.password) }} disabled={!this.state.password} > diff --git a/components/settings/index.js b/components/settings/index.js index 4a20916..e0828d8 100644 --- a/components/settings/index.js +++ b/components/settings/index.js @@ -11,7 +11,7 @@ import TempReminderPicker from './temp-reminder-picker' import TempSlider from './temp-slider' import openImportDialogAndImport from './import-dialog' import openShareDialogAndExport from './export-dialog' -import PasswordSetting from './password-setting' +import PasswordSetting from './password' export default class Settings extends Component { constructor(props) { diff --git a/components/settings/password/check-current-password.js b/components/settings/password/check-current-password.js new file mode 100644 index 0000000..b177de4 --- /dev/null +++ b/components/settings/password/check-current-password.js @@ -0,0 +1,23 @@ +import { Alert } from 'react-native' +import { openDb } from '../../../db' +import { shared } from '../../labels' + +export default async function checkPassword({hash, onCancel, onTryAgain }) { + try { + await openDb({ hash, persistConnection: false }) + return true + } catch (err) { + Alert.alert( + shared.incorrectPassword, + shared.incorrectPasswordMessage, + [{ + text: shared.cancel, + onPress: onCancel + }, { + text: shared.tryAgain, + onPress: onTryAgain + }] + ) + return false + } +} \ No newline at end of file diff --git a/components/settings/password/create.js b/components/settings/password/create.js new file mode 100644 index 0000000..97559ee --- /dev/null +++ b/components/settings/password/create.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react' +import { + View, + TouchableOpacity, +} from 'react-native' +import nodejs from 'nodejs-mobile-react-native' +import { AppText } from '../../app-text' +import styles from '../../../styles' +import { settings as labels } from '../../labels' +import { requestHash, changeEncryptionAndRestartApp } from '../../../db' +import PasswordField from './password-field' +import showBackUpReminder from './show-backup-reminder' + +export default class CreatePassword extends Component { + constructor() { + super() + this.state = { + enteringNewPassword: false, + newPassword: null + } + nodejs.channel.addListener( + 'create-pw-hash', + changeEncryptionAndRestartApp, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener('create-pw-hash', changeEncryptionAndRestartApp) + } + + render () { + return ( + + {this.state.enteringNewPassword && + this.setState({newPassword: val})} + /> + } + { + if (!this.state.enteringNewPassword) { + showBackUpReminder(() => { + this.setState({ enteringNewPassword: true }) + }) + } else { + requestHash('create-pw-hash', this.state.newPassword) + } + }} + disabled={this.state.enteringNewPassword && !this.state.newPassword} + style={styles.settingsButton}> + + {labels.passwordSettings.setPassword} + + + + ) + } +} \ No newline at end of file diff --git a/components/settings/password/delete.js b/components/settings/password/delete.js new file mode 100644 index 0000000..0673961 --- /dev/null +++ b/components/settings/password/delete.js @@ -0,0 +1,76 @@ +import React, { Component } from 'react' +import { + View, + TouchableOpacity +} from 'react-native' +import nodejs from 'nodejs-mobile-react-native' +import { AppText } from '../../app-text' +import styles from '../../../styles' +import { settings as labels } from '../../labels' +import { requestHash, changeEncryptionAndRestartApp } from '../../../db' +import PasswordField from './password-field' +import showBackUpReminder from './show-backup-reminder' +import checkCurrentPassword from './check-current-password' + +export default class DeletePassword extends Component { + constructor() { + super() + this.state = { + enteringCurrentPassword: false, + currentPassword: null + } + + nodejs.channel.addListener( + 'pre-delete-pw-check', + this.removeEncryption, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener('pre-delete-pw-check', this.removeEncryption) + } + + removeEncryption = async hash => { + const passwordIsCorrect = await checkCurrentPassword({ + hash, + onTryAgain: () => this.setState({currentPassword: null}), + onCancel: () => this.setState({ + enteringCurrentPassword: false, + currentPassword: null + }) + }) + + if (passwordIsCorrect) await changeEncryptionAndRestartApp() + } + + render() { + return ( + + {this.state.enteringCurrentPassword && + this.setState({ currentPassword: val })} + value={this.state.currentPassword} + placeholder={labels.passwordSettings.enterCurrent} + /> + } + { + if (!this.state.enteringCurrentPassword) { + showBackUpReminder(() => { + this.setState({ enteringCurrentPassword: true }) + }) + } else { + requestHash('pre-delete-pw-check', this.state.currentPassword) + } + }} + style={styles.settingsButton} + > + + {labels.passwordSettings.deletePassword} + + + + ) + } +} \ No newline at end of file diff --git a/components/settings/password/index.js b/components/settings/password/index.js new file mode 100644 index 0000000..87ece3d --- /dev/null +++ b/components/settings/password/index.js @@ -0,0 +1,50 @@ +import React, { Component } from 'react' +import { View } from 'react-native' +import CreatePassword from './create' +import ChangePassword from './update' +import DeletePassword from './delete' +import { AppText } from '../../app-text' +import { + hasEncryptionObservable +} from '../../../local-storage' +import styles from '../../../styles/index' +import { settings as labels } from '../../labels' + +export default class PasswordSetting extends Component { + constructor(props) { + super(props) + this.state = { + showUpdateAndDelete: hasEncryptionObservable.value, + showCreate: !hasEncryptionObservable.value + } + } + + render() { + return ( + + + + {labels.passwordSettings.title} + + + {this.state.showUpdateAndDelete ? + {labels.passwordSettings.explainerEnabled} + : + {labels.passwordSettings.explainerDisabled} + } + + {this.state.showUpdateAndDelete && + + + + + } + + {this.state.showCreate && + + } + + + ) + } +} \ No newline at end of file diff --git a/components/settings/password/password-field.js b/components/settings/password/password-field.js new file mode 100644 index 0000000..7193566 --- /dev/null +++ b/components/settings/password/password-field.js @@ -0,0 +1,16 @@ +import React from 'react' +import { TextInput } from 'react-native' +import styles from '../../../styles' + +export default function PasswordField(props) { + return ( + + ) +} diff --git a/components/settings/password/show-backup-reminder.js b/components/settings/password/show-backup-reminder.js new file mode 100644 index 0000000..ea50dcc --- /dev/null +++ b/components/settings/password/show-backup-reminder.js @@ -0,0 +1,16 @@ +import { Alert } from 'react-native' +import { settings as labels, shared } from '../../labels' + +export default function showBackUpReminder(okHandler) { + Alert.alert( + labels.passwordSettings.backupReminderTitle, + labels.passwordSettings.backupReminder, + [{ + text: shared.cancel, + style: 'cancel' + }, { + text: shared.ok, + onPress: okHandler + }] + ) +} \ No newline at end of file diff --git a/components/settings/password/update.js b/components/settings/password/update.js new file mode 100644 index 0000000..12af518 --- /dev/null +++ b/components/settings/password/update.js @@ -0,0 +1,129 @@ + +import React, { Component } from 'react' +import { + View, + TouchableOpacity} from 'react-native' +import nodejs from 'nodejs-mobile-react-native' +import { AppText } from '../../app-text' +import styles from '../../../styles' +import { settings as labels, shared } from '../../labels' +import { requestHash, changeEncryptionAndRestartApp } from '../../../db' +import PasswordField from './password-field' +import showBackUpReminder from './show-backup-reminder' +import checkCurrentPassword from './check-current-password' + +export default class ChangePassword extends Component { + constructor() { + super() + this.state = { + enteringCurrentPassword: false, + currentPassword: null, + enteringNewPassword: false, + newPassword: null + } + + nodejs.channel.addListener( + 'pre-change-pw-check', + this.openNewPasswordField, + this + ) + + nodejs.channel.addListener( + 'change-pw', + changeEncryptionAndRestartApp, + this + ) + } + + componentWillUnmount() { + nodejs.channel.removeListener('pre-change-pw-check', this.openNewPasswordField) + nodejs.channel.removeListener('change-pw', changeEncryptionAndRestartApp) + } + + openNewPasswordField = async hash => { + const passwordCorrect = await checkCurrentPassword({ + hash, + onTryAgain: () => this.setState({ currentPassword: null }), + onCancel: () => this.setState({ + enteringCurrentPassword: false, + currentPassword: null + }) + }) + + if (passwordCorrect) { + this.setState({ + enteringCurrentPassword: false, + currentPassword: null, + enteringNewPassword: true + }) + } + } + + render() { + return ( + + {!this.state.enteringCurrentPassword && + !this.state.enteringNewPassword && + showBackUpReminder(() => { + this.setState({ enteringCurrentPassword: true }) + })} + disabled={this.state.currentPassword} + style={styles.settingsButton}> + + {labels.passwordSettings.changePassword} + + + } + + {this.state.enteringCurrentPassword && + + { + this.setState({ + currentPassword: val, + wrongPassword: false + }) + }} + value={this.state.currentPassword} + placeholder={labels.passwordSettings.enterCurrent} + /> + requestHash('pre-change-pw-check', this.state.currentPassword)} + disabled={!this.state.currentPassword} + style={styles.settingsButton}> + + {shared.unlock} + + + + } + + {this.state.enteringNewPassword && + + { + this.setState({ + newPassword: val + }) + }} + value={this.state.changedPassword} + placeholder={labels.passwordSettings.enterNew} + /> + + requestHash('change-pw', this.state.newPassword)} + disabled={ !this.state.newPassword } + style={styles.settingsButton}> + + {labels.passwordSettings.changePassword} + + + + } + + + ) + } +} \ No newline at end of file diff --git a/db/index.js b/db/index.js index 4173c1d..6dc4feb 100644 --- a/db/index.js +++ b/db/index.js @@ -11,7 +11,6 @@ import { longAndComplicatedCycleWithCervix, cycleWithTempAndNoCervixShift } from './fixtures' -import { saveEncryptionFlag } from '../local-storage' import dbSchema from './schema' let db @@ -154,9 +153,9 @@ export function tryToImportWithoutDelete(cycleDays) { }) } -export function requestHash(pw) { - nodejs.channel.send(JSON.stringify({ - type: 'request-SHA512', +export function requestHash(type, pw) { + nodejs.channel.post('request-SHA512', JSON.stringify({ + type: type, message: pw })) } @@ -190,7 +189,6 @@ export async function changeEncryptionAndRestartApp(hash) { db.close() await fs.unlink(defaultPath) await fs.moveFile(copyPath, defaultPath) - await saveEncryptionFlag(key ? true : false) restart.Restart() } @@ -198,7 +196,6 @@ export async function deleteDbAndOpenNew() { const exists = await fs.exists(Realm.defaultPath) if (exists) await fs.unlink(Realm.defaultPath) await openDb({ persistConnection: true }) - await saveEncryptionFlag(false) } function hashToInt8Array(hash) { diff --git a/nodejs-assets/nodejs-project/main.js b/nodejs-assets/nodejs-project/main.js index 20227c7..616196d 100644 --- a/nodejs-assets/nodejs-project/main.js +++ b/nodejs-assets/nodejs-project/main.js @@ -3,15 +3,10 @@ const rnBridge = require('rn-bridge') const crypto = require('crypto') -rnBridge.channel.on('message', (msg) => { +rnBridge.channel.on('request-SHA512', (msg) => { msg = JSON.parse(msg) - if (msg.type === 'request-SHA512') { - const hash = crypto.createHash('sha512') - hash.update(msg.message) - const result = hash.digest('hex') - rnBridge.channel.send(JSON.stringify({ - type: 'sha512', - message: result - })) - } + const hash = crypto.createHash('sha512') + hash.update(msg.message) + const result = hash.digest('hex') + rnBridge.channel.post(msg.type, result) }) \ No newline at end of file