@@ -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
201232func (a * Auth ) DisplayUserTable () {
202233 var users []BlogUser
0 commit comments