How to detect if security rules are preventing me connecting to auth() in rn-firebase? - react-native

How can I figure out if I'm in an error state and that's why the onUserChanged() returned user was null?
My app is still in development and uses React-Native and Firebase. My security rules are still in test mode, but recently expired:
allow read, write: if
request.time < timestamp.date(2023, 2, 3);
}
In my main App.tsx I have a useEffect like this that gets the current user and, if there isn't one, signs the user in anonymously:
const currentUserRef = useRef<FirebaseAuthUser | null>();
useEffect(() => {
const unsubscribe = auth().onUserChanged((user: FirebaseAuthUser | null) => {
if (user !== currentUserRef.current) {
if (user !== null) {
// Set up current user stuff
} else {
// signInAnonymously
}
}
}
Now my code is hanging here. We get a null user but currentUserRef is also null, so nothing happens.
The device I'm developing on does have an account which should be returned here so I'm guessing this is related to my security rule expiration. I could just attempt to sign the user in anonymously in this current case and maybe that would give me the error info I'm seeking, but I'm wondering if there's some other way to get this information

Related

How to unregister middleware in Telegraf?

When I add bot.hears(...), it registers middleware for handling matching text messages. But now it will handle those messages even if they are sent any time, even if not expected.
So if I am creating a stateful service, I would like to listen to particular messages only at appropriate time.
How can I unregister middleware, so that it does not hear any more previously handled messages?
I turned out I was looking for Scenes. How to use them is described on Github.
I'll just post a slightly modified code from the links above:
const { Telegraf, Scenes, session } = require('telegraf')
const contactDataWizard = new Scenes.WizardScene(
'CONTACT_DATA_WIZARD_SCENE_ID', // first argument is Scene_ID, same as for BaseScene
(ctx) => {
ctx.reply('Please enter guest\'s first name', Markup.removeKeyboard());
ctx.wizard.state.contactData = {};
return ctx.wizard.next();
},
(ctx) => {
// validation example
if (ctx.message.text.length < 2) {
ctx.reply('Please enter real name');
return;
}
ctx.wizard.state.contactData.firstName = ctx.message.text;
ctx.reply('And last name...');
return ctx.wizard.next();
},
);
const stage = new Scenes.Stage();
stage.register(contactDataWizard);
bot.use(session());
bot.use(stage.middleware());
But I still don't know how to generally implement it, so I need to find it out in the Scenes code of Telegraf.

How to generate authentication credentials for Google Sheets API?

Step 1 (completed):
As instructed, I have followed every step in Google's Node.js Quickstart:
https://developers.google.com/sheets/api/quickstart/nodejs
and it ran perfectly without any errors. My understanding was that after I run index.js, a token.json is created. After which, I can somehow use the token.json for any future authentication purpose without needing credentials.json every time. Please, correct me if I am wrong in this assumption. I am only making this assumption based on what Google is saying in the previously given link - "Authorization information is stored on the file system, so subsequent executions will not prompt for authorization."
Step 2 (problem):
Below is another code snippet is given by a Google Sheet's API's documentation page for reading multiple range data:
https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet
I, however, do not know how to generate authentication credentials for authClient. Am I suppose to do something with token.json? If yes, what is the protocol of using the token.json file?
Please feel free to ask any follow-up questions if needed.
enter code here// BEFORE RUNNING:
// ---------------
// 1. If not already done, enable the Google Sheets API
// and check the quota for your project at
// https://console.developers.google.com/apis/api/sheets
// 2. Install the Node.js client library by running
// `npm install googleapis --save`
const {google} = require('googleapis');
var sheets = google.sheets('v4');
authorize(function(authClient) {
var request = {
// The ID of the spreadsheet to retrieve data from.
spreadsheetId: 'my-spreadsheet-id', // TODO: Update placeholder value.
// The A1 notation of the values to retrieve.
ranges: [], // TODO: Update placeholder value.
// How values should be represented in the output.
// The default render option is ValueRenderOption.FORMATTED_VALUE.
valueRenderOption: '', // TODO: Update placeholder value.
// How dates, times, and durations should be represented in the output.
// This is ignored if value_render_option is
// FORMATTED_VALUE.
// The default dateTime render option is [DateTimeRenderOption.SERIAL_NUMBER].
dateTimeRenderOption: '', // TODO: Update placeholder value.
auth: authClient,
};
sheets.spreadsheets.values.batchGet(request, function(err, response) {
if (err) {
console.error(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
});
function authorize(callback) {
// TODO: Change placeholder below to generate authentication credentials. See
// https://developers.google.com/sheets/quickstart/nodejs#step_3_set_up_the_sample
//
// Authorize using one of the following scopes:
// 'https://www.googleapis.com/auth/drive'
// 'https://www.googleapis.com/auth/drive.file'
// 'https://www.googleapis.com/auth/drive.readonly'
// 'https://www.googleapis.com/auth/spreadsheets'
// 'https://www.googleapis.com/auth/spreadsheets.readonly'
var authClient = null;
if (authClient == null) {
console.log('authentication failed');
return;
}
callback(authClient);
}

RxJs: How to conditionally chain observable of BehaviorSubject?

I've got an observable data service (UserService) that returns the currently logged in user. I followed this tutorial - https://coryrylan.com/blog/angular-observable-data-services, which describes using a BehaviorSubject to return a default currentUser immediately, then emit the real currentUser once it's loaded or altered. The service is basically like this...
private _currentUser: BehaviorSubject<User> = new BehaviorSubject(new User());
public currentUser: Observable<User> = this._currentUser.asObservable();
constructor(private http: Http) {}
loadUser() { // app.component onInit and login component call this
return this.http.get('someapi.com/getcurrentuser')
.map(response => <User>this.extractData(response))
.do(
(user) => {
this.dataStore.currentUser = user;
this._currentUser.next(Object.assign(new User(), this.dataStore).currentUser);
},
(error) => this.handleError(error)
)
.catch(error -> this.handleError(error));
}
I'm having problems whenever a user hits F5 to reload the entire spa. When a consuming component subscribes to the currentUser on the UserService, it immediately receives a default user while the UserService waits for an api call to receive the actual user. The moment that api call finishes, the real user is emitted by UserService and all the subscribers get the real user. The first value emitted by the BehaviorSubject, however, is the default value and it always has an id of "undefined", so we can't make our next api call yet. In fact, when the real user comes through and I CAN make a valid call using the user.id, the chained subscription never happens and I don't get the values out of the response.
I know I'm doing something stupid, but I haven't figured out exactly what yet. I just stumbled across concatMap, but I'm not sure how to use it. While I pursue that, I'd like to know why the below code doesn't work. I particularly want to know why the subscribe never fires, even after the real user comes in, just to help my newbie understanding of Observables.
this.userService.currentUser
.flatMap((user) => {
this.user = user;
// Need to NOT call this if the user does not have an id!!!
this.someOtherService.getSomethingElse(user.id); // user.id is always undefined the first time
})
.subscribe((somethingElse) => {
// This never gets called, even after the real user is emitted by the UserService
// and I see the getSomethingElse() call above get executed with a valid user.id
this.somethingElse = somethingElse;
});
If you want to ignore user instances that do not have an id, use the filter operator:
import 'rxjs/add/operator/filter';
this.userService.currentUser
.filter((user) => Boolean(user.id))
.flatMap((user) => {
this.user = user;
this.someOtherService.getSomethingElse(user.id);
})
.subscribe((somethingElse) => {
this.somethingElse = somethingElse;
});
Regarding "why the subscribe never fires", it's likely due to an error arising from the undefined id. You only pass a next function to subscribe, so any errors will be unhandled. And if an error occurs, the observable will terminate and will unsubscribe any subscribers - as that is how observables behave - so any subsequent users with defined id properties will not be received.

How do I force AWS Cognito to retrieve an IdentityId from the server?

In the iOS SDK (v2.4.8) I can't logout a user and then login as a different user correctly.
The (correct) cognityIdentityId returned by AWS for the first user (since an app start) is also returned for the second user (unless the app is restarted). This gives access to the AWSCognitoDataset of one user by another.
I think this is because the iOS SDK has cached the id and the documented call to clear that cache, doesn't fully work.
When logging in:
// one-off initialisation
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType:AWSRegionType.USEast1, identityPoolId:Constants.CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region:AWSRegionType.USEast1, credentialsProvider:self.credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
…
// I get idToken from my external provider serice (Auth0)
func doAmazonLogin(idToken: String, success : () -> (), _ failure : (NSError) -> ()) {
var task: AWSTask?
//Initialize clients for new idToken
if self.credentialsProvider?.identityProvider.identityProviderManager == nil || idToken != Application.sharedInstance.retrieveIdToken() {
let logins = [Constants.CognitoIDPUrl: idToken]
task = self.initializeClients(logins)
} else {
//Use existing clients
self.credentialsProvider?.invalidateCachedTemporaryCredentials()
task = self.credentialsProvider?.getIdentityId()
}
//Make login
task!.continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
failure(task.error!)
} else {
// the task result will contain the identity id
let cognitoId:String? = task.result as? String
self.customIdentityProviderManager!.addToken(Constants.CognitoIDPUrl, value:idToken)
//Store Cognito token in keychain
Application.sharedInstance.storeCognitoToken(cognitoId)
success()
}
return nil
}
}
func initializeClients(logins: [NSObject:AnyObject]?) -> AWSTask? {
//Create identity provider managet with logins
let manager = CustomIdentityProviderManager(tokens: logins!)
self.credentialsProvider?.setIdentityProviderManagerOnce(manager)
return self.credentialsProvider?.getIdentityId()
}
When logging out:
// Clear ALL saved values for this provider (identityId, credentials, logins). [docs][1]
let keychain = A0SimpleKeychain(service:"…")
keychain.clearAll()
I've also tried adding:
credentialsProvider!.clearCredentials()
credentialsProvider!.clearKeychain()
Is anyone using the AWS iOS SDK and has coded logout successfully such that a new user can login cleanly?
There is the oddly named method credentialsProvider.setIdentityProviderManagerOnce() - I can't find this documented but its name suggests that it should only be called once per session. But if keychain.clearAll() removes logins then one would need to call setIdentityProviderManagerOnce each time a new user logins in in order to setup logins each time.
Can you describe your login/logout flow a bit more? Cognito doesn't support multiple logins from the same provider per identity, so it sounds like, unless you're using multiple, it isn't actually changing the identity.
In any case, have you tried the clearKeyChain method on the credentials provider? It's largely for use cases like this - clearing everything.

Limit Google Sign-In to .edu accounts in Meteor

I'm trying to limit my Google + Sign-In Button to only allow #something.edu accounts to sign in. How would I go about doing this. This is my code so far:
Template.googleLogin.events({
'click #gLogin': function(event) {
Meteor.loginWithGoogle({}, function(err){
if (err) {
throw new Meteor.Error("Google login didn't work!");
}
else {
Router.go('/home')
}
});
}
})
Template.primaryLayout.events({
'click #gLogout': function(event) {
Meteor.logout(function(err){
if (err) {
throw new Meteor.Error("Hmm looks like your logout failed. ");
}
else {
Router.go('/')
}
})
}
})
You can accomplish this using Accounts.config (in the root directory, so it runs on both the client and server)
Accounts.config({ restrictCreationByEmailDomain: 'something.edu' })
If you need something more custom, you can replace something.edu with a method if you need to fine grain your requirement, i.e for any .edu domain:
Accounts.config({ restrictCreationByEmailDomain: function(address) {
return new RegExp('\\.edu$', 'i')).test(address)
}
});
The accounts package allows configuring account creation domain through:
Accounts.config({
restrictCreationByEmailDomain: 'something.edu'
})
But this has some limitations in case of google:
This is only client side and only allows for the login form to get properly styled to represent the domain's logo etc. But it can be very easily overcome by crafting the google oauth signin url by hand
In case you need to configure extra options like allowing multiple domains or a domain and some outside users (perhaps third party contractors or support from a software company etc) this does not work. In case of accounts-google, the package checks if restrictCreationByEmailDomain is a String and if it is instead a function, it just discards it.
Therefore, to be able to properly and securely utilize such functionality, you need to use the official Accounts.validateNewUser callback:
Accounts.validateNewUser(function(newUser) {
var newUserEmail = newUser.services.google.email;
if (!newUserEmail) throw new Meteor.Error(403,'You need a valid email address to sign up.');
if (!checkEmailAgainstAllowed(newUserEmail)) throw new Meteor.Error(403,'You need an accepted organization email address to sign up.');
return true;
});
var checkEmailAgainstAllowed = function(email) {
var allowedDomains = ['something.edu'];
var allowedEmails = ['someone#example.com'];
var domain = email.replace(/.*#/,'').toLowerCase();
return _.contains(allowedEmails, email) || _.contains(allowedDomains, domain);
};
If you want to be extra cautious, you can implement the same for the Accounts.validateLoginAttempt and Accounts.onCreateUser callbacks as well.