Skip to content

Commit 02355f7

Browse files
authored
Merge pull request #531 from goblogplatform/fix/wizard-when-no-admin-user
Auto-promote first OAuth login to admin when no admin exists
2 parents b08177e + 1f722b6 commit 02355f7

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

auth/auth.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ func (a *Auth) LoginPostHandler(c *gin.Context) {
189189
existingUser = *user
190190
}
191191

192+
// On a fresh install where the operator has pre-populated .env (e.g. via
193+
// configuration management), the install wizard's UI flow is bypassed and
194+
// no admin user is ever created. Promote the first successful OAuth login
195+
// to admin so the operator can administer the site without re-running the
196+
// wizard. Same trust model as the wizard: whoever first completes OAuth
197+
// against the server's client_secret becomes the admin.
198+
if err := a.EnsureAdmin(user.ID); err != nil {
199+
log.Println("Error ensuring admin user: " + err.Error())
200+
}
201+
192202
//save the access token in the session
193203
session := sessions.Default(c)
194204
session.Set("token", data.AccessToken)
@@ -197,6 +207,27 @@ func (a *Auth) LoginPostHandler(c *gin.Context) {
197207
c.JSON(http.StatusOK, existingUser)
198208
}
199209

210+
// EnsureAdmin promotes the given BlogUser to admin if and only if no admin
211+
// user currently exists. Idempotent and safe to call on every login.
212+
//
213+
// The check-and-create runs inside a transaction so two concurrent first
214+
// logins can't both observe an empty admin_users table and both create a
215+
// row. Lookup errors other than "record not found" are surfaced rather
216+
// than swallowed as if no admin existed.
217+
func (a *Auth) EnsureAdmin(blogUserID int) error {
218+
return (*a.db).Transaction(func(tx *gorm.DB) error {
219+
var existing AdminUser
220+
err := tx.First(&existing).Error
221+
if err == nil {
222+
return nil // an admin already exists; nothing to do
223+
}
224+
if !errors.Is(err, gorm.ErrRecordNotFound) {
225+
return err
226+
}
227+
return tx.Create(&AdminUser{BlogUserID: blogUserID}).Error
228+
})
229+
}
230+
200231
// DisplayUserTable is a debug function, shows the user table
201232
func (a *Auth) DisplayUserTable() {
202233
var users []BlogUser

auth/auth_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,48 @@ func TestIsWizardMode_WithAdminUser_ReturnsFalse(t *testing.T) {
6060
t.Fatal("IsWizardMode must be false once an admin_users row exists")
6161
}
6262
}
63+
64+
func TestEnsureAdmin_NoAdmin_CreatesRow(t *testing.T) {
65+
a, db := newAuth(t)
66+
user := auth.BlogUser{ID: 42, Login: "operator"}
67+
db.Create(&user)
68+
69+
if err := a.EnsureAdmin(user.ID); err != nil {
70+
t.Fatalf("EnsureAdmin: %v", err)
71+
}
72+
73+
var count int64
74+
db.Model(&auth.AdminUser{}).Count(&count)
75+
if count != 1 {
76+
t.Fatalf("expected 1 admin_users row, got %d", count)
77+
}
78+
var got auth.AdminUser
79+
db.First(&got)
80+
if got.BlogUserID != user.ID {
81+
t.Fatalf("expected admin BlogUserID=%d, got %d", user.ID, got.BlogUserID)
82+
}
83+
}
84+
85+
func TestEnsureAdmin_AdminExists_NoOp(t *testing.T) {
86+
a, db := newAuth(t)
87+
first := auth.BlogUser{ID: 1, Login: "first"}
88+
second := auth.BlogUser{ID: 2, Login: "second"}
89+
db.Create(&first)
90+
db.Create(&second)
91+
db.Create(&auth.AdminUser{BlogUserID: first.ID, BlogUser: first})
92+
93+
if err := a.EnsureAdmin(second.ID); err != nil {
94+
t.Fatalf("EnsureAdmin: %v", err)
95+
}
96+
97+
var count int64
98+
db.Model(&auth.AdminUser{}).Count(&count)
99+
if count != 1 {
100+
t.Fatalf("expected EnsureAdmin to be a no-op when an admin exists, got %d admin rows", count)
101+
}
102+
var got auth.AdminUser
103+
db.First(&got)
104+
if got.BlogUserID != first.ID {
105+
t.Fatalf("expected first admin to remain (id=%d), got id=%d", first.ID, got.BlogUserID)
106+
}
107+
}

0 commit comments

Comments
 (0)