Skip to content

Commit

Permalink
Add iOS example
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Nov 20, 2023
1 parent 2fa661a commit bef2db4
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 161 deletions.
12 changes: 11 additions & 1 deletion languages/swift/iOS/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F2B04F2B0511C700E1E91C /* Biometrics.swift */; };
55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153D2A8678B300BE93F4 /* testApp.swift */; };
55B615402A8678B300BE93F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B6153F2A8678B300BE93F4 /* ContentView.swift */; };
55B615422A8678B400BE93F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55B615412A8678B400BE93F4 /* Assets.xcassets */; };
Expand All @@ -15,6 +16,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
33F2B04F2B0511C700E1E91C /* Biometrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Biometrics.swift; sourceTree = "<group>"; };
55B6153A2A8678B300BE93F4 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
55B6153D2A8678B300BE93F4 /* testApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testApp.swift; sourceTree = "<group>"; };
55B6153F2A8678B300BE93F4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -60,6 +62,7 @@
55B6153F2A8678B300BE93F4 /* ContentView.swift */,
55B615412A8678B400BE93F4 /* Assets.xcassets */,
55B615432A8678B400BE93F4 /* Preview Content */,
33F2B04F2B0511C700E1E91C /* Biometrics.swift */,
);
path = App;
sourceTree = "<group>";
Expand Down Expand Up @@ -118,7 +121,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1420;
LastUpgradeCheck = 1500;
TargetAttributes = {
55B615392A8678B300BE93F4 = {
CreatedOnToolsVersion = 14.2;
Expand Down Expand Up @@ -160,6 +163,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33F2B0502B0511C700E1E91C /* Biometrics.swift in Sources */,
55B615402A8678B300BE93F4 /* ContentView.swift in Sources */,
55B6153E2A8678B300BE93F4 /* testApp.swift in Sources */,
);
Expand All @@ -172,6 +176,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -204,6 +209,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -232,6 +238,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -264,6 +271,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down Expand Up @@ -293,6 +301,7 @@
DEVELOPMENT_TEAM = LTZ2PFU5D6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down Expand Up @@ -323,6 +332,7 @@
DEVELOPMENT_TEAM = LTZ2PFU5D6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSFaceIDUsageDescription = "Unlock vault with biometrics";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
85 changes: 85 additions & 0 deletions languages/swift/iOS/App/Biometrics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Biometrics.swift
// App
//
// Created by Dani on 15/11/23.
//


import Foundation
import LocalAuthentication

let SERVICE: String = "com.example.app"

// We should separate keys for each user by appending the user_id
let KEY: String = "biometric_key"


func biometricStoreValue(value: String) {
var error: Unmanaged<CFError>?
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
&error)

guard accessControl != nil && error == nil else {
fatalError("SecAccessControlCreateWithFlags failed")
}

let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: SERVICE,
kSecAttrAccount: KEY,
kSecValueData: value.data(using: .utf8)!,
kSecAttrAccessControl: accessControl as Any
] as CFDictionary

// Try to delete the previous secret, if it exists
// Otherwise we get `errSecDuplicateItem`
SecItemDelete(query)

let status = SecItemAdd(query, nil)
guard status == errSecSuccess else {
fatalError("Unable to store the secret: " + errToString(status: status))
}
}

private func errToString(status: OSStatus) -> String {
if let err = SecCopyErrorMessageString(status, nil) as String? {
err
} else {
"Unknown error"
}
}

func biometricRetrieveValue() -> String? {
let searchQuery = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: SERVICE,
kSecAttrAccount: KEY,
kSecMatchLimit: kSecMatchLimitOne,
kSecReturnData: true,
kSecReturnAttributes: true,
] as CFDictionary

var item: AnyObject?
let status = SecItemCopyMatching(searchQuery, &item)

// If the item is not found, we just return nil
if status == errSecItemNotFound {
return nil
}

// TODO: We probably want to handle these errors better
guard status == noErr else {
fatalError("Unable to retrieve the secret: " + errToString(status: status))
}

if let resultDictionary = item as? [String: Any],
let data = resultDictionary[kSecValueData as String] as? Data {
return String(decoding: data, as: UTF8.self)
}

return nil
}
Loading

0 comments on commit bef2db4

Please sign in to comment.