how to link a nickname with a keychain - swift

Before, I was storing sensitive data(email, nickame, and password) in a json file. Now I am storing the email and the password into keychains ( kSecAttrAccount is the email)
But I have no ideea how can I store the nickname in the keychain. Or where can I store the nickname so it gets linked with a specific keychain.
import Foundation
import SwiftUI
func save(account: String, password: String) {
do {
try KeychainManager.save(
service: "loseamp",
account: account,
password: password.data(using: .utf8) ?? Data())
} catch {
print(error)
}
}
func getPassword(account: String, password: String) {
guard let data = KeychainManager.get(
service: "loseamp",
account: account
) else {
print("Failed to read password")
return
}
let password = String(decoding: data, as: UTF8.self)
print("read password here: \(password)")
}
class KeychainManager {
enum KeychainError: Error {
case duplicateEntry
case unknown(OSStatus)
}
static func save(service: String, account: String, password: Data) throws {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecValueData as String: password as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status != errSecDuplicateItem else {
throw KeychainError.duplicateEntry
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
static func get(service: String, account: String) -> Data? {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
print("Read status: \(status)")
return result as? Data
}
func update(service: String, account: String, password: Data) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject,
] as CFDictionary
let updatedData = [kSecValueData: password] as CFDictionary
SecItemUpdate(query, updatedData)
}
func delete(service: String, account: String) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject
] as CFDictionary
SecItemDelete(query)
}
func isEmailDuplicate(service: String, account: String) -> Bool {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecDuplicateItem
}
}

Related

Firebase AuthUI returns nil for display name and email in Sign in with Apple

When I try to sign in with Apple, Firebase AuthUI returns nil for display name and email. Here's my code
lazy var authUI: FUIAuth? = {
let UIAuth = FUIAuth.defaultAuthUI()
UIAuth?.delegate = self
UIAuth?.shouldHideCancelButton = true
return UIAuth
}()
func presentLogin(){
// providers
var providers: [FUIAuthProvider] = [
FUIEmailAuth(),
FUIGoogleAuth(),
FUIFacebookAuth()
]
if #available(iOS 13.0, *) {
let appleProvider = FUIOAuth.appleAuthProvider()
providers.append(appleProvider)
}
self.authUI?.providers = providers
let loginController = self.authUI!.authViewController()
present(loginController, animated: true, completion: nil)
}
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
print(authDataResult?.user.displayName)
}
I had the same issue! I don't have the Apple sign in, but even just with Firebase it would show nil for this information. My issue was that I hadn't properly set up the data for Firebase and Xcode to talk. You code seems right, but it looks like you might be missing a few things..? You're more experienced than me, so I might be wrong! But here's my code...
// SIGN UP FUNCTION, first name, last name, email, password
let db = Database.database().reference()
func signUp(firstName: String, lastName: String, email: String, password: String, completion: #escaping (LocalUser?, Error?) -> Void) {
let usersRef = db.child("users")
Auth.auth().createUser(withEmail: email, password: password) {[weak self] (result, error) in
if let uid = result?.user.uid {
let newUser: [String: String] = ["firstName": firstName, "lastName": lastName, "email": email]
let newLocalUser = LocalUser(firstName: firstName, lastName: lastName, email: email)
usersRef.child(uid).setValue(newUser){(error: Error?, ref: DatabaseReference) in
completion(newLocalUser, error)
}
} else {
completion(nil, nil)
}
}
}
// SIGN IN FUNCTION, email, password
func signIn(email: String, password: String, completion: #escaping (LocalUser?, Error?) -> Void) {
let usersRef = db.child("users")
Auth.auth().signIn(withEmail: email, password: password) { [weak self]
(result, error) in
guard let user = result?.user else {
completion(nil, error)
return
}
let uid = user.uid
usersRef.child(uid).observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? [String: Any]
if let user = value {
let userObject = LocalUser.makeObjectFrom(user)
newUser = userObject
completion(userObject, nil)
} else {
completion(nil, error)
}
}
}
}
I didn't see a reference to the db and uid, so this may help!

How can I use Keychain in Mac Catalyst app on Mac?

I can't write/read from Keychain in a Mac-Catalyst app on Mac, it returns errors 34018 and 25300 respectively. Is there a way to make Keychain work on Mac in a Catalyst app?
Xcode: 11.0, MacOS: 10.15
Here is a sample code, it works on iOS but not on Mac. The code prints "My secretive bee 🐝" to indicate that we have successfully written this text to Keychain and then read from it.
override func viewDidLoad() {
super.viewDidLoad()
let itemKey = "My key"
let itemValue = "My secretive bee 🐝"
deleteFromKeychain(itemKey: itemKey)
addToKeychain(itemKey: itemKey, itemValue: itemValue)
readFromKeychain(itemKey: itemKey)
}
func deleteFromKeychain(itemKey: String) {
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)
if resultCodeDelete != noErr {
print("Error deleting from Keychain: \(resultCodeDelete)")
}
}
func addToKeychain(itemKey: String, itemValue: String) {
guard let valueData = itemValue.data(using: String.Encoding.utf8) else {
print("Error saving text to Keychain")
return
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: valueData as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
]
let resultCode = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != noErr {
print("Error saving to Keychain: \(resultCode)")
}
}
func readFromKeychain(itemKey: String) {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad == noErr {
if let result = result as? Data,
let keyValue = NSString(data: result,
encoding: String.Encoding.utf8.rawValue) as? String {
// Found successfully
print(keyValue)
}
} else {
print("Error loading from Keychain: \(resultCodeLoad)")
}
}
I enabled the keychain sharing from signing and capabilities section in xcode, and now I am able to store values in keychain.

Parsing Swift dictionary's secondary level

I have a JSON object that comes from my server as so:
LOGIN_SUCCESS with JSON: {
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS5kb21haW4uY29tIiwic3ViIjoiNTkwOTRkNjRjMmRhN2E3MWI4NTljYTFhIiwiaWF0IjoxNDk0MjY1NTE1LCJleHAiOjE0OTQ4NzAzMTV9.SqsLeToG8-_3CV1Yr4Z4SUIv4-vqGbntGwFLB4i7n-w";
user = {
"__v" = 0;
"_id" = 59094d64c2da7a71b859ca1a;
createdAt = "2017-05-03T03:24:20.309Z";
email = "dylan#msn.com";
name = Dylan;
updatedAt = "2017-05-03T03:24:20.309Z";
};
}
This get converted to a dictionary as userInfo. However, since my token is not returned in userinfo how can I parse this into a class. Currently, my
initialization looks like this with hard coded strings:
self.loggedInUser.setUser(firstName: "Dylan" as String!,
email: "dylan#msn.com"as String!,
token: "test" as String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
Full Request Attempt:
func loginPost(email: String, password: String, completion: #escaping (_ userInfo: User?, _ error: [[String : Any]]?) -> Void) {
let headers: HTTPHeaders = ["Content-Type" : "application/json"]
let parameters: Parameters = ["email": "\(email)","password": "\(password)"]
Alamofire.request(loginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.validate(contentType: ["application/json"])
.responseJSON { response in
if response.response?.statusCode == 200 {
print("LOGIN_SUCCESS with JSON: \(response.result.value!)")
if let userInfo = response.value as? [String : Any] {
self.loggedInUser.setUser(firstName: userInfo.["user"]["name"] as! String!,
email: userInfo["email"] as! String!,
token: userInfo["token"] as! String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
return completion(self.loggedInUser, nil)
}
} else {
print("LOGIN_FAILURE with JSON: \(response.result.value!)")
if let error = response.result.value as? [[String: Any]] {
//If you want array of task id you can try like
return completion(nil, error)
}
}
}
}
Find the corrected code below:
if response.response?.statusCode == 200 {
print("LOGIN_SUCCESS with JSON: \(response.result.value!)")
if let responseData = response.value as? [String : Any] {
let token = responseData["token"] as? String
if let userInfo = responseData as? [String : Any] {
self.loggedInUser.setUser(firstName: userInfo.["user"]["name"] as! String!,
email: userInfo["email"] as! String!,
token: userInfo["token"] as! String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
return completion(self.loggedInUser, nil)
}
}
}

Swift version of Facebook iOS SDK and how to access data in Response

I have taken over a Swift project and need to add Facebook login functionality. I am getting it to mostly work but am having a problem with this sample code here (https://developers.facebook.com/docs/swift/graph):
import FacebookCore
struct MyProfileRequest: GraphRequestProtocol {
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
}
}
var graphPath = "/me"
var parameters: [String : Any]? = ["fields": "id, name"]
var accessToken = AccessToken.current
var httpMethod: GraphRequestHTTPMethod = .GET
var apiVersion: GraphAPIVersion = .defaultVersion
}
let connection = GraphRequestConnection()
connection.add(MyProfileRequest()) { response, result in
switch result {
case .success(let response):
print("Custom Graph Request Succeeded: \(response)")
print("My facebook id is \(response.dictionaryValue?["id"])")
print("My name is \(response.dictionaryValue?["name"])")
case .failed(let error):
print("Custom Graph Request Failed: \(error)")
}
}
connection.start()
I'm getting an error on compiling the for the line with the dictionaryValue optional saying /Users/jt/a-dev/tabfb/tabfb/LoginViewController.swift:72:31: Value of type 'MyProfileRequest.Response' has no member 'dictionaryValue' . How would I access the user name or id using this?
I faced this problem today as well. I got the user id and name inside MyProfileRequest
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
guard let response = rawResponse as? Dictionary<String, Any> else {
return
}
if let name = response["name"],
let id = response["id"] {
print(name)
print(id)
}
}
}
EDIT: I redesigned my code like this to use the values in .success(let response) case
struct Response: GraphResponseProtocol {
var name: String?
var id: String?
var gender: String?
var email: String?
var profilePictureUrl: String?
init(rawResponse: Any?) {
// Decode JSON from rawResponse into other properties here.
guard let response = rawResponse as? Dictionary<String, Any> else {
return
}
if let name = response["name"] as? String {
self.name = name
}
if let id = response["id"] as? String {
self.id = id
}
if let gender = response["gender"] as? String {
self.gender = gender
}
if let email = response["email"] as? String {
self.email = email
}
if let picture = response["picture"] as? Dictionary<String, Any> {
if let data = picture["data"] as? Dictionary<String, Any> {
if let url = data["url"] as? String {
self.profilePictureUrl = url
}
}
}
}
}
And in the success case you can get the values like this:
let connection = GraphRequestConnection()
connection.add(MyProfileRequest()) { response, result in
switch result {
case .success(let response):
print("My facebook id is \(response.id!)") //Make sure to safely unwrap these :)
print("My name is \(response.name!)")
case .failed(let error):
print("Custom Graph Request Failed: \(error)")
}
}
connection.start()
import FBSDKLoginKit //FBSDKLoginKit installs automatically when you install FacebookCore through CocoaPods
///Inside your view controller
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
/// DEFAULT
//fired when fb logged in through fb's default login btn
if error != nil {
print(error)
return
}
showDetails()
}
fileprivate func showDetails(){
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, first_name, last_name, email, gender"]).start { (connection, result, err) in
////use link for more fields:::https://developers.facebook.com/docs/graph-api/reference/user
if err != nil {
print("Failed to start graph request:", err ?? "")
return
}
let dict: NSMutableDictionary = result as! NSMutableDictionary
print("The result dict of fb profile::: \(dict)")
let email = dict["email"] as! String!
print("The result dict[email] of fb profile::: \(email)")
let userID = dict["id"] as! String
print("The result dict[id] of fb profile::: \(userID)")
// self.profileImage.image = UIImage(named: "profile")
let facebookProfileUrl = "http://graph.facebook.com/\(userID)/picture?type=large"
}
}
//make sure you add read permissions for email and public profile
override func viewDidLoad(){
super.viewDidLoad()
loginButtonFromFB.delegate = self //inherit FBSDKLoginButtonDelegate to your class
loginButtonFromFB.readPermissions = ["email", "public_profile"]
}

Facebook Graph Request using Swift3 -

I am rewriting my graph requests with the latest Swift3. I am following the guide found here - https://developers.facebook.com/docs/swift/graph.
fileprivate struct UserProfileRequest: GraphRequestProtocol {
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON into other properties
}
}
let graphPath: String = "me"
let parameters: [String: Any]? = ["fields": "email"]
let accessToken: AccessToken? = AccessToken.current
let httpMethod: GraphRequestHTTPMethod = .GET
let apiVersion: GraphAPIVersion = .defaultVersion
}
fileprivate func returnUserData() {
let connection = GraphRequestConnection()
connection.add(UserProfileRequest()) {
(response: HTTPURLResponse?, result: GraphRequestResult<UserProfileRequest.Response>) in
// Process
}
connection.start()
However, I am getting this error in the connection.add method:
Type ViewController.UserProfileRequest.Response does not conform to protocol GraphRequestProtocol.
I can't seem to figure this out what to change here. It seems like the developer guide is not up to date on Swift3, but I am not sure that is the issue.
Is anyone able to see what is wrong here?
Thanks.
Browsing on the github issues, i found a solution.
https://github.com/facebook/facebook-sdk-swift/issues/63
Facebook documentation for Swift 3.0 and SDK 0.2.0 is not yet updated.
This works for me:
let params = ["fields" : "email, name"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(let error):
print("error in graph request:", error)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
print(responseDictionary)
print(responseDictionary["name"])
print(responseDictionary["email"])
}
}
}
enjoy.
This code works for me, first I make a login with the correct permissions, then I build the GraphRequest for get the user information.
let login: FBSDKLoginManager = FBSDKLoginManager()
// Make login and request permissions
login.logIn(withReadPermissions: ["email", "public_profile"], from: self, handler: {(result, error) -> Void in
if error != nil {
// Handle Error
NSLog("Process error")
} else if (result?.isCancelled)! {
// If process is cancel
NSLog("Cancelled")
}
else {
// Parameters for Graph Request without image
let parameters = ["fields": "email, name"]
// Parameters for Graph Request with image
let parameters = ["fields": "email, name, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parameters).start {(connection, result, error) -> Void in
if error != nil {
NSLog(error.debugDescription)
return
}
// Result
print("Result: \(result)")
// Handle vars
if let result = result as? [String:String],
let email: String = result["email"],
let fbId: String = result["id"],
let name: String = result["name"] as? String,
// Add this lines for get image
let picture: NSDictionary = result["picture"] as? NSDictionary,
let data: NSDictionary = picture["data"] as? NSDictionary,
let url: String = data["url"] as? String {
print("Email: \(email)")
print("fbID: \(fbId)")
print("Name: \(name)")
print("URL Picture: \(url)")
}
}
}
})
Here is my code like. I use Xcode 8, Swift 3 and it works fine for me.
let parameters = ["fields": "email, id, name"]
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: parameters)
_ = graphRequest?.start { [weak self] connection, result, error in
// If something went wrong, we're logged out
if (error != nil) {
// Clear email, but ignore error for now
return
}
// Transform to dictionary first
if let result = result as? [String: Any] {
// Got the email; send it to Lucid's server
guard let email = result["email"] as? String else {
// No email? Fail the login
return
}
guard let username = result["name"] as? String else {
// No username? Fail the login
return
}
guard let userId = result["id"] as? String else {
// No userId? Fail the login
return
}
}
} // End of graph request
Your UserProfileRequest should look like this:
fileprivate struct UserProfileRequest: GraphResponseProtocol {
fileprivate let rawResponse: Any?
public init(rawResponse: Any?) {
self.rawResponse = rawResponse
}
public var dictionaryValue: [String : Any]? {
return rawResponse as? [String : Any]
}
public var arrayValue: [Any]? {
return rawResponse as? [Any]
}
public var stringValue: String? {
return rawResponse as? String
}
}