diff --git a/.changeset/remove-zero-session-deadlines.md b/.changeset/remove-zero-session-deadlines.md new file mode 100644 index 00000000000..465a33fd638 --- /dev/null +++ b/.changeset/remove-zero-session-deadlines.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +'@clerk/backend': patch +--- + +Session `expireAt` and `abandonAt` no longer special-case a `0` deadline; the backend now always sends a real deadline. diff --git a/packages/backend/src/api/resources/Session.ts b/packages/backend/src/api/resources/Session.ts index 41f32d54f7b..968be156748 100644 --- a/packages/backend/src/api/resources/Session.ts +++ b/packages/backend/src/api/resources/Session.ts @@ -52,13 +52,9 @@ export class Session { readonly status: string, /** The time the session was last active on the [`Client`](https://clerk.com/docs/reference/backend/types/backend-client). */ readonly lastActiveAt: number, - /** - * The Unix timestamp when the `Session` will expire. For an indefinite native session, this is `0`, meaning no expiration deadline. Check `status` to determine whether the session is active. - */ + /** The Unix timestamp when the `Session` will expire. */ readonly expireAt: number, - /** - * The Unix timestamp when the `Session` will be abandoned. For an indefinite native session, this is `0`, meaning no abandonment deadline. - */ + /** The Unix timestamp when the `Session` will be abandoned. */ readonly abandonAt: number, /** The Unix timestamp when the `Session` was first created. */ readonly createdAt: number, diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 0ac226923f8..1a2c4023e65 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -44,7 +44,7 @@ import type { } from '@clerk/shared/types'; import { isWebAuthnSupported as isWebAuthnSupportedOnWindow } from '@clerk/shared/webauthn'; -import { unixEpochToDate, unixEpochToDeadline } from '@/utils/date'; +import { unixEpochToDate } from '@/utils/date'; import { debugLogger } from '@/utils/debug'; import { getTabState, isTabFocused } from '@/utils/isTabFocused'; import { TokenId } from '@/utils/tokenId'; @@ -438,8 +438,8 @@ export class Session extends BaseResource implements SessionResource { this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToDeadline(data.expire_at); - this.abandonAt = unixEpochToDeadline(data.abandon_at); + this.expireAt = unixEpochToDate(data.expire_at); + this.abandonAt = unixEpochToDate(data.abandon_at); this.factorVerificationAge = data.factor_verification_age; this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.lastActiveOrganizationId = data.last_active_organization_id; diff --git a/packages/clerk-js/src/core/resources/SessionWithActivities.ts b/packages/clerk-js/src/core/resources/SessionWithActivities.ts index d925f0fc7e2..23c8ababed2 100644 --- a/packages/clerk-js/src/core/resources/SessionWithActivities.ts +++ b/packages/clerk-js/src/core/resources/SessionWithActivities.ts @@ -6,7 +6,7 @@ import type { SessionWithActivitiesResource, } from '@clerk/shared/types'; -import { unixEpochToDate, unixEpochToDeadline } from '../../utils/date'; +import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './internal'; const mapSessionActivityJSONToSessionActivity = (data: SessionActivityJSON): SessionActivity => ({ @@ -65,8 +65,8 @@ export class SessionWithActivities extends BaseResource implements SessionWithAc this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToDeadline(data.expire_at); - this.abandonAt = unixEpochToDeadline(data.abandon_at); + this.expireAt = unixEpochToDate(data.expire_at); + this.abandonAt = unixEpochToDate(data.abandon_at); this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.latestActivity = mapSessionActivityJSONToSessionActivity(data.latest_activity ?? ({} as SessionActivityJSON)); this.actor = data.actor; diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts index 567e16055d8..5514e1cc855 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts @@ -193,31 +193,6 @@ describe('Client Singleton', () => { expect(client.signIn.status).toBe('needs_second_factor'); }); - it('restores a session without a maximum lifetime from a client snapshot', () => { - const user = createUser({ first_name: 'John', last_name: 'Doe', id: 'user_1' }); - const clientJSON: ClientJSON = { - object: 'client', - id: 'test_id', - status: 'active', - last_active_session_id: 'session_1', - sign_in: null, - sign_up: null, - sessions: [{ ...createSession({ id: 'session_1', status: 'active' }, user), expire_at: 0, abandon_at: 0 }], - created_at: Date.now() - 1000, - updated_at: Date.now(), - } as any; - - // @ts-expect-error We cannot mess with the singleton when tests are running in parallel - const snapshot = new Client(clientJSON).__internal_toSnapshot(); - // @ts-expect-error We cannot mess with the singleton when tests are running in parallel - const [restored] = new Client(snapshot).sessions; - - expect(snapshot.sessions[0]).toMatchObject({ expire_at: 0, abandon_at: 0 }); - expect(restored.expireAt.getTime()).toBe(0); - expect(restored.abandonAt.getTime()).toBe(0); - expect(restored.status).toBe('active'); - }); - it('replaces sign up and sign in identity when fromJSON receives new ids', () => { const user = createUser({ first_name: 'John', last_name: 'Doe', id: 'user_1' }); const session = createSession({ id: 'session_1' }, user); diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index 551ed54c2cc..b776ddd1753 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -16,7 +16,6 @@ import { eventBus } from '../../events'; import { createFapiClient } from '../../fapiClient'; import { SessionTokenCache } from '../../tokenCache'; import { BaseResource, Client, Organization, Session } from '../internal'; -import { SessionWithActivities } from '../SessionWithActivities'; const baseFapiClientOptions = { frontendApi: 'clerk.example.com', @@ -41,44 +40,6 @@ describe('Session', () => { vi.useRealTimers(); }); - it.each([0, 2000000000000])('preserves a session deadline of %s through snapshots', deadline => { - const session = new Session({ - id: 'session_lifetime', - status: 'active', - expire_at: deadline, - abandon_at: deadline, - public_user_data: { user_id: 'user_1' }, - } as SessionJSON); - expect(session.expireAt.getTime()).toBe(deadline); - expect(session.abandonAt.getTime()).toBe(deadline); - const restored = new Session(session.__internal_toSnapshot()); - expect(restored.expireAt.getTime()).toBe(deadline); - expect(restored.abandonAt.getTime()).toBe(deadline); - const withActivities = new SessionWithActivities( - { id: session.id, expire_at: deadline, abandon_at: deadline } as any, - '/me/sessions', - ); - expect(withActivities.expireAt.getTime()).toBe(deadline); - expect(withActivities.abandonAt.getTime()).toBe(deadline); - }); - - it('keeps zero only for session deadlines', () => { - const session = new Session({ - id: 'session_lifetime', - status: 'active', - expire_at: 0, - abandon_at: 0, - created_at: 0, - updated_at: 0, - public_user_data: { user_id: 'user_1' }, - } as SessionJSON); - - expect(session.expireAt.getTime()).toBe(0); - expect(session.abandonAt.getTime()).toBe(0); - expect(session.createdAt.getTime()).toBe(Date.now()); - expect(session.updatedAt.getTime()).toBe(Date.now()); - }); - describe('getToken()', () => { let dispatchSpy; diff --git a/packages/clerk-js/src/utils/__tests__/date.test.ts b/packages/clerk-js/src/utils/__tests__/date.test.ts index 0b8a303999e..3c1ff3a3ca7 100644 --- a/packages/clerk-js/src/utils/__tests__/date.test.ts +++ b/packages/clerk-js/src/utils/__tests__/date.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { unixEpochToDate, unixEpochToDeadline } from '../date'; +import { unixEpochToDate } from '../date'; describe('date utilities', () => { beforeAll(() => { @@ -13,10 +13,6 @@ describe('date utilities', () => { }); describe('unixEpochToDate', () => { - it('fallbacks to current date for zero', function () { - expect(unixEpochToDate(0).getTime()).toBe(Date.now()); - }); - it('converts a unix epoch to Date', function () { const epoch = 1622575087; const date = unixEpochToDate(epoch); @@ -33,18 +29,4 @@ describe('date utilities', () => { expect(date.getTime()).toBe(Date.now()); }); }); - - describe('unixEpochToDeadline', () => { - it('keeps zero as Unix epoch zero', () => { - expect(unixEpochToDeadline(0).getTime()).toBe(0); - }); - - it('converts a unix epoch to Date', () => { - expect(unixEpochToDeadline(1622575087).getTime()).toBe(1622575087); - }); - - it('fallbacks to current date for undefined values', () => { - expect(unixEpochToDeadline(undefined).getTime()).toBe(Date.now()); - }); - }); }); diff --git a/packages/clerk-js/src/utils/date.ts b/packages/clerk-js/src/utils/date.ts index f785cbe118e..9541ca70bab 100644 --- a/packages/clerk-js/src/utils/date.ts +++ b/packages/clerk-js/src/utils/date.ts @@ -6,7 +6,3 @@ export function unixEpochToDate(epochInSeconds?: number): Date { const date = new Date(epochInSeconds || new Date()); return isValidDate(date) ? date : new Date(); } - -export function unixEpochToDeadline(epoch?: number): Date { - return epoch === 0 ? new Date(0) : unixEpochToDate(epoch); -} diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index 7c9bf8cde50..fdab3d21fa8 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -221,11 +221,11 @@ export interface SessionResource extends ClerkResource { */ status: SessionStatus; /** - * The date and time when the session will expire. For an indefinite native session, this represents Unix epoch zero, meaning no expiration deadline is configured. + * The date and time when the session will expire. */ expireAt: Date; /** - * The abandonment deadline. For an indefinite native session, this represents Unix epoch zero, meaning no abandonment deadline is configured. + * The date and time when the session will be abandoned due to inactivity. */ abandonAt: Date; /**