Did anyone experience the issue that randomly peop...
# support-questions
s
Did anyone experience the issue that randomly people that login suddenly end up as another user? We had this case now multiple times very randomly. Of course the users can't really tell what they did but one simply logged in yesterday and ended up being someone else. We use the method
EmailPassword.signIn
with no logic in between or anything hooking in.
n
Hi @segidev , Could you elaborate a little on the issue you were facing? (Examples and code for your setup would help)
s
Sure, this is the code we have in the Frontend:
Copy code
javascript
async login(payload: { email: string; password: string }) {
  const response = await EmailPassword.signIn({
    formFields: [
      {
        id: "email",
        value: payload.email,
      },
      {
        id: "password",
        value: payload.password,
      },
    ],
  });

  if (response.status === "OK") {
    const verification = await EmailVerification.isEmailVerified();

    if (!verification.isVerified) {
      await EmailVerification.sendVerificationEmail();
      throw Error("emailverification");
    } else {
      await this.verifyAuth();
    }
    return;
  }
  throw Error(response.status);
}
Initialized it is with that:
Copy code
javascript
SuperTokens.init({
  appInfo: {
    appName: "ono",
    apiDomain: import.meta.env.VITE_API_HOST,
    apiBasePath: "/auth",
  },
  recipeList: [EmailVerification.init(), EmailPassword.init(), Session.init()],
});
The user was not logged in (at least that's what he told) he then logged in and saw that the account he was using was from another user
n
And how are you identifying users?
s
We send a GET request to the /user route (which contains the cookies then) and use the session to receive the user from the database
Copy code
go
func (c *UserController) getUser(ctx *gin.Context) {
    session := session.GetSessionFromRequestContext(ctx.Request.Context())
    userID := uuid.MustParse(session.GetUserID())

    // Request the full user
    u, err := c.getFullUser(userID)
    if err != nil {
        NewError(ctx, err)
        return
    }

    ctx.JSON(http.StatusOK, u)
}
Using
"github.com/supertokens/supertokens-golang/recipe/session"
n
What does
getFullUser(userID)
do?
s
The route protection (using Gin) is done with
Copy code
go
func OnlyAuthorized(options *sessmodels.VerifySessionOptions) gin.HandlerFunc {
    return func(c *gin.Context) {
        session.VerifySession(
            options,
            func(rw http.ResponseWriter, r *http.Request) {
                c.Request = c.Request.WithContext(r.Context())
                c.Next()
            },
        )(c.Writer, c.Request)
        // We call Abort so that the next handler in the chain is not called, unless we call Next explicitly
        c.Abort()
    }
}
The
getFullUser
does this:
Copy code
go
func (c *UserController) getFullUser(userID uuid.UUID) (*models.User, error) {
    u, err := c.dataService.DB.User.Query().Where(user.ID(userID)).WithProfile().Only(context.Background())
    if err != nil {
        return nil, err
    }
    response := models.User{
        ID:    u.ID.String(),
        EMail: u.Email,
    }
    if u.Edges.Profile != nil {
        response.Profile = &models.UserProfileResponse{
            BaseViewModel: models.BaseViewModel{
                ID:         u.Edges.Profile.ID,
                CreateTime: u.Edges.Profile.CreateTime.UTC().Format(time.RFC3339),
            },
            UserProfileRequest: models.UserProfileRequest{
                FirstName: u.Edges.Profile.FirstName,
                LastName:  u.Edges.Profile.LastName,
            },
        }
    }
    return &response, nil
}
Could there be a relation to users that were logged in once, but then the account was deleted but they still have an old refresh token or something? But still then it doesn't make sense why they end up being another user
n
How are you populating users in your DB after sign up? Also to answer your question users who's accounts were deleted would get an unauthorised response, they still would not be treated as a different user
s
The signup is simply:
Copy code
ts
async register(payload: { email: string; password: string }) {
  const response = await EmailPassword.signUp({
    formFields: [
      {
        id: "email",
        value: payload.email,
      },
      {
        id: "password",
        value: payload.password,
      },
    ],
  });

  if (response.status === "OK") {
    await EmailVerification.sendVerificationEmail();
    return;
  }
  throw Error(response.status);
}
n
I meant on your backend
s
There is nothing done in the API further
We use the SuperTokens with GIN which exposes those routes
We have no control over these routes, or let's say we do not interfer with them
n
So
c.dataService.DB.User.Query().Where(user.ID(userID))
is not your custom own database then?
s
The only thing we have is this to connect ST to Gin
Copy code
go
// Adding the SuperTokens middleware
r.Use(func(c *gin.Context) {
    supertokens.Middleware(
        http.HandlerFunc(
            func(rw http.ResponseWriter, r *http.Request) {
                c.Next()
            },
        ),
    ).ServeHTTP(c.Writer, c.Request)
    c.Abort()
})
Currently we are using ent framework. We have "faked" the ent model to be able to use the supertokens generated table
But that is just a query builder
And the query will query the
emailpassword_users
table
Copy code
go
type User struct {
    ent.Schema
}

func (User) Annotations() []schema.Annotation {
    return []schema.Annotation{
        entsql.Annotation{Table: "emailpassword_users"},
    }
}

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("id").
            Unique().
            GoType(uuid.New()).
            Immutable().
            StorageKey("user_id"),
        field.String("email").Unique().Immutable(),
        field.String("password_hash").Unique().Sensitive().Immutable(),
        field.Int("time_joined").Immutable(),
    }
}

