Inconsistent error format with Meteor Accounts.changePassword - meteor

I'm using Meteor 1.8.1 and have found what seems like inconsistent and undocumented behaviour in the errors returned by Accounts.changePassword.
The docs say that a Meteor error object will include a 'reason' parameter.
But if the attempt to change password fails because the user is not logged in, the error object does not contain 'reason' or 'error', only 'message', which I cannot find in the documentation.
'message' appears to be always returned, despite being undocumented, but is inconsistent in that it includes the error code 403 in the case of incorrect password but not in the case where the user is not logged in.
Accounts.changePassword(oldPassword, newPassword, (error) => {
console.log('error.message', error.message);
// not logged in provides message
console.log('error.reason', error.reason);
// incorrect password provides reason and message
if (error) {
const text = error.reason || error.message;
console.log('error', text);
}
// success
});
So my questions are:
have I missed something? Or is the behaviour really inconsistent and undocumented?
is there an easy way to get a consistent error message in both cases?
are there any other 'gotchas' I should test for where Meteor returns an error in a different format again?
Many thanks for any enlightenment.

according to documentation, changePassword method runs only on the client, so you could always check if the user is logged in before attempting to change the password
if (!Meteor.user()) {
return reportError(new Error("Must be logged in to change password."), callback);
}

According to the docs the description of Accounts.changePassword is:
Change the current user's password. Must be logged in.
Therefore, when you try to call this when no user is logged in, it is reasonable for the response to be undefined or inconsistent. Yes, the docs for a Meteor.Error object specify a reason property, but Meteor.Errors are only thrown when a method wants to return a descriptive error, not when a method is called illegally.
Your user interface code should ensure that a change password form is never shown unless a user is logged in. If your change password form is only ever shown to logged in users, then you don't need to worry about catching these undefined errors.

Related

Why there is no Rules tab inside Firebase Authentication?

Before adding a new user to Firebase Authentication should the name be qualified first:
The name must not be null
The name must not be empty
The name must contain one D character at least
Examples:
"Frank van Puffelen" => It is unacceptable because there is no D character
"Doug Stevenson" => It is acceptable
"Alex Mamo" => It is unacceptable because there is no D character
"Renaud Tarnec" => It is acceptable
"" => It is unacceptable because it is empty value
NULL => It is unacceptable because it is a null value
On the client side before adding a new user I check if the name follows the above qualifiers or not but the problem is if someone modifies the code.
The client side is not safe and I should check again on the server side if the name follows the rules or not.
So the question is why there is no Rules tab inside Firebase Authentication?
Since you want to check that the user name (the displayName I guess) follows the set of constraints listed at the top of your question you can take advantage of the new blocking Cloud Functions that "let you execute custom code that modifies the result of a user signing in to your app".
For example:
exports.checkDisplayName = functions.auth.user().beforeCreate((user, context) => {
if (!user.displayName || !user.displayName.toUpperCase().includes('D')) {
throw new functions.auth.HttpsError(
'invalid-argument', `displayName is invalid`); // adapt as follows
}
});
More details in the specific section of the doc, and in particular on how to catch and handle the error in your front-end.
The security rules concept is used to prevent unauthorized access to your Firebase resources such as database and storage. The displayName property is optional irrespective of which authentication method you chose.
If you require users to have a displayName then you can:
Check if user has displayName set every time they login. If not, then redirect them to a screen where they can set a name.
Disable sign-ups directly from Firebase client SDKs and use Firebase Cloud Functions with the Admin SDK to create user. No one else can reverse engineer the functions code so the validation on server side will ensure a user has displayName.
exports.createUser = functions.https.onCall((data, context) => {
const { displayName, email, password } = data;
// check if displayName is valid
// if not return error
// create user using Admin SDK if all data provided is valid
return { message: "User created" };
});
Then you can login your user with the Client SDK using signInWithEmailAndPassword()
In case you are using any Auth providers e.g. Google, Facebook and the display name is unavailable for some reason, then you'll need some custom logic as explain in method 1 above.
Either of the solution does not prevent users from using updateProfile() APIs so make sure have some validation on client end as well and report such events somewhere in the database where you can monitor it.

Nuxt Middleware with Firebase and FirebaseUI: Error: Redirected when going from "/anything" to "/login" via a navigation guard

