Skip to content

Commit

Permalink
Password protected entry point for debug menu (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
AZielinsky95 authored Dec 8, 2021
1 parent fbf65ad commit 9501e2c
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 8 deletions.
File renamed without changes.
105 changes: 105 additions & 0 deletions DebugMenu/DebugMenu/DebugPasswordAlertWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// File.swift
//
//
// Created by Alejandro Zielinsky on 2021-12-06.
//

import Foundation
import UIKit
import SwiftUI

struct DebugPasswordAlertWrapper<Content: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let alert: DebugPasswordAlert
let content: Content

func makeUIViewController(context: UIViewControllerRepresentableContext<DebugPasswordAlertWrapper>) -> UIHostingController<Content> {
UIHostingController(rootView: content)
}

final class Coordinator {
var alertController: UIAlertController?
init(_ controller: UIAlertController? = nil) {
self.alertController = controller
}
}

func makeCoordinator() -> Coordinator {
return Coordinator()
}

func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<DebugPasswordAlertWrapper>) {
uiViewController.rootView = content
if isPresented && uiViewController.presentedViewController == nil {
var alert = self.alert
alert.action = {
self.isPresented = false
self.alert.action($0)
}
context.coordinator.alertController = UIAlertController(alert: alert)
uiViewController.present(context.coordinator.alertController!, animated: true)
}
if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController {
uiViewController.dismiss(animated: true)
}
}
}

struct DebugPasswordAlert {

var title: String
var message: String
var placeholder: String
var accept: String
var cancel: String?
var secondaryActionTitle: String?
var keyboardType: UIKeyboardType
var action: (String?) -> Void
var secondaryAction: (() -> Void)?

init(title: String = "Debug Settings",
message: String = "Enter Password",
placeholder: String = "",
accept: String = "OK",
cancel: String? = "Cancel",
secondaryActionTitle: String? = nil,
keyboardType: UIKeyboardType = .default,
action: @escaping (String?) -> Void,
secondaryAction: (() -> Void)? = nil) {
self.title = title
self.message = message
self.placeholder = placeholder
self.accept = accept
self.cancel = cancel
self.secondaryActionTitle = secondaryActionTitle
self.keyboardType = keyboardType
self.action = action
self.secondaryAction = secondaryAction
}
}

extension UIAlertController {
convenience init(alert: DebugPasswordAlert) {
self.init(title: alert.title, message: alert.message, preferredStyle: .alert)
addTextField {
$0.placeholder = alert.placeholder
$0.keyboardType = alert.keyboardType
}
if let cancel = alert.cancel {
addAction(UIAlertAction(title: cancel, style: .cancel) { _ in
alert.action(nil)
})
}
if let secondaryActionTitle = alert.secondaryActionTitle {
addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in
alert.secondaryAction?()
}))
}
let textField = self.textFields?.first
addAction(UIAlertAction(title: alert.accept, style: .default) { _ in
alert.action(textField?.text)
})
}
}

106 changes: 106 additions & 0 deletions DebugMenu/DebugMenu/DebugPasswordEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// DebugPasswordEntry.swift
//
//
// Created by Alejandro Zielinsky on 2021-12-06.
//

import Foundation
import SwiftUI
import CommonCrypto

struct DebugPasswordEntry: ViewModifier {

private let debugDataSource: DebugMenuDataSource
private let longPressDuration: CGFloat
private let passwordHash: String
@State private var showDialog = false
@State private var showDebugMenu = false
private let forceShow: Binding<Bool>?

init(dataSource: DebugMenuDataSource,
passwordSHA256: String,
longPressDuration: CGFloat,
forceShow: Binding<Bool>? = nil) {
self.passwordHash = passwordSHA256
self.longPressDuration = longPressDuration
self.debugDataSource = dataSource
self.forceShow = forceShow
}

func body(content: Content) -> some View {
content
.onLongPressGesture(minimumDuration: longPressDuration) {
if let forceShow = forceShow?.wrappedValue, forceShow == true {
showDebugMenu = true
} else {
showDialog = true
}
}
.debugPasswordAlert(isPresented: $showDialog, DebugPasswordAlert(action: self.onPasswordEntered))
.sheet(isPresented: $showDebugMenu) {
DebugMenuView(dataSource: debugDataSource)
}
}

private func onPasswordEntered(input: String?) {
guard let input = input else { return }
if input.sha256 == self.passwordHash {
showDialog = false
showDebugMenu = true
forceShow?.wrappedValue = true
} else {
showDialog = false
}
}
}

public extension View {
func debugMenuNavigation(dataSource: DebugMenuDataSource,
passwordSHA256: String,
longPressDuration: CGFloat = 5.0,
forceShow: Binding<Bool>? = nil) -> some View {
modifier(DebugPasswordEntry(dataSource: dataSource,
passwordSHA256: passwordSHA256,
longPressDuration: longPressDuration,
forceShow: forceShow))
}
}

private extension Data {

var sha256: String {
hexStringFromData(input: digest(input: self as NSData))
}

private func digest(input: NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}

private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)

var hexString = ""
for byte in bytes {
hexString += String(format: "%02x", UInt8(byte))
}

return hexString
}
}

private extension String {
var sha256: String {
self.data(using: String.Encoding.utf8)?.sha256 ?? ""
}
}

extension View {
func debugPasswordAlert(isPresented: Binding<Bool>, _ alert: DebugPasswordAlert) -> some View {
DebugPasswordAlertWrapper(isPresented: isPresented, alert: alert, content: self)
}
}
2 changes: 1 addition & 1 deletion DebugMenu/DebugMenuExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "file:///Users/alejandrozielinsky/iOSProjects/debug-menu-ios";
requirement = {
branch = "az/1-datasource";
branch = "az/password-protection";
kind = branch;
};
};
Expand Down
13 changes: 6 additions & 7 deletions DebugMenu/DebugMenuExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import DebugMenu

struct ContentView: View {

@State var showDebugMenu = false
@SceneStorage("bypassDebugPasswordEntry") var bypassDebugPasswordEntry = false

var body: some View {
Button("Debug Menu Entry Point") {
showDebugMenu = true
}
.sheet(isPresented: $showDebugMenu) {
DebugMenuView(dataSource: DebugMenuStore.shared)
}
Text("Debug Hidden Entry")
.debugMenuNavigation(dataSource: DebugMenuStore.shared,
passwordSHA256: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
longPressDuration: 2.0,
forceShow: $bypassDebugPasswordEntry)
}
}

Expand Down

0 comments on commit 9501e2c

Please sign in to comment.