diff --git a/hscontrol/types/users.go b/hscontrol/types/users.go index f983d7f5..d0a7c19b 100644 --- a/hscontrol/types/users.go +++ b/hscontrol/types/users.go @@ -2,6 +2,8 @@ package types import ( "cmp" + "database/sql" + "net/mail" "strconv" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" @@ -22,19 +24,19 @@ type User struct { // Username for the user, is used if email is empty // Should not be used, please use Username(). - Name string `gorm:"unique"` + Name sql.NullString `gorm:"unique"` // Typically the full name of the user DisplayName string // Email of the user // Should not be used, please use Username(). - Email string + Email sql.NullString // Unique identifier of the user from OIDC, // comes from `sub` claim in the OIDC token // and is used to lookup the user. - ProviderIdentifier string `gorm:"index"` + ProviderIdentifier sql.NullString `gorm:"index"` // Provider is the origin of the user account, // same as RegistrationMethod, without authkey. @@ -51,7 +53,7 @@ type User struct { // should be used throughout headscale, in information returned to the // user and the Policy engine. func (u *User) Username() string { - return cmp.Or(u.Email, u.Name, u.ProviderIdentifier, strconv.FormatUint(uint64(u.ID), 10)) + return cmp.Or(u.Email.String, u.Name.String, u.ProviderIdentifier.String, strconv.FormatUint(uint64(u.ID), 10)) } // DisplayNameOrUsername returns the DisplayName if it exists, otherwise @@ -103,11 +105,11 @@ func (u *User) TailscaleUserProfile() tailcfg.UserProfile { func (u *User) Proto() *v1.User { return &v1.User{ Id: strconv.FormatUint(uint64(u.ID), util.Base10), - Name: u.Name, + Name: u.Name.String, CreatedAt: timestamppb.New(u.CreatedAt), DisplayName: u.DisplayName, - Email: u.Email, - ProviderId: u.ProviderIdentifier, + Email: u.Email.String, + ProviderId: u.ProviderIdentifier.String, Provider: u.Provider, ProfilePicUrl: u.ProfilePicURL, } @@ -129,10 +131,20 @@ type OIDCClaims struct { // FromClaim overrides a User from OIDC claims. // All fields will be updated, except for the ID. func (u *User) FromClaim(claims *OIDCClaims) { - u.ProviderIdentifier = claims.Sub + err := util.CheckForFQDNRules(claims.Username) + if err == nil { + u.Name.String = claims.Username + } + + if claims.EmailVerified { + _, err = mail.ParseAddress(claims.Email) + if err == nil { + u.Email.String = claims.Email + } + } + + u.ProviderIdentifier.String = claims.Sub u.DisplayName = claims.Name - u.Email = claims.Email - u.Name = claims.Username u.ProfilePicURL = claims.ProfilePictureURL u.Provider = util.RegisterMethodOIDC }