feat(server): scope every user lookup by instance
users is unique on (instance_id, email) now, so an unscoped FindOne could return an arbitrary one of several matching users. On the login path that means signing someone into a tenant that is not theirs. GetUserByEmail is deleted rather than left unused. Local sign-in resolves its instance from the host, falling back to the single instance a self-hosted deployment has, and refuses to guess otherwise. The OIDC cross-instance guard goes: a scoped lookup cannot return another instance's user, which is a stronger guarantee than the check it replaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,12 +62,21 @@ func CreateUser(instanceID, email, password, role, authSource string) (*models.U
|
||||
return u, err
|
||||
}
|
||||
|
||||
func GetUserByEmail(email string) (*models.User, error) {
|
||||
// GetUserInInstanceByEmail finds a user by address WITHIN one instance.
|
||||
//
|
||||
// There is deliberately no unscoped lookup by email. users is unique on
|
||||
// (instance_id, email), not on email alone, so an unscoped FindOne would return
|
||||
// an arbitrary one of several matching users — which on the login path means
|
||||
// signing someone into a tenant that is not theirs.
|
||||
func GetUserInInstanceByEmail(instanceID, email string) (*models.User, error) {
|
||||
email = strings.ToLower(strings.TrimSpace(email))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
var u models.User
|
||||
err := db.Col("users").FindOne(ctx, bson.M{"email": email}).Decode(&u)
|
||||
err := db.Col("users").FindOne(ctx, bson.M{
|
||||
"instance_id": instanceID,
|
||||
"email": email,
|
||||
}).Decode(&u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user