Files
phishingclub/backend/seed/migrate.go
Ronni Skansing e6bdd7100f fix migrate
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
2026-01-22 15:47:43 +01:00

388 lines
8.9 KiB
Go

package seed
import (
"crypto/rand"
"strings"
"github.com/go-errors/errors"
"github.com/google/uuid"
"github.com/phishingclub/phishingclub/app"
"github.com/phishingclub/phishingclub/build"
"github.com/phishingclub/phishingclub/data"
"github.com/phishingclub/phishingclub/database"
"github.com/phishingclub/phishingclub/errs"
"github.com/phishingclub/phishingclub/model"
"go.uber.org/zap"
"gorm.io/gorm"
)
// InitialInstallAndSeed installs the initial database migrations
func initialInstallAndSeed(
db *gorm.DB,
repositories *app.Repositories,
logger *zap.SugaredLogger,
usingSystemd bool,
) error {
tables := []any{
&database.Asset{},
&database.Option{},
&database.Company{},
&database.APISender{},
&database.APISenderHeader{},
&database.Role{},
&database.User{},
&database.Session{},
&database.Recipient{},
&database.RecipientGroup{},
&database.RecipientGroupRecipient{},
&database.Domain{},
&database.Page{},
&database.Proxy{},
&database.SMTPHeader{},
&database.SMTPConfiguration{},
&database.Email{},
&database.CampaignTemplate{},
&database.Campaign{},
&database.CampaignRecipientGroup{},
&database.CampaignRecipient{},
&database.Event{},
&database.CampaignEvent{},
&database.Attachment{},
&database.EmailAttachment{},
&database.AllowDeny{},
&database.CampaignAllowDeny{},
&database.Webhook{},
&database.Identifier{},
&database.CampaignStats{},
&database.OAuthProvider{},
&database.OAuthState{},
}
// disable foreign key constraints temporarily for sqlite to allow table recreation
logger.Debug("disabling foreign key constraints for migration")
err := db.Exec("PRAGMA foreign_keys = OFF").Error
if err != nil {
return errs.Wrap(errors.Errorf("failed to disable foreign keys: %w", err))
}
// create tables
logger.Debug("migrating tables")
err = db.AutoMigrate(
tables...,
)
if err != nil {
// re-enable foreign keys before returning error
db.Exec("PRAGMA foreign_keys = ON")
return errs.Wrap(
errors.Errorf("failed to migrate database: %w", err),
)
}
// re-enable foreign key constraints
logger.Debug("re-enabling foreign key constraints after migration")
err = db.Exec("PRAGMA foreign_keys = ON").Error
if err != nil {
return errs.Wrap(errors.Errorf("failed to re-enable foreign keys: %w", err))
}
for _, table := range tables {
t, ok := table.(database.Migrater)
if !ok {
// logger.Debugw("table has no extra migration", "table", table)
continue
}
// logger.Debugw("running extra migration for table", "table", table)
err := t.Migrate(db)
if err != nil {
return errs.Wrap(
errors.Errorf("failed to run extra migration for table %T: %w", table, err),
)
}
}
// seed settings levels default values
err = SeedSettings(db, usingSystemd)
if err != nil {
return errs.Wrap(
errors.Errorf("failed to seed log levels: %w", err),
)
}
// seed user roles
err = SeedRoles(repositories.Role)
if err != nil {
return errs.Wrap(
errors.Errorf("failed to seed roles: %w", err),
)
}
// seed events
err = SeedEvents(db)
if err != nil {
return errs.Wrap(
errors.Errorf("failed to seed events: %w", err),
)
}
// seed identifiers
err = SeedIdentifiers(db, repositories.Identifier)
if err != nil {
return errs.Wrap(
errors.Errorf("failed to seed identifiers: %w", err),
)
}
return nil
}
func SeedSettings(
db *gorm.DB,
usingSystemd bool,
) error {
// seed log levels
if build.Flags.Production {
err := seedLogLevels(db, "info", "silent")
if err != nil {
return err
}
} else {
err := seedLogLevels(db, "info", "info")
if err != nil {
return err
}
}
{
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyDBLogLevel).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyDBLogLevel,
Value: "info",
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
// seed max file size
{
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyMaxFileUploadSizeMB).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyMaxFileUploadSizeMB,
Value: data.OptionValueKeyMaxFileUploadSizeMBDefault,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed repeat offender threshold
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyRepeatOffenderMonths).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyRepeatOffenderMonths,
Value: "12", // Default to 12 months
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed sso option
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyAdminSSOLogin).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
v, err := model.NewSSOOptionDefault().ToJSON()
if err != nil {
return errs.Wrap(err)
}
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyAdminSSOLogin,
Value: string(v),
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed obfuscation template option
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyObfuscationTemplate).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyObfuscationTemplate,
Value: data.OptionValueObfuscationTemplateDefault,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed display mode option
// default to blackbox if option doesn't exist
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyDisplayMode).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyDisplayMode,
Value: data.OptionValueDisplayModeBlackbox,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed using systemd option
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyUsingSystemd).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
isUsingSystemdStr := data.OptionValueUsingSystemdYes
if !usingSystemd {
isUsingSystemdStr = data.OptionValueUsingSystemdNo
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyUsingSystemd,
Value: isUsingSystemdStr,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed proxy cookie name
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyProxyCookieName).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
// generate random 8-character cookie name
b := make([]byte, 8)
_, err := rand.Read(b)
if err != nil {
return errs.Wrap(err)
}
charset := "abcdefghijklmnopqrstuvwxyz"
cookieName := ""
for i := range b {
cookieName += string(charset[int(b[i])%len(charset)])
}
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyProxyCookieName,
Value: cookieName,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
return nil
}
// Migration migrates db
func migrate(db *gorm.DB) error {
// migration for attachments.embedded_content
// first add column as nullable
if err := db.Exec(`ALTER TABLE attachments ADD COLUMN embedded_content BOOLEAN`).Error; err != nil {
// column might already exist, ignore error
errMsg := strings.ToLower(err.Error())
if !strings.Contains(errMsg, "duplicate") && !strings.Contains(errMsg, "already exists") {
return errs.Wrap(err)
}
}
// update existing rows
if err := db.Exec(`UPDATE attachments SET embedded_content = false WHERE embedded_content IS NULL`).Error; err != nil {
return errs.Wrap(err)
}
// migration for email_attachments.is_inline
// first add column as nullable
if err := db.Exec(`ALTER TABLE email_attachments ADD COLUMN is_inline BOOLEAN`).Error; err != nil {
// column might already exist, ignore error
errMsg := strings.ToLower(err.Error())
if !strings.Contains(errMsg, "duplicate") && !strings.Contains(errMsg, "already exists") {
return errs.Wrap(err)
}
}
// update existing rows - default to false (regular attachments)
if err := db.Exec(`UPDATE email_attachments SET is_inline = false WHERE is_inline IS NULL`).Error; err != nil {
return errs.Wrap(err)
}
return nil
}