fix(deezer): reduce JWT token expiration buffer from 10 minutes to 1 minute

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2025-11-21 11:04:46 -05:00
parent 98a2a9f3ac
commit 60730d8164
3 changed files with 25 additions and 25 deletions

View File

@ -83,13 +83,13 @@ func (c *client) getJWT(ctx context.Context) (string, error) {
return "", fmt.Errorf("deezer: failed to parse JWT token: %w", err)
}
// Calculate TTL with a 10-minute buffer for clock skew and network delays
// Calculate TTL with a 1-minute buffer for clock skew and network delays
expiresAt := token.Expiration()
if expiresAt.IsZero() {
return "", errors.New("deezer: JWT token has no expiration time")
}
ttl := time.Until(expiresAt) - 10*time.Minute
ttl := time.Until(expiresAt) - 1*time.Minute
if ttl <= 0 {
return "", errors.New("deezer: JWT token already expired or expires too soon")
}

View File

@ -28,7 +28,7 @@ var _ = Describe("JWT Authentication", func() {
Describe("getJWT", func() {
Context("with a valid JWT response", func() {
It("successfully fetches and caches a JWT token", func() {
testJWT := createTestJWT(1 * time.Hour)
testJWT := createTestJWT(5 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, testJWT))),
@ -40,7 +40,7 @@ var _ = Describe("JWT Authentication", func() {
})
It("returns the cached token on subsequent calls", func() {
testJWT := createTestJWT(1 * time.Hour)
testJWT := createTestJWT(5 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, testJWT))),
@ -61,7 +61,7 @@ var _ = Describe("JWT Authentication", func() {
})
It("parses the JWT expiration time correctly", func() {
expectedExpiration := time.Now().Add(2 * time.Hour)
expectedExpiration := time.Now().Add(5 * time.Minute)
testToken, err := jwt.NewBuilder().
Expiration(expectedExpiration).
Build()
@ -79,16 +79,16 @@ var _ = Describe("JWT Authentication", func() {
Expect(token).ToNot(BeEmpty())
// Verify the token is cached until close to expiration
// The cache should expire 10 minutes before the JWT expires
expectedCacheExpiry := expectedExpiration.Add(-10 * time.Minute)
// The cache should expire 1 minute before the JWT expires
expectedCacheExpiry := expectedExpiration.Add(-1 * time.Minute)
Expect(client.jwt.expiresAt).To(BeTemporally("~", expectedCacheExpiry, 2*time.Second))
})
})
Context("with JWT tokens that expire soon", func() {
It("rejects tokens that expire in less than 10 minutes", func() {
// Create a token that expires in 5 minutes
testJWT := createTestJWT(5 * time.Minute)
It("rejects tokens that expire in less than 1 minute", func() {
// Create a token that expires in 30 seconds (less than 1-minute buffer)
testJWT := createTestJWT(30 * time.Second)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, testJWT))),
@ -100,8 +100,8 @@ var _ = Describe("JWT Authentication", func() {
})
It("rejects already expired tokens", func() {
// Create a token that expired 1 hour ago
testJWT := createTestJWT(-1 * time.Hour)
// Create a token that expired 1 minute ago
testJWT := createTestJWT(-1 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, testJWT))),
@ -112,9 +112,9 @@ var _ = Describe("JWT Authentication", func() {
Expect(err.Error()).To(ContainSubstring("JWT token already expired or expires too soon"))
})
It("accepts tokens that expire in exactly 11 minutes", func() {
// Create a token that expires in 11 minutes (just over the 10-minute buffer)
testJWT := createTestJWT(11 * time.Minute)
It("accepts tokens that expire in more than 1 minute", func() {
// Create a token that expires in 2 minutes (just over the 1-minute buffer)
testJWT := createTestJWT(2 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, testJWT))),
@ -197,8 +197,8 @@ var _ = Describe("JWT Authentication", func() {
Context("token caching behavior", func() {
It("fetches a new token when the cached token expires", func() {
// First token expires in 15 minutes
firstJWT := createTestJWT(15 * time.Minute)
// First token expires in 5 minutes
firstJWT := createTestJWT(5 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, firstJWT))),
@ -211,8 +211,8 @@ var _ = Describe("JWT Authentication", func() {
// Manually expire the cached token
client.jwt.expiresAt = time.Now().Add(-1 * time.Second)
// Second token with different expiration
secondJWT := createTestJWT(30 * time.Minute)
// Second token with different expiration (10 minutes)
secondJWT := createTestJWT(10 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s"}`, secondJWT))),
@ -241,7 +241,7 @@ var _ = Describe("JWT Authentication", func() {
})
It("returns true for valid tokens", func() {
cache.set("test-token", 1*time.Hour)
cache.set("test-token", 4*time.Minute)
token, valid := cache.get()
Expect(valid).To(BeTrue())
Expect(token).To(Equal("test-token"))

View File

@ -47,8 +47,8 @@ var _ = Describe("client", func() {
Describe("ArtistBio", func() {
BeforeEach(func() {
// Mock the JWT token endpoint with a valid JWT that expires in 1 hour
testJWT := createTestJWT(1 * time.Hour)
// Mock the JWT token endpoint with a valid JWT that expires in 5 minutes
testJWT := createTestJWT(5 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s","refresh_token":""}`, testJWT))),
@ -70,7 +70,7 @@ var _ = Describe("client", func() {
It("uses the configured language", func() {
client = newClient(httpClient, "fr")
// Mock JWT token for the new client instance with a valid JWT
testJWT := createTestJWT(1 * time.Hour)
testJWT := createTestJWT(5 * time.Minute)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s","refresh_token":""}`, testJWT))),
@ -158,8 +158,8 @@ var _ = Describe("client", func() {
})
It("handles JWT token that expires too soon", func() {
// Create a JWT that expires in 5 minutes (less than the 10-minute buffer)
expiredJWT := createTestJWT(5 * time.Minute)
// Create a JWT that expires in 30 seconds (less than the 1-minute buffer)
expiredJWT := createTestJWT(30 * time.Second)
httpClient.mock("https://auth.deezer.com/login/anonymous", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s","refresh_token":""}`, expiredJWT))),