func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("profile", UserProfile.Type).
            Unique(),
}
n
So im a little unclear on a few things here: - You use EmailPassword.signin/signUp on the frontend which is correct - On the backend you dont seem to be doing anything post sign up so im not sure how you are storing profile information of a user - Your logic to fetch user information seems fully custom as well (the backend SDK has some helper function for this but you dont seem to be using them)
s
The profile is not relevant
It's not used yet
n
So how does the user identify whether or not they ended up being logged in as someone else?
s
We have the getFullUser to be able to fetch the Profile along with what Supertokens provides
Because the email that comes back from the query to the
emailpassword_users
table is wrong. The whole session was for another user and therefore all the data of that user could be seen
It feels like a wrong access or refresh token was given
That's my only explanation
Since we always use
session.GetSessionFromRequestContext
to retreive the current user
n
So I would first recommend using some of the helper functions to fetch user information from SuperTokens instead of querying the database yourself. For example using https://supertokens.com/docs/emailpassword/common-customizations/get-user-info#using-getuserbyid instead of querying the
emailpassword_users
table directly
Users would never get incorrect tokens in the responses, the only thing I can think of is that they logged in as a different user at some point (incognito, different browser etc) and then did not realise when they switched between them making it appear as if its the wrong account This is assuming ofcourse you dont have any custom logic for interacting directly with the SuperTokens databases. To rule this out, can you post the logic for where you call SuperTokens init on the backend?
s
Sure i have to upload a file cause the message is too long
n
Sure
s
But the user never was logged in as the user who he ended up
He is an external user that should never have access to that account
n
That looks right, what does
this.verifyAuth()
on the frontend do?
s
Copy code
ts
async verifyAuth() {
  if (await Session.doesSessionExist()) {
    if (!(await EmailVerification.isEmailVerified()).isVerified) {
      return false;
    }
    try {
      const resp = await axiosInstance.get<SuperTokenUserViewModel>(
        "/api/v1/user"
      );
      this.setUser(resp.data);
      return true;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        if (error.code === "ERR_NETWORK") {
          this.setGlobalToast({
            detail: i18n.global.t(trans.NETWORK_PROBLEM_PLEASE_TRY_AGAIN),
            life: 3000,
            severity: "error",
          });
        }
      }
      return false;
    }
  }
  return false;
}
r
have you been able to replicate this issue at all? Cause otherwise it's really hard to debug what's going on
s
Not on my side
But it happened already 3 times
So it is an existing issue
But totally not reproducible
r
hmm. This is a tough one
do you have any recordings for what the user did?
s
Sadly not but i will ask if he can reproduce it
And if then have a session where he can show it so i can retreive the current cookies, browser etc
Thank you so far. I will come back if i have news
r
sounds good. Thanks
s
@rp So the client said he was logged in and did a simple browser refresh, then was the other user.
Could it be that due to a database migration somehow the
session_info
table is wrongly mixed up with the
emailpassword_users
?
What would be the proper way to clear all sessions. By truncating the
session_info
table?
n
@kakashi_44 Can help here
k
Hey @segidev , truncating
session_info
table should be good enough if you want to remove all the sessions
s
Ok thank you 👍
2 Views