Generate XKCD style password
This commit is contained in:
parent
71c793029a
commit
fe21f1c8da
7 changed files with 7866 additions and 24 deletions
|
|
@ -163,7 +163,6 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
|
|
||||||
PasswordGeneratorFlavor.allCases.forEach { flavor in
|
PasswordGeneratorFlavor.allCases.forEach { flavor in
|
||||||
let actionTitlePrefix = Defaults.passwordGeneratorFlavor
|
|
||||||
var actionTitle = flavor.longNameLocalized
|
var actionTitle = flavor.longNameLocalized
|
||||||
if Defaults.passwordGeneratorFlavor == flavor {
|
if Defaults.passwordGeneratorFlavor == flavor {
|
||||||
actionTitle = "✓ " + actionTitle
|
actionTitle = "✓ " + actionTitle
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
||||||
["SwiftyUserDefaults",
|
["SwiftyUserDefaults",
|
||||||
"https://github.com/radex/SwiftyUserDefaults",
|
"https://github.com/radex/SwiftyUserDefaults",
|
||||||
"https://github.com/radex/SwiftyUserDefaults/blob/master/LICENSE"],
|
"https://github.com/radex/SwiftyUserDefaults/blob/master/LICENSE"],
|
||||||
|
["EFF's Wordlists",
|
||||||
|
"https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases",
|
||||||
|
"http://creativecommons.org/licenses/by/3.0"],
|
||||||
]
|
]
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import OneTimePassword
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
enum PasswordEditorCellType {
|
enum PasswordEditorCellType {
|
||||||
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell, memorablePasswordGeneratorCell
|
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell, passwordFlavorCell
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PasswordEditorCellKey {
|
enum PasswordEditorCellKey {
|
||||||
|
|
@ -41,7 +41,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
var additionsCell: TextViewTableViewCell?
|
var additionsCell: TextViewTableViewCell?
|
||||||
private var deletePasswordCell: UITableViewCell?
|
private var deletePasswordCell: UITableViewCell?
|
||||||
private var scanQRCodeCell: UITableViewCell?
|
private var scanQRCodeCell: UITableViewCell?
|
||||||
private var memorablePasswordGeneratorCell: UITableViewCell?
|
private var passwordFlavorCell: UITableViewCell?
|
||||||
|
|
||||||
var plainText: String {
|
var plainText: String {
|
||||||
var plainText = (fillPasswordCell?.getContent())!
|
var plainText = (fillPasswordCell?.getContent())!
|
||||||
|
|
@ -69,14 +69,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
scanQRCodeCell?.selectionStyle = .default
|
scanQRCodeCell?.selectionStyle = .default
|
||||||
scanQRCodeCell?.accessoryType = .disclosureIndicator
|
scanQRCodeCell?.accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
memorablePasswordGeneratorCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
passwordFlavorCell = UITableViewCell(style: .value1, reuseIdentifier: "default")
|
||||||
memorablePasswordGeneratorCell?.textLabel?.text = "GetMemorableOne".localize()
|
passwordFlavorCell?.textLabel?.text = "PasswordGeneratorFlavor".localize()
|
||||||
memorablePasswordGeneratorCell?.textLabel?.textColor = Colors.systemBlue
|
passwordFlavorCell?.textLabel?.textColor = Colors.systemBlue
|
||||||
memorablePasswordGeneratorCell?.selectionStyle = .default
|
passwordFlavorCell?.selectionStyle = .none
|
||||||
memorablePasswordGeneratorCell?.accessoryType = .disclosureIndicator
|
passwordFlavorCell?.accessoryType = .disclosureIndicator
|
||||||
|
passwordFlavorCell?.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
print("viewDidLoad")
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
if navigationItemTitle != nil {
|
if navigationItemTitle != nil {
|
||||||
navigationItem.title = navigationItemTitle
|
navigationItem.title = navigationItemTitle
|
||||||
|
|
@ -94,15 +96,15 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
|
|
||||||
tableData = [
|
tableData = [
|
||||||
[[.type: PasswordEditorCellType.nameCell, .title: "Name".localize(), .content: password?.namePath ?? ""]],
|
[[.type: PasswordEditorCellType.nameCell, .title: "Name".localize(), .content: password?.namePath ?? ""]],
|
||||||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "Password".localize(), .content: password?.password ?? ""]],
|
[
|
||||||
|
[.type: PasswordEditorCellType.fillPasswordCell, .title: "Password".localize(), .content: password?.password ?? ""]],
|
||||||
[[.type: PasswordEditorCellType.additionsCell, .title: "Additions".localize(), .content: password?.additionsPlainText ?? ""]],
|
[[.type: PasswordEditorCellType.additionsCell, .title: "Additions".localize(), .content: password?.additionsPlainText ?? ""]],
|
||||||
[[.type: PasswordEditorCellType.scanQRCodeCell],
|
[[.type: PasswordEditorCellType.scanQRCodeCell],
|
||||||
[.type: PasswordEditorCellType.deletePasswordCell]]
|
[.type: PasswordEditorCellType.deletePasswordCell]]
|
||||||
]
|
]
|
||||||
if Defaults.passwordGeneratorFlavor == .random {
|
|
||||||
tableData[1].append([.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"])
|
tableData[1].append([.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"])
|
||||||
}
|
tableData[1].append([.type: PasswordEditorCellType.passwordFlavorCell])
|
||||||
tableData[1].append([.type: PasswordEditorCellType.memorablePasswordGeneratorCell])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
|
@ -144,8 +146,8 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
defaultValue: defaultLength)
|
defaultValue: defaultLength)
|
||||||
passwordLengthCell?.delegate = self
|
passwordLengthCell?.delegate = self
|
||||||
return passwordLengthCell!
|
return passwordLengthCell!
|
||||||
case .memorablePasswordGeneratorCell:
|
case .passwordFlavorCell:
|
||||||
return memorablePasswordGeneratorCell!
|
return passwordFlavorCell!
|
||||||
case .additionsCell:
|
case .additionsCell:
|
||||||
additionsCell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as?TextViewTableViewCell
|
additionsCell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as?TextViewTableViewCell
|
||||||
additionsCell?.contentTextView.delegate = self
|
additionsCell?.contentTextView.delegate = self
|
||||||
|
|
@ -186,6 +188,8 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let selectedCell = tableView.cellForRow(at: indexPath)
|
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||||
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
if selectedCell == deletePasswordCell {
|
if selectedCell == deletePasswordCell {
|
||||||
let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert)
|
let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert)
|
||||||
alert.addAction(UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in
|
alert.addAction(UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in
|
||||||
|
|
@ -195,16 +199,32 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
} else if selectedCell == scanQRCodeCell {
|
} else if selectedCell == scanQRCodeCell {
|
||||||
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self)
|
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self)
|
||||||
} else if selectedCell == memorablePasswordGeneratorCell {
|
} else if selectedCell == passwordFlavorCell {
|
||||||
// open the webpage
|
showPasswordGeneratorFlavorActionSheet(sourceCell: selectedCell!, tableView: tableView)
|
||||||
if let url = URL(string: "https://xkpasswd.net/") {
|
|
||||||
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
|
|
||||||
vc.delegate = self
|
|
||||||
present(vc, animated: true)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
}
|
||||||
|
|
||||||
|
func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell, tableView: UITableView) {
|
||||||
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
|
|
||||||
|
PasswordGeneratorFlavor.allCases.forEach { flavor in
|
||||||
|
var actionTitle = flavor.longNameLocalized
|
||||||
|
if Defaults.passwordGeneratorFlavor == flavor {
|
||||||
|
actionTitle = "✓ " + actionTitle
|
||||||
|
}
|
||||||
|
let action = UIAlertAction(title: actionTitle, style: .default) { _ in
|
||||||
|
Defaults.passwordGeneratorFlavor = flavor
|
||||||
|
sourceCell.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized
|
||||||
|
tableView.reloadSections([self.passwordSection], with: .none)
|
||||||
|
}
|
||||||
|
optionMenu.addAction(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelAction = UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: nil)
|
||||||
|
optionMenu.addAction(cancelAction)
|
||||||
|
optionMenu.popoverPresentationController?.sourceView = sourceCell
|
||||||
|
|
||||||
|
self.present(optionMenu, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate password, copy to pasteboard, and set the cell
|
// generate password, copy to pasteboard, and set the cell
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@
|
||||||
"Random" = "Random";
|
"Random" = "Random";
|
||||||
"RandomString" = "Random String";
|
"RandomString" = "Random String";
|
||||||
"ApplesKeychainStyle" = "Apple's Keychain Style";
|
"ApplesKeychainStyle" = "Apple's Keychain Style";
|
||||||
|
"XKCD" = "XKCD";
|
||||||
|
"XKCDStyle" = "XKCD Style";
|
||||||
|
|
||||||
// Git
|
// Git
|
||||||
"FailedToFetchPasswords" = "Failed to fetch passwords";
|
"FailedToFetchPasswords" = "Failed to fetch passwords";
|
||||||
|
|
|
||||||
13
passKit/Assets.xcassets/WordLists.dataset/Contents.json
Normal file
13
passKit/Assets.xcassets/WordLists.dataset/Contents.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
|
"data" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "EN_wordlist.txt",
|
||||||
|
"universal-type-identifier" : "public.plain-text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7776
passKit/Assets.xcassets/WordLists.dataset/EN_wordlist.txt
Normal file
7776
passKit/Assets.xcassets/WordLists.dataset/EN_wordlist.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,7 @@ import KeychainAccess
|
||||||
public enum PasswordGeneratorFlavor: String {
|
public enum PasswordGeneratorFlavor: String {
|
||||||
case apple = "Apple"
|
case apple = "Apple"
|
||||||
case random = "Random"
|
case random = "Random"
|
||||||
|
case xkcd = "XKCD"
|
||||||
|
|
||||||
public var localized: String {
|
public var localized: String {
|
||||||
return rawValue.localize()
|
return rawValue.localize()
|
||||||
|
|
@ -22,6 +23,8 @@ public enum PasswordGeneratorFlavor: String {
|
||||||
return "ApplesKeychainStyle".localize()
|
return "ApplesKeychainStyle".localize()
|
||||||
case .random:
|
case .random:
|
||||||
return "RandomString".localize()
|
return "RandomString".localize()
|
||||||
|
case .xkcd:
|
||||||
|
return "XKCDStyle".localize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,6 +34,8 @@ public enum PasswordGeneratorFlavor: String {
|
||||||
return (15, 15, 15)
|
return (15, 15, 15)
|
||||||
case .random:
|
case .random:
|
||||||
return (4, 64, 16)
|
return (4, 64, 16)
|
||||||
|
case .xkcd:
|
||||||
|
return (2, 5, 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +45,8 @@ public enum PasswordGeneratorFlavor: String {
|
||||||
return Keychain.generatePassword()
|
return Keychain.generatePassword()
|
||||||
case .random:
|
case .random:
|
||||||
return PasswordGeneratorFlavor.generateRandom(length: length)
|
return PasswordGeneratorFlavor.generateRandom(length: length)
|
||||||
|
case .xkcd:
|
||||||
|
return PasswordGeneratorFlavor.generateXKCD(length: length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +54,28 @@ public enum PasswordGeneratorFlavor: String {
|
||||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
||||||
return String((0..<length).map { _ in chars.randomElement()! })
|
return String((0..<length).map { _ in chars.randomElement()! })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func generateXKCD(length: Int) -> String {
|
||||||
|
// Get the word list
|
||||||
|
let bundle = Bundle(identifier: Globals.passKitBundleIdentifier)!
|
||||||
|
guard let asset = NSDataAsset(name: "WordLists", bundle: bundle),
|
||||||
|
let data = String(data: asset.data, encoding: .utf8) else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
let words = data.splitByNewline()
|
||||||
|
|
||||||
|
// Generate a password
|
||||||
|
let delimiters = "0123456789!@#$%^&*_+-="
|
||||||
|
var password = ""
|
||||||
|
(0..<length).forEach { _ in
|
||||||
|
var word = words.randomElement()!
|
||||||
|
if Bool.random() {
|
||||||
|
word = word.uppercased()
|
||||||
|
}
|
||||||
|
password += word + String(delimiters.randomElement()!)
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PasswordGeneratorFlavor: CaseIterable {}
|
extension PasswordGeneratorFlavor: CaseIterable {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue