Skip to content

Commit

Permalink
App is now more asynchronical
Browse files Browse the repository at this point in the history
OATH: added base32 error handler, app will not crash
LibrePassCipherView: added alert which indicates a bad 2FA configuration, added copy button for copying TOTP one time password
LibrePassManagerWindow: some tasks have been made asynchronical
  • Loading branch information
aeoliux committed Mar 17, 2024
1 parent ae3cff4 commit 2d89990
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 91 deletions.
9 changes: 6 additions & 3 deletions LibrePass/API/OATH.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct OATHParams {
var period: Int = 30
var counter: Int = 0

init(uri: String) {
init(uri: String) throws {
self.uri = uri

if uri.starts(with: "otpauth://totp/") {
Expand All @@ -74,13 +74,16 @@ struct OATHParams {

let uriSplit = uri.components(separatedBy: "?")[1].components(separatedBy: "&")

uriSplit.forEach { keyVal in
for keyVal in uriSplit {
let split = keyVal.components(separatedBy: "=")
let key = split[0], val = split[1]

switch key {
case "secret":
self.secret = Data(base32Decode(val)!)
guard let secret = base32Decode(val) else {
throw LibrePassApiErrors.WithMessage(message: "Bad 2FA secret")
}
self.secret = Data(secret)
break
case "algorithm":
self.algorithm = val.toOTPAlgorithm()
Expand Down
148 changes: 73 additions & 75 deletions LibrePass/LibrePassCipherView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ struct CipherLoginDataView: View {
Section(header: Text("Two factor")) {
if let _ = self.twoFactorUri {
HStack {
Button(action: {
UIPasteboard.general.string = self.oneTimePassword
}, label: {
Image(systemName: "doc.on.doc")
})
.buttonStyle(.plain)
Text(self.oneTimePassword)
Spacer()
Text(String(self.timeLeft))
Expand All @@ -102,6 +108,12 @@ struct CipherLoginDataView: View {
stop = running
self.twoFactorUri = nil
}

.alert("Incorrect 2FA configuration", isPresented: self.$twoFactorError) {
Button("OK", role: .cancel) {
self.twoFactorUri = nil
}
}
} else {
Button("Set up 2FA") {
self.editTwoFactor = true
Expand Down Expand Up @@ -142,44 +154,59 @@ struct CipherLoginDataView: View {
runAuthenticatorJob()
}) {
List {
TextField("Secret", text: self.$twoFactorSecret)
Picker("Type", selection: self.$twoFactorType) {
Text("TOTP").tag(OATHParams.OATHType.TOTP)
Text("HOTP").tag(OATHParams.OATHType.HOTP)
}
TextField("Digits", value: self.$twoFactorDigits, formatter: NumberFormatter())
if self.twoFactorType == .TOTP {
TextField("Period", value: self.$twoFactorPeriod, formatter: NumberFormatter())
} else {
TextField("Counter", value: self.$twoFactorCounter, formatter: NumberFormatter())
}

Button("Apply") {
var str = "otpauth://" + self.twoFactorType.toString()
str += "/randomlabel?secret=" + self.twoFactorSecret
str += "&algorithm=" + self.twoFactorAlgorithm.toString()
str += "&digits=" + String(self.twoFactorDigits)

Section(header: Text("Manual configuration")) {
TextField("Secret", text: self.$twoFactorSecret)
Picker("Type", selection: self.$twoFactorType) {
Text("TOTP").tag(OATHParams.OATHType.TOTP)
}
TextField("Digits", value: self.$twoFactorDigits, formatter: NumberFormatter())
if self.twoFactorType == .TOTP {
str += "&period=" + String(self.twoFactorPeriod)
TextField("Period", value: self.$twoFactorPeriod, formatter: NumberFormatter())
} else {
str += "&counter=" + String(self.twoFactorCounter)
TextField("Counter", value: self.$twoFactorCounter, formatter: NumberFormatter())
}

self.twoFactorUri = str

self.editTwoFactor = false
Button("Apply") {
let split = self.twoFactorSecret.components(separatedBy: " ")
if split.count > 0 {
self.twoFactorSecret = ""
split.forEach {
if $0 != "" {
self.twoFactorSecret += $0
}
}
}

var str = "otpauth://" + self.twoFactorType.toString()
str += "/randomlabel?secret=" + self.twoFactorSecret
str += "&algorithm=" + self.twoFactorAlgorithm.toString()
str += "&digits=" + String(self.twoFactorDigits)

if self.twoFactorType == .TOTP {
str += "&period=" + String(self.twoFactorPeriod)
} else {
str += "&counter=" + String(self.twoFactorCounter)
}

self.twoFactorUri = str

self.editTwoFactor = false
}
}
}
.onAppear {
if let twoFactorUri = self.twoFactorUri {
let params = OATHParams(uri: twoFactorUri)
self.twoFactorType = params.type
self.twoFactorAlgorithm = params.algorithm
self.twoFactorSecret = base32Encode(params.secret)
self.twoFactorDigits = params.digits
self.twoFactorPeriod = params.period
self.twoFactorCounter = params.counter
do {
if let twoFactorUri = self.twoFactorUri {
let params = try OATHParams(uri: twoFactorUri)
self.twoFactorType = params.type
self.twoFactorAlgorithm = params.algorithm
self.twoFactorSecret = base32Encode(params.secret)
self.twoFactorDigits = params.digits
self.twoFactorPeriod = params.period
self.twoFactorCounter = params.counter
}
} catch {
self.twoFactorError = true
}
}
}
Expand All @@ -191,21 +218,26 @@ struct CipherLoginDataView: View {
@State var twoFactorDigits = 6
@State var twoFactorPeriod = 30
@State var twoFactorCounter = 0
@State var twoFactorError = false

func runAuthenticatorJob() {
Task {
if let twoFactorUri = self.twoFactorUri {
let engine = OATHParams(uri: twoFactorUri)
switch engine.type {
case .TOTP:
await engine.runTOTPCounter { oneTimePassword, timeLeft in
self.oneTimePassword = oneTimePassword
self.timeLeft = timeLeft
do {
if let twoFactorUri = self.twoFactorUri {
let engine = try OATHParams(uri: twoFactorUri)
switch engine.type {
case .TOTP:
await engine.runTOTPCounter { oneTimePassword, timeLeft in
self.oneTimePassword = oneTimePassword
self.timeLeft = timeLeft
}
break
case .HOTP:
break
}
break
case .HOTP:
break
}
} catch {
self.twoFactorError = true
}
}
}
Expand Down Expand Up @@ -354,37 +386,3 @@ struct CipherButton: View {
}
}
}

struct CipherDeleteButton: View {
@Environment(\.presentationMode) var presentationMode

@Binding var lClient: LibrePassClient
var id: String

@State var areYouSure = false
@State var errorString = String()
@State var showAlert = false

var body: some View {
Button(action: {
self.areYouSure = true
}) {
Image(systemName: "trash")
}
.foregroundColor(Color.red)

.alert("Are you sure you want to delete this cipher?", isPresented: $areYouSure) {
Button("Yes", role: .destructive) {
_ = try? lClient.delete(id: id)

self.presentationMode.wrappedValue.dismiss()
}

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

.alert(self.errorString, isPresented: $showAlert) {
Button("OK", role: .cancel) {}
}
}
}
30 changes: 17 additions & 13 deletions LibrePass/LibrePassManagerWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,27 @@ struct LibrePassManagerWindow: View {
}

func deleteCiphers() throws {
for index in self.toDelete {
try self.lClient.delete(id: self.lClient.vault.vault[index].id)
Task {
for index in self.toDelete {
try self.lClient.delete(id: self.lClient.vault.vault[index].id)
}
}
}

func newCipher(type: LibrePassCipher.CipherType) {
let cipher = LibrePassCipher(id: lClient.generateId(), owner: lClient.credentialsDatabase!.userId, type: type)

do {
try self.lClient.put(cipher: cipher)
try self.syncVault()
} catch ApiClientErrors.StatusCodeNot200(let statusCode, let body){
self.errorString = String(statusCode) + ": " + body.error
self.showAlert = true
} catch {
self.errorString = error.localizedDescription
self.showAlert = true
Task {
let cipher = LibrePassCipher(id: lClient.generateId(), owner: lClient.credentialsDatabase!.userId, type: type)

do {
try self.lClient.put(cipher: cipher)
try self.syncVault()
} catch ApiClientErrors.StatusCodeNot200(let statusCode, let body){
self.errorString = String(statusCode) + ": " + body.error
self.showAlert = true
} catch {
self.errorString = error.localizedDescription
self.showAlert = true
}
}
}
}

0 comments on commit 2d89990

Please sign in to comment.