diff --git a/cmd/server/assets/users/_form.html b/cmd/server/assets/users/_form.html index 6039469b8..38031c7e3 100644 --- a/cmd/server/assets/users/_form.html +++ b/cmd/server/assets/users/_form.html @@ -1,6 +1,7 @@ {{define "users/_form"}} -{{$currentRealm := .currentRealm}} {{$user := .user}} +{{$currentRealm := .currentRealm}} +{{$user := .user}} {{if $user.ID}}
diff --git a/cmd/server/assets/users/index.html b/cmd/server/assets/users/index.html index 8399474c6..854405f1c 100644 --- a/cmd/server/assets/users/index.html +++ b/cmd/server/assets/users/index.html @@ -59,13 +59,20 @@

Users

{{end}} - {{- /* cannot delete yourself */ -}} {{if not (eq .ID $currentUser.ID)}} + {{- /* cannot delete yourself */ -}} + {{else if $currentUser.Admin}} + {{- /* system admins can remove themselves */ -}} + + + {{end}} diff --git a/pkg/controller/user/create.go b/pkg/controller/user/create.go index 61b07d636..fb6ffc9c3 100644 --- a/pkg/controller/user/create.go +++ b/pkg/controller/user/create.go @@ -18,10 +18,8 @@ import ( "context" "net/http" - "firebase.google.com/go/auth" "github.com/google/exposure-notifications-verification-server/pkg/controller" "github.com/google/exposure-notifications-verification-server/pkg/database" - "github.com/sethvargo/go-password/password" ) func (c *Controller) HandleCreate() http.Handler { @@ -98,27 +96,13 @@ func (c *Controller) HandleCreate() http.Handler { return } - if _, err := c.client.GetUserByEmail(ctx, user.Email); auth.IsUserNotFound(err) { - pwd, err := password.Generate(24, 8, 8, false, true) - if err != nil { - flash.Alert("Failed to generate password for '%v'", form.Email) - c.renderNew(ctx, w, user, false) - return - } - - fbUser := &auth.UserToCreate{} - fbUser.Email(user.Email).DisplayName(user.Name).Password(pwd) - if _, err = c.client.CreateUser(ctx, fbUser); err != nil { - flash.Alert("Error creating user '%v'", form.Email) - c.renderNew(ctx, w, user, false) - return - } - - c.renderNew(ctx, w, user, true) - return + created, err := user.CreateFirebaseUser(ctx, c.client) + if err != nil { + flash.Alert("Failed to create user: %v", err) + c.renderNew(ctx, w, user, false) } - c.renderNew(ctx, w, user, false) + c.renderNew(ctx, w, user, created) }) } diff --git a/pkg/controller/user/delete.go b/pkg/controller/user/delete.go index 374a5d561..c0f77a7ec 100644 --- a/pkg/controller/user/delete.go +++ b/pkg/controller/user/delete.go @@ -40,6 +40,12 @@ func (c *Controller) HandleDelete() http.Handler { return } + currentUser := controller.UserFromContext(ctx) + if realm == nil { + controller.MissingUser(w, r, c.h) + return + } + user, err := realm.FindUser(c.db, vars["id"]) if err != nil { if database.IsNotFound(err) { @@ -51,6 +57,13 @@ func (c *Controller) HandleDelete() http.Handler { return } + // Do not allow users to remove themselves unless they are admins. + if user.ID == currentUser.ID && !currentUser.Admin { + flash.Error("Failed to remove user from realm: cannot remove self") + http.Redirect(w, r, "/users", http.StatusSeeOther) + return + } + user.RemoveRealm(realm) if err := c.db.SaveUser(user); err != nil { @@ -59,6 +72,15 @@ func (c *Controller) HandleDelete() http.Handler { return } + // If the user removed themselves from a realm, clear it from the session to + // avoid a weird redirect. + if user.ID == currentUser.ID { + flash.Alert("Successfully removed you from the realm") + controller.ClearSessionRealm(session) + http.Redirect(w, r, "/home", http.StatusSeeOther) + return + } + flash.Alert("Successfully removed user %v from realm", user.Email) http.Redirect(w, r, "/users", http.StatusSeeOther) }) diff --git a/pkg/database/user.go b/pkg/database/user.go index 4437700cd..ed4001de2 100644 --- a/pkg/database/user.go +++ b/pkg/database/user.go @@ -15,11 +15,14 @@ package database import ( + "context" "fmt" "strings" "time" + "firebase.google.com/go/auth" "github.com/jinzhu/gorm" + "github.com/sethvargo/go-password/password" ) // User represents a user of the system @@ -177,6 +180,34 @@ func (db *Database) TouchUserRevokeCheck(u *User) error { Error } +// CreateFirebaseUser creates the associated Firebase user for this database +// user. It does nothing if the firebase user already exists. If the firebase +// user does not exist, it generates a random password. The returned boolean +// indicates if the user was created. +func (u *User) CreateFirebaseUser(ctx context.Context, fbAuth *auth.Client) (bool, error) { + if _, err := fbAuth.GetUserByEmail(ctx, u.Email); err != nil { + if !auth.IsUserNotFound(err) { + return false, fmt.Errorf("failed lookup firebase user: %w", err) + } + + pwd, err := password.Generate(24, 8, 8, false, true) + if err != nil { + return false, fmt.Errorf("failed to generate password: %w", err) + } + + fbUser := &auth.UserToCreate{} + fbUser = fbUser.Email(u.Email) + fbUser = fbUser.Password(pwd) + fbUser = fbUser.DisplayName(u.Name) + if _, err := fbAuth.CreateUser(ctx, fbUser); err != nil { + return false, fmt.Errorf("failed to create firebase user: %w", err) + } + return true, nil + } + + return false, nil +} + // SaveUser updates the user in the database. func (db *Database) SaveUser(u *User) error { db.db.Model(u).Association("Realms").Replace(u.Realms)