package core_test import ( "fmt" "testing" "time" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tests" "github.com/pocketbase/pocketbase/tools/security" "github.com/spf13/cast" ) func TestNewStaticAuthToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypeAuth, func(record *core.Record) (string, error) { return record.NewStaticAuthToken(0) }, map[string]any{ core.TokenClaimRefreshable: false, }) } func TestNewStaticAuthTokenWithCustomDuration(t *testing.T) { t.Parallel() app, _ := tests.NewTestApp() defer app.Cleanup() user, err := app.FindAuthRecordByEmail("users", "test@example.com") if err != nil { t.Fatal(err) } var tolerance int64 = 1 // in sec durations := []int64{-100, 0, 100} for i, d := range durations { t.Run(fmt.Sprintf("%d_%d", i, d), func(t *testing.T) { now := time.Now() duration := time.Duration(d) * time.Second token, err := user.NewStaticAuthToken(duration) if err != nil { t.Fatal(err) } claims, err := security.ParseUnverifiedJWT(token) if err != nil { t.Fatal(err) } exp := cast.ToInt64(claims["exp"]) expectedDuration := duration // should fallback to the collection setting if expectedDuration <= 0 { expectedDuration = user.Collection().AuthToken.DurationTime() } expectedMinExp := now.Add(expectedDuration).Unix() - tolerance expectedMaxExp := now.Add(expectedDuration).Unix() + tolerance if exp < expectedMinExp { t.Fatalf("Expected token exp to be greater than %d, got %d", expectedMinExp, exp) } if exp > expectedMaxExp { t.Fatalf("Expected token exp to be less than %d, got %d", expectedMaxExp, exp) } }) } } func TestNewAuthToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypeAuth, func(record *core.Record) (string, error) { return record.NewAuthToken() }, map[string]any{ core.TokenClaimRefreshable: true, }) } func TestNewVerificationToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypeVerification, func(record *core.Record) (string, error) { return record.NewVerificationToken() }, nil) } func TestNewPasswordResetToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypePasswordReset, func(record *core.Record) (string, error) { return record.NewPasswordResetToken() }, nil) } func TestNewEmailChangeToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypeEmailChange, func(record *core.Record) (string, error) { return record.NewEmailChangeToken("new@example.com") }, nil) } func TestNewFileToken(t *testing.T) { t.Parallel() testRecordToken(t, core.TokenTypeFile, func(record *core.Record) (string, error) { return record.NewFileToken() }, nil) } func testRecordToken( t *testing.T, tokenType string, tokenFunc func(record *core.Record) (string, error), expectedClaims map[string]any, ) { app, _ := tests.NewTestApp() defer app.Cleanup() demo1, err := app.FindRecordById("demo1", "84nmscqy84lsi1t") if err != nil { t.Fatal(err) } user, err := app.FindAuthRecordByEmail("users", "test@example.com") if err != nil { t.Fatal(err) } t.Run("non-auth record", func(t *testing.T) { _, err = tokenFunc(demo1) if err == nil { t.Fatal("Expected error for non-auth records") } }) t.Run("auth record", func(t *testing.T) { token, err := tokenFunc(user) if err != nil { t.Fatal(err) } tokenRecord, _ := app.FindAuthRecordByToken(token, tokenType) if tokenRecord == nil || tokenRecord.Id != user.Id { t.Fatalf("Expected auth record\n%v\ngot\n%v", user, tokenRecord) } if len(expectedClaims) > 0 { claims, _ := security.ParseUnverifiedJWT(token) for k, v := range expectedClaims { if claims[k] != v { t.Errorf("Expected claim %q with value %#v, got %#v", k, v, claims[k]) } } } }) t.Run("empty signing key", func(t *testing.T) { user.SetTokenKey("") collection := user.Collection() *collection = core.Collection{} collection.Type = core.CollectionTypeAuth _, err := tokenFunc(user) if err == nil { t.Fatal("Expected empty signing key error") } }) }