Skip to content

Commit e67f815

Browse files
committed
refactor(utm): simplify UTM parameter handling by removing sanitization and directly validating parameters
1 parent 5d3ee35 commit e67f815

File tree

4 files changed

+92
-304
lines changed

4 files changed

+92
-304
lines changed

src/models/usersFactory.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,9 @@ export default class UsersFactory extends AbstractModelFactory<UserDBScheme, Use
7676
email,
7777
password: hashedPassword,
7878
notifications: UserModel.generateDefaultNotificationsSettings(email),
79+
utm: utm,
7980
};
8081

81-
if (utm && Object.keys(utm).length > 0) {
82-
userData.utm = utm;
83-
}
84-
8582
const userId = (await this.collection.insertOne(userData)).insertedId;
8683

8784
const user = new UserModel({

src/resolvers/user.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import { dateFromObjectId } from '../utils/dates';
1111
import { UserDBScheme } from '@hawk.so/types';
1212
import * as telegram from '../utils/telegram';
1313
import { MongoError } from 'mongodb';
14-
import { validateUtmParams, sanitizeUtmParams } from '../utils/utm/utm';
15-
import HawkCatcher from '@hawk.so/nodejs';
14+
import { validateUtmParams } from '../utils/utm/utm';
1615

1716
/**
1817
* See all types and fields here {@see ../typeDefs/user.graphql}
@@ -47,36 +46,13 @@ export default {
4746
{ email, utm }: { email: string; utm?: UserDBScheme['utm'] },
4847
{ factories }: ResolverContextBase
4948
): Promise<boolean | string> {
50-
// Validate and sanitize UTM parameters
51-
let sanitizedUtm;
52-
if (utm) {
53-
const validationResult = validateUtmParams(utm);
54-
55-
// Always try to sanitize valid keys (if any)
56-
if (validationResult.validKeys.length > 0) {
57-
sanitizedUtm = sanitizeUtmParams(utm, validationResult);
58-
}
5949

60-
// Log invalid keys for monitoring (if any)
61-
if (validationResult.invalidKeys.length > 0) {
62-
const errorMessage =
63-
validationResult.validKeys.length > 0
64-
? 'Some UTM parameters are invalid'
65-
: 'All UTM parameters are invalid';
66-
67-
HawkCatcher.send(new Error(errorMessage), {
68-
email,
69-
utm,
70-
invalidKeys: JSON.stringify(validationResult.invalidKeys),
71-
validKeys: JSON.stringify(validationResult.validKeys),
72-
});
73-
}
74-
}
50+
const validatedUtm = validateUtmParams(utm);
7551

7652
let user;
7753

7854
try {
79-
user = await factories.usersFactory.create(email, undefined, sanitizedUtm);
55+
user = await factories.usersFactory.create(email, undefined, validatedUtm);
8056

8157
const password = user.generatedPassword!;
8258

src/utils/utm/utm.ts

Lines changed: 20 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { UserDBScheme } from '@hawk.so/types';
2-
31
/**
42
* Valid UTM parameter keys
53
*/
@@ -11,131 +9,44 @@ const VALID_UTM_KEYS = ['source', 'medium', 'campaign', 'content', 'term'];
119
*/
1210
const VALID_UTM_CHARACTERS = /^[a-zA-Z0-9\s\-_.]+$/;
1311

14-
/**
15-
* Regular expression for invalid UTM characters (inverse of VALID_UTM_CHARACTERS)
16-
* Used for cleaning/sanitizing values
17-
*/
18-
const INVALID_UTM_CHARACTERS = /[^a-zA-Z0-9\s\-_.]/g;
19-
2012
/**
2113
* Maximum allowed length for UTM parameter values
2214
*/
23-
const MAX_UTM_VALUE_LENGTH = 200;
15+
const MAX_UTM_VALUE_LENGTH = 50;
2416

2517
/**
26-
* Validates UTM parameters per key
27-
* @param utm - Data form where user went to sign up. Used for analytics purposes
28-
* @returns object with validation results per key and overall validity
18+
* Validates and filters UTM parameters
19+
* @param {Object} utm - UTM parameters to validate
20+
* @returns {Object} - filtered valid UTM parameters
2921
*/
30-
export function validateUtmParams(utm: UserDBScheme['utm']): {
31-
isValid: boolean;
32-
validKeys: string[];
33-
invalidKeys: string[];
34-
} {
35-
if (!utm) {
36-
return {
37-
isValid: true,
38-
validKeys: [],
39-
invalidKeys: [],
40-
};
41-
}
42-
43-
// Check if utm is an object
44-
if (typeof utm !== 'object' || Array.isArray(utm)) {
45-
return {
46-
isValid: false,
47-
validKeys: [],
48-
invalidKeys: [ '_structure' ],
49-
};
50-
}
51-
52-
const providedKeys = Object.keys(utm);
53-
54-
// Check if utm object is not empty
55-
if (providedKeys.length === 0) {
56-
return {
57-
isValid: true,
58-
validKeys: [],
59-
invalidKeys: [],
60-
};
22+
export function validateUtmParams(utm: any): Record<string, string> {
23+
if (!utm || typeof utm !== 'object' || Array.isArray(utm)) {
24+
return {};
6125
}
6226

63-
const validKeys: string[] = [];
64-
const invalidKeys: string[] = [];
27+
const result: Record<string, string> = {};
6528

6629
for (const [key, value] of Object.entries(utm)) {
67-
// Check if key is valid UTM key
30+
// 1) Remove keys that are not VALID_UTM_KEYS
6831
if (!VALID_UTM_KEYS.includes(key)) {
69-
invalidKeys.push(key);
7032
continue;
7133
}
7234

73-
// Check if value is valid
74-
if (value !== undefined && value !== null) {
75-
if (typeof value !== 'string') {
76-
invalidKeys.push(key);
77-
continue;
78-
}
79-
80-
// Check length
81-
if (value.length === 0 || value.length > MAX_UTM_VALUE_LENGTH) {
82-
invalidKeys.push(key);
83-
continue;
84-
}
85-
86-
// Check for valid characters
87-
if (!VALID_UTM_CHARACTERS.test(value)) {
88-
invalidKeys.push(key);
89-
continue;
90-
}
35+
// 2) Check each condition separately
36+
if (!value || typeof value !== 'string') {
37+
continue;
9138
}
9239

93-
validKeys.push(key);
94-
}
95-
96-
return {
97-
isValid: invalidKeys.length === 0,
98-
validKeys,
99-
invalidKeys,
100-
};
101-
}
102-
103-
/**
104-
* Sanitizes UTM parameters by keeping only valid keys and cleaning values
105-
* @param utm - Data form where user went to sign up. Used for analytics purposes
106-
* @param validationResult - Optional validation result to use valid keys from
107-
* @returns sanitized UTM parameters or undefined if no valid data
108-
*/
109-
export function sanitizeUtmParams(
110-
utm: UserDBScheme['utm'],
111-
validationResult?: { validKeys: string[]; invalidKeys: string[] }
112-
): UserDBScheme['utm'] {
113-
if (!utm) {
114-
return undefined;
115-
}
116-
117-
const sanitized: UserDBScheme['utm'] = {};
118-
119-
// Use validation result if provided, otherwise validate inline
120-
const keysToProcess = validationResult
121-
? validationResult.validKeys
122-
: Object.keys(utm).filter((key) => VALID_UTM_KEYS.includes(key));
123-
124-
for (const key of keysToProcess) {
125-
const value = (utm as any)[key];
126-
127-
if (value && typeof value === 'string') {
128-
// Sanitize value: keep only allowed characters and limit length
129-
const cleanValue = value
130-
.replace(INVALID_UTM_CHARACTERS, '')
131-
.trim()
132-
.substring(0, MAX_UTM_VALUE_LENGTH);
40+
if (value.length === 0 || value.length > MAX_UTM_VALUE_LENGTH) {
41+
continue;
42+
}
13343

134-
if (cleanValue.length > 0) {
135-
(sanitized as any)[key] = cleanValue;
136-
}
44+
if (!VALID_UTM_CHARACTERS.test(value)) {
45+
continue;
13746
}
47+
48+
result[key] = value;
13849
}
13950

140-
return Object.keys(sanitized).length > 0 ? sanitized : undefined;
51+
return result;
14152
}

0 commit comments

Comments
 (0)