mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
Sweep the surviving auth-side tests so they no longer assert on
deleted constants / models / config knobs:
- collection / record exports + indexes / record-table-sync drop
the password column from auth-collection bootstrap fixtures and
the JSON snapshots
- TestRecordPublicExportAndMarshalJSON keeps the legacy "drop
password key from auth-record export" invariant (now enforced as
a hard-coded delete on the record_model.go side, not via the
field type) so accidental Set("password", x) calls never leak
- api-layer crud/auth-methods/collection-import scenarios drop the
"password / passwordConfirm / oldPassword / MFA / OTP" expected
keys and switch the auth-collection-create error fixtures to
email-validation + manager-access scenarios that still exist
- migrate-cmd / jsvm bind counts updated; jsvm $mails surface
drops to {verification, email-change}; PasswordField + the
sendRecordPasswordReset / sendRecordOTP / sendRecordAuthAlert
bindings removed from the type generator's expected fixture
- migrations/1747094400 raw-SQL implementation: the old delete
path went through the collection model (which can't rehydrate
pre-rip rows that still encode the deleted password field type)
and the system-collection guard. New approach: DROP TABLE IF
EXISTS + DELETE FROM _collections WHERE name=$x, then strip the
password entry from each surviving auth collection's JSON
fields array and ALTER TABLE DROP COLUMN. Idempotent on fresh
installs (no legacy table → no-op).
apis ./core ./plugins/jsvm ./forms all run green now.
109 lines
3.5 KiB
Go
109 lines
3.5 KiB
Go
package migrations
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/hanzoai/dbx"
|
|
"github.com/hanzoai/base/core"
|
|
)
|
|
|
|
// IAM-native cleanup: drop the four legacy local-auth artifact
|
|
// collections (_mfas, _otps, _externalAuths, _authOrigins) and strip
|
|
// the `password` field from every surviving auth collection.
|
|
//
|
|
// On a fresh install these tables never exist (the init migration
|
|
// stopped emitting them), so the steps no-op. On an upgrade from a
|
|
// pre-IAM Base build, this migration retires the dead schema. There
|
|
// is no Down: IAM is the only auth source going forward.
|
|
//
|
|
// We operate at the raw SQL layer because the deleted password Field
|
|
// type means the collection-model unmarshaller cannot rehydrate the
|
|
// pre-rip auth-collection schema rows. Going through the regular
|
|
// app.Delete + collection-save path explodes on the
|
|
// "unknown field type: password" decoder error.
|
|
func init() {
|
|
core.SystemMigrations.Register(func(txApp core.App) error {
|
|
legacyAuxCollections := []string{
|
|
"_mfas",
|
|
"_otps",
|
|
"_externalAuths",
|
|
"_authOrigins",
|
|
}
|
|
|
|
for _, name := range legacyAuxCollections {
|
|
// Drop the record table first if it exists. Use IF EXISTS
|
|
// so a fresh install (where the init migration no longer
|
|
// creates these tables) is a no-op.
|
|
if _, err := txApp.DB().NewQuery(
|
|
fmt.Sprintf("DROP TABLE IF EXISTS {{%s}}", name),
|
|
).Execute(); err != nil {
|
|
return fmt.Errorf("failed to drop legacy auth table %q: %w", name, err)
|
|
}
|
|
if _, err := txApp.DB().Delete("_collections", dbx.HashExp{"name": name}).Execute(); err != nil {
|
|
return fmt.Errorf("failed to remove legacy auth collection row %q: %w", name, err)
|
|
}
|
|
}
|
|
|
|
// Strip the `password` field from every auth collection. Pull
|
|
// the JSON fields-list straight out of the row, drop entries
|
|
// whose name is "password", write it back, and ALTER TABLE to
|
|
// drop the column. Skip the underlying ALTER TABLE if the
|
|
// column never existed.
|
|
var rows []struct {
|
|
Id string
|
|
Name string
|
|
Fields string
|
|
}
|
|
err := txApp.DB().NewQuery("SELECT id, name, fields FROM {{_collections}} WHERE type = 'auth'").All(&rows)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to enumerate auth collections: %w", err)
|
|
}
|
|
|
|
for _, row := range rows {
|
|
var fields []map[string]any
|
|
if err := json.Unmarshal([]byte(row.Fields), &fields); err != nil {
|
|
return fmt.Errorf("failed to decode fields for %q: %w", row.Name, err)
|
|
}
|
|
|
|
filtered := fields[:0]
|
|
hadPassword := false
|
|
for _, f := range fields {
|
|
if name, _ := f["name"].(string); name == "password" {
|
|
hadPassword = true
|
|
continue
|
|
}
|
|
filtered = append(filtered, f)
|
|
}
|
|
if !hadPassword {
|
|
continue
|
|
}
|
|
|
|
encoded, err := json.Marshal(filtered)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode trimmed fields for %q: %w", row.Name, err)
|
|
}
|
|
|
|
if _, err := txApp.DB().Update(
|
|
"_collections",
|
|
dbx.Params{"fields": string(encoded)},
|
|
dbx.HashExp{"id": row.Id},
|
|
).Execute(); err != nil {
|
|
return fmt.Errorf("failed to persist password-stripped %q: %w", row.Name, err)
|
|
}
|
|
|
|
// Drop the column on the record table; SQLite supports
|
|
// ALTER TABLE DROP COLUMN since 3.35. Soft-fail if the
|
|
// column doesn't exist (older fork that never wrote one).
|
|
tableName := row.Name
|
|
if _, err := txApp.DB().NewQuery(
|
|
fmt.Sprintf("ALTER TABLE {{%s}} DROP COLUMN [[password]]", tableName),
|
|
).Execute(); err != nil {
|
|
txApp.Logger().Warn("could not drop password column (likely already absent)", "table", tableName, "error", err)
|
|
}
|
|
}
|
|
|
|
return txApp.ReloadCachedCollections()
|
|
}, nil)
|
|
}
|