Nuxt SSR app using FirebaseUI to handle auth flows. Logging in and out works perfectly. When I add Middleware to check auth state and redirect if not logged in I get this error:
Error: Redirected when going from "/list-cheatsheets" to "/login" via a navigation guard.
middleware/auth.js
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.user) {
return redirect('/login')
}
}
There is absolutely no other redirecting that I can find in the app....
I have been digging and trying things for hours. Others who get this error that I have found aren't using Nuxt and none of those solutions work.
As there is a bounty one cannot mark it duplicate thus following up is a copy of my answer at Redirecting twice in a single Vue navigation
tldr: vm.$router.push(route) is a promise and needs to .catch(e=>gotCaught(e)) errors.
This will be changed in the next major#4
Currently#3 errors are not distinguished whether they are NavigationFailures or regular Errors.
The naive expected route after vm.$router.push(to) should be to. Thus one can expect some failure message once there was a redirect. Before patching router.push to be a promise the error was ignored silently.
The current solution is to antipattern a .catch(...) onto every push, or to anticipate the change in design and wrap it to expose the failure as result.
Future plans have it to put those informations into the result:
let failure = await this.$router.push(to);
if(failure.type == NavigationFailureType[type]){}
else{}
Imo this error is just by design and should be handled:
hook(route, current, (to: any) => { ... abort(createNavigationRedirectedError(current, route)) ...}
So basically if to contains a redirect it is an error, which kinda is equal to using vm.$router.push into a guard.
To ignore the unhandled error behaviour one can pass an empty onComplete (breaks in future releases):
vm.$router.push(Route, ()=>{})
or wrap it in try .. catch
try {
await this.$router.push("/")
} catch {
}
which prevents the promise to throw uncaught.
to support this without redirecting twice means you put the guard to your exit:
let path = "/"
navguard({path}, undefined, (to)=>this.$router.push(to||path))
which will polute every component redirecting to home
btw the router-link component uses an empty onComplete
Assumption that redirecting twice is not allowed is wrong.

Can Meteor.loginWithPassword be changed to return a generic error?

Using Meteor, the Meteor.loginWithPassword function calls the server, which then returns a User not found error when the user does not exist, or a Match failed when the password does not match the user's password.
Is there an easy way for the server to return the same error (or no error) during both conditions of a failed login?
I don't want hackers to know when they found a valid username or UserID on my system. I don't want the server to say User not found, telling potential hackers when they have (or have not) found a valid user. It would be great if there's an easy way to change the error message the server returns from the accounts-password Meteor module, to harden the security a little bit. I'd like the server's error result to be something generic, like failed or undefined or null, regardless of the reason of the login error.
I know I can probably fork/re-purpose the accounts-password module, but hoping there's something simpler.
Thanks!
Yes, you can place the function somewhere in the /server-folder and change the error message. Like this:
Accounts.validateLoginAttempt(function(options) {
if (!options.allowed) { //that is, if there's an error
throw new Meteor.Error('login-error', 'Error!');
}
//...other code for other purposes below
});
/server/accounts.js

Accounts.onLogin how to get user Id?

How do you get the _id of the user that logged in. I have tried the following combinations and I get errors, or undefined
Upon user creation, the user is automatically signed into the application. Is the user that is returned by the Accounts.onCreateUser function occurring after the user is logged in?
Accounts.onLogin(function(){
var user = this.userId / Meteor.user() / Meteor.user()._id
console.log(user)
})
http://docs.meteor.com/#/full/accounts_onlogin
The Accounts.onLogin(function(){}), come with 1 parameter user.
When it is known which user was attempting to login, the Meteor user
object. This will always be present for successful logins.
from the docs.
So this should work.
Accounts.onLogin(function(user){
console.log(user.user._id)
});
And you should see, all the user document into the server console,check this meteorpad for an example.
NOTE: this feature is only available on server side check this Hooks Accounts.onLogin/onLoginFailure should be available on client
You can always get the _id of the logged-in user via Meteor.userId(). This also works inside the Accounts.onLogin callback
Accounts.onLogin(function() {
console.log(Meteor.userId());
})

Why Membership.CreateUser Failed?

For this overload:
if CreateUser("username#email.com","pwd") , failed with error message :The e-mail address provided is invalid. Please check the value and try again.
but CreateUser("username#email.com","pwd","username#email.com") , succeed!
why?
It may be better to think of the methods like this:
CreateUser(Username, Password);
CreateUser(Username, Password, Email);
Without reflecting the code I couldn't say 100% for sure, but I'll bet those functions just call an internal function that takes in every possible parameter.
So your first call (simplified) is actually:
CreateUser("username#email.com", "pwd", null);
If you have configured your provider to require email address, then clearly null is not valid.
From the MSDN docs:
The SqlMembershipProvider provides an option to require a unique
e-mail address for each user. If the RequiresUniqueEmail property is
true, you will need to use one of the CreateUser overloads that allows
you to specify an e-mail address for the user being created.
Otherwise, a MembershipCreateUserException will be thrown.

Resources