From 01f3dcc1899702e64f6b1e0a810d6fb139ef55d4 Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 25 Sep 2026 16:52:33 +0100 Subject: [PATCH] createuser: require exactly one of -phone or -email An account with neither can never be logged in to, since login resolves the account by phone or signup email. --- cmd/createuser/main.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/createuser/main.go b/cmd/createuser/main.go index d3557418..8dc3c014 100644 --- a/cmd/createuser/main.go +++ b/cmd/createuser/main.go @@ -14,7 +14,9 @@ // createuser -id 1000 [-first-name Test] [-last-name User] [-username testuser] -phone "15550001234" // createuser -id 1000 [-first-name Test] [-last-name User] [-username testuser] -email "test@example.com" // -// -phone and -email are mutually exclusive: an email-signup account never +// Exactly one of -phone or -email is required: login resolves the account by +// one of them, so an account with neither can never be signed in to. They are +// mutually exclusive because an email-signup account never // stores the address in users.phone directly (see internal/domain/emailphone.go) // -- it gets a synthetic "888"-prefixed display phone instead (the same one // assignEmailSignupDisplayPhone hands a real email-signup account), with the @@ -58,8 +60,8 @@ func main() { firstName := flag.String("first-name", "Test", "first name") lastName := flag.String("last-name", "", "last name") username := flag.String("username", "", "username, without @ (optional)") - phone := flag.String("phone", "", "phone number (optional; mutually exclusive with -email)") - email := flag.String("email", "", "email address for an email-signup account (optional; mutually exclusive with -phone)") + phone := flag.String("phone", "", "phone number (exactly one of -phone or -email is required)") + email := flag.String("email", "", "email address for an email-signup account (exactly one of -phone or -email is required)") force := flag.Bool("force", false, "skip the reserved-id / sequence-collision safety checks") flag.Parse() @@ -67,6 +69,10 @@ func main() { fmt.Fprintln(os.Stderr, "createuser: -id is required and must be positive") os.Exit(2) } + if *phone == "" && *email == "" { + fmt.Fprintln(os.Stderr, "createuser: one of -phone or -email is required - an account with neither cannot be logged in to") + os.Exit(2) + } if *phone != "" && *email != "" { fmt.Fprintln(os.Stderr, "createuser: -phone and -email are mutually exclusive") os.Exit(2) @@ -116,8 +122,8 @@ func main() { } // phone/username/signup_email all sit under partial unique indexes that - // exclude '', so leaving any of them blank never collides with another - // blank-valued account. + // exclude '', so a blank username or (for a phone account) signup_email + // never collides with another blank-valued account. row := pool.QueryRow(ctx, ` INSERT INTO users (id, access_hash, phone, signup_email, first_name, last_name, username, country_code) VALUES ($1, $2, $3, $4, $5, $6, $7, '')