Meteor-Useraccounts and Accounts.onCreateUser - meteor

Am using meteor-useraccounts and alanning:roles in my Meteor webapp. The idea is that a user can register and automatically gets assigned a role. So I was thinking of using the Accounts.onCreateUser hook, however can't get that to work.
Accounts.onCreateUser(function(options, user) {
var user = user.user;
var defaultRole = ['admin'];
if (!user.roles){
Roles.addUsersToRoles(user, defaultRole);
}
});
I'm getting the following error message:
Exception while invoking method 'ATCreateUserServer' TypeError: Cannot read property 'roles' of undefined
Seems that the user is not known, hence undefined. Can you user Meteor-useraccounts and assign a role upon user creation.
NEW EDIT: tried a bit further and the below code is working but I'm not really in favor of using this as it is strange to add the role each time upon login of the user:
Accounts.onLogin(function(user) {
check(user, Object);
var user = user.user;
var defaultRole = ['admin'];
if (!user.roles){
Roles.addUsersToRoles(user, defaultRole);
}
return user;
});

There are always many ways to do that. On of which is to add the roles after the user is created and officially obtained a userId, like so:
var id = Accounts.createUser(
createOptions
);
Roles.addUsersToRoles(id, ["user","guest"]);
If you want to add roles using the hook onCreateUser you could extend the user object, like so:
Accounts.onCreateUser(function(options, user)
{
if(!options || !user) {
throw new Meteor.Error('Problem to create new user');
return;
}
var user_extend =
{
username: options.username,
email: options.email,
profile: options.profile,
roles: ["user","guest"]
};
_.extend(user,user_extend);
return user
}
I prefer method number one because you can decide when and where you want to at the roles to that user because you will set those roles to every user that registers on your site. I hope it helps.

Related

Downstream SwiftUI view doesn't seem to pick up the Firebase Auth/users collection values from login

I'm trying to store a user profile from a Cloud Firestore Users collection, and have it be instantiated right when logging into the app, and make it editable from a downstream profile view, but I keep running into this " 'self' used before all stored properties are initialized" error.
I have an AuthenticationState class that handles login with Firebase Auth, stores the current logged-in user, and creates a document in my users collection that matches the auth user ID.
It also stores this as a userProfile variable within the authState class.
I think this is probably happening because I'm not storing this in a way that makes sense - I want to have this userProfile be a global variable that can be accessed by any view or View Model, and to make sure that it's instantiated based on the currently logged in Firebase Auth user, no matter where I access it from.
Current Code
I instantiate the authState class directly from my main app, and set it up as an environmentObject from the main view:
#main
struct GoalTogetherApp: App {
let authState: AuthenticationState
init() {
FirebaseApp.configure()
self.authState = AuthenticationState.shared
setupFirebase()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(authState)
}
}
}
And the class contains these variables (among others). Both loggedInUser and userProfile (which is the one I'm most interested in), start out as nil, and there are no initializers.
Once the sign in functions are called, that triggers loggedInUser to be set, and loggedInUser being set triggers userProfile to be set to the profile of the matching user in the users collection (or create a new one in Firestore if none exists, and set this variable to that):
class AuthenticationState: NSObject, ObservableObject {
// The firebase logged in user, and the userProfile associated with the users collection
#Published var loggedInUser: User?
#Published var userProfile: UserProfile?
static let shared = AuthenticationState()
Then I try to initialize it from the ProfilePage, like so (I'm trying to have email, and then other variables, be editable within the page, and then when they're saved, call back to the Firestore users collection document to update it):
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
// #State var profile: UserProfile
#State var email: String
init() {
let profile = authState.userProfile!
if profile.email != nil {
_email = State(initialValue: profile.email!)
} else {
_email = State(initialValue: "")
}
}
And I get a " 'self' used before all stored properties are initialized' error on the first line of the init (let profile = ...)
I'm assuming this is because it's creating a new instance of authState where userProfile is not already set. Wondering if there are any best practices for how to store this so I'm able to pick up that value that was set when the user logged in?
The simplest solution is to just give your email variable an initial value when declaring it:
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
#State var email: String = "" //<-- Here
init() {
let profile = authState.userProfile!
if profile.email != nil {
_email = State(initialValue: profile.email!)
}
}
var body: some View {
TextField("Email", text: $email)
}
}
However, I'd suggest that you may find potential issues with this strategy of doing everything in init. Because SwiftUI views are transient, if you had something that re-rendered the view hierarchy while a user was editing the field on this page, for example, you might find the #State getting reset while editing, for example. To avoid that, you might want to consider setting your values in onAppear:
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
#State var email: String = ""
var body: some View {
Form {
TextField("Email", text: $email)
}.onAppear {
email = authState.userProfile?.email ?? ""
}
}
}

Dynamically adding steps to SSO with IdentityServer4

I`m facing some problems when trying to customize one of the quickstarts from identityServer4 QuickStart 9, basically, I need to create a single sign-on application that will be used by several services, multiple web applications, one electron, and PhoneGap app.
Currently, my flow is a bit more complicated than simply authenticating the user, see below:
User inputs login and password -> system validates this piece of data and presents the user with a selection of possible sub-applications to select -> the user selects one of the sub-applications -> the system now requests the user to select a possible environment for this application (staging/production can be customized)
I want to do this flow on the authentication layer because otherwise, I would have to replicate all these steps on all the apps, and off-course I want the authentication to have separate development lifecycle.
Currently, I'm trying to make 3 modifications to achieve this:
PersistentGrantStore -> save this steps to a custom table using the
grant key as a reference. (something like
Key/application/environment)
IProfileService -> add custom claims that represent this steps
(stuck here), and are temporary, they only have meaning for this token and subsequent refreshes.
authenticationHandler -> validate if the user went through all the
steps
I will also need to make a modification to the token endpoint to accept these 2 parameters via custom header due to my spa`s apps
my question boils down to: is there a better way to this? am I overcomplicating this?
sorry if this question is too basic, but I`m not used to doing this type of auth.
If i understand you correctly, following way might be helpful.
Create a temp cookie and display select page after user loggedin:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
if (ModelState.IsValid)
{
var loginResult = .service.Check(model.Username, model.Password);
if (loginResult.IsSucceed)
{
await HttpContext.SignInAsync("TempCookies", loginResult.Principal);
var selectViewModel = new SelectViewModel();
model.ReturnUrl = model.ReturnUrl;
return View("SelectUserAndEnvironment", selectViewModel);
}
else
{
ModelState.AddModelError("", "****.");
return View(model);
}
}
return View(model);
}
Add claims you want and sign in for IdentityServerConstants.DefaultCookieAuthenticationScheme
[HttpPost]
[Authorize(AuthenticationSchemes = "TempCookies")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SelectUserAndEnvironment(SelectModel model)
{
// add claims from select input
var claims = new List<Claim>();
claims.Add(new Claim(<Type>, <Value>));
var p = new ClaimsPrincipal(new ClaimsIdentity(auth.Principal?.Identity, claims));
await HttpContext.SignOutAsync("TempCookies");
await HttpContext.SignInAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme, p);
return Redirect(model.ReturnUrl);
}
And use claims in ProfileService
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// you can get claims added in login action by using context.Subject.Claims
// other stuff
context.IssuedClaims = claims;
await Task.CompletedTask;
}
Finally add authentication scheme in Startup.cs
services.AddAuthentication()
.AddCookie("TempCookies", options =>
{
options.ExpireTimeSpan = new TimeSpan(0, 0, 300);
})
If you want to use external login, change above code appropriately.

asp.net core identity extract and save external login tokens and add claims to local identity

I am a stackoverflow noob so please go easy if I am doing this wrong.
I am using asp.net core with the default core identity template (local accounts).
I have accertained how to add claims to user principal when they login locally like so
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var user = await _userManager.FindByNameAsync(model.Email);
await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
And I have figured out how to get claims returned from the external login but I cannot figure out how I would add these before the user principal gets created in the ExternalLoginCallback function
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
else {
// extract claims from external token here
}
// assume add claims to user here before cookie gets created??
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
I am assuming the the _signInManager.ExternalLoginSignInAsync function works similar to the local login _signInManager.PasswordSignInAsync in the sense that once it is called, the cookie will be created. But I am just not sure.
Essentially what I am hoping to achieve, is understanding of how to add custom claims into the cookie that gets created regardless of how to user logins in (local or external), and how to persist these claims to the database if required.
I am planning on doing some work where if I have a user login using say google auth, I need to save that access_token from google, because I wish to call into the Google APIs later with it. So I need to be able to include this access_token in with the User Principal that gets created, and I would hope the cookie would have a claim on it I could use at the front end as well.
This might be out of scope on this question but I would also like when the google token expires, for some-how it to use the refresh token and go get a new one, or force the user to relogin.
Any help on this would be super appreciated, I have really tried hard to understand this without posting this question to stackoverflow. I have read many articles with lots of useful info, but does not provide the answers this specific question is asking. So Thank you very much in advance.
cheers
When you use await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value")); that actually updates the Identity's aspnetuserclaims table.
Whenever you sign in (by using _signInManager.PasswordSignIn or _signInManager.ExternalLoginSignInAsync) the claims from that table are read and added to the cookie that on every request becomes the Principal.
So you probably don't want to be calling the AddClaimAsync method from UserManager on every login.
Regarding external login providers, you have access to the claims when you call (in ExternalCallback and ExternalCallbackConfirmation if you are using the default templates) here:
var info = await _signInManager.GetExternalLoginInfoAsync();
The claims are in info.Principal.Claims.
The access token is not included by default. When it is, it will be here (along with the type and expiry date):
var accessToken = info.AuthenticationTokens.Single(f => f.Name == "access_token").Value;
var tokenType = info.AuthenticationTokens.Single(f => f.Name == "token_type").Value;
var expiryDate = info.AuthenticationTokens.Single(f => f.Name == "expires_at").Value;
To have the access token be included in the AuthenticationTokens collection, when you are configuring the GoogleAuthentication middleware set the SaveTokens flag to true:
app.UseGoogleAuthentication(new GoogleOptions{
ClientId = "...",
ClientSecret = "...",
SaveTokens = true
Now, if you want to have control over which claims go in the cookie you have to "take over" the process of creating the claims principal.
This is done for you when you use _signInManager.PasswordSignIn/ExternalLoginSignInAsync.
So, for example, for ExternalLoginSignInAsync replace:
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
With:
var user = await this._userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var claimsPrincipal = await this._signInManager.CreateUserPrincipalAsync(user);
((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim("accessToken", info.AuthenticationTokens.Single(t => t.Name == "access_token").Value));
await HttpContext.Authentication.SignInAsync("Identity.Application", claimsPrincipal);
"Identity.Application" is the default cookie name. You can change it in Startup's ConfigureServices method, for example to MainCookie:
services.Configure<IdentityOptions>(options => {
options.Cookies.ApplicationCookie.AuthenticationScheme = "MainCookie";
});
You still need to handle the ExternalCallbackConfirmation action in the AccountController. It will be similar to the example above.

Can IUserMapper be used to Change User Details

In Nancy FX how can I use the IUserMapper (if at all) to change a logged in users account details (name, email, password)?
// registering is straight forward
Post["/register", true] = async(parameters, ct) =>
{
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.RegisterUser(user); // user is registered
...
}
// but how can I change a registered user's details?
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var response = await mapper.?????(user);
...
}
You wouldn't use the IUserMapper at all, this really only exists for authentication purposes and nothing more.
When a user is authenticated then you get access to the UserName property. If you setup your mapper to assign the user's Id to the UserName then you can load your user, modify, and commit.
i.e:
Post["/profile", true] = async(parameters, ct) =>
{
this.RequiresAuthenticationAndLogOut();
var user = this.BindAndValidate<UserRegistration>();
var existingUser = await db.LoadAsync(int.Parse(CurrentUser.UserName));
existingUser.Name = user.Name;
...
return ...;
}
Also, you should never persist an object that's been bound from a client. The user may submit additional information you don't want them to.
Also I don't know where you got your IUserMapper from because in Nancy there is no Register.

Is is possible to use the userId in meteor Accounts.onCreateUser?

I want to initialize the default variables for a user on creation. Is there a good way to do this - I've tried using the userId in the onCreateUser without success.
Thanks
FYI, the _id is now valid in OnCreateUser as of Meteor 0.5.8...
You could simply extend the user collection to add any properties you want:
Accounts.onCreateUser(function(options, user) {
// We still want the default hook's 'profile' behavior.
if (options.profile) {
user.profile = options.profile;
user.profile.user_status = "new";
}
return user;
});
This will not update any collection other than users, so it cannot be used to the store the new user _id as a foreign key in another collection.

Resources