1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-02-16 01:19:46 +02:00
pocketbase/core/record_model_superusers.go

119 lines
3.6 KiB
Go

package core
import (
"database/sql"
"errors"
"fmt"
"github.com/pocketbase/pocketbase/tools/hook"
"github.com/pocketbase/pocketbase/tools/router"
)
const CollectionNameSuperusers = "_superusers"
// DefaultInstallerEmail is the default superuser email address
// for the initial autogenerated superuser account.
const DefaultInstallerEmail = "__pbinstaller@example.com"
func (app *BaseApp) registerSuperuserHooks() {
app.OnRecordDelete(CollectionNameSuperusers).Bind(&hook.Handler[*RecordEvent]{
Id: "pbSuperusersRecordDelete",
Func: func(e *RecordEvent) error {
originalApp := e.App
txErr := e.App.RunInTransaction(func(txApp App) error {
e.App = txApp
total, err := e.App.CountRecords(CollectionNameSuperusers)
if err != nil {
return fmt.Errorf("failed to fetch total superusers count: %w", err)
}
if total == 1 {
return router.NewBadRequestError("You can't delete the only existing superuser", nil)
}
return e.Next()
})
e.App = originalApp
return txErr
},
Priority: -99,
})
recordSaveHandler := &hook.Handler[*RecordEvent]{
Id: "pbSuperusersRecordSaveExec",
Func: func(e *RecordEvent) error {
e.Record.SetVerified(true) // always mark superusers as verified
if err := e.Next(); err != nil {
return err
}
// ensure that the installer superuser is deleted
if e.Type == ModelEventTypeCreate && e.Record.Email() != DefaultInstallerEmail {
record, err := app.FindAuthRecordByEmail(CollectionNameSuperusers, DefaultInstallerEmail)
if errors.Is(err, sql.ErrNoRows) {
// already deleted
} else if err != nil {
e.App.Logger().Warn("Failed to fetch installer superuser", "error", err)
} else {
err = e.App.Delete(record)
if err != nil {
e.App.Logger().Warn("Failed to delete installer superuser", "error", err)
}
}
}
return nil
},
Priority: -99,
}
app.OnRecordCreateExecute(CollectionNameSuperusers).Bind(recordSaveHandler)
app.OnRecordUpdateExecute(CollectionNameSuperusers).Bind(recordSaveHandler)
// prevent sending password reset emails to the installer address
app.OnMailerRecordPasswordResetSend(CollectionNameSuperusers).Bind(&hook.Handler[*MailerRecordEvent]{
Id: "pbSuperusersInstallerPasswordReset",
Func: func(e *MailerRecordEvent) error {
if e.Record.Email() == DefaultInstallerEmail {
return errors.New("cannot reset the password for the installer superuser")
}
return e.Next()
},
})
collectionSaveHandler := &hook.Handler[*CollectionEvent]{
Id: "pbSuperusersCollectionSaveExec",
Func: func(e *CollectionEvent) error {
// don't allow name change even if executed with SaveNoValidate
e.Collection.Name = CollectionNameSuperusers
// for now don't allow superusers OAuth2 since we don't want
// to accidentally create a new superuser by just OAuth2 signin
e.Collection.OAuth2.Enabled = false
e.Collection.OAuth2.Providers = nil
// force password auth
e.Collection.PasswordAuth.Enabled = true
// for superusers we don't allow for now standalone OTP auth and always require to be combined with MFA
if e.Collection.OTP.Enabled {
e.Collection.MFA.Enabled = true
}
return e.Next()
},
Priority: 99,
}
app.OnCollectionCreateExecute(CollectionNameSuperusers).Bind(collectionSaveHandler)
app.OnCollectionUpdateExecute(CollectionNameSuperusers).Bind(collectionSaveHandler)
}
// IsSuperuser returns whether the current record is a superuser, aka.
// whether the record is from the _superusers collection.
func (m *Record) IsSuperuser() bool {
return m.Collection().Name == CollectionNameSuperusers
}