fix(backend): disable one-registration-per-IP by default

Shared NAT egress makes client IP unusable for uniqueness checks. Gate the
behavior behind REGISTRATION_ENFORCE_IP (default false) for public/invite signup.
This commit is contained in:
ahp
2026-10-07 23:13:22 +03:30
parent 92b1681241
commit 3cce2158e9
15 changed files with 103 additions and 53 deletions
+17 -15
View File
@@ -344,16 +344,14 @@ func EvaluateInviteAvailability(inv *models.RegistrationInvite, now time.Time) e
return nil
}
// RegisterInviteStudent creates a student, consumes the IP, and atomically consumes one invite seat.
// RegisterInviteStudent creates a student, optionally records the client IP, and consumes one invite seat.
// Empty ip skips one-registration-per-IP enforcement (NAT / shared egress).
func (s *Store) RegisterInviteStudent(
ctx context.Context,
inviteID int64,
name, email, username, passwordHash, phone, securityQuestion, securityAnswerHash, ip string,
) (*models.User, error) {
ip = strings.TrimSpace(ip)
if ip == "" {
return nil, errors.New("empty registration ip")
}
tx, err := s.pool.Begin(ctx)
if err != nil {
return nil, err
@@ -366,12 +364,14 @@ func (s *Store) RegisterInviteStudent(
return nil, ErrPhoneBlacklisted
}
var taken bool
if err := tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM registration_ips WHERE ip=$1)`, ip).Scan(&taken); err != nil {
return nil, err
}
if taken {
return nil, ErrRegistrationIPTaken
if ip != "" {
var taken bool
if err := tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM registration_ips WHERE ip=$1)`, ip).Scan(&taken); err != nil {
return nil, err
}
if taken {
return nil, ErrRegistrationIPTaken
}
}
tag, err := tx.Exec(ctx, `
@@ -436,12 +436,14 @@ func (s *Store) RegisterInviteStudent(
ON CONFLICT (user_id) DO NOTHING`, u.ID); err != nil {
return nil, err
}
if _, err := tx.Exec(ctx, `
INSERT INTO registration_ips (ip, user_id) VALUES ($1, $2)`, ip, u.ID); err != nil {
if isUniqueViolation(err) {
return nil, ErrRegistrationIPTaken
if ip != "" {
if _, err := tx.Exec(ctx, `
INSERT INTO registration_ips (ip, user_id) VALUES ($1, $2)`, ip, u.ID); err != nil {
if isUniqueViolation(err) {
return nil, ErrRegistrationIPTaken
}
return nil, err
}
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err