PHP's device flow on golem15_user_device_auth_sessions (PHP's columns,
the token stored as its SHA-256, plus jwt_jti): POST /devices/initiate
answers a 64-character token, an XXXX-XXXX short code, the configurable
auth_url (golem15.user.device_auth.auth_url) and the expiry; a signed-in
user authorizes it with POST /devices/authorize (short_code or token,
one guarded pending-to-confirmed UPDATE); the device's first poll of
GET /devices/status/{token} after that receives a session JWT through one
guarded confirmed-to-used UPDATE, so exactly one poll gets it. Used and
revoked sessions never turn into expired; each initiate deletes a bounded
batch of stale sessions.
135 lines
4.2 KiB
Go
135 lines
4.2 KiB
Go
package updates
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"github.com/go-gormigrate/gormigrate/v2"
|
|
)
|
|
|
|
func TestExtendUsersMigration(t *testing.T) {
|
|
sqlDB := dedicatedDB(t, "user_extend")
|
|
gdb, err := lagoon.Use(t.Context(), sqlDB)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := gormigrate.New(gdb, &gormigrate.Options{
|
|
TableName: "summer_migrations_golem15_user",
|
|
UseTransaction: true,
|
|
}, All())
|
|
if err := m.Migrate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cols := []string{
|
|
"name", "surname", "is_activated", "activated_at", "activation_code",
|
|
"activation_code_issued_at", "reset_password_code", "reset_password_code_issued_at",
|
|
"has_self_set_password", "marketing_consent", "is_onboarded", "organisation_id",
|
|
"organisation_role", "preferred_locale", "tokens_valid_after", "created_ip_address",
|
|
"last_ip_address",
|
|
}
|
|
for _, col := range cols {
|
|
if !gdb.Migrator().HasColumn("users", col) {
|
|
t.Fatalf("missing column %s", col)
|
|
}
|
|
}
|
|
if err := gdb.Exec(`INSERT INTO users (email) VALUES ('defaults@example.test')`).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var row struct {
|
|
HasSelfSetPassword bool
|
|
IsActivated bool
|
|
MarketingConsent bool
|
|
IsOnboarded bool
|
|
}
|
|
if err := gdb.Raw(`SELECT has_self_set_password, is_activated, marketing_consent, is_onboarded FROM users WHERE email = ?`, "defaults@example.test").Scan(&row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !row.HasSelfSetPassword || row.IsActivated || row.MarketingConsent || row.IsOnboarded {
|
|
t.Fatalf("defaults = %+v", row)
|
|
}
|
|
// Device auth sessions (golem15_user_device_auth_sessions), backend links, email verified at, handle, OAuth
|
|
// identities, the three admin migrations, API tokens, user groups, jwt blacklist, throttle, then extend-users.
|
|
for range 13 {
|
|
if err := m.RollbackLast(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, col := range cols {
|
|
if gdb.Migrator().HasColumn("users", col) {
|
|
t.Fatalf("column %s survived rollback", col)
|
|
}
|
|
}
|
|
if gdb.Migrator().HasTable("user_throttle") || gdb.Migrator().HasTable("jwt_blacklist") {
|
|
t.Fatal("new tables survived rollback")
|
|
}
|
|
if !gdb.Migrator().HasTable("users") {
|
|
t.Fatal("users dropped")
|
|
}
|
|
}
|
|
|
|
func TestCreateUserThrottleMigration(t *testing.T) {
|
|
sqlDB := dedicatedDB(t, "user_throttle")
|
|
gdb, err := lagoon.Use(t.Context(), sqlDB)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := gormigrate.New(gdb, &gormigrate.Options{
|
|
TableName: "summer_migrations_golem15_user",
|
|
UseTransaction: true,
|
|
}, All())
|
|
if err := m.Migrate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !gdb.Migrator().HasTable("user_throttle") {
|
|
t.Fatal("user_throttle missing")
|
|
}
|
|
for _, col := range []string{"id", "user_id", "ip_address", "attempts", "is_suspended", "suspended_at", "is_banned", "created_at", "updated_at"} {
|
|
if !gdb.Migrator().HasColumn("user_throttle", col) {
|
|
t.Fatalf("missing column %s", col)
|
|
}
|
|
}
|
|
// Device auth sessions (golem15_user_device_auth_sessions), backend links, email verified at, handle, OAuth
|
|
// identities, the three admin migrations, API tokens, user groups, jwt blacklist, then throttle.
|
|
for range 12 {
|
|
if err := m.RollbackLast(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if gdb.Migrator().HasTable("user_throttle") {
|
|
t.Fatal("user_throttle survived rollback")
|
|
}
|
|
}
|
|
|
|
func TestCreateJwtBlacklistMigration(t *testing.T) {
|
|
sqlDB := dedicatedDB(t, "user_jwt_blacklist")
|
|
gdb, err := lagoon.Use(t.Context(), sqlDB)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m := gormigrate.New(gdb, &gormigrate.Options{
|
|
TableName: "summer_migrations_golem15_user",
|
|
UseTransaction: true,
|
|
}, All())
|
|
if err := m.Migrate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !gdb.Migrator().HasTable("jwt_blacklist") {
|
|
t.Fatal("jwt_blacklist missing")
|
|
}
|
|
for _, col := range []string{"jti", "expires_at", "valid_until"} {
|
|
if !gdb.Migrator().HasColumn("jwt_blacklist", col) {
|
|
t.Fatalf("missing column %s", col)
|
|
}
|
|
}
|
|
// Device auth sessions (golem15_user_device_auth_sessions), backend links, email verified at, handle, OAuth
|
|
// identities, the three admin migrations, API tokens, user groups, then jwt blacklist.
|
|
for range 11 {
|
|
if err := m.RollbackLast(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if gdb.Migrator().HasTable("jwt_blacklist") {
|
|
t.Fatal("jwt_blacklist survived rollback")
|
|
}
|
|
}
|