-
Notifications
You must be signed in to change notification settings - Fork 2
Registration page #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| export type RegistrationForm = { | ||
| firstName: string; | ||
| lastName: string; | ||
|
|
||
| school: string; | ||
|
|
||
| major: string; | ||
| gradYear: string; | ||
|
|
||
| email: string; | ||
| dob: string; | ||
|
|
||
| allergies: string[]; | ||
| allergiesOther: string; | ||
|
|
||
| tshirtSize: string; | ||
|
|
||
| projectIdea: string; | ||
| }; | ||
|
|
||
| export type ValidationResult = { | ||
| ok: boolean; | ||
| errors: Record<string, string>; | ||
| data?: RegistrationForm; | ||
| }; | ||
|
|
||
|
|
||
| export function countWords(text: string) { | ||
| return text.trim().split(/\s+/).filter(Boolean).length; | ||
| } | ||
|
|
||
| export function validateRegistration(payload: RegistrationForm): ValidationResult { | ||
| const errors: Record<string, string> = {}; | ||
|
|
||
| const reqStr = (key: keyof RegistrationForm) => { | ||
| const v = payload?.[key]; | ||
| if (typeof v !== "string" || v.trim().length === 0) errors[String(key)] = "Required"; | ||
| }; | ||
|
|
||
| reqStr("firstName"); | ||
| reqStr("lastName"); | ||
| reqStr("email"); | ||
| reqStr("dob"); | ||
| reqStr("major"); | ||
| reqStr("gradYear"); | ||
| reqStr("school"); | ||
| reqStr("tshirtSize"); | ||
| reqStr("projectIdea"); | ||
|
|
||
| if (typeof payload?.email === "string" && !/^\S+@\S+\.\S+$/.test(payload.email)) { | ||
| errors.email = "Invalid email"; | ||
| } | ||
|
Comment on lines
+50
to
+52
|
||
|
|
||
| if (payload?.school === "__OTHER__") { | ||
| errors.schoolOther = "Please type your school"; | ||
| } | ||
|
Comment on lines
+54
to
+56
|
||
|
|
||
| if (payload?.allergies != null) { | ||
| if (!Array.isArray(payload.allergies) || payload.allergies.some((x: any) => typeof x !== "string")) { | ||
| errors.allergies = "Invalid allergies"; | ||
| } | ||
| } | ||
|
|
||
| if (typeof payload?.projectIdea === "string") { | ||
| const words = countWords(payload.projectIdea); | ||
| if (words > 250) errors.projectIdea = `Too long (${words}/250 words)`; | ||
| } | ||
|
|
||
| if (Object.keys(errors).length > 0) { | ||
| return { ok: false, errors }; | ||
| } | ||
|
|
||
| const data: RegistrationForm = { | ||
| firstName: payload.firstName.trim(), | ||
| lastName: payload.lastName.trim(), | ||
| school: payload.school.trim(), | ||
| major: payload.major.trim(), | ||
| gradYear: payload.gradYear.trim(), | ||
| email: payload.email.trim(), | ||
| dob: payload.dob.trim(), | ||
| allergies: Array.isArray(payload.allergies) ? payload.allergies : [], | ||
| allergiesOther: (payload.allergiesOther ?? "").trim(), | ||
| tshirtSize: payload.tshirtSize.trim(), | ||
| projectIdea: payload.projectIdea.trim() | ||
| }; | ||
|
|
||
| return { ok: true, errors: {}, data }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,34 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { json } from "@sveltejs/kit"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { RequestHandler } from "./$types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { validateRegistration } from "$lib/registration"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NOTE: In-memory store for development/debugging only. Not suitable for production. | |
| // In production, replace this with persistent storage (e.g., database or filesystem). |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no rate limiting or authentication on this endpoint. Without proper protection, the API is vulnerable to spam submissions and abuse. Consider implementing rate limiting (e.g., by IP address) and/or authentication to prevent malicious actors from flooding the system with registrations.
| export const POST: RequestHandler = async ({ request }) => { | |
| const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute | |
| const RATE_LIMIT_MAX_REQUESTS = 10; // max registrations per IP per window | |
| type RateLimitEntry = { | |
| count: number; | |
| firstRequestTime: number; | |
| }; | |
| const rateLimitMap = new Map<string, RateLimitEntry>(); | |
| function isRateLimited(ip: string): boolean { | |
| const now = Date.now(); | |
| const existing = rateLimitMap.get(ip); | |
| if (!existing) { | |
| rateLimitMap.set(ip, { count: 1, firstRequestTime: now }); | |
| return false; | |
| } | |
| if (now - existing.firstRequestTime > RATE_LIMIT_WINDOW_MS) { | |
| // window has passed; reset counter | |
| rateLimitMap.set(ip, { count: 1, firstRequestTime: now }); | |
| return false; | |
| } | |
| existing.count += 1; | |
| rateLimitMap.set(ip, existing); | |
| return existing.count > RATE_LIMIT_MAX_REQUESTS; | |
| } | |
| export const POST: RequestHandler = async ({ request, getClientAddress }) => { | |
| const clientIp = typeof getClientAddress === "function" ? getClientAddress() : null; | |
| if (clientIp && isRateLimited(clientIp)) { | |
| return json({ ok: false, error: "Too many requests" }, { status: 429 }); | |
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'any' type removes type safety. Consider creating a proper type for the submission object or using 'unknown' with proper type guards. This would help catch potential type-related bugs at compile time.
| const submissions: any[] = []; | |
| export const POST: RequestHandler = async ({ request }) => { | |
| let body: any; | |
| type RegistrationData = ReturnType<typeof validateRegistration> extends { ok: true; data: infer D } | |
| ? D | |
| : never; | |
| type Submission = RegistrationData & { | |
| id: string; | |
| createdAt: string; | |
| }; | |
| const submissions: Submission[] = []; | |
| export const POST: RequestHandler = async ({ request }) => { | |
| let body: unknown; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only valid thing, but we r in web. Types here are basically non existent u little piece of scrap
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'any' type removes type safety. Since the request body should conform to the RegistrationForm type, consider typing this as 'unknown' initially and letting the validation function handle type checking, or explicitly type it as 'any' only in the catch block.
| let body: any; | |
| let body: unknown; |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Invalid JSON" is vague and doesn't help users understand what went wrong. Consider providing more context, such as "Request body must be valid JSON" or including the parsing error details in development mode.
| } catch { | |
| return json({ ok: false, error: "Invalid JSON" }, { status: 400 }); | |
| } catch (err: unknown) { | |
| let message = "Request body must be valid JSON."; | |
| if (process.env.NODE_ENV === "development" && err instanceof Error && err.message) { | |
| message += ` Parsing error: ${err.message}`; | |
| } | |
| return json({ ok: false, error: message }, { status: 400 }); |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GET endpoint exposes all submission data without any authentication or authorization. This is a security concern as it allows anyone to view potentially sensitive user information (emails, DOB, etc.). Consider adding authentication or removing this endpoint in production, or at minimum add a comment that it's for debugging only.
| // return last 20 for debugging | |
| return json({ ok: true, count: submissions.length, submissions: submissions.slice(-20).reverse() }); | |
| // Debugging endpoint: returns last 20 submissions. | |
| // This is disabled in production to avoid exposing potentially sensitive data. | |
| if (process.env.NODE_ENV === "production") { | |
| return json({ ok: false, error: "Not available in production" }, { status: 404 }); | |
| } | |
| return json({ | |
| ok: true, | |
| count: submissions.length, | |
| submissions: submissions.slice(-20).reverse() | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The date of birth field lacks validation to ensure users are of appropriate age for the hackathon. There's no check to prevent future dates or verify minimum age requirements. Consider adding validation to ensure the DOB is in the past and meets any age eligibility requirements.