fix(send): reset visit state on session drift and add missing 2FA translations
This commit is contained in:
1 parent
c3a70b123d
commit
cb9cb27fbe
53 files changed
+148
-67
No files matched your search
@@ -93,7 +93,7 @@ function callPOST(
|
||||
);
|
||||
}
|
||||
|
||||
function makeComputedSessionId(sourceId: string, timestamp = 1704067200) {
|
||||
function makeComputedSessionId(sourceId: string, timestamp = Math.floor(Date.now() / 1000)) {
|
||||
const createdAt = new Date(timestamp * 1000);
|
||||
const sessionSalt = getSalt(process.env.SALT_ROTATION || 'month', createdAt);
|
||||
|
||||
@@ -564,7 +564,7 @@ describe('cache token handling', () => {
|
||||
test('a valid cache token creates the computed session before event writes when the cached session differs', async () => {
|
||||
const token = makeCacheToken({ sessionId: 'cached-session' });
|
||||
|
||||
await callPOST(
|
||||
const response = await callPOST(
|
||||
{ type: 'event', payload: { website: WEBSITE_ID, url: '/' } },
|
||||
{ headers: { 'x-umami-cache': token } },
|
||||
);
|
||||
@@ -572,15 +572,19 @@ describe('cache token handling', () => {
|
||||
expect(createSessionMock).toHaveBeenCalledTimes(1);
|
||||
const createdSession = createSessionMock.mock.calls[0][0] as Record<string, any>;
|
||||
const savedEvent = saveEventMock.mock.calls[0][0] as Record<string, any>;
|
||||
const body = (await response.json()) as Record<string, any>;
|
||||
|
||||
expect(createdSession.id).not.toBe('cached-session');
|
||||
expect(savedEvent.sessionId).toBe(createdSession.id);
|
||||
expect(savedEvent.visitId).not.toBe('cached-visit');
|
||||
expect(body.sessionId).toBe(createdSession.id);
|
||||
expect(body.visitId).toBe(savedEvent.visitId);
|
||||
});
|
||||
|
||||
test('a valid cache token creates the computed session before identify writes when the cached session differs', async () => {
|
||||
const token = makeCacheToken({ sessionId: 'cached-session' });
|
||||
|
||||
await callPOST(
|
||||
const response = await callPOST(
|
||||
{ type: 'identify', payload: { website: WEBSITE_ID, id: 'user-42', data: { plan: 'pro' } } },
|
||||
{ headers: { 'x-umami-cache': token } },
|
||||
);
|
||||
@@ -590,11 +594,32 @@ describe('cache token handling', () => {
|
||||
const savedLink = saveSessionLinkMock.mock.calls[0][0] as Record<string, any>;
|
||||
const updatedSession = updateSessionMock.mock.calls[0][0] as Record<string, any>;
|
||||
const savedSessionData = saveSessionDataMock.mock.calls[0][0] as Record<string, any>;
|
||||
const body = (await response.json()) as Record<string, any>;
|
||||
|
||||
expect(createdSession.id).not.toBe('cached-session');
|
||||
expect(savedLink.sessionId).toBe(createdSession.id);
|
||||
expect(updatedSession.sessionId).toBe(createdSession.id);
|
||||
expect(savedSessionData.sessionId).toBe(createdSession.id);
|
||||
expect(body.sessionId).toBe(createdSession.id);
|
||||
expect(body.visitId).not.toBe('cached-visit');
|
||||
});
|
||||
|
||||
test('a drifted cache token resets the visit in clickhouse mode without creating a session row', async () => {
|
||||
(clickhouse as any).enabled = true;
|
||||
const token = makeCacheToken({ sessionId: 'cached-session' });
|
||||
|
||||
const response = await callPOST(
|
||||
{ type: 'event', payload: { website: WEBSITE_ID, url: '/' } },
|
||||
{ headers: { 'x-umami-cache': token } },
|
||||
);
|
||||
|
||||
const savedEvent = saveEventMock.mock.calls[0][0] as Record<string, any>;
|
||||
const body = (await response.json()) as Record<string, any>;
|
||||
|
||||
expect(createSessionMock).not.toHaveBeenCalled();
|
||||
expect(savedEvent.sessionId).toBe(body.sessionId);
|
||||
expect(savedEvent.visitId).not.toBe('cached-visit');
|
||||
expect(body.visitId).toBe(savedEvent.visitId);
|
||||
});
|
||||
|
||||
test('an invalid cache token falls back to website lookup', async () => {
|
||||
@@ -688,7 +713,7 @@ describe('30-minute visit expiry', () => {
|
||||
{
|
||||
type: CACHE_TOKEN_TYPE,
|
||||
websiteId: WEBSITE_ID,
|
||||
sessionId: 'cached-session',
|
||||
sessionId: makeComputedSessionId(WEBSITE_ID),
|
||||
visitId: 'cached-visit',
|
||||
iat: recentIat,
|
||||
},
|
||||
@@ -704,12 +729,13 @@ describe('30-minute visit expiry', () => {
|
||||
});
|
||||
|
||||
test('does not expire the visit when an explicit timestamp is supplied', async () => {
|
||||
const timestamp = 1000000000;
|
||||
const oldIat = Math.floor(Date.now() / 1000) - 5000;
|
||||
const token = createToken(
|
||||
{
|
||||
type: CACHE_TOKEN_TYPE,
|
||||
websiteId: WEBSITE_ID,
|
||||
sessionId: 'cached-session',
|
||||
sessionId: makeComputedSessionId(WEBSITE_ID, timestamp),
|
||||
visitId: 'cached-visit',
|
||||
iat: oldIat,
|
||||
},
|
||||
@@ -719,7 +745,7 @@ describe('30-minute visit expiry', () => {
|
||||
const response = await callPOST(
|
||||
{
|
||||
type: 'event',
|
||||
payload: { website: WEBSITE_ID, url: '/', timestamp: 1000000000 },
|
||||
payload: { website: WEBSITE_ID, url: '/', timestamp },
|
||||
},
|
||||
{ headers: { 'x-umami-cache': token } },
|
||||
);
|
||||
|
||||
@@ -156,8 +156,8 @@ export async function POST(request: Request) {
|
||||
const visitSalt = hash(startOfHour(createdAt).toUTCString());
|
||||
|
||||
const sessionId = uuid(sourceId, ip, userAgent, sessionSalt);
|
||||
const shouldEnsureSession =
|
||||
!clickhouse.enabled && !!websiteId && !!cache?.sessionId && cache.sessionId !== sessionId;
|
||||
const sessionDrift = !!websiteId && !!cache?.sessionId && cache.sessionId !== sessionId;
|
||||
const shouldEnsureSession = !clickhouse.enabled && sessionDrift;
|
||||
|
||||
// Create a session if not found
|
||||
if ((!clickhouse.enabled && !cache?.sessionId) || shouldEnsureSession) {
|
||||
@@ -181,6 +181,12 @@ export async function POST(request: Request) {
|
||||
let visitId = cache?.visitId || uuid(sessionId, visitSalt);
|
||||
let iat = cache?.iat || now;
|
||||
|
||||
// A drifted cache session should start a fresh visit on the recomputed session.
|
||||
if (sessionDrift) {
|
||||
visitId = uuid(sessionId, visitSalt);
|
||||
iat = now;
|
||||
}
|
||||
|
||||
// Expire visit after 30 minutes
|
||||
if (!timestamp && now - iat > 1800) {
|
||||
visitId = uuid(sessionId, visitSalt);
|
||||
|
||||
Reference in new issue
Block a user