-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow guest users to checkout with conflicting email address #911
Comments
Posting the chat from Slack before the context is lost: matt Hi we are currently working to add multi channel support, in which one store requires login and the second store does not allow login (i.e. guest checkout only). As a result if a customer has an account in store 1 and attempts to purchase something in store 2, then they can run into an email address conflict error. This will occur if the email address of the guest checkout matches an email of an existing customer/user. We've pinpointed the blocking point to be the customer.service.ts file (see snippet below) and are wondering if there is any advice on how to handle this. I understand the logic in the snippet below - a guest order from store 2 should not be able to update an existing user account. However, a guest order from store 2 also shouldn't be blocked by an existing user account. if (existing) {
if (existing.user && errorOnExistingUser) {
// It is not permitted to modify an existing *registered* Customer
return new EmailAddressConflictError();
}
customer = patchEntity(existing, input);
customer.channels.push(await this.connection.getEntityOrThrow(ctx, Channel, ctx.channelId));
} michael This is an interesting edge case! matt I think we can generally assume it's the same person, because the order confirmation and details would be going to that email address. Even that customer record is updated, then the session still would show as unauthenticated, correct? We wouldn't want the user to be able access any account information based on the email they entered in the checkout process (i.e. previously used addresses, previous orders, etc.). I assume this wouldn't be an issue because the session service would still show it as unauthenticated with no active user, correct? michael I tend to agree with your assessment, but the problem is that doing so might open up a vulnerability where it would become possible to alter another person's address data if you know their email address. Although no data would be exposed, it is still an undesirable exploit which could conceivably be automated to cause trouble for the merchant. matt That certainly makes sense! Would it be possible to allow the guest checkout to proceed without trying to link to the customer? We don't need that link to occur since there is no need for the customer to login and view past orders. michael I think you should open a feature request for making this configurable in some way. I'm not totally sure of the right way to handle it at the moment, but I think we should be able to come up with a design that accommodates both the current behaviour (which is often desirable) and also allow you to opt-out of this constraint as in your use-case. |
Hey Michael, any thoughts or updates on this feature request? |
Hi Matt, No updates yet. It's in the "planned" queue, so I do plan to address it. I just have other issues that I plan to address first. What could speed up the process is some input around this:
and once we agree on something, a PR would be welcome. It could conceivable make it into the next minor release in that case. |
Hi Michael, I spoke with a fellow team member @WilliamMilne about this item and we came up with some ideas for discussion. We specifically looked at the
Prior to listing the options we discussed below, we decided that the solution should come from the server side. The opt out concept shouldn't be implemented through the GraqphQL API, since that would allow any client to define the behavior. That would not solve the security concern you mentioned above. Option 1
Option 2 The primary challenge or difficulty with this one is that it would be a bad UI to have to update every channel to have the exact same shared channels value. For example, if channel A is configured to share with channel B, then channel B must also be configured to share with channel A. Otherwise there would be some gaps in the logic or behavior. Alternatively, there could be a separate configuration/entity that groups user sharing in a single source (loosely similar to customer groups). Option 3
After additional consideration of this approach, I feel this would have more side effects than Option 1 and may change how behavior should be handled by other entities and services. So this doesn't feel like the most generic option. We are happy to elaborate on any of these options and are very interested in your thoughts! |
Hi, Thanks for taking the time to explore these solutions and lay them out clearly. That's really helpful! So, here's are the constraints I would like to work with:
Those 3 constraints would rule out the 3 options above, since they all would require adding a field to the Channel entity. I also want to clarify one point: in all 3 of your examples, in the case where "user sharing" is enabled, you are creating a new Customer entity. Is that a requirement in your case? Or is it acceptable to re-use the existing Customer, potentially update their name/contact details, and add them to the new Channel? The latter is what I understood from your initial issue. In either case, I think we can have a solution that covers both. So I propose a strategy-based solution whereby you can define interface GuestConflictStrategy extends InjectableStrategy {
handleGuestIdentifierConflict(
ctx: RequestContext,
existingCustomer: Customer,
input: CreateCustomerInput): Promise<Customer | EmailAddressConflictError> | Customer | EmailAddressConflictError;
} This function would return a Customer entity which would then be used for the guest order. Note that this way you could even (if you want to) create a brand new Customer entity rather than returning the So the async createOrUpdate(
ctx: RequestContext,
input: Partial<CreateCustomerInput> & { emailAddress: string },
errorOnExistingUser: boolean = false,
): Promise<Customer | EmailAddressConflictError> {
// omitted
if (existing) {
if (existing.user && errorOnExistingUser) {
const customerOrError = await this.configService.authOptions
.guestConflictStrategy.handleGuestIdentifierConflict(ctx, customer, input);
if (result instanceof Customer) {
customer = customerOrError ;
} else {
return customerOrError ;
}
}
customer = patchEntity(existing, input);
customer.channels.push(await this.connection.getEntityOrThrow(ctx, Channel, ctx.channelId));
} else {
// same as before
}
return this.connection.getRepository(ctx, Customer).save(customer);
} Would this solution make sense for you guys? |
Hi Michael, Thank you for the feedback, I definitely understand the constraints you want to work with and appreciate the alternative solution to a schema change. I think this is a great idea and allows for anyone to define the logic they are looking for. We aren't yet looking for a specific outcome, other than avoiding the email address conflict. If we were to return an existing customer instead of creating a new one, does that leave us vulnerable to the address pollution that you mentioned before? Regardless this solution does offer the flexibility to do whatever we decide on! |
Referencing this issue: #762 |
OK current thinking on this: GuestCheckoutStrategy {
addCustomerToOrder(ctx, input) {
// possible actions to take here
// - create a new guest customer, ignore existing registered customer
// - return registered customer
// - return error on conflict
// - always error (no guest checkout allowed)
}
} This strategy would live in Going to play around with an implementation of this and see what comes up. |
That sounds like a good solution 👌 To create a new guest customer, we do need to make sure that emailadresses dont have to be unique anymore. I am not sure if any other logic is dependant on that? |
|
Is your feature request related to a problem? Please describe.
See slack discussion here
We currently support two stores, one requires login and one does not allow for login at all. Since all checkouts in the second store are considered guest checkouts, the user is asked to enter their name and email address. If the email address matches an email address for the first store, then the user cannot checkout in the second store.
Describe the solution you'd like
It would be great to allow the user to checkout as a guest user and not be associated with any existing customer accounts.
Describe alternatives you've considered
Some alternatives discussed in the slack conversation.
Additional context
N/A
The text was updated successfully, but these errors were encountered: