Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions web-components/src/api/folksonomy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,63 @@ describe("Folksonomy API", () => {
expect(global.fetch).toHaveBeenCalledTimes(100)
})
})

describe("Token expiration", () => {
beforeEach(() => {
localStorage.clear()
})

it("should reject expired tokens (more than 1 hour old)", async () => {
const oneHourAgoMs = Date.now() - (3600000 + 1000) // 1 hour + 1 second ago

localStorage.setItem("folksonomy-bearer-token", "expired-token")
localStorage.setItem("folksonomy-bearer-date", oneHourAgoMs.toString())

// Mock fetch for auth_by_cookie to return new token
;(global.fetch as any).mockResolvedValue({
ok: true,
json: async () => ({ access_token: "new-token" }),
})

// Call any authenticated API - it should request new token
await folksonomyApi.addProductProperty("123", "key", "value")

// Verify old token was not used, new auth was requested
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining("/auth_by_cookie"),
expect.any(Object)
)
})

it("should accept fresh tokens (less than 1 hour old)", () => {
const now = Date.now()

localStorage.setItem("folksonomy-bearer-token", "fresh-token")
localStorage.setItem("folksonomy-bearer-date", now.toString())

// Token should be available
const token = localStorage.getItem("folksonomy-bearer-token")
expect(token).toBe("fresh-token")
})

it("should clear expired tokens from localStorage", async () => {
const oneHourAgoMs = Date.now() - (3600000 + 1000)

localStorage.setItem("folksonomy-bearer-token", "expired-token")
localStorage.setItem("folksonomy-bearer-date", oneHourAgoMs.toString())

// Mock auth response
;(global.fetch as any).mockResolvedValue({
ok: true,
json: async () => ({ access_token: "new-token" }),
})

// Trigger API call that checks token
await folksonomyApi.addProductProperty("123", "key", "value")

// Verify localStorage was cleared (because new auth was needed)
// The new token should be stored
expect(localStorage.getItem("folksonomy-bearer-token")).toBeTruthy()
})
})
})
18 changes: 17 additions & 1 deletion web-components/src/api/folksonomy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { folksonomyConfiguration, userInfo, userInfoLoading } from "../signals/f
// Constants for localStorage
const FOLKSONOMY_BEARER_TOKEN_KEY = "folksonomy-bearer-token"
const FOLKSONOMY_BEARER_DATE_KEY = "folksonomy-token-date"
const TOKEN_MAX_AGE_MS = 3600000 // 1 hour in milliseconds

/**
* Get the API URL for a given path with the current configuration
Expand All @@ -30,10 +31,25 @@ const getApiUrl = (path: string) => {

/**
* Get stored token from localStorage
* Returns null if token doesn't exist or has expired
* @returns {string | null}
*/
function getStoredToken(): string | null {
return localStorage.getItem(FOLKSONOMY_BEARER_TOKEN_KEY)
const token = localStorage.getItem(FOLKSONOMY_BEARER_TOKEN_KEY)
const tokenDate = localStorage.getItem(FOLKSONOMY_BEARER_DATE_KEY)

if (!token || !tokenDate) {
return null
}

// Check if token has expired
const age = Date.now() - parseInt(tokenDate)
if (age > TOKEN_MAX_AGE_MS) {
clearStoredToken() // Clean up expired token
return null
}

return token
}

/**
Expand Down
Loading