Skip to content

Commit 2f9edc4

Browse files
Merge pull request #76 from aryansondharva/copilot/profile-validation-fixes
Harden profile URL validation and unblock typechecking
2 parents 3aa058e + af85bf5 commit 2f9edc4

3 files changed

Lines changed: 31 additions & 19 deletions

File tree

Client/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
44
"compilerOptions": {
55
"baseUrl": ".",
6+
"ignoreDeprecations": "5.0",
67
"paths": {
78
"@/*": ["./src/*"]
89
},

backend/lib/validations/profile.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
11
import { z } from 'zod'
2+
import { isIP } from 'node:net'
3+
4+
/**
5+
* Validates public profile links.
6+
* Accepts http/https URLs with a real hostname, localhost, or a valid IP address.
7+
*/
8+
const isValidHttpUrl = (value: string) => {
9+
try {
10+
const parsed = new URL(value)
11+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
12+
return false
13+
}
14+
15+
const hostname = parsed.hostname.toLowerCase()
16+
return hostname === 'localhost' || isIP(hostname) > 0 || /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i.test(hostname)
17+
} catch {
18+
return false
19+
}
20+
}
21+
22+
const httpUrlSchema = (message: string) =>
23+
z.string().trim().refine(isValidHttpUrl, message)
224

325
/**
426
* Profile update validation schema
@@ -21,21 +43,9 @@ export const profileUpdateSchema = z.object({
2143
.max(500, 'Bio must not exceed 500 characters')
2244
.optional()
2345
.or(z.literal('')),
24-
github_url: z
25-
.string()
26-
.regex(/^https?:\/\/.+/i, 'GitHub URL must be a valid URL')
27-
.optional()
28-
.or(z.literal('')),
29-
linkedin_url: z
30-
.string()
31-
.regex(/^https?:\/\/.+/i, 'LinkedIn URL must be a valid URL')
32-
.optional()
33-
.or(z.literal('')),
34-
portfolio_url: z
35-
.string()
36-
.regex(/^https?:\/\/.+/i, 'Portfolio URL must be a valid URL')
37-
.optional()
38-
.or(z.literal('')),
46+
github_url: httpUrlSchema('GitHub URL must be a valid URL').optional().or(z.literal('')),
47+
linkedin_url: httpUrlSchema('LinkedIn URL must be a valid URL').optional().or(z.literal('')),
48+
portfolio_url: httpUrlSchema('Portfolio URL must be a valid URL').optional().or(z.literal('')),
3949
skills: z
4050
.array(z.string())
4151
.max(10, 'Cannot have more than 10 skills')
@@ -45,8 +55,7 @@ export const profileUpdateSchema = z.object({
4555
graduation_year: z.union([z.number().int().min(1900).max(2100), z.string(), z.null()]).optional(),
4656
phone: z.string().max(20).optional().or(z.literal('')),
4757
address: z.string().max(200).optional().or(z.literal('')),
48-
avatar_url: z.string().url().optional().or(z.literal('')),
49-
banner_url: z.string().regex(/^https?:\/\/.+/i, 'Banner URL must be a valid URL').optional().or(z.literal('')),
58+
banner_url: httpUrlSchema('Banner URL must be a valid URL').optional().or(z.literal('')),
5059
interests: z.array(z.string()).max(10, 'Cannot have more than 10 interests').optional(),
5160
first_name: z.string().max(50).optional().or(z.literal('')),
5261
last_name: z.string().max(50).optional().or(z.literal('')),
@@ -59,9 +68,10 @@ export const profileUpdateSchema = z.object({
5968
degree_type: z.string().optional().or(z.literal('')),
6069
graduation_month: z.string().optional().or(z.literal('')),
6170
roles: z.array(z.string()).optional(),
62-
resume_url: z.string().url().optional().or(z.literal('')),
71+
avatar_url: httpUrlSchema('Avatar URL must be a valid URL').optional().or(z.literal('')),
72+
resume_url: httpUrlSchema('Resume URL must be a valid URL').optional().or(z.literal('')),
6373
has_experience: z.boolean().optional(),
64-
twitter_url: z.string().regex(/^https?:\/\/.+/i, 'Twitter URL must be a valid URL').optional().or(z.literal('')),
74+
twitter_url: httpUrlSchema('Twitter URL must be a valid URL').optional().or(z.literal('')),
6575
emergency_contact_name: z.string().max(100).optional().or(z.literal('')),
6676
emergency_contact_phone: z.string().max(20).optional().or(z.literal('')),
6777
is_email_public: z.boolean().optional(),

backend/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
}
1919
],
2020
"baseUrl": ".",
21+
"ignoreDeprecations": "5.0",
2122
"paths": {
2223
"@/*": ["./*"]
2324
},

0 commit comments

Comments
 (0)