Swift: Firebase auth observer not called if observers of child removed - ios

Assume ref = Firebase(url: "your firebase url").
A child of ref would be childRef = ref.childByAppendingPath("child")
If I have ref.observeAuthEventWithBlock listening for authentication changes at ref, and I then use childRef.removeAllObservers(), the auth observer at ref is no longer listening for changes.
Why is this?

I crafted up a small app to duplicate the issue (ObjC code to follow)
The code to watch for auth'ing is:
[myRootRef observeAuthEventWithBlock:^(FAuthData *authData) {
NSLog(#"got an auth event");
}];
and we have the child node
child = [myRootRef childByAppendingPath:#"child_path"];
then the initial auth is
[myRootRef authUser:#"dude#thing.com" password:#"pw" withCompletionBlock:^(NSError *error, FAuthData *authData) {
NSLog(#"authentication 1 success");
[child removeAllObservers];
[self doAuth];
}
}];
The doAuth method simply auth's another user and outputs 'authentication 2 success'
got an auth event
got an auth event
authentication 1 success
authentication 2 success
So as you can see it worked as advertised - I was unable to duplicate the issue. My guess is the error may lie somewhere else in your code.

Related

The app with id xxxxxxxx does not have the right view_structure on app with id yyyy

I'm trying to create a new item in a podio app in an iOS app with. The following lines of code as per the documentation in podio but I am having a 403 error with the subject as the error message. I assumed that I needed to authenticate the session as an app as I think that it will use the app id to create the PKTItem in the app used to sign in.
let item = PKTItem(forAppWithID: lastItem + 1)
item?.setValue(1, forField: "service")
item?.setValue(testPet, forField: "pet")
item?.save().onComplete({(response, error) in
if (error != nil){
print("error: \(error)")
}
else{
print("response")
}
})
I was in the assumption that the item would be created in the app that was used to sign with the item having an id of lastItem + 1, I tried to see if the object's IDs are correct
//this is supposed to be lastItem + 1
print("PKTItem ID: \(item?.appItemID)")
//this is supposed to be the app ID where it should create the new item
print("PKTItem AppID: \(item?.appID)")
PKTItem ID: Optional(0)
PKTItem AppID: Optional(1)
I don't what I was doing wrong. Please assist me.
Sounds like you are using the wrong set of credentials to look at the app item you are attempting to work with. How did you authorize? Do you have the right App ID and Token combination for this script?
Typically you will see [PodioKit authenticateAutomaticallyAsAppWithID:123456 token:#"my-app-token"]; and that allows you to interact with anything in the the app 123456, but it sounds like you are trying to interact with data in 2 apps based on the error message. If this is a small scale implementation (or for testing purposes) you can use the Authenticate as User method instead. Here is the documentation they have on Authentication for the Objective C library.
PKTAsyncTask *authTask = [PodioKit authenticateAsUserWithEmail:#"myname#mydomain.com" password:#"p4$$w0rD"];
[authTask onComplete:^(PKTResponse *response, NSError *error) {
if (!error) {
// Successfully authenticated
} else {
// Failed to authenticate, double check your credentials
}
}];

Firebase Phone Auth

I'm trying to add Firebase Phone Auth to an app that I'm making in XCode. However, I'm having trouble with steps 3 of the firebase documentation and everything after that.
I don't understand where my code is supposed to go. I try some of it already and I attached the image of what I have done so far. Please help.
Thank you.
Ok, the code seems right.
Now you must add another textfield where the user can add the verification code arrived from the SMS.
In a new method triggered by the user after adding the code you must set a FIRAuthCredential like in the code of the example:
FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
credentialWithVerificationID:verificationID
verificationCode:newTextField.text!];
And then do the signin with:
[[FIRAuth auth] signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error) {
// ...
return;
}
// User successfully signed in. Get user data from the FIRUser object
if (authResult == nil) { return; }
FIRUser *user = authResult.user;
// ...
}];

Easy way to unsubscribe for all topic subscribed with GCM (iOS device)

I am trying to push notifications through topic system in a iOS device with the new API of Google Cloud Messaging designed for iOS device.
I have the right certificates so I can receive notifications from a topic created. My code to subscribe to a topic is the following :
if (_registrationToken && _connectedToGCM) {
[[GCMPubSub sharedInstance] subscribeWithToken:_registrationToken
topic:topicToSubscribe
options:nil
handler:^(NSError *error) {
if (error) {
//handle error here
} else {
self.subscribedToTopic = true;
}
}];
}
I know the equivalent function to unsubscribe but this function need a topic name.
Is there a way to retrieve all topics where my app is possibly subscribed to unregistered them before subscribing ?
There is no way to retrieve the list of topics that your app is subscribed to from the Google Cloud Messaging service.
You have to keep track of the list and persist it on your app (hard coded, stored in preferences, database, file, etc.) or your server.
When you decide to let the user unsubscribe, retrieve the list of topics from where you stored it and pass it to unsubscribeWithToken:token:topic:options:handler as mentioned on the Implementing Topic Messaging page
Alternatively, when receiving messages you can check who is the message 'from'. If it is from a topic you are no longer interested in, you can unsubscribe instead of processing the message.
If you have the registration token it's possible to get the topics the device is subscribed to by using https://iid.googleapis.com/iid/info/IID_TOKEN (with authorization key in the header). Where IID_TOKEN is the registration token.
Find more info at https://developers.google.com/instance-id/reference/server#get_information_about_app_instances.
If you want unsubscribe from all topics simply execute:
GGLInstanceID *iid = [GGLInstanceID sharedInstance];
GGLInstanceIDDeleteHandler deleteHandler = ^void(NSError *error) {
if (error) {
// failed to delete the identity for the app
// do an exponential backoff and retry again.
} else {
// try to get a new ID
dispatch_async(dispatch_get_main_queue(), ^{
GGLInstanceIDHandler handler =
^void(NSString *identity, NSError *error) {
if (error) {
// failed to get the identity for the app
// handle error
} else {
NSString *instanceID = identity;
// handle InstanceID for the app
}
}
[iid getIDWithHandler:handler];
});
}
}
[iid deleteIDWithHandler:deleteHandler];
More info
Don't forget to refresh your TOKEN!

Google OAuth Login Error: Invalid credentials

I have an iPad application which allows users to login to their Gmail account(s) using OAuth2. Thus far, the login process and email fetching is successful. However, when the app is closed and then re-opened after a (long) period of time, an error is produced "invalid credentials,' even though previous logins with the same credentials were successful.
Login Flow:
1) User logs in to gmail using OAuth 2.
2) User email address and oAuthToken provided by the GTMOAuth2Authentication object are saved to core data for future logins.
3) IMAP Session is created using saved email address and OAuthToken.
Here is the relevant code
Google Login
- (void)gmailOAuthLogin
{
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
GTMOAuth2ViewControllerTouch *googleSignInController =
[[GTMOAuth2ViewControllerTouch alloc] initWithScope:GmailScope clientID:GmailAppClientID clientSecret:GmailClientSecret keychainItemName:KeychainItemName completionHandler:^(GTMOAuth2ViewControllerTouch *googleSignInController, GTMOAuth2Authentication *auth, NSError *error){
if (error != nil) {
//handle error
} else {
[[ModelManager sharedInstance] authenticateWithEmailAddress:[auth userEmail]
oAuthToken:[auth accessToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session using above arguments
} failure:^(NSError *error) {
//handle error
}];
}
}];
[self presentGoogleSignInController:googleSignInController];
}
Create IMAP Session Using MailCore2
- (void)authenticateWithEmailAddress:(NSString *)emailAddress password:(NSString *)password oAuthToken:(NSString *)oAuthToken imapHostname:(NSString *)imapHostname imapPort:(NSInteger)imapPort smtpHostname:(NSString *)smtpHostname smtpPort:(NSInteger)smtpPort success:(void (^)())success failure:(void (^)(NSError *))failure
{
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = imapHostname;
self.imapSession.port = imapPort;
self.imapSession.username = emailAddress;
self.imapSession.connectionType = MCOConnectionTypeTLS;
self.imapSession.password = nil;
self.imapSession.OAuth2Token = oAuthToken;
self.imapSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.imapSession.authType;
[self.imapSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type,
NSData * data){
NSLog(#"MCOIMAPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
self.smtpSession = [[MCOSMTPSession alloc] init];
self.smtpSession.hostname = smtpHostname;
self.smtpSession.port = smtpPort;
self.smtpSession.username = emailAddress;
self.smtpSession.connectionType = MCOConnectionTypeTLS;
self.smtpSession.password = nil;
self.smtpSession.OAuth2Token = oAuthToken;
self.smtpSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.smtpSession.authType;
[self.smtpSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type, NSData * data){
NSLog(#"MCOSMTPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[[self.imapSession checkAccountOperation] start:^(NSError *error) {
if (nil == error) {
success();
} else {
failure(error); //FAILS WITH INVALID CREDENTIALS ERROR
}
}];
}
Once again, the above code works fine, unless the application has not been used in some time. I was not sure if I needed to refresh the OAuthToken or not, so I tried doing the following on launch of the application:
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:KeychainItemName clientID:GmailAppClientID clientSecret:GmailClientSecret];
BOOL canAuthorize = [auth canAuthorize]; //returns YES
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
[[ModelManager sharedDefaultInstance] authenticateWithEmailAddress:[auth userEmail] oAuthToken:[auth refreshToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session
} failure:^(NSError *error) {
NSLog(#"failure %#", error);
}];
But I still get the same error. I have no idea why the OAuth token stops working or how to resolve this. Since the user is able to save multiple accounts, I am wondering if I need to save the refresh token for each account in core data and use that if the access token stops working?
(Disclaimer - I don't know iOS or the gtm-oauth2 libraries, but I do know Google's OAuth implementation.)
Conceptually you do need to persist the refresh token for the user. The refresh token is a long-lived credential which is used (along with your client secret) to get a short-lived access token that is used for actual API calls.
If you anticipate making multiple calls in a short period of time then your app will normally actually persist both the refresh token and access token (currently access tokens will last 1 hour).
That all said, it looks like the gtm-oauth2 library should be taking care of persisting these already (looks like authForGoogleFromKeychainForName does this).
What I think you need help with is getting an up-to-date access token that you can use to initiate your IMAP session.
The gtm-oauth2 library does contain an authorizeRequest method. It takes information about an HTTP request you intend to make and adds the appropriate authorization headers. It looks like this code will examine the state of the access token, and refresh it if necessary.
While I know you aren't able to make an HTTP request (you need to speak IMAP), my suggestion is to use this method with a dummy NSMutableURLRequest - and then, once it's finished, don't actually send the HTTP request, instead examine the headers it added and pull the access token from there.
See:
https://code.google.com/p/gtm-oauth2/wiki/Introduction#Using_the_Authentication_Tokens
Hope that helps - I don't have an iOS environment to test it on.

Is it possible to get oauth token when requesting access to Facebook via Accounts framework?

I need to do make some API calls in the background on the server that's why I need the oauth token.
Using ACAccountCredential's oauthToken method, it returns null. My question is, it it possible to somehow get the oauth token?
For example Instagram does this. They authenticate via Accounts framework and send the token to their servers where they do all the graph API calls.
Thanks
(Edited to address #0xSina's comment)
Yes. I understand that you've tried accessing the oauthToken property of the ACAccountCredential, of the Facebook ACAccount object and that it is returning nil. That said, this IS the way to do it and it does not return nil when everything is set up correctly.
I created a test project to prove this and the relevant code is below. A few things to check:
1) on your Facebook app information page, ensure that you have your bundle ID set in the Native iOS App section.
2) (pretty obviously) make sure you have your Facebook account setup in iOS settings.
3) if you can't get your main project to work, try an empty test project, as I did, and see if that works. You'll have to remember to set your test project's bundleID to match what you entered on the Facebook settings page!
Here's the code with some asserts to notify you if something's failing:
- (void) viewDidAppear:(BOOL)animated
{
ACAccountStore* as = [ACAccountStore new];
ACAccountType* fbat = [as accountTypeWithAccountTypeIdentifier: ACAccountTypeIdentifierFacebook];
NSAssert( fbat != nil, #"oops, can't find account-type for facebook!");
[as requestAccessToAccountsWithType: fbat
options: #{ ACFacebookAppIdKey : #"99999999999", // your FB appid here!
ACFacebookPermissionsKey : #[ #"email" ] }
completion: ^(BOOL granted, NSError *error) {
NSAssert( granted && error==nil, #"oops, access not granted!");
if ( granted )
{
NSArray* accounts = [as accountsWithAccountType: fbat];
NSAssert( accounts.count > 0, #"oops, no accounts??" );
ACAccount* fbaccount = [accounts lastObject];
ACAccountCredential* ac = [fbaccount credential];
NSAssert( ac != nil, #"oops, no credential??" );
NSString* token = [ac oauthToken];
NSAssert( token.length > 0, #"oops, no credential??" );
NSLog( #"token=%#", token );
}
}];
}
One common issue, I think, is to mis-format the ACFacebookPermissionsKey as a string instead of an array of strings. Or to forego it altogether because the docs say it is optional (it isn't.)
And, here's the output from my test (slightly altered so I'm not publishing a real token here)
2013-07-26 16:54:23.699 testfb[26870:1303] token=CAAAAEZBYxezsBAK6OK9YPQIfA0GjiG7rn2XjdZBC6lZCpfAAAocr2cyEnmne9eMh59ZA4UabQyZAsdQMu0gMruxEJXJI1tFfBVTmLqNpRK31GPQvr3rXRAr50HUXI37iS46gPwXwZAroUHT7WqZCza6HcV6L35gVThBcOttWNxozj1df70XluEreM14nucSmvimjVGu5i4ZBZAUrSYLsvO
It seems that sometimes, either due to one's development process or timed expiration, the Facebook credentials for an app can become invalid. At this point, calls to account.credential will return invalid or nil. In this case you must renew the credential. To do this, simply call -renewCredentialsForAccount:completion: on the account in question. For Facebook accounts this will renew the oauth token automatically.
For other service types the user may be prompted for their password, check the docs in this case.
I was having the exact same issue. The problem for me was that I was setting my Facebook account in the completion handler of: requestAccessToAccountsWithType and then attempting to access the ACAccountCredential in a different function.
Getting the oauth token in the completion handler and saving it for later solved the problem for me:
[account requestAccessToAccountsWithType:accountType options:options completion:
^(BOOL granted, NSError *error) {
...
dispatch_async(dispatch_get_main_queue(), ^{
facebookAccount = [arrayOfAccounts lastObject];
oAuthToken = facebookAccount.credential.oauthToken;
});
...
}
];

Resources