Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Implemented simple password generator.
Browse files Browse the repository at this point in the history
Added option to delete user\'s account in account settings.
  • Loading branch information
aeoliux committed Mar 1, 2024
1 parent 36d63a9 commit 56d854d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 13 deletions.
8 changes: 6 additions & 2 deletions LibrePass.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
B253F1E52B83976900D048F9 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = B253F1E32B83976900D048F9 /* LICENSE */; };
B253F1E62B83976900D048F9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = B253F1E42B83976900D048F9 /* README.md */; };
B2780F062B8A43C200835EE3 /* LibrePassRegistrationWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2780F052B8A43C200835EE3 /* LibrePassRegistrationWindow.swift */; };
B28808912B9254E300A526C6 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28808902B9254E300A526C6 /* PasswordGenerator.swift */; };
B28E06202B8BBD1900F48661 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28E061F2B8BBD1900F48661 /* Utils.swift */; };
B2D4447B2B87D0DF0031A685 /* Vault.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D4447A2B87D0DF0031A685 /* Vault.swift */; };
B2D91A1D2B8393C70004561A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B2D91A1C2B8393C70004561A /* Assets.xcassets */; };
Expand Down Expand Up @@ -51,6 +52,7 @@
B253F1E32B83976900D048F9 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
B253F1E42B83976900D048F9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
B2780F052B8A43C200835EE3 /* LibrePassRegistrationWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrePassRegistrationWindow.swift; sourceTree = "<group>"; };
B28808902B9254E300A526C6 /* PasswordGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = "<group>"; };
B28DFDBD2B8B6DC1007D5837 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
B28E061F2B8BBD1900F48661 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
B2D4447A2B87D0DF0031A685 /* Vault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vault.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -91,6 +93,7 @@
B2D4447A2B87D0DF0031A685 /* Vault.swift */,
B253F1D42B83942B00D048F9 /* Crypto.swift */,
B253F1D32B83942B00D048F9 /* ApiClient.swift */,
B28808902B9254E300A526C6 /* PasswordGenerator.swift */,
);
path = API;
sourceTree = "<group>";
Expand Down Expand Up @@ -281,6 +284,7 @@
buildActionMask = 2147483647;
files = (
B253F1E02B83942B00D048F9 /* LibrePassCipher.swift in Sources */,
B28808912B9254E300A526C6 /* PasswordGenerator.swift in Sources */,
B2DD90712B8D10D4003D84B7 /* NetworkMonitor.swift in Sources */,
B253F1DC2B83942B00D048F9 /* LibrePassAPI.swift in Sources */,
B253F1D92B83942B00D048F9 /* LibrePassLoginWindow.swift in Sources */,
Expand Down Expand Up @@ -445,7 +449,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.github.zapomnij.LibrePass;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down Expand Up @@ -484,7 +488,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.github.zapomnij.LibrePass;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down
18 changes: 18 additions & 0 deletions LibrePass/API/LibrePassAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,24 @@ struct LibrePassClient {
_ = try self.client.request(path: path, body: requestBody, method: "PATCH")
}

func deleteAccount(password: String) throws {
struct LibrePassDeleteAccountRequest: Codable {
var sharedKey: String
var code: String
}

let (oldPrivateData, oldPublicData, oldSharedData) = try self.getKeys(email: self.credentialsDatabase!.email, password: password, argon2options: self.credentialsDatabase!.argon2idParams)

if dataToHexString(data: oldPublicData) != self.credentialsDatabase!.publicKey {
throw LibrePassApiErrors.WithMessage(message: "Invalid credentials")
}

let request = LibrePassDeleteAccountRequest(sharedKey: dataToHexString(data: oldSharedData), code: "")
let requestBody = try JSONEncoder().encode(request)

_ = try self.client.request(path: "/api/user/delete", body: requestBody, method: "DELETE")
}

func generateId() -> String {
var uuid = UUID().uuidString.lowercased()
while self.vault.vault.firstIndex(where: { cipher in cipher.id == uuid }) != nil {
Expand Down
71 changes: 71 additions & 0 deletions LibrePass/API/PasswordGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// PasswordGenerator.swift
// LibrePass
//
// Created by Zapomnij on 01/03/2024.
//

import Foundation

func generatePassword(length: Int) throws -> String {
if length < 8 {
throw LibrePassApiErrors.WithMessage(message: "Password length must be longer or equal to 8")
}

let nOfNumbers = length / 4
let nOfSpecial = length / 4
let nOfLowercased = length / 4
let nOfUppercased = length - nOfSpecial - nOfLowercased - nOfNumbers

var password = [Character?](repeating: nil, count: length)

func fill(from: Int, to: Int, times: Int) {
var filled = 0
while filled < times {
let char = Character(UnicodeScalar(Int.random(in: from...to))!)

if password.first(where: { ch in ch == char }) == nil {
while true {
let index = Int.random(in: 0...length - 1)
if password[index] == nil {
password[index] = char
filled += 1
break
}
}
}
}
}

for _ in stride(from: 0, to: nOfSpecial, by: 1) {
switch Int.random(in: 0...3) {
case 0:
fill(from: 32, to: 47, times: 1)
break
case 1:
fill(from: 58, to: 64, times: 1)
break
case 2:
fill(from: 91, to: 96, times: 1)
break
case 3:
fill(from: 123, to: 126, times: 1)
break
default:
print("This won't ever happen")
}
}

fill(from: 48, to: 57, times: nOfNumbers)
fill(from: 97, to: 122, times: nOfLowercased)
fill(from: 65, to: 90, times: nOfUppercased)

var string = ""
password.forEach {
if let char = $0 {
string += String(char)
}
}

return string
}
24 changes: 13 additions & 11 deletions LibrePass/LibrePassAccountSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ struct LibrePassAccountSettings: View {
@State var newPasswordHint = String()

@State var done = false
@State var logOut = false

var body: some View {
List {
Expand All @@ -41,24 +40,16 @@ struct LibrePassAccountSettings: View {
SecureField("Current password", text: self.$password)
.autocapitalization(.none)
ButtonWithSpinningWheel(text: "Update credentials", task: self.updateCredentials)
ButtonWithSpinningWheel(text: "Delete account", task: self.deleteAccount, color: Color.red)
}

Section {
Button("Log out", role: .destructive) {
self.logOut = true
self.logOut()
}
}
}

.alert("Are you sure you want to log out?", isPresented: self.$logOut) {
Button("Yes", role: .destructive) {
self.lClient.logOut()
self.locallyLoggedIn = false
self.loggedIn = false
}
Button("No", role: .cancel) {}
}

.alert("Operation is finished. You'll be logged out. If you've changed email address, check your mailbox and verify email address", isPresented: self.$done) {
Button("OK") {
self.locallyLoggedIn = false
Expand All @@ -71,6 +62,17 @@ struct LibrePassAccountSettings: View {
}
}

func deleteAccount() throws {
try self.lClient.deleteAccount(password: self.password)
self.logOut()
}

func logOut() {
self.locallyLoggedIn = false
self.loggedIn = false
self.lClient.logOut()
}

func updateCredentials() throws {
let (oldPrivateData, oldPublicData, oldSharedData) = try self.lClient.getKeys(email: self.lClient.credentialsDatabase!.email, password: self.password, argon2options: self.lClient.credentialsDatabase!.argon2idParams)

Expand Down
15 changes: 15 additions & 0 deletions LibrePass/LibrePassCipherView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ struct CipherLoginDataView: View {
@State var uris: [String] = []
@State var notes = String()

@State var passwordLength = 0
@State var generatePasswordAlert = false

var body: some View {
List {
Section(header: Text("Login data")) {
TextField("Name", text: $name)
TextFieldWithCopyButton(text: "Username", textBind: self.$username)
SecureFieldWithCopyAndShowButton(text: "Password", textBind: self.$password)
Button("Generate random password") { self.generatePasswordAlert = true }
}

Section(header: Text("URIs")) {
Expand All @@ -78,6 +82,17 @@ struct CipherLoginDataView: View {
}
}

.alert("Length of password (must be longer or equal to 8)", isPresented: self.$generatePasswordAlert) {
TextField("Length", value: self.$passwordLength, formatter: NumberFormatter())
Button("Generate") {
if let password = try? generatePassword(length: self.passwordLength) {
self.password = password
}
}

Button("Cancel", role: .cancel) {}
}

.onAppear {
self.name = self.cipher.loginData!.name
self.username = self.cipher.loginData!.username ?? ""
Expand Down

0 comments on commit 56d854d

Please sign in to comment.