Update viewer fields and connection on Store - graphql

I'm trying to update the values and connections on my current viewer within the Relay store.
So without calling the mutation signIn if I print:
console.log(viewer.name) // "Visitor"
console.log(viewer.is_anonymous) // true
on Mutations we got the method updater which gives us the store, so in my mutation I'm doing something like this:
mutation SignInMutation($input: SignInInput!){
signIn(input: $input){
user {
id
name
email
is_anonymous
notifications{
edges{
node {
id
...NotificationItem_notification
}
}
}
}
token
}
}
So my updater method has:
const viewer = store.get(viewer_id);
const signIn = store.getRootField('signIn');
viewer.copyFieldsFrom(signIn.getLinkedRecord('user'))
After this I updated the store I got the name email is_anonymous fields updated with the data that just came from the graphql endpoint (I mean now name is "Erick", is_anonymous is now false, which is great), but If I try to do viewer.notifications and render it, the length of the viewer.connections seem to be 0 even when it has notifications.
How can I update my current viewer and add the notifications from the MutationPayload into the store without the need to force fetch?
Im using the latest relay-modern and graphql.
PS: Sorry for the bad formation, but is just impossible to format the code the way OF wants me to, i formated it to 4 spaces and still gave me errors.

With some reorganisation of your GraphQL schema it might be possible to remove the need to interact directly with the Relay store after your sign-in mutation. Consider:
viewer {
id
currentUser {
name
email
}
}
When a user that is not logged in, currentUser would return null.
You could then modify your login mutation to be:
mutation SignInMutation($input: SignInInput!){
signIn(input: $input){
viewer {
id
currentUser {
name
email
token
}
}
}
}
Knowing the 'nullability' of the currentUser field provides an elegant way of determining if the user is logged in or not.
Based on the presence of the token field implies that you are using JWT or similar to track login status. You would need to store this token in local storage and attach it to the headers of the outgoing Relay requests to your GraphQL endpoint if it is present.
Storing the token itself would have to be done in the onCompleted callback of where you make the mutation request (you will have access to the payload returned by the server in the arguments of the callback function).
As an alternative to the token, you could also explore using cookies which would provide the same user experience but likely require less work to implement then JWT tokens.

Related

Migrating User to Cognito on Sign In

I am trying to migrate users to Cognito when they sign in the first time. For this I wrote a lambda function that does call an API to check if the users exist in db or not ? if the user exists, it will be created in cognito but I am not sure how do I tell the application that user is created and it should allow the user to login .
Here is the code in c#:
public async Task<Stream> FunctionHandlerAsync(Stream stream, ILambdaContext context)
{
RootObject rootObj = DeserializeStream(stream);
User user = new User(rootObj.userName, rootObj.request.password);
ApiResponse apiResponse = await MobileAuthenticateAsync(user.UserName, user.Password);
// Considering apiResponse returns "user authenticated", we create the user in //cognito. This is working.
// How do I send response back to Application so it knows that user is // //created and authenticated and should be allowed to login.
//Before returning stream, I am setting following 2 status.
rootObj.response.finalUserStatus = "CONFIRMED"; // is this correct ?
rootObj.response.messageAction = "SUPPRESS";
return SerializeToStream(rootObj);;
}
You're pretty close.
You can see the full documentation on the Migrate User Lambda Trigger page, however in short you need your response to look like:
{
response: {
userAttributes: {
email: 'user#example.com',
email_verified: true,
custom:myAttribute: 123,
},
finalUserStatus: 'CONFIRMED',
messageAction: 'SUPPRESS',
forceAliasCreation: false,
}
}
Where:
userAttribute: this is a dictionary/map of the user's attributes keys in cognito (note that any custom attributes need to be prefixed with custom:), to the values from the system you're migrating from. You do not need to provide all of these, although if you're using an email alias you may want to set email_verified: true to prevent the user having to re-verify their e-mail address.
finalUserStatus: if you set this to CONFIRMED then the user will not have to re-confirm their email address/phone number, which is probably a sensible default. If you are concerned that the password is given as plain-text to cognito this first-time, you can instead use RESET_REQUIRED to force them to change their password on first sign-in.
messageAction: should probably be SUPPRESS unless you want to send them a welcome email on migration.
forceAliasCreation: is important only if you're using email aliases, as it stops users who manage to sign-up into cognito being replaced on migration.
If you respond with this (keeping the rest of the original rootObj is convenient but not required then the user will migrated with attributes as specified.
If you throw (or fail to respond with the correct event shape) then the migration lambda fails and the user is told that they couldn't migrated. For example, because they do not exist in your old user database, or they haven't provided the right credentials.

What is a good way to pass additional information to the http response when issuing access token with Owin middleware in Asp Net Web Api?

I am using Owin middleware to implement token-based security for my application. When issuing the access token to the client I would also like to pass User Id along with the token, so that, the client application will know the User Id and will be able to call GetUserById (one of the methods inside UserController) in order to show the user his starting page. The best solution I could come up with so far is just adding User Id to the response header. Take a look at my OAuthAuthorizationServerProvider class, in GrantResourceOwnerCredentialsmethod I am adding User Id to the header, using context.Response.Headers.Add("User-Id", new string[] { "1" })
Here is the implementation of my OAuthAuthorizationServerProviderclass
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
//The actual credential check will be added later
if (context.UserName=="user" && context.Password=="user")
{
identity.AddClaim(new Claim("Id", "1"));
context.Validated(identity);
//Here I am adding User Id to the response header
context.Response.Headers.Add("User-Id", new string[] { "1" });
}
else
{
context.SetError("invalid_grant","The credentials provided are not valid");
return;
}
}
}
Basically the client then will have to read User-Id from the header. Is this a good solution I came up with or there is a better one? Also what if I want to pass the whole User object with all its properties to the response is it possible and how to do this?
Since you store the ID already in the claims, why don't you just decode your token on the client and read out the user id like that? Passing it through the headers could allow tampering with it (security).
Have a look on how you could achieve to decode your token and read the claims. This is a c# example https://contos.io/peeking-inside-your-jwt-tokens-using-c-bf6a729d06c8 but this could also be done even through javascript.
This, assuming you use the JWT-format as token (was not specified in your initial question).
Bad way to store UserID as a response header back to client side. This is a huge security concern.
The best way would be to store it as a Claims.
It is very easy to achieve this and get back the claims in the client side.
In your controller, call this and remember to reference
using Microsoft.AspNet.Identity;
var userId = User.Identity.GetUserId();

Braintree, How do I delete a users credit card with the nonce from the client?

Server side there is a function to delete a payment method (result = Braintree::PaymentMethod.delete("the_token")) but it takes a payment method token. How do I get the payment methods token with the nonce from the client?
edit: I'm not using the drop in UI. I have a custom list of credit cards the user has (using the Javascript v3 SDK). I want to have a button to delete cards. The JS SDK dosnt give the credit cards token, just a nonce. What is the process for turning the data available to the client into something I can use to delete the card on the server?
edit2: The list of credit cards on the clent side uses the VaultManager from the JavaScript v3 SDK. It returns a fetchPaymentMethodsPayload.
This is the client side code:
_loadPaymentMethods() {
this.paymentService.getBraintreeToken().then( token => {
this.braintreeClient.create({
authorization: token
}, (clientErr, clientInstance) => {
if (clientErr) {
// Handle error in client creation
return;
}
var options = {
client: clientInstance,
};
this.vaultManager.create(options, (err, vaultInstance) => {
if (err) {
console.log(err);
return;
}
vaultInstance.fetchPaymentMethods({ defaultFirst: true }, (err, paymentMethods) => {
paymentMethods.forEach( paymentMethod => {
if(paymentMethod.type == 'CreditCard') {
this.cards.push(paymentMethod);
if(paymentMethod.default) {
this.card = paymentMethod;
}
}
});
});
});
});
});
}
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
If using VaultManager on the client-side to populate your cards, you will not have the functionality to allow a user to delete one of those cards. The reason for this goes back to what you said, that nonces are what's returned on the fetchPaymentMethodsPayload method. VaultManager can populate a nonce that's associated with an already created card, since it's only passing that nonce into a Transaction.sale() call. Since the nonce is populated when the form is rendered, you can not search for that nonce compared to a payment method in the vault, since it will not exist previously and nonces are meant for one time use. This is why nonces aren't passed into PaymentMethod.find() calls.
To accomplish your task you would need to build out custom logic that mimics what Vault Manager does; however, would need to returns the tokens. One way would be as mentioned in my comment: by finding the customer object and grabbing the customer's payment methods, and then pulling out the tokens associated with those payment methods.
I noticed that when you create a payment method with same creds, it won't get duplicated. So it's kinda like "find or create", you can then use that result to get the token and then do a gateway.payment_method.delete( result.payment_method.token )

Should I store the ID token persistently in Google sign in?

I am using Google sign-in in my app, and I will send the ID token to my backhand server as soon as the user signed in and the ID token is retrieved. For now I will add the ID token to the header of each HTTP request, and I validate it, get user's ID and respond data back to my app. I am wondering if it is OK to store the ID token persistently and use it for all the future request. Will the ID token change or expire some time? If so, how to get new ID token? I can't find any approach other than asking user to sign in again. Or should I only validate the ID token for once and use ID directly in the future requests?
Don't store an ID token. Google ID tokens are issued for one hour validity and will expire, you can simply use silentSignIn in your app to get a new one without any user interaction. If your existing token hasn't expired yet, you will get the (cached) version back (OptionalPendingResult returned will have isDone() == true); if it expired already, you will get a refreshed one (but it will take a little longer and thus OptionalPendingResult isDone() will be false).
Here is sample code (UI thread, see note below about a worker thread):
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
...
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result); // result.getSignInAccount().getIdToken(), etc.
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(GoogleSignInResult googleSignInResult) {
handleSignInResult(googleSignInResult); // result.getSignInAccount().getIdToken(), etc.
}
});
}
Keep in mind whether you call silentSignIn on a UI thread or worker thread. If you call it on worker thread, take a look at this post with blockingConnect() + await() which simplifies the code a lot:
Silent sign in to retrieve token with GoogleApiClient

Parse.com not returning emailVerified value on User object

I've set up my app to enable email verification and the emails come through fine. Trouble is, when I make a call to retrieve a User object, either with Parse.User.current() or by querying by id, the response does not contain the emailVerified field. I can't therefore check if the user is email verified or not.
I've tried this both in client side code and in cloud code with the same result.
You can try this out in your own code with a very simple snippet in the console:
var user = Parse.User.logIn("your_username", "your_password", {
success: function(user) {
console.log(user)
}
});
That field seems to be protected, the only solution I can think of would be to query for where the value is true using some variant of where and check if you get a user back or not. If user is null, they haven't verified their email.

Resources