diff --git a/README.md b/README.md
index d4a7576..b2a7a00 100644
--- a/README.md
+++ b/README.md
@@ -14,21 +14,24 @@ testing. Thank you.
## Features
-- Try to be compatible with Password Store command line tool
+- Try to be compatible with the Password Store command line tool
- Support to view, copy, add, edit password entries
- Encrypt and decrypt password entries by PGP keys
-- Synchronize with you password Git repository
+- Synchronize with your password Git repository
- User-friendly interface: search, long press to copy, copy and open link, etc.
+- Support one-time password (OTP) tokens
- Written in Swift
- No need to jailbreak your devices
-- Get from App Store (stay tuned, under review)
+- Get from App Store (stay tuned)
## Screenshots
+
+
## Build
diff --git a/pass/Base.lproj/Main.storyboard b/pass/Base.lproj/Main.storyboard
index 84b2fb4..3883447 100644
--- a/pass/Base.lproj/Main.storyboard
+++ b/pass/Base.lproj/Main.storyboard
@@ -1,11 +1,11 @@
-
+
-
+
@@ -288,20 +288,20 @@
-
+
-
-
+
+
-
+
-
+
@@ -340,10 +340,10 @@
-
+
-
+
@@ -366,10 +366,10 @@
-
+
-
+
@@ -392,10 +392,10 @@
-
+
-
+
@@ -423,10 +423,10 @@
-
+
-
+
@@ -503,7 +503,7 @@
-
+
@@ -533,7 +533,7 @@
-
+
@@ -727,6 +727,9 @@
+
+
+
@@ -788,7 +791,7 @@
-
+
@@ -818,7 +821,7 @@
-
+
@@ -848,7 +851,7 @@
-
+
@@ -950,7 +953,28 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -965,7 +989,7 @@
-
+
@@ -990,7 +1014,8 @@
-
+
+
@@ -1110,7 +1135,7 @@
-
+
@@ -1168,7 +1193,7 @@
-
+
@@ -1224,7 +1249,7 @@ pfZ36xQbOAQYKKf6ZTT5R/Y=
-
+
diff --git a/pass/Controllers/AboutRepositoryTableViewController.swift b/pass/Controllers/AboutRepositoryTableViewController.swift
index f6e7178..c58cd6a 100644
--- a/pass/Controllers/AboutRepositoryTableViewController.swift
+++ b/pass/Controllers/AboutRepositoryTableViewController.swift
@@ -10,26 +10,19 @@ import UIKit
class AboutRepositoryTableViewController: BasicStaticTableViewController {
- var needRefresh = false
- var indicatorLabel: UILabel!
- var indicator: UIActivityIndicatorView!
- let passwordStore = PasswordStore.shared
+ private var needRefresh = false
+ private var indicator: UIActivityIndicatorView = {
+ let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
+ return indicator
+ }()
+ private let passwordStore = PasswordStore.shared
override func viewDidLoad() {
navigationItemTitle = "About Repository"
super.viewDidLoad()
-
- indicatorLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 21))
- indicatorLabel.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382 + 22)
- indicatorLabel.backgroundColor = UIColor.clear
- indicatorLabel.textColor = UIColor.gray
- indicatorLabel.text = "calculating"
- indicatorLabel.textAlignment = .center
- indicatorLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
- indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
- indicator.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382)
+
+ indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)
tableView.addSubview(indicator)
- tableView.addSubview(indicatorLabel)
setTableData()
@@ -40,7 +33,6 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if needRefresh {
- indicatorLabel.text = "reloading"
setTableData()
needRefresh = false
}
@@ -51,45 +43,32 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
// clear current contents (if any)
self.tableData.removeAll(keepingCapacity: true)
self.tableView.reloadData()
- indicatorLabel.isHidden = false
indicator.startAnimating()
// reload the table
DispatchQueue.global(qos: .userInitiated).async {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
- let fm = FileManager.default
- let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
- let numberOfPasswords = numberFormatter.string(from: NSNumber(value: passwordEntities.count))!
-
- var size = UInt64(0)
- do {
- if fm.fileExists(atPath: self.passwordStore.storeURL.path) {
- size = try fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.passwordStore.storeURL)
- }
- } catch {
- print(error)
- }
- let sizeOfRepository = ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: ByteCountFormatter.CountStyle.file)
+ let numberOfPasswordsString = numberFormatter.string(from: NSNumber(value: self.passwordStore.numberOfPasswords))!
+ let sizeOfRepositoryString = ByteCountFormatter.string(fromByteCount: Int64(self.passwordStore.sizeOfRepositoryByteCount), countStyle: ByteCountFormatter.CountStyle.file)
let numberOfCommits = self.passwordStore.storeRepository?.numberOfCommits(inCurrentBranch: NSErrorPointer(nilLiteral: ())) ?? 0
let numberOfCommitsString = numberFormatter.string(from: NSNumber(value: numberOfCommits))!
-
DispatchQueue.main.async { [weak self] in
let type = UITableViewCellAccessoryType.none
self?.tableData = [
// section 0
- [[.style: CellDataStyle.value1, .accessoryType: type, .title: "Passwords", .detailText: numberOfPasswords],
- [.style: CellDataStyle.value1, .accessoryType: type, .title: "Size", .detailText: sizeOfRepository], [.style: CellDataStyle.value1, .accessoryType: type, .title: "Unsynced", .detailText: String(self?.passwordStore.getNumberOfUnsyncedPasswords() ?? 0)],
+ [[.style: CellDataStyle.value1, .accessoryType: type, .title: "Passwords", .detailText: numberOfPasswordsString],
+ [.style: CellDataStyle.value1, .accessoryType: type, .title: "Size", .detailText: sizeOfRepositoryString],
+ [.style: CellDataStyle.value1, .accessoryType: type, .title: "Local Commits", .detailText: String(self?.passwordStore.numberOfLocalCommits() ?? 0)],
[.style: CellDataStyle.value1, .accessoryType: type, .title: "Last Synced", .detailText: Utils.getLastUpdatedTimeString()],
[.style: CellDataStyle.value1, .accessoryType: type, .title: "Commits", .detailText: numberOfCommitsString],
[.title: "Commit Logs", .action: "segue", .link: "showCommitLogsSegue"],
],
]
self?.indicator.stopAnimating()
- self?.indicatorLabel.isHidden = true
self?.tableView.reloadData()
}
}
diff --git a/pass/Controllers/AboutTableViewController.swift b/pass/Controllers/AboutTableViewController.swift
index 58882d5..2562149 100644
--- a/pass/Controllers/AboutTableViewController.swift
+++ b/pass/Controllers/AboutTableViewController.swift
@@ -14,6 +14,7 @@ class AboutTableViewController: BasicStaticTableViewController {
tableData = [
// section 0
[[.title: "Website", .action: "link", .link: "https://github.com/mssun/pass-ios.git"],
+ [.title: "Help", .action: "link", .link: "https://github.com/mssun/passforios/wiki"],
[.title: "Contact Developer", .action: "link", .link: "mailto:bob@mssun.me?subject=passforiOS"],],
// section 1,
diff --git a/pass/Controllers/AddPasswordTableViewController.swift b/pass/Controllers/AddPasswordTableViewController.swift
index d50afb1..bb47726 100644
--- a/pass/Controllers/AddPasswordTableViewController.swift
+++ b/pass/Controllers/AddPasswordTableViewController.swift
@@ -10,8 +10,6 @@ import UIKit
import SwiftyUserDefaults
class AddPasswordTableViewController: PasswordEditorTableViewController {
-
- var password: Password?
var tempContent: String = ""
let passwordStore = PasswordStore.shared
@@ -28,20 +26,29 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveAddPasswordSegue" {
// check PGP key
- if passwordStore.privateKey == nil {
+ guard passwordStore.privateKey != nil else {
let alertTitle = "Cannot Add Password"
let alertMessage = "PGP Key is not set. Please set your PGP Key first."
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
return false
}
+
// check name
let nameCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! TextFieldTableViewCell
- if nameCell.getContent()!.isEmpty {
+ guard nameCell.getContent()!.isEmpty == false else {
let alertTitle = "Cannot Add Password"
let alertMessage = "Please fill in the name."
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
return false
}
+
+ // check "/"
+ guard nameCell.getContent()!.contains("/") == false else {
+ let alertTitle = "Cannot Add Password"
+ let alertMessage = "Illegal character."
+ Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
+ return false
+ }
}
return true
}
diff --git a/pass/Controllers/AdvancedSettingsTableViewController.swift b/pass/Controllers/AdvancedSettingsTableViewController.swift
index 7b22c93..c3ddc76 100644
--- a/pass/Controllers/AdvancedSettingsTableViewController.swift
+++ b/pass/Controllers/AdvancedSettingsTableViewController.swift
@@ -8,20 +8,34 @@
import UIKit
import SVProgressHUD
+import SwiftyUserDefaults
class AdvancedSettingsTableViewController: UITableViewController {
+ @IBOutlet weak var encryptInASCIIArmoredTableViewCell: UITableViewCell!
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
let passwordStore = PasswordStore.shared
+
+ let encryptInASCIIArmoredSwitch: UISwitch = {
+ let uiSwitch = UISwitch()
+ uiSwitch.onTintColor = Globals.blue
+ uiSwitch.sizeToFit()
+ uiSwitch.addTarget(self, action: #selector(encryptInASCIIArmoredAction(_:)), for: UIControlEvents.valueChanged)
+ return uiSwitch
+ }()
override func viewDidLoad() {
super.viewDidLoad()
+ navigationItem.title = "Advanced"
+ encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
+ encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
+ encryptInASCIIArmoredTableViewCell.selectionStyle = .none
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ tableView.deselectRow(at: indexPath, animated: true)
if tableView.cellForRow(at: indexPath) == eraseDataTableViewCell {
- print("erase data")
let alert = UIAlertController(title: "Erase Password Store Data?", message: "This will delete all local data and settings. Password store data on your remote server will not be affected.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Erase Password Data", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
SVProgressHUD.show(withStatus: "Erasing ...")
@@ -32,7 +46,6 @@ class AdvancedSettingsTableViewController: UITableViewController {
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
self.present(alert, animated: true, completion: nil)
- tableView.deselectRow(at: indexPath, animated: true)
} else if tableView.cellForRow(at: indexPath) == discardChangesTableViewCell {
let alert = UIAlertController(title: "Discard All Changes?", message: "Do you want to permanently discard all changes to the local copy of your password data? You cannot undo this action.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Discard All Changes", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
@@ -52,9 +65,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
}
SVProgressHUD.dismiss(withDelay: 1)
} catch {
- DispatchQueue.main.async {
- Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
- }
+ Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
}
}
}
@@ -62,8 +73,11 @@ class AdvancedSettingsTableViewController: UITableViewController {
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
self.present(alert, animated: true, completion: nil)
- tableView.deselectRow(at: indexPath, animated: true)
}
}
+
+ func encryptInASCIIArmoredAction(_ sender: Any?) {
+ Defaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
+ }
}
diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift
index 405e321..641d074 100644
--- a/pass/Controllers/EditPasswordTableViewController.swift
+++ b/pass/Controllers/EditPasswordTableViewController.swift
@@ -9,15 +9,13 @@
import UIKit
class EditPasswordTableViewController: PasswordEditorTableViewController {
-
- var password: Password?
-
override func viewDidLoad() {
tableData = [
[[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.name]],
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
+ [[.type: PasswordEditorCellType.deletePasswordCell]],
]
super.viewDidLoad()
}
@@ -43,10 +41,11 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
var cellContents = [String: String]()
for cell in cells {
let indexPath = tableView.indexPath(for: cell)!
- let contentCell = cell as! ContentTableViewCell
- let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
- if let cellContent = contentCell.getContent() {
- cellContents[cellTitle] = cellContent
+ if let contentCell = cell as? ContentTableViewCell {
+ let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
+ if let cellContent = contentCell.getContent() {
+ cellContents[cellTitle] = cellContent
+ }
}
}
var plainText = ""
diff --git a/pass/Controllers/GeneralSettingsTableViewController.swift b/pass/Controllers/GeneralSettingsTableViewController.swift
index 9d83de8..df1bede 100644
--- a/pass/Controllers/GeneralSettingsTableViewController.swift
+++ b/pass/Controllers/GeneralSettingsTableViewController.swift
@@ -169,10 +169,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
func hideUnknownSwitchAction(_ sender: Any?) {
Defaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
+ NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
}
func hideOTPSwitchAction(_ sender: Any?) {
Defaults[.isHideOTPOn] = hideOTPSwitch.isOn
+ NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
}
func rememberPassphraseSwitchAction(_ sender: Any?) {
@@ -184,7 +186,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
func showFolderSwitchAction(_ sender: Any?) {
Defaults[.isShowFolderOn] = showFolderSwitch.isOn
- NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
+ NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
}
}
diff --git a/pass/Controllers/GitServerSettingTableViewController.swift b/pass/Controllers/GitServerSettingTableViewController.swift
index 21e29a0..3f940d5 100644
--- a/pass/Controllers/GitServerSettingTableViewController.swift
+++ b/pass/Controllers/GitServerSettingTableViewController.swift
@@ -77,15 +77,12 @@ class GitServerSettingTableViewController: UITableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveGitServerSettingSegue" {
- if gitRepositoryURLTextField.text == "" || authenticationMethod == nil {
- var alertMessage = ""
- if gitRepositoryURLTextField.text == "" {
- alertMessage = "Git Server is not set. Please set the Git server first."
- }
- if authenticationMethod == nil {
- alertMessage = "Authentication method is not set. Please set your authentication method first."
- }
- Utils.alert(title: "Cannot Save Settings", message: alertMessage, controller: self, completion: nil)
+ guard let _ = URL(string: gitRepositoryURLTextField.text!) else {
+ Utils.alert(title: "Cannot Save", message: "Git Server is not set.", controller: self, completion: nil)
+ return false
+ }
+ guard authenticationMethod != nil else {
+ Utils.alert(title: "Cannot Save", message: "Authentication method is not set.", controller: self, completion: nil)
return false
}
}
diff --git a/pass/Controllers/PGPKeySettingTableViewController.swift b/pass/Controllers/PGPKeySettingTableViewController.swift
index f19422b..6423b1e 100644
--- a/pass/Controllers/PGPKeySettingTableViewController.swift
+++ b/pass/Controllers/PGPKeySettingTableViewController.swift
@@ -26,24 +26,15 @@ class PGPKeySettingTableViewController: UITableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "savePGPKeySegue" {
- guard pgpPublicKeyURLTextField.text != nil else {
- return false
- }
- guard pgpPrivateKeyURLTextField.text != nil else {
- return false
- }
-
- guard URL(string: pgpPublicKeyURLTextField.text!) != nil else {
+ guard let pgpPublicKeyURL = URL(string: pgpPublicKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil)
return false
}
- guard URL(string: pgpPrivateKeyURLTextField.text!) != nil else {
+ guard let pgpPrivateKeyURL = URL(string: pgpPrivateKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Private Key URL first.", controller: self, completion: nil)
return false
}
-
- if URL(string: pgpPublicKeyURLTextField.text!)!.scheme! == "http" &&
- URL(string: pgpPrivateKeyURLTextField.text!)!.scheme! == "http" {
+ guard pgpPublicKeyURL.scheme! == "https", pgpPrivateKeyURL.scheme! == "https" else {
Utils.alert(title: "Cannot Save Settings", message: "HTTP connection is not supported.", controller: self, completion: nil)
return false
}
@@ -55,7 +46,9 @@ class PGPKeySettingTableViewController: UITableViewController {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
- self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
+ if self.shouldPerformSegue(withIdentifier: "savePGPKeySegue", sender: self) {
+ self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
+ }
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase
diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift
index ba3f278..551ec75 100644
--- a/pass/Controllers/PasswordDetailTableViewController.swift
+++ b/pass/Controllers/PasswordDetailTableViewController.swift
@@ -13,26 +13,25 @@ import SVProgressHUD
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var passwordEntity: PasswordEntity?
- var passwordCategoryText = ""
- var password: Password?
- var passwordImage: UIImage?
- var oneTimePasswordIndexPath : IndexPath?
- var shouldPopCurrentView = false
- let passwordStore = PasswordStore.shared
+ private var password: Password?
+ private var passwordCategoryText = ""
+ private var passwordImage: UIImage?
+ private var oneTimePasswordIndexPath : IndexPath?
+ private var shouldPopCurrentView = false
+ private let passwordStore = PasswordStore.shared
- let indicator: UIActivityIndicatorView = {
+ private let indicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382)
return indicator
}()
- lazy var editUIBarButtonItem: UIBarButtonItem = {
+ private lazy var editUIBarButtonItem: UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
return uiBarButtonItem
}()
-
- struct TableCell {
+ private struct TableCell {
var title: String
var content: String
init() {
@@ -46,12 +45,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
- struct TableSection {
+ private struct TableSection {
var title: String
var item: Array
}
- var tableData = Array()
+ private var tableData = Array()
override func viewDidLoad() {
super.viewDidLoad()
@@ -67,7 +66,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 0, 0);
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 52
-
indicator.startAnimating()
tableView.addSubview(indicator)
@@ -96,16 +94,35 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.present(alert, animated: true, completion: nil)
}
- self.setupUpdateOneTimePassword()
- self.addNotificationObservers()
-
+ self.setupOneTimePasswordAutoRefresh()
+
+ // pop the current view because this password might be "discarded"
+ NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
+
+ // reset the data table if some password (maybe another one) has been updated
+ NotificationCenter.default.addObserver(self, selector: #selector(showPassword), name: .passwordStoreUpdated, object: nil)
+
+ // reset the data table if the disaply settings have been changed
+ NotificationCenter.default.addObserver(self, selector: #selector(showPassword), name: .passwordDetailDisplaySettingChanged, object: nil)
}
- func decryptThenShowPassword(passphrase: String) {
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ if self.shouldPopCurrentView {
+ let alert = UIAlertController(title: "Notice", message: "All previous local changes have been discarded. Your current Password Store will be shown.", preferredStyle: UIAlertControllerStyle.alert)
+ alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
+ _ = self.navigationController?.popViewController(animated: true)
+ }))
+ self.present(alert, animated: true, completion: nil)
+ }
+ }
+
+ private func decryptThenShowPassword(passphrase: String) {
if Defaults[.isRememberPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase
}
DispatchQueue.global(qos: .userInitiated).async {
+ // decrypt password
do {
self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)!
} catch {
@@ -118,39 +135,42 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
return
}
-
- let password = self.password!
- DispatchQueue.main.async { [weak self] in
- self?.showPassword(password: password)
+ // display password
+ self.showPassword()
+ }
+ }
+
+ @objc private func showPassword() {
+ DispatchQueue.main.async { [weak self] in
+ self?.indicator.stopAnimating()
+ self?.setTableData()
+ UIView.performWithoutAnimation {
+ self?.tableView.reloadData()
+ // add layoutIfNeeded solves the "flickering problem" during refresh
+ self?.tableView.layoutIfNeeded()
+ }
+ self?.editUIBarButtonItem.isEnabled = true
+ if let urlString = self?.password?.getURLString() {
+ if self?.passwordEntity?.image == nil {
+ self?.updatePasswordImage(urlString: urlString)
+ }
}
}
}
- func showPassword(password: Password) {
- setTableData()
- self.tableView.reloadData()
- indicator.stopAnimating()
- editUIBarButtonItem.isEnabled = true
- if let urlString = password.getURLString() {
- if self.passwordEntity?.image == nil{
- self.updatePasswordImage(urlString: urlString)
- }
- }
- }
-
- func setupUpdateOneTimePassword() {
+ private func setupOneTimePasswordAutoRefresh() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] timer in
// bail out of the timer code if the object has been freed
guard let strongSelf = self,
- let token = strongSelf.password?.otpToken,
+ let otpType = strongSelf.password?.otpType,
+ otpType != .none,
let indexPath = strongSelf.oneTimePasswordIndexPath,
let cell = strongSelf.tableView.cellForRow(at: indexPath) as? LabelTableViewCell else {
return
}
- switch token.generator.factor {
- case .timer:
- // totp
+ switch otpType {
+ case .totp:
if let (title, otp) = strongSelf.password?.getOtpStrings() {
strongSelf.tableData[indexPath.section].item[indexPath.row].title = title
strongSelf.tableData[indexPath.section].item[indexPath.row].content = otp
@@ -163,16 +183,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
- func pressEdit(_ sender: Any?) {
- print("pressEdit")
+ @objc private func pressEdit(_ sender: Any?) {
performSegue(withIdentifier: "editPasswordSegue", sender: self)
}
- @IBAction func cancelEditPassword(segue: UIStoryboardSegue) {
+ @objc private func setShouldPopCurrentView() {
+ self.shouldPopCurrentView = true
+ }
+
+ @IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
}
- @IBAction func saveEditPassword(segue: UIStoryboardSegue) {
+ @IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
if self.password!.changed {
SVProgressHUD.show(withStatus: "Saving")
DispatchQueue.global(qos: .userInitiated).async {
@@ -193,7 +216,13 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
- func setTableData() {
+ @IBAction private func deletePassword(segue: UIStoryboardSegue) {
+ print("delete")
+ passwordStore.delete(passwordEntity: passwordEntity!)
+ let _ = navigationController?.popViewController(animated: true)
+ }
+
+ private func setTableData() {
self.tableData = Array()
tableData.append(TableSection(title: "", item: []))
tableData[0].item.append(TableCell())
@@ -206,26 +235,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.tableData[tableDataIndex].item.append(TableCell(title: "password", content: password.password))
// show one time password
- if let token = password.otpToken {
- switch token.generator.factor {
- case .counter(_):
- // counter-based one time password
+ if password.otpType != .none {
+ if let (title, otp) = self.password?.getOtpStrings() {
self.tableData.append(TableSection(title: "One time password", item: []))
tableDataIndex += 1
oneTimePasswordIndexPath = IndexPath(row: 0, section: tableDataIndex)
- if let crtPassword = password.otpToken?.currentPassword {
- self.tableData[tableDataIndex].item.append(TableCell(title: "HMAC-based", content: crtPassword))
- }
- case .timer(let period):
- // time-based one time password
- self.tableData.append(TableSection(title: "One time password", item: []))
- tableDataIndex += 1
- oneTimePasswordIndexPath = IndexPath(row: 0, section: tableDataIndex)
- if let crtPassword = password.otpToken?.currentPassword {
- let timeSinceEpoch = Date().timeIntervalSince1970
- let validTime = Int(period - timeSinceEpoch.truncatingRemainder(dividingBy: period))
- self.tableData[tableDataIndex].item.append(TableCell(title: "time-based (expiring in \(validTime)s)", content: crtPassword))
- }
+ self.tableData[tableDataIndex].item.append(TableCell(title: title, content: otp))
}
}
@@ -233,7 +248,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let filteredAdditionKeys = password.additionKeys.filter {
$0.lowercased() != "username" &&
$0.lowercased() != "password" &&
- (!$0.hasPrefix("unknown") || !Defaults[.isHideOTPOn]) &&
+ (!$0.hasPrefix("unknown") || !Defaults[.isHideUnknownOn]) &&
(!Password.otpKeywords.contains($0) || !Defaults[.isHideOTPOn]) }
if filteredAdditionKeys.count > 0 {
@@ -255,7 +270,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
- func updatePasswordImage(urlString: String) {
+ private func updatePasswordImage(urlString: String) {
var newUrlString = urlString
if urlString.lowercased().hasPrefix("http://") {
// try to replace http url to https url
@@ -294,7 +309,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
- func tapMenu(recognizer: UITapGestureRecognizer) {
+ @objc private func tapMenu(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
@@ -303,9 +318,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let menuController = UIMenuController.shared
let revealItem = UIMenuItem(title: "Reveal", action: #selector(LabelTableViewCell.revealPassword(_:)))
let concealItem = UIMenuItem(title: "Conceal", action: #selector(LabelTableViewCell.concealPassword(_:)))
- let nextPasswordItem = UIMenuItem(title: "Next Password", action: #selector(LabelTableViewCell.nextPassword(_:)))
+ let nextHOTPItem = UIMenuItem(title: "Next Password", action: #selector(LabelTableViewCell.getNextHOTP(_:)))
let openURLItem = UIMenuItem(title: "Copy Password & Open Link", action: #selector(LabelTableViewCell.openLink(_:)))
- menuController.menuItems = [revealItem, concealItem, nextPasswordItem, openURLItem]
+ menuController.menuItems = [revealItem, concealItem, nextHOTPItem, openURLItem]
menuController.setTargetRect(tappedCell.contentLabel.frame, in: tappedCell.contentLabel.superview!)
menuController.setMenuVisible(true, animated: true)
}
@@ -313,6 +328,46 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
+ func getNextHOTP() {
+ guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
+ DispatchQueue.main.async {
+ Utils.alert(title: "Error", message: "Get next password of a non-HOTP entry.", controller: self, completion: nil)
+ }
+ return;
+ }
+
+ // increase HOTP counter
+ password!.increaseHotpCounter()
+
+ // copy HOTP to pasteboard
+ if let plainPassword = password!.getOtp() {
+ Utils.copyToPasteboard(textToCopy: plainPassword)
+ }
+
+ // commit the change of HOTP counter
+ if password!.changed {
+ DispatchQueue.global(qos: .userInitiated).async {
+ self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: {_ in })
+ DispatchQueue.main.async {
+ self.passwordEntity!.synced = false
+ self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!)
+ SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
+ SVProgressHUD.dismiss(withDelay: 1)
+ }
+ }
+ }
+ }
+
+ func openLink() {
+ guard let urlString = self.password?.getURLString(), let url = URL(string: urlString) else {
+ DispatchQueue.main.async {
+ Utils.alert(title: "Error", message: "Cannot find a valid URL", controller: self, completion: nil)
+ }
+ return;
+ }
+ Utils.copyToPasteboard(textToCopy: password?.password)
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
+ }
override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count
@@ -340,7 +395,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell", for: indexPath) as! LabelTableViewCell
let titleData = tableData[sectionIndex].item[rowIndex].title
let contentData = tableData[sectionIndex].item[rowIndex].content
- cell.passwordTableView = self
+ cell.delegatePasswordTableView = self
cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false)
cell.isURLCell = (titleData.lowercased() == "url" ? true : false)
cell.isHOTPCell = (titleData == "HMAC-based" ? true : false)
@@ -381,23 +436,4 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return true
}
-
- private func addNotificationObservers() {
- NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
- }
-
- func setShouldPopCurrentView() {
- self.shouldPopCurrentView = true
- }
-
- override func viewDidAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- if self.shouldPopCurrentView {
- let alert = UIAlertController(title: "Notice", message: "All previous local changes have been discarded. Your current Password Store will be shown.", preferredStyle: UIAlertControllerStyle.alert)
- alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
- _ = self.navigationController?.popViewController(animated: true)
- }))
- self.present(alert, animated: true, completion: nil)
- }
- }
}
diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift
index 874e3e9..ccd5b03 100644
--- a/pass/Controllers/PasswordEditorTableViewController.swift
+++ b/pass/Controllers/PasswordEditorTableViewController.swift
@@ -8,23 +8,40 @@
import UIKit
enum PasswordEditorCellType {
- case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell
+ case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell
}
enum PasswordEditorCellKey {
case type, title, content, placeholders
}
-class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate {
- var navigationItemTitle: String?
+class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, UIGestureRecognizerDelegate {
+
var tableData = [
[Dictionary]
- ]()
- var sectionHeaderTitles = ["name", "password", "additions"].map {$0.uppercased()}
- var sectionFooterTitles = ["", "", "It is recommended to use \"key: value\" format to store additional fields as follows:\n url: https://www.apple.com\n username: passforios@gmail.com."]
+ ]()
+ var password: Password?
+
+ private var navigationItemTitle: String?
+
+ private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
+ private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
+ private let passwordSection = 1
+ private var hidePasswordSettings = true
+
+ private var fillPasswordCell: FillPasswordTableViewCell?
+ private var passwordLengthCell: SliderTableViewCell?
+ private var deletePasswordCell: UITableViewCell?
+
+ override func loadView() {
+ super.loadView()
+
+ deletePasswordCell = UITableViewCell(style: .default, reuseIdentifier: "default")
+ deletePasswordCell!.textLabel?.text = "Delete Password"
+ deletePasswordCell!.textLabel?.textColor = Globals.red
+ deletePasswordCell?.selectionStyle = .default
+ }
- var passwordLengthCell: SliderTableViewCell?
-
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = navigationItemTitle
@@ -35,33 +52,40 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 48
- tableView.allowsSelection = false
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionFooterHeight = 0;
+
+ let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tableTapped))
+ tapGesture.delegate = self
+ tapGesture.cancelsTouchesInView = false
+ tableView.addGestureRecognizer(tapGesture)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = tableData[indexPath.section][indexPath.row]
- var cell = ContentTableViewCell()
switch cellData[PasswordEditorCellKey.type] as! PasswordEditorCellType {
case .textViewCell:
- cell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as! ContentTableViewCell
+ let cell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as! ContentTableViewCell
+ cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
+ return cell
case .fillPasswordCell:
- let fillPasswordCell = tableView.dequeueReusableCell(withIdentifier: "fillPasswordCell", for: indexPath) as?FillPasswordTableViewCell
+ fillPasswordCell = tableView.dequeueReusableCell(withIdentifier: "fillPasswordCell", for: indexPath) as? FillPasswordTableViewCell
fillPasswordCell?.delegate = self
- cell = fillPasswordCell!
+ fillPasswordCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
+ return fillPasswordCell!
case .passwordLengthCell:
passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell
passwordLengthCell?.reset(title: "Length", minimumValue: Globals.passwordMinimumLength, maximumValue: Globals.passwordMaximumLength, defaultValue: Globals.passwordDefaultLength)
- cell = passwordLengthCell!
+ passwordLengthCell?.delegate = self
+ return passwordLengthCell!
+ case .deletePasswordCell:
+ return deletePasswordCell!
default:
- cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
+ let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
+ cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
+ return cell
}
- if let content = cellData[PasswordEditorCellKey.content] as? String {
- cell.setContent(content: content)
- }
- return cell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@@ -73,9 +97,14 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return tableData[section].count
+ if section == passwordSection, hidePasswordSettings {
+ // hide the password section, only the password should be shown
+ return 1
+ } else {
+ return tableData[section].count
+ }
}
-
+
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionHeaderTitles[section]
}
@@ -84,8 +113,81 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
return sectionFooterTitles[section]
}
- func generatePassword() -> String {
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ if tableView.cellForRow(at: indexPath) == deletePasswordCell {
+ let alert = UIAlertController(title: "Delete Password?", message: nil, preferredStyle: UIAlertControllerStyle.alert)
+ alert.addAction(UIAlertAction(title: "Delete", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
+ self.performSegue(withIdentifier: "deletePasswordSegue", sender: self)
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler:nil))
+ self.present(alert, animated: true, completion: nil)
+ }
+ tableView.deselectRow(at: indexPath, animated: true)
+ }
+
+ // generate password, copy to pasteboard, and set the cell
+ // check whether the current password looks like an OTP field
+ func generateAndCopyPassword() {
+ if let currentPassword = fillPasswordCell?.getContent(),
+ Password.LooksLikeOTP(line: currentPassword) {
+ let alert = UIAlertController(title: "Overwrite?", message: "Overwrite the one-time password configuration?", preferredStyle: UIAlertControllerStyle.alert)
+ alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.destructive, handler: {_ in
+ self.generateAndCopyPasswordNoOtpCheck()
+ }))
+ alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil))
+ self.present(alert, animated: true, completion: nil)
+ } else {
+ self.generateAndCopyPasswordNoOtpCheck()
+ }
+ }
+
+ // generate the password, don't care whether the original line is otp
+ func generateAndCopyPasswordNoOtpCheck() {
+ // show password settings (e.g., the length slider)
+ if hidePasswordSettings == true {
+ hidePasswordSettings = false
+ tableView.reloadSections([passwordSection], with: .fade)
+ }
let length = passwordLengthCell?.roundedValue ?? Globals.passwordDefaultLength
- return Utils.generatePassword(length: length)
+ let plainPassword = Utils.generatePassword(length: length)
+ Utils.copyToPasteboard(textToCopy: plainPassword)
+
+ // update tableData so to make sure reloadData() works correctly
+ tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword
+
+ // update cell manually, no need to call reloadData()
+ fillPasswordCell?.setContent(content: plainPassword)
+ }
+
+ func tableTapped(recognizer: UITapGestureRecognizer) {
+ if recognizer.state == UIGestureRecognizerState.ended {
+ let tapLocation = recognizer.location(in: self.tableView)
+ let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation)
+
+ // do nothing, if delete is tapped (a temporary solution)
+ if tapIndexPath != nil, deletePasswordCell != nil,
+ tableView.cellForRow(at: tapIndexPath!) == deletePasswordCell {
+ return
+ }
+
+ // hide password settings (e.g., the length slider)
+ if tapIndexPath?.section != passwordSection, hidePasswordSettings == false {
+ hidePasswordSettings = true
+ tableView.reloadSections([passwordSection], with: .fade)
+ // select the row at tapIndexPath manually
+ if tapIndexPath != nil {
+ self.tableView(self.tableView, didSelectRowAt: tapIndexPath!)
+ }
+ }
+ }
+ }
+
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ if gestureRecognizer is UITapGestureRecognizer {
+ // so that the tap gesture could be passed by
+ return true
+ } else {
+ return false
+ }
}
}
diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift
index 035f889..be90cf6 100644
--- a/pass/Controllers/PasswordsViewController.swift
+++ b/pass/Controllers/PasswordsViewController.swift
@@ -25,13 +25,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil
- let passwordStore = PasswordStore.shared
+ private let passwordStore = PasswordStore.shared
private var tapTabBarTime: TimeInterval = 0
- var sections : [(index: Int, length :Int, title: String)] = Array()
- var searchActive : Bool = false
- lazy var searchController: UISearchController = {
+ private var sections : [(index: Int, length :Int, title: String)] = Array()
+ private var searchActive : Bool = false
+
+ private lazy var searchController: UISearchController = {
let uiSearchController = UISearchController(searchResultsController: nil)
uiSearchController.searchResultsUpdater = self
uiSearchController.dimsBackgroundDuringPresentation = false
@@ -40,20 +41,40 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
uiSearchController.searchBar.sizeToFit()
return uiSearchController
}()
- lazy var refreshControl: UIRefreshControl = {
- let refreshControl = UIRefreshControl()
- refreshControl.addTarget(self, action: #selector(PasswordsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
- return refreshControl
+ private lazy var syncControl: UIRefreshControl = {
+ let syncControl = UIRefreshControl()
+ syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControlEvents.valueChanged)
+ return syncControl
}()
- lazy var searchBarView: UIView = {
- let uiView = UIView(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 44))
+ private lazy var searchBarView: UIView = {
+ let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 44))
uiView.addSubview(self.searchController.searchBar)
return uiView
}()
- lazy var backUIBarButtonItem: UIBarButtonItem = {
+ private lazy var backUIBarButtonItem: UIBarButtonItem = {
let backUIBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(self.backAction(_:)))
return backUIBarButtonItem
}()
+
+ private lazy var transitionFromRight: CATransition = {
+ let transition = CATransition()
+ transition.type = kCATransitionPush
+ transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
+ transition.fillMode = kCAFillModeForwards
+ transition.duration = 0.25
+ transition.subtype = kCATransitionFromRight
+ return transition
+ }()
+
+ private lazy var transitionFromLeft: CATransition = {
+ let transition = CATransition()
+ transition.type = kCATransitionPush
+ transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
+ transition.fillMode = kCAFillModeForwards
+ transition.duration = 0.25
+ transition.subtype = kCATransitionFromLeft
+ return transition
+ }()
@IBOutlet weak var tableView: UITableView!
@@ -102,11 +123,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
- func syncPasswords() {
+ private func syncPasswords() {
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "Sync Password Store")
- let numberOfUnsyncedPasswords = self.passwordStore.getNumberOfUnsyncedPasswords()
+ let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do {
try self.passwordStore.pullRepository(transferProgressBlock: {(git_transfer_progress, stop) in
@@ -114,7 +135,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Pull Remote Repository")
}
})
- if numberOfUnsyncedPasswords > 0 {
+ if numberOfLocalCommits > 0 {
try self.passwordStore.pushRepository(transferProgressBlock: {(current, total, bytes, stop) in
DispatchQueue.main.async {
SVProgressHUD.showProgress(Float(current)/Float(total), status: "Push Remote Repository")
@@ -122,11 +143,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
})
}
DispatchQueue.main.async {
- self.passwordStore.updatePasswordEntityCoreData()
- self.initPasswordsTableEntries(parent: nil)
- self.reloadTableView(data: self.passwordsTableEntries)
- self.passwordStore.setAllSynced()
- self.setNavigationItemTitle()
+ self.reloadTableView(parent: nil)
Defaults[.lastUpdatedTime] = Date()
Defaults[.gitRepositoryPasswordAttempts] = 0
SVProgressHUD.showSuccess(withStatus: "Done")
@@ -140,26 +157,27 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
- private func addNotificationObservers() {
- NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordStoreUpdatedNotification), name: .passwordStoreUpdated, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnSearchNotification), name: .passwordSearch, object: nil)
- }
override func viewDidLoad() {
super.viewDidLoad()
- setNavigationItemTitle()
- initPasswordsTableEntries(parent: nil)
- addNotificationObservers()
- generateSections(item: passwordsTableEntries)
+
tabBarController!.delegate = self
tableView.delegate = self
tableView.dataSource = self
definesPresentationContext = true
view.addSubview(searchBarView)
- tableView.insertSubview(refreshControl, at: 0)
+ tableView.insertSubview(syncControl, at: 0)
SVProgressHUD.setDefaultMaskType(.black)
- updateRefreshControlTitle()
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
+
+ // initialize the password table
+ reloadTableView(parent: nil)
+
+ // reset the data table if some password (maybe another one) has been updated
+ NotificationCenter.default.addObserver(self, selector: #selector(reloadTableView as (Void) -> Void), name: .passwordStoreUpdated, object: nil)
+ // reset the data table if the disaply settings have been changed
+ NotificationCenter.default.addObserver(self, selector: #selector(reloadTableView as (Void) -> Void), name: .passwordDisplaySettingChanged, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
@@ -243,15 +261,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} else {
tableView.deselectRow(at: indexPath, animated: true)
searchController.isActive = false
- initPasswordsTableEntries(parent: entry.passwordEntity)
- reloadTableView(data: passwordsTableEntries)
+ reloadTableView(parent: entry.passwordEntity, anim: transitionFromRight)
}
}
func backAction(_ sender: Any?) {
guard Defaults[.isShowFolderOn] else { return }
- initPasswordsTableEntries(parent: parentPasswordEntity?.parent)
- reloadTableView(data: passwordsTableEntries)
+ reloadTableView(parent: parentPasswordEntity?.parent, anim: transitionFromLeft)
}
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
@@ -279,7 +295,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
copyToPasteboard(from: indexPath)
}
- func copyToPasteboard(from indexPath: IndexPath) {
+ private func copyToPasteboard(from indexPath: IndexPath) {
guard self.passwordStore.privateKey != nil else {
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
return
@@ -305,7 +321,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
- func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) {
+ private func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) {
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.dark)
SVProgressHUD.show(withStatus: "Decrypting")
@@ -327,7 +343,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
- func generateSections(item: [PasswordsTableEntry]) {
+ private func generateSections(item: [PasswordsTableEntry]) {
sections.removeAll()
guard item.count != 0 else {
return
@@ -349,27 +365,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
sections.append(newSection)
}
- func actOnPasswordStoreUpdatedNotification() {
- initPasswordsTableEntries(parent: nil)
- reloadTableView(data: passwordsTableEntries)
- setNavigationItemTitle()
- }
-
- private func setNavigationItemTitle() {
- var title = ""
- if parentPasswordEntity != nil {
- title = parentPasswordEntity!.name!
- } else {
- title = "Password Store"
- }
- let numberOfUnsynced = self.passwordStore.getNumberOfUnsyncedPasswords()
- if numberOfUnsynced == 0 {
- navigationItem.title = "\(title)"
- } else {
- navigationItem.title = "\(title) (\(numberOfUnsynced))"
- }
- }
-
func actOnSearchNotification() {
searchController.searchBar.becomeFirstResponder()
}
@@ -410,27 +405,47 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
- func updateRefreshControlTitle() {
- var atribbutedTitle = "Pull to Sync Password Store"
- atribbutedTitle = "Last Synced: \(Utils.getLastUpdatedTimeString())"
- refreshControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
- }
-
- func reloadTableView(data: [PasswordsTableEntry]) {
- setNavigationItemTitle()
+ private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
+ // set navigation item
+ var numberOfLocalCommitsString = ""
+ let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits()
+ if numberOfLocalCommits > 0 {
+ numberOfLocalCommitsString = " (\(numberOfLocalCommits))"
+ }
if parentPasswordEntity != nil {
+ navigationItem.title = "\(parentPasswordEntity!.name!)\(numberOfLocalCommitsString)"
navigationItem.leftBarButtonItem = backUIBarButtonItem
} else {
+ navigationItem.title = "Password Store\(numberOfLocalCommitsString)"
navigationItem.leftBarButtonItem = nil
}
+
+ // set the password table
generateSections(item: data)
+ if anim != nil {
+ self.tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey")
+ }
tableView.reloadData()
- updateRefreshControlTitle()
+ self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
+
+ // set the sync control title
+ let atribbutedTitle = "Last Synced: \(Utils.getLastUpdatedTimeString())"
+ syncControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
}
- func handleRefresh(_ refreshControl: UIRefreshControl) {
+ private func reloadTableView(parent: PasswordEntity?, anim: CAAnimation? = nil) {
+ initPasswordsTableEntries(parent: parent)
+ reloadTableView(data: passwordsTableEntries, anim: anim)
+ }
+
+ func reloadTableView() {
+ initPasswordsTableEntries(parent: nil)
+ reloadTableView(data: passwordsTableEntries)
+ }
+
+ func handleRefresh(_ syncControl: UIRefreshControl) {
syncPasswords()
- refreshControl.endRefreshing()
+ syncControl.endRefreshing()
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
diff --git a/pass/Controllers/SSHKeySettingTableViewController.swift b/pass/Controllers/SSHKeySettingTableViewController.swift
index ad14dd0..517ea23 100644
--- a/pass/Controllers/SSHKeySettingTableViewController.swift
+++ b/pass/Controllers/SSHKeySettingTableViewController.swift
@@ -32,22 +32,22 @@ class SSHKeySettingTableViewController: UITableViewController {
}
func doneButtonTapped(_ sender: UIButton) {
- guard URL(string: publicKeyURLTextField.text!) != nil else {
+ guard let publicKeyURL = URL(string: publicKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil)
return
}
- guard URL(string: privateKeyURLTextField.text!) != nil else {
+ guard let privateKeyURL = URL(string: privateKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Private Key URL first.", controller: self, completion: nil)
return
}
- Defaults[.gitRepositorySSHPublicKeyURL] = URL(string: publicKeyURLTextField.text!)
- Defaults[.gitRepositorySSHPrivateKeyURL] = URL(string: privateKeyURLTextField.text!)
+ Defaults[.gitRepositorySSHPublicKeyURL] = publicKeyURL
+ Defaults[.gitRepositorySSHPrivateKeyURL] = privateKeyURL
Utils.addPasswordToKeychain(name: "gitRepositorySSHPrivateKeyPassphrase", password: passphraseTextField.text!)
do {
- try Data(contentsOf: Defaults[.gitRepositorySSHPublicKeyURL]!).write(to: Globals.sshPublicKeyURL, options: .atomic)
- try Data(contentsOf: Defaults[.gitRepositorySSHPrivateKeyURL]!).write(to: Globals.sshPrivateKeyURL, options: .atomic)
+ try Data(contentsOf: publicKeyURL).write(to: Globals.sshPublicKeyURL, options: .atomic)
+ try Data(contentsOf: privateKeyURL).write(to: Globals.sshPrivateKeyURL, options: .atomic)
} catch {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
}
diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift
index 2d26a34..0e1c3c3 100644
--- a/pass/Controllers/SettingsTableViewController.swift
+++ b/pass/Controllers/SettingsTableViewController.swift
@@ -140,7 +140,6 @@ class SettingsTableViewController: UITableViewController {
}
})
DispatchQueue.main.async {
- self.passwordStore.updatePasswordEntityCoreData()
Defaults[.lastUpdatedTime] = Date()
Defaults[.gitRepositoryURL] = URL(string: gitRepostiroyURL)
Defaults[.gitRepositoryUsername] = username
diff --git a/pass/Helpers/DefaultsKeys.swift b/pass/Helpers/DefaultsKeys.swift
index a97b1a3..bdfe3f0 100644
--- a/pass/Helpers/DefaultsKeys.swift
+++ b/pass/Helpers/DefaultsKeys.swift
@@ -10,7 +10,6 @@ import Foundation
import SwiftyUserDefaults
extension DefaultsKeys {
-// static let pgpKeyURL = DefaultsKey("pgpKeyURL")
static let pgpKeySource = DefaultsKey("pgpKeySource")
static let pgpPublicKeyURL = DefaultsKey("pgpPublicKeyURL")
static let pgpPrivateKeyURL = DefaultsKey("pgpPrivateKeyURL")
@@ -35,6 +34,6 @@ extension DefaultsKeys {
static let isRememberPassphraseOn = DefaultsKey("isRememberPassphraseOn")
static let isShowFolderOn = DefaultsKey("isShowFolderOn")
static let passwordGeneratorFlavor = DefaultsKey("passwordGeneratorFlavor")
-
-
+
+ static let encryptInArmored = DefaultsKey("encryptInArmored")
}
diff --git a/pass/Helpers/Globals.swift b/pass/Helpers/Globals.swift
index 0621464..3959550 100644
--- a/pass/Helpers/Globals.swift
+++ b/pass/Helpers/Globals.swift
@@ -28,6 +28,9 @@ class Globals {
static let passwordMaximumLength = 24
static let passwordDefaultLength = 16
+ static let passwordDots = "••••••••••••"
+ static let passwordFonts = "Menlo"
+
private init() { }
}
diff --git a/pass/Helpers/NotificationNames.swift b/pass/Helpers/NotificationNames.swift
index e4bf3d8..e59f843 100644
--- a/pass/Helpers/NotificationNames.swift
+++ b/pass/Helpers/NotificationNames.swift
@@ -13,4 +13,7 @@ extension Notification.Name {
static let passwordStoreErased = Notification.Name("passwordStoreErased")
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
static let passwordSearch = Notification.Name("passwordSearch")
+
+ static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
+ static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
}
diff --git a/pass/Helpers/Utils.swift b/pass/Helpers/Utils.swift
index 726e107..462c83c 100644
--- a/pass/Helpers/Utils.swift
+++ b/pass/Helpers/Utils.swift
@@ -133,7 +133,7 @@ class Utils {
for (index, element) in plainPassword.unicodeScalars.enumerated() {
if NSCharacterSet.decimalDigits.contains(element) {
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1))
- } else if NSCharacterSet.punctuationCharacters.contains(element) {
+ } else if !NSCharacterSet.letters.contains(element) {
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1))
}
}
diff --git a/pass/Info.plist b/pass/Info.plist
index f1ca210..17cc245 100644
--- a/pass/Info.plist
+++ b/pass/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.2.1
+ 0.2.2
CFBundleVersion
1
ITSAppUsesNonExemptEncryption
diff --git a/pass/Models/Password.swift b/pass/Models/Password.swift
index 25d20bb..1d17846 100644
--- a/pass/Models/Password.swift
+++ b/pass/Models/Password.swift
@@ -17,16 +17,35 @@ struct AdditionField {
}
class Password {
- static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter"]
+ static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"]
var name = ""
var password = ""
var additions = [String: String]()
var additionKeys = [String]()
- var plainText = ""
var changed = false
- var firstLineIsOTPField = false
- var otpToken: Token?
+
+ private var plainText = ""
+ private var firstLineIsOTPField = false
+ private var otpToken: Token?
+
+ enum OtpType {
+ case totp, hotp, none
+ }
+
+ var otpType: OtpType {
+ get {
+ guard let token = self.otpToken else {
+ return OtpType.none
+ }
+ switch token.generator.factor {
+ case .counter:
+ return OtpType.hotp
+ case .timer:
+ return OtpType.totp
+ }
+ }
+ }
init(name: String, plainText: String) {
self.initEverything(name: name, plainText: plainText)
@@ -39,7 +58,7 @@ class Password {
}
}
- func initEverything(name: String, plainText: String) {
+ private func initEverything(name: String, plainText: String) {
self.name = name
self.plainText = plainText
@@ -57,7 +76,7 @@ class Password {
// check whether the first line of the plainText looks like an otp entry
let (key, value) = Password.getKeyValuePair(from: plainTextSplit[0])
- if key != nil && Password.otpKeywords.contains(key!) {
+ if Password.otpKeywords.contains(key ?? "") {
firstLineIsOTPField = true
self.additions[key!] = value
self.additionKeys.insert(key!, at: 0)
@@ -79,7 +98,7 @@ class Password {
// return a key-value pair from the line
// key might be nil, if there is no ":" in the line
- static func getKeyValuePair(from line: String) -> (key: String?, value: String) {
+ static private func getKeyValuePair(from line: String) -> (key: String?, value: String) {
let items = line.characters.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true).map(String.init)
var key : String?
var value = ""
@@ -92,7 +111,7 @@ class Password {
return (key, value)
}
- static func getAdditionFields(from additionFieldsPlainText: String) -> ([String: String], [String]){
+ static private func getAdditionFields(from additionFieldsPlainText: String) -> ([String: String], [String]){
var additions = [String: String]()
var additionKeys = [String]()
var unknownIndex = 0
@@ -125,7 +144,7 @@ class Password {
}
}
- func getPlainText() -> String {
+ private func getPlainText() -> String {
return self.plainText
}
@@ -140,21 +159,37 @@ class Password {
/*
Set otpType and otpToken, if we are able to construct a valid token.
- Example of TOTP fields
+ Example of TOTP otpauth
+ (Key Uri Format: https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
+ otpauth://totp/totp-secret?secret=AAAAAAAAAAAAAAAA&issuer=totp-secret
+
+ Example of TOTP fields [Legacy, lower priority]
otp_secret: secretsecretsecretsecretsecretsecret
otp_type: totp
otp_algorithm: sha1 (default: sha1, optional)
otp_period: 30 (default: 30, optional)
otp_digits: 6 (default: 6, optional)
- Example of HOTP fields
+ Example of HOTP fields [Legacy, lower priority]
otp_secret: secretsecretsecretsecretsecretsecret
otp_type: hotp
otp_counter: 1
otp_digits: 6 (default: 6, optional)
*/
- func updateOtpToken() {
+ private func updateOtpToken() {
+ // get otpauth, if we are able to generate a token, return
+ if var otpauthString = getAdditionValue(withKey: "otpauth") {
+ if !otpauthString.hasPrefix("otpauth:") {
+ otpauthString = "otpauth:\(otpauthString)"
+ }
+ if let otpauthUrl = URL(string: otpauthString),
+ let token = Token(url: otpauthUrl) {
+ self.otpToken = token
+ return
+ }
+ }
+
// get secret data
guard let secretString = getAdditionValue(withKey: "otp_secret"),
let secretData = MF_Base32Codec.data(fromBase32String: secretString),
@@ -175,11 +210,11 @@ class Password {
if let algoString = getAdditionValue(withKey: "otp_algorithm") {
switch algoString.lowercased() {
case "sha256":
- algorithm = Generator.Algorithm.sha256
+ algorithm = .sha256
case "sha512":
- algorithm = Generator.Algorithm.sha512
+ algorithm = .sha512
default:
- algorithm = Generator.Algorithm.sha1
+ algorithm = .sha1
}
}
@@ -259,4 +294,18 @@ class Password {
let otp = self.otpToken?.currentPassword ?? "error"
return (description, otp)
}
+
+ // return the password strings
+ func getOtp() -> String? {
+ if let otp = self.otpToken?.currentPassword {
+ return otp
+ } else {
+ return nil
+ }
+ }
+
+ static func LooksLikeOTP(line: String) -> Bool {
+ let (key, _) = getKeyValuePair(from: line)
+ return Password.otpKeywords.contains(key ?? "")
+ }
}
diff --git a/pass/Models/PasswordEntity.swift b/pass/Models/PasswordEntity.swift
index 61b6430..f65d1e7 100644
--- a/pass/Models/PasswordEntity.swift
+++ b/pass/Models/PasswordEntity.swift
@@ -35,7 +35,7 @@ extension PasswordEntity {
name = password.name
let plainData = password.getPlainData()
let pgp = PasswordStore.shared.pgp
- let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: false)
+ let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored])
return encryptedData
}
diff --git a/pass/Models/PasswordStore.swift b/pass/Models/PasswordStore.swift
index be28d0f..8c63aa7 100644
--- a/pass/Models/PasswordStore.swift
+++ b/pass/Models/PasswordStore.swift
@@ -136,6 +136,23 @@ class PasswordStore {
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
+
+ var numberOfPasswords : Int {
+ return self.fetchPasswordEntityCoreData(withDir: false).count
+ }
+
+ var sizeOfRepositoryByteCount : UInt64 {
+ let fm = FileManager.default
+ var size = UInt64(0)
+ do {
+ if fm.fileExists(atPath: self.storeURL.path) {
+ size = try fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)
+ }
+ } catch {
+ print(error)
+ }
+ return size
+ }
private init() {
@@ -285,6 +302,8 @@ class PasswordStore {
}
storeRepository = try GTRepository(url: storeURL)
gitCredential = credential
+ self.updatePasswordEntityCoreData()
+
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
}
@@ -298,12 +317,12 @@ class PasswordStore {
]
let remote = try GTRemote(name: "origin", in: storeRepository!)
try storeRepository?.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
+ self.setAllSynced()
+ self.updatePasswordEntityCoreData()
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
}
-
-
- func updatePasswordEntityCoreData() {
+ private func updatePasswordEntityCoreData() {
deleteCoreData(entityName: "PasswordEntity")
let fm = FileManager.default
do {
@@ -359,6 +378,9 @@ class PasswordStore {
}
func getRecentCommits(count: Int) -> [GTCommit] {
+ guard storeRepository != nil else {
+ return []
+ }
var commits = [GTCommit]()
do {
let enumerator = try GTEnumerator(repository: storeRepository!)
@@ -482,19 +504,17 @@ class PasswordStore {
return nil
}
- func createRemoveCommitInRepository(message: String, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? {
+ func createRemoveCommitInRepository(message: String, path: String) -> GTCommit? {
do {
- try storeRepository?.index().removeFile(filename)
+ try storeRepository?.index().removeFile(path)
try storeRepository?.index().write()
let newTree = try storeRepository!.index().writeTree()
let headReference = try storeRepository!.headReference()
let commitEnum = try GTEnumerator(repository: storeRepository!)
try commitEnum.pushSHA(headReference.targetOID.sha!)
let parent = commitEnum.nextObject() as! GTCommit
- progressBlock(0.5)
let signature = gitSignatureForNow
let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
- progressBlock(0.7)
return commit
} catch {
print(error)
@@ -565,6 +585,18 @@ class PasswordStore {
}
}
+ public func delete(passwordEntity: PasswordEntity) {
+ Utils.removeFileIfExists(at: storeURL.appendingPathComponent(passwordEntity.path!))
+ let _ = createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!)
+ context.delete(passwordEntity)
+ do {
+ try context.save()
+ } catch {
+ fatalError("Failed to delete a PasswordEntity: \(error)")
+ }
+ NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
+ }
+
func saveUpdated(passwordEntity: PasswordEntity) {
do {
try context.save()
@@ -634,6 +666,40 @@ class PasswordStore {
// return the number of discarded commits
func reset() throws -> Int {
+ // get a list of local commits
+ if let localCommits = try getLocalCommits(),
+ localCommits.count > 0 {
+ // get the oldest local commit
+ guard let firstLocalCommit = localCommits.last,
+ firstLocalCommit.parents.count == 1,
+ let newHead = firstLocalCommit.parents.first else {
+ throw NSError(domain: "me.mssun.pass.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot decide how to reset."])
+ }
+ try self.storeRepository?.reset(to: newHead, resetType: GTRepositoryResetType.hard)
+ self.setAllSynced()
+ self.updatePasswordEntityCoreData()
+ NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
+ NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
+ return localCommits.count
+ } else {
+ return 0 // no new commit
+ }
+ }
+
+ func numberOfLocalCommits() -> Int {
+ do {
+ if let localCommits = try getLocalCommits() {
+ return localCommits.count
+ } else {
+ return 0
+ }
+ } catch {
+ print(error)
+ }
+ return 0
+ }
+
+ private func getLocalCommits() throws -> [GTCommit]? {
// get the remote origin/master branch
guard let remoteBranches = try storeRepository?.remoteBranches(),
let index = remoteBranches.index(where: { $0.shortName == "master" })
@@ -644,22 +710,6 @@ class PasswordStore {
//print("remoteMasterBranch \(remoteMasterBranch)")
// get a list of local commits
- if let localCommits = try storeRepository?.localCommitsRelative(toRemoteBranch: remoteMasterBranch),
- localCommits.count > 0 {
- // get the oldest local commit
- guard let firstLocalCommit = localCommits.last,
- firstLocalCommit.parents.count == 1,
- let newHead = firstLocalCommit.parents.first else {
- throw NSError(domain: "me.mssun.pass.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot decide how to reset."])
- }
- try self.storeRepository?.reset(to: newHead, resetType: GTRepositoryResetType.hard)
- self.updatePasswordEntityCoreData()
- NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
- NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
- self.setAllSynced()
- return localCommits.count
- } else {
- return 0 // no new commit
- }
+ return try storeRepository?.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
}
}
diff --git a/pass/Views/ContentTableViewCell.swift b/pass/Views/ContentTableViewCell.swift
index a434dd2..d68dea4 100644
--- a/pass/Views/ContentTableViewCell.swift
+++ b/pass/Views/ContentTableViewCell.swift
@@ -23,6 +23,6 @@ class ContentTableViewCell: UITableViewCell {
return nil
}
- func setContent(content: String) { }
+ func setContent(content: String?) { }
}
diff --git a/pass/Views/FillPasswordTableViewCell.swift b/pass/Views/FillPasswordTableViewCell.swift
index 75dfb9f..e97edde 100644
--- a/pass/Views/FillPasswordTableViewCell.swift
+++ b/pass/Views/FillPasswordTableViewCell.swift
@@ -9,7 +9,7 @@
import UIKit
protocol FillPasswordTableViewCellDelegate {
- func generatePassword() -> String
+ func generateAndCopyPassword()
}
class FillPasswordTableViewCell: ContentTableViewCell {
@@ -20,6 +20,7 @@ class FillPasswordTableViewCell: ContentTableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
+ contentTextField.font = UIFont(name: Globals.passwordFonts, size: (contentTextField.font?.pointSize)!)
}
override func setSelected(_ selected: Bool, animated: Bool) {
@@ -29,16 +30,19 @@ class FillPasswordTableViewCell: ContentTableViewCell {
}
@IBAction func generatePassword(_ sender: UIButton) {
- let plainPassword = self.delegate?.generatePassword() ?? Utils.generatePassword(length: 16)
- contentTextField.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
- Utils.copyToPasteboard(textToCopy: plainPassword)
+ self.delegate?.generateAndCopyPassword()
+ }
+
+ // re-color
+ @IBAction func textFieldDidChange(_ sender: UITextField) {
+ contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
}
override func getContent() -> String? {
return contentTextField.attributedText?.string
}
- override func setContent(content: String) {
- contentTextField.attributedText = Utils.attributedPassword(plainPassword: content)
+ override func setContent(content: String?) {
+ contentTextField.attributedText = Utils.attributedPassword(plainPassword: content ?? "")
}
}
diff --git a/pass/Views/FillPasswordTableViewCell.xib b/pass/Views/FillPasswordTableViewCell.xib
index 0f6d8b2..c27362f 100644
--- a/pass/Views/FillPasswordTableViewCell.xib
+++ b/pass/Views/FillPasswordTableViewCell.xib
@@ -27,6 +27,9 @@
+
+
+
diff --git a/pass/Views/LabelTableViewCell.swift b/pass/Views/LabelTableViewCell.swift
index 242ca3b..726b535 100644
--- a/pass/Views/LabelTableViewCell.swift
+++ b/pass/Views/LabelTableViewCell.swift
@@ -19,15 +19,13 @@ class LabelTableViewCell: UITableViewCell {
@IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
- let passwordStore = PasswordStore.shared
var isPasswordCell = false
var isURLCell = false
var isReveal = false
var isHOTPCell = false
- let passwordDots = "••••••••••••"
- weak var passwordTableView : PasswordDetailTableViewController?
+ weak var delegatePasswordTableView : PasswordDetailTableViewController?
var cellData: LabelTableViewCellData? {
didSet {
@@ -36,14 +34,14 @@ class LabelTableViewCell: UITableViewCell {
if isReveal {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "")
} else {
- contentLabel.text = passwordDots
+ contentLabel.text = Globals.passwordDots
}
- contentLabel.font = UIFont(name: "Menlo", size: contentLabel.font.pointSize)
+ contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else if isHOTPCell {
if isReveal {
contentLabel.text = cellData?.content ?? ""
} else {
- contentLabel.text = passwordDots
+ contentLabel.text = Globals.passwordDots
}
} else {
contentLabel.text = cellData?.content
@@ -78,9 +76,9 @@ class LabelTableViewCell: UITableViewCell {
}
if isHOTPCell {
if isReveal {
- return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.nextPassword(_:))
+ return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
} else {
- return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.nextPassword(_:))
+ return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:))
}
}
return action == #selector(copy(_:))
@@ -104,48 +102,17 @@ class LabelTableViewCell: UITableViewCell {
}
func concealPassword(_ sender: Any?) {
- contentLabel.text = passwordDots
+ contentLabel.text = Globals.passwordDots
isReveal = false
}
- func nextPassword(_ sender: Any?) {
- guard let password = passwordTableView?.password,
- let passwordEntity = passwordTableView?.passwordEntity else {
- print("Cannot find password/passwordEntity of a cell")
- return;
- }
-
- // increase HOTP counter
- password.increaseHotpCounter()
-
- // only the HOTP password needs update
- if let plainPassword = password.otpToken?.currentPassword {
- cellData?.content = plainPassword
- // contentLabel will be updated automatically
- }
-
- // commit
- if password.changed {
- DispatchQueue.global(qos: .userInitiated).async {
- self.passwordStore.update(passwordEntity: passwordEntity, password: password, progressBlock: {_ in })
- DispatchQueue.main.async {
- passwordEntity.synced = false
- self.passwordStore.saveUpdated(passwordEntity: passwordEntity)
- // reload so that the "unsynced" symbol could be added
- self.passwordTableView?.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: UITableViewRowAnimation.automatic)
- SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
- SVProgressHUD.dismiss(withDelay: 1)
- }
- }
- }
+ func openLink(_ sender: Any?) {
+ // if isURLCell, passwordTableView should not be nil
+ delegatePasswordTableView!.openLink()
}
- func openLink(_ sender: Any?) {
- guard let password = passwordTableView?.password else {
- print("Cannot find password of a cell")
- return;
- }
- Utils.copyToPasteboard(textToCopy: password.password)
- UIApplication.shared.open(URL(string: cellData!.content)!, options: [:], completionHandler: nil)
+ func getNextHOTP(_ sender: Any?) {
+ // if isHOTPCell, passwordTableView should not be nil
+ delegatePasswordTableView!.getNextHOTP()
}
}
diff --git a/pass/Views/SliderTableViewCell.swift b/pass/Views/SliderTableViewCell.swift
index 7de2c9d..a641c44 100644
--- a/pass/Views/SliderTableViewCell.swift
+++ b/pass/Views/SliderTableViewCell.swift
@@ -9,15 +9,21 @@
import UIKit
+protocol PasswordSettingSliderTableViewCellDelegate {
+ func generateAndCopyPassword()
+}
+
class SliderTableViewCell: ContentTableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var valueLabel: UILabel!
@IBOutlet weak var slider: UISlider!
+ var delegate: UITableViewController?
+
var roundedValue: Int {
get {
- return Int(slider.value)
+ return Int(valueLabel.text!)!
}
}
@@ -33,9 +39,17 @@ class SliderTableViewCell: ContentTableViewCell {
}
@IBAction func handleSliderValueChange(_ sender: UISlider) {
- let roundedValue = round(sender.value)
- sender.value = roundedValue
- valueLabel.text = "\(Int(roundedValue))"
+ let oldRoundedValue = self.roundedValue
+ let newRoundedValue = Int(sender.value)
+ // proceed only when the rounded value gets updated
+ guard newRoundedValue != oldRoundedValue else {
+ return;
+ }
+ sender.value = Float(newRoundedValue)
+ valueLabel.text = "\(newRoundedValue)"
+ if let delegate: PasswordSettingSliderTableViewCellDelegate = self.delegate as? PasswordSettingSliderTableViewCellDelegate {
+ delegate.generateAndCopyPassword()
+ }
}
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) {
diff --git a/pass/Views/SliderTableViewCell.xib b/pass/Views/SliderTableViewCell.xib
index 2d1a911..09698e8 100644
--- a/pass/Views/SliderTableViewCell.xib
+++ b/pass/Views/SliderTableViewCell.xib
@@ -13,20 +13,20 @@
-
+
-
+
-
+
-
+
@@ -35,7 +35,7 @@
-
+
diff --git a/pass/Views/TextFieldTableViewCell.swift b/pass/Views/TextFieldTableViewCell.swift
index d558abf..1522d36 100644
--- a/pass/Views/TextFieldTableViewCell.swift
+++ b/pass/Views/TextFieldTableViewCell.swift
@@ -23,7 +23,7 @@ class TextFieldTableViewCell: ContentTableViewCell {
override func getContent() -> String? {
return contentTextField.text
}
- override func setContent(content: String) {
+ override func setContent(content: String?) {
contentTextField.text = content
}
}
diff --git a/pass/Views/TextViewTableViewCell.swift b/pass/Views/TextViewTableViewCell.swift
index bd54287..616d254 100644
--- a/pass/Views/TextViewTableViewCell.swift
+++ b/pass/Views/TextViewTableViewCell.swift
@@ -22,7 +22,7 @@ class TextViewTableViewCell: ContentTableViewCell {
return contentTextView.text
}
- override func setContent(content: String) {
+ override func setContent(content: String?) {
contentTextView.text = content
}
}
diff --git a/pass/Views/TextViewTableViewCell.xib b/pass/Views/TextViewTableViewCell.xib
index bfed387..83178bb 100644
--- a/pass/Views/TextViewTableViewCell.xib
+++ b/pass/Views/TextViewTableViewCell.xib
@@ -1,5 +1,5 @@
-
+
@@ -20,10 +20,10 @@
-
+
-
+
@@ -32,8 +32,8 @@
-
-
+
+