api_token_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package panel
  2. import (
  3. "errors"
  4. "strings"
  5. "testing"
  6. "gorm.io/gorm"
  7. "github.com/mhsanaei/3x-ui/v3/internal/config"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database"
  9. "github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
  10. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  11. )
  12. var errInjectedTokenCreate = errors.New("injected token create failure")
  13. func TestApiTokenCreatedAtSeconds(t *testing.T) {
  14. tests := []struct {
  15. name string
  16. in int64
  17. want int64
  18. }{
  19. {name: "seconds", in: 1_782_485_394, want: 1_782_485_394},
  20. {name: "legacy milliseconds", in: 1_782_485_394_270, want: 1_782_485_394},
  21. {name: "unset", in: 0, want: 0},
  22. }
  23. for _, tt := range tests {
  24. t.Run(tt.name, func(t *testing.T) {
  25. if got := apiTokenCreatedAtSeconds(tt.in); got != tt.want {
  26. t.Fatalf("apiTokenCreatedAtSeconds(%d) = %d, want %d", tt.in, got, tt.want)
  27. }
  28. })
  29. }
  30. }
  31. func TestRecreateByNamePreservesTokenWhenReplacementFails(t *testing.T) {
  32. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  33. dbtest.InitDB(t, config.GetDBPath())
  34. svc := ApiTokenService{}
  35. first, err := svc.RecreateByName("cli-fallback")
  36. if err != nil {
  37. t.Fatalf("first recreate: %v", err)
  38. }
  39. db := database.GetDB()
  40. const callback = "test:fail-token-replacement"
  41. if err := db.Callback().Create().Before("gorm:create").Register(callback, func(tx *gorm.DB) {
  42. if token, ok := tx.Statement.Dest.(*model.ApiToken); ok && token.Name == "cli-fallback" {
  43. tx.AddError(errInjectedTokenCreate)
  44. }
  45. }); err != nil {
  46. t.Fatalf("register callback: %v", err)
  47. }
  48. t.Cleanup(func() { _ = db.Callback().Create().Remove(callback) })
  49. if _, err := svc.RecreateByName("cli-fallback"); !errors.Is(err, errInjectedTokenCreate) {
  50. t.Fatalf("recreate error = %v, want %v", err, errInjectedTokenCreate)
  51. }
  52. var row model.ApiToken
  53. if err := db.Where("name = ?", "cli-fallback").First(&row).Error; err != nil {
  54. t.Fatalf("load preserved token: %v", err)
  55. }
  56. if !svc.Match(first.Token) {
  57. t.Fatal("original token was revoked after replacement failure")
  58. }
  59. }
  60. // Create caps the name at 64 characters; RecreateByName writes the same column
  61. // and now takes operator input from -tokenName, so it must cap it too.
  62. func TestRecreateByNameRejectsOverlongName(t *testing.T) {
  63. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  64. dbtest.InitDB(t, config.GetDBPath())
  65. const wantErr = "token name must be 64 characters or fewer"
  66. svc := ApiTokenService{}
  67. _, err := svc.RecreateByName(strings.Repeat("n", 65))
  68. if err == nil {
  69. t.Fatal("expected a 65-character token name to be rejected")
  70. }
  71. if got := strings.TrimSpace(err.Error()); got != wantErr {
  72. t.Fatalf("error = %q, want %q — any other error would pass a bare nil check", got, wantErr)
  73. }
  74. if _, err := svc.RecreateByName(strings.Repeat("n", 64)); err != nil {
  75. t.Fatalf("64 characters is the documented limit, got: %v", err)
  76. }
  77. }
  78. func TestRecreateByNameKeepsOneToken(t *testing.T) {
  79. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  80. dbtest.InitDB(t, config.GetDBPath())
  81. svc := ApiTokenService{}
  82. first, err := svc.RecreateByName("cli-fallback")
  83. if err != nil {
  84. t.Fatalf("first recreate: %v", err)
  85. }
  86. second, err := svc.RecreateByName("cli-fallback")
  87. if err != nil {
  88. t.Fatalf("second recreate: %v", err)
  89. }
  90. if first.Token == second.Token {
  91. t.Fatal("second call returned the same plaintext, want a rotated token")
  92. }
  93. var count int64
  94. if err := database.GetDB().Model(model.ApiToken{}).Where("name = ?", "cli-fallback").Count(&count).Error; err != nil {
  95. t.Fatalf("count: %v", err)
  96. }
  97. if count != 1 {
  98. t.Fatalf("token rows = %d, want 1", count)
  99. }
  100. }