Skip to content

Commit 687a04f

Browse files
authored
Merge pull request #68 from OpencodeNIT-R/development
Development
2 parents 5dcba90 + 1013066 commit 687a04f

7 files changed

Lines changed: 170 additions & 42 deletions

File tree

‎src/app/api/register/route.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import { getAuthenticatedUser } from '@/lib/auth-server';
55
import { paymentProofExists } from '@/lib/payment-proofs';
66
import { ambassadorGuardError, findOwnAmbassador, resolveReferralCode, type Registrant } from '@/lib/ambassadors';
77
import { registrationsClosedResponse, registrationsOpen } from '@/lib/registration-gate';
8-
import { isDuplicatePhoneError, PHONE_IN_USE_ERROR_MESSAGE } from '@/lib/validation';
8+
import {
9+
isDuplicatePhoneError,
10+
isIterSoaCollege,
11+
isIterSoaEmail,
12+
isIterSoaRefusal,
13+
ITER_SOA_ERROR_MESSAGE,
14+
PHONE_IN_USE_ERROR_MESSAGE,
15+
} from '@/lib/validation';
916
import { ATTEMPTS_USED_UP_ERROR, attemptsLeft, isAttemptsUsedUpError } from '@/lib/rejections';
1017
import {
1118
classifyStudentType,
@@ -193,6 +200,12 @@ export async function POST(req: NextRequest) {
193200
const input = parsed.data;
194201
const college = input.college;
195202

203+
// ITER - SOA students may not register: by the college they type or by an ITER / SOA email. The database refuses
204+
// them too (trg_refuse_iter_soa_registration, supabase/schema.sql section 18).
205+
if (isIterSoaCollege(college) || isIterSoaEmail(verifiedEmail)) {
206+
return NextResponse.json({ error: ITER_SOA_ERROR_MESSAGE, field: 'college' }, { status: 403 });
207+
}
208+
196209
const adminClient = getSupabaseAdmin(token);
197210

198211
// 3. Prevent duplicate registrations for the same authenticated user. A rejected one doesn't count: the user may
@@ -305,6 +318,9 @@ export async function POST(req: NextRequest) {
305318
if (isAttemptsUsedUpError(regError)) {
306319
return NextResponse.json({ error: ATTEMPTS_USED_UP_ERROR }, { status: 403 });
307320
}
321+
if (isIterSoaRefusal(regError)) {
322+
return NextResponse.json({ error: ITER_SOA_ERROR_MESSAGE, field: 'college' }, { status: 403 });
323+
}
308324
if (regError.code !== '23505') break;
309325
// Unique violation on utr: another registration claimed this UTR between the pre-check and the insert (possibly
310326
// this user's own concurrent submit, when the UTR index is checked before the user_id index).

‎src/components/innovision/AuthOverlay.tsx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Logo } from './icons';
99
import EntryPassCard from './EntryPassCard';
1010
import SeatCounter from './SeatCounter';
1111
import styles from './AuthOverlay.module.css';
12+
import { isIterSoaCollege, isIterSoaEmail, ITER_SOA_ERROR_MESSAGE } from '@/lib/validation';
1213

1314
/* ── Payment QR lightbox state lives here, outside JSX ── */
1415

@@ -17,6 +18,7 @@ export default function AuthOverlay({ v }: { v: V }) {
1718
useDialogFocus(v.authHidden === 'false', dialog, v.closeAuthH);
1819
const [userCollege, setUserCollege] = useState<string | null>(null);
1920
const collegeValue = userCollege !== null ? userCollege : (v.regVals?.college || '');
21+
const isIterSoa = isIterSoaCollege(collegeValue) || isIterSoaEmail(v.regVals?.email);
2022
const [payQrOpen, setPayQrOpen] = useState(false);
2123
return (
2224
<>
@@ -91,6 +93,10 @@ export default function AuthOverlay({ v }: { v: V }) {
9193

9294
<div style={{ display: v.d.s0, flexDirection: "column", gap: "20px" }}>
9395
<h3 data-s-in="" style={{ margin: "0 0 4px", fontFamily: "var(--font-display)", fontWeight: "400", fontSize: "clamp(24px,2.2vw,32px)", lineHeight: "1.1" }}>Your details</h3>
96+
<p data-s-in="" role="note" style={{ display: "flex", alignItems: "flex-start", gap: "10px", margin: "-4px 0 0", padding: "12px 14px", border: "1px solid rgba(239,68,68,.55)", background: "rgba(239,68,68,.1)", fontSize: "14px", lineHeight: "1.45", color: "#fca5a5", overflowWrap: "anywhere" }}>
97+
<svg viewBox="0 0 16 16" aria-hidden="true" style={{ flex: "none", width: "16px", height: "16px", marginTop: "1px" }}><circle cx="8" cy="8" r="6.5" fill="none" stroke="currentColor" strokeWidth="1.5"></circle><path d="M3.5 12.5l9-9" stroke="currentColor" strokeWidth="1.5"></path></svg>
98+
<span>Students of ITER / SOA (Siksha &lsquo;O&rsquo; Anusandhan) are not allowed to register.</span>
99+
</p>
94100
{v.gNote ? (
95101
<p data-s-in="" role="status" style={{ display: "flex", alignItems: "flex-start", gap: "10px", margin: "-4px 0 0", padding: "12px 14px", border: "1px solid rgba(236,232,223,.18)", background: "rgba(236,232,223,.04)", fontSize: "14px", lineHeight: "1.45", color: "rgba(236,232,223,.86)", overflowWrap: "anywhere" }}>
96102
<svg viewBox="0 0 16 16" aria-hidden="true" style={{ flex: "none", width: "16px", height: "16px", marginTop: "1px", color: "oklch(0.78 0.075 85)" }}><path d="M3 8.5 6.5 12 13 4.5" fill="none" stroke="currentColor" strokeWidth="2"></path></svg>
@@ -143,7 +149,7 @@ export default function AuthOverlay({ v }: { v: V }) {
143149
}}
144150
style-focus="border-color:oklch(0.78 0.075 85);background:rgba(236,232,223,.08)"
145151
/>
146-
{v.err.college ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.college}</span>) : null}
152+
{v.err.college || isIterSoa ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{isIterSoa ? ITER_SOA_ERROR_MESSAGE : v.err.college}</span>) : null}
147153
</label>
148154
<label data-s-in="" style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
149155
<span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>EMAIL</span>

‎src/components/innovision/Innovision.tsx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { fetchSeatCount, SEATS_FULL_MESSAGE } from '@/lib/seats';
5959
import { AMBASSADOR_FORM_URL, AMBASSADOR_SECTION } from '@/config/ambassador';
6060
import { REGISTRATION_FEE, UPI_ID, UPI_PAY_LINK, UPI_QR_SRC } from '@/config/payment';
6161
import { SKIP_INTRO_KEY } from '@/components/coming-soon/SkipIntroOnReturn';
62-
import { isValidGoogleDriveUrl, GENDER_OPTIONS as GENDERS, normalizeReferralCode, isReferralCode, formatReferralCode, REFERRAL_CODE_FORMAT_ERROR } from '@/lib/validation';
62+
import { isValidGoogleDriveUrl, GENDER_OPTIONS as GENDERS, normalizeReferralCode, isReferralCode, formatReferralCode, REFERRAL_CODE_FORMAT_ERROR, isIterSoaCollege, isIterSoaEmail, ITER_SOA_ERROR_MESSAGE } from '@/lib/validation';
6363
import { httpErrorMessage, isTransientFailure } from '@/lib/api-errors';
6464

6565
gsap.registerPlugin(ScrollTrigger, ScrambleTextPlugin);
@@ -2590,6 +2590,8 @@ export class Innovision extends Component<Props, State> {
25902590
err.college = 'Which college are you from?';
25912591
} else if (college.length > 200) {
25922592
err.college = 'Keep the college name under 200 characters.';
2593+
} else if (isIterSoaCollege(college) || isIterSoaEmail(email)) {
2594+
err.college = ITER_SOA_ERROR_MESSAGE;
25932595
}
25942596
if (!/^[6-9]\d{9}$/.test(phone)) err.phone = 'Use a 10-digit Indian mobile number.';
25952597
if (!enrollment_no) err.enrollment_no = 'Enter your college roll / enrollment number.';

‎src/lib/validation.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,46 @@
22
* Client & Server safe validation utilities for Innovision 2026.
33
*/
44

5+
export const ITER_SOA_ERROR_MESSAGE =
6+
"Students of ITER / SOA (Siksha 'O' Anusandhan) are not allowed to register for Innovision 2026.";
7+
8+
/** Whole words: ITER, SOA, SOAU, and the same spelt out with dots or spaces (I.T.E.R., S O A). */
9+
const ITER_SOA_WORD = /(^| )(iter|soa|soau|i t e r|s o a|s o a u)( |$)/;
10+
11+
/**
12+
* With spaces and punctuation gone: ITER-SOA / SOA ITER, Institute of Technical Education (and/&) Research, and
13+
* Siksha 'O' Anusandhan however it is spelt (Shiksha, Sikha, O or no O, Anushandhan, Anusandhaan).
14+
*/
15+
const ITER_SOA_COMPACT = /itersoa|soaiter|technicaleducationa?n?d?research|sh?ik?s?h?a+o?a*nush?andh?a+n/;
16+
17+
/**
18+
* True when a college name is ITER (Institute of Technical Education and Research) or SOA (Siksha 'O' Anusandhan),
19+
* whatever the case, spelling, punctuation, accents or full-width letters. Must match public.is_iter_soa_college in
20+
* supabase/schema.sql (section 18).
21+
*/
22+
export function isIterSoaCollege(college: string | null | undefined): boolean {
23+
if (typeof college !== 'string') return false;
24+
// "I.T.E.R., S.O.A" -> "i t e r s o a"
25+
const spaced = college
26+
.normalize('NFKD')
27+
.replace(/[\u0300-\u036f]/g, '')
28+
.toLowerCase()
29+
.replace(/[^a-z0-9]+/g, ' ')
30+
.trim();
31+
return ITER_SOA_WORD.test(spaced) || ITER_SOA_COMPACT.test(spaced.replace(/ /g, ''));
32+
}
33+
34+
/** True for an ITER / SOA email address (@soa.ac.in, @iter.ac.in and their subdomains). */
35+
export function isIterSoaEmail(email: string | null | undefined): boolean {
36+
if (typeof email !== 'string') return false;
37+
return /@([a-z0-9-]+\.)*(soa|iter)\./.test(email.trim().toLowerCase());
38+
}
39+
40+
/** The database refused a registration as ITER / SOA (trg_refuse_iter_soa_registration in supabase/schema.sql). */
41+
export function isIterSoaRefusal(err: { message?: string } | null | undefined): boolean {
42+
return /ITER \/ SOA registrations are not allowed/i.test(err?.message ?? '');
43+
}
44+
545
export const PHONE_IN_USE_ERROR_MESSAGE =
646
'This phone number is already linked to another account. Please use a different number.';
747

‎supabase/manual/allow_iter_soa.sql‎

Lines changed: 0 additions & 38 deletions
This file was deleted.

‎supabase/manual/ban_iter_soa.sql‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
-- ==============================================================================
2+
-- MIGRATION (run once in the Supabase SQL editor): ITER - SOA students may not register.
3+
--
4+
-- Why: ITER (Institute of Technical Education and Research) and SOA (Siksha 'O' Anusandhan) students are not allowed
5+
-- at Innovision 2026. /api/register and the form refuse them by the college they type (any case or spelling: ITER,
6+
-- I.T.E.R., iter-soa, SOA University, Shiksha O Anushandhan...) or by an @soa. / @iter. email; this makes the
7+
-- database refuse them too, whoever inserts (the service role included).
8+
-- This adds section 18 of supabase/schema.sql: public.is_iter_soa_college and trg_refuse_iter_soa_registration.
9+
-- No existing row changes: registrations made before the ban stay, and staff can still confirm or reject them.
10+
--
11+
-- Run it before or after deploying the code: either refuses ITER - SOA on its own.
12+
-- Kept out of supabase/migrations/ on purpose so the Supabase CLI never applies it automatically. Safe to re-run.
13+
-- ==============================================================================
14+
15+
-- Students of ITER (Institute of Technical Education and Research) and SOA (Siksha 'O' Anusandhan) may not register.
16+
-- True for any spelling of either name, whatever the case or punctuation: ITER, I.T.E.R., iter-soa, SOA University,
17+
-- Institute of Technical Education & Research, Shiksha O Anushandhan... Must match isIterSoaCollege in
18+
-- src/lib/validation.ts.
19+
CREATE OR REPLACE FUNCTION public.is_iter_soa_college(p_college TEXT) RETURNS BOOLEAN AS $$
20+
DECLARE
21+
v_spaced TEXT;
22+
BEGIN
23+
IF p_college IS NULL THEN
24+
RETURN FALSE;
25+
END IF;
26+
-- "I.T.E.R., S.O.A" -> "i t e r s o a": accents and full-width letters folded, everything but a-z and 0-9 a space.
27+
v_spaced := btrim(regexp_replace(
28+
lower(regexp_replace(normalize(p_college, NFKD), '[\u0300-\u036f]', '', 'g')) COLLATE "C",
29+
'[^a-z0-9]+', ' ', 'g'
30+
));
31+
RETURN v_spaced ~ '(^| )(iter|soa|soau|i t e r|s o a|s o a u)( |$)'
32+
OR replace(v_spaced, ' ', '') ~ '(itersoa|soaiter|technicaleducationa?n?d?research|sh?ik?s?h?a+o?a*nush?andh?a+n)';
33+
END;
34+
$$ LANGUAGE plpgsql IMMUTABLE SET search_path = public;
35+
36+
-- Refuses a registration from ITER - SOA, by its college or by an ITER / SOA email (@soa.ac.in, @iter.ac.in and their
37+
-- subdomains, as isIterSoaEmail), whoever inserts: /api/register (service role) included. Registrations made before
38+
-- the ban stay, and staff can still confirm or reject them: only a new row, or a change to its college or email, is
39+
-- checked. /api/register looks for this message.
40+
CREATE OR REPLACE FUNCTION public.refuse_iter_soa_registration() RETURNS TRIGGER AS $$
41+
BEGIN
42+
IF public.is_iter_soa_college(NEW.college) OR lower(btrim(COALESCE(NEW.email, ''))) ~ '@([a-z0-9-]+\.)*(soa|iter)\.' THEN
43+
RAISE EXCEPTION 'ITER / SOA registrations are not allowed' USING ERRCODE = '23514';
44+
END IF;
45+
RETURN NEW;
46+
END;
47+
$$ LANGUAGE plpgsql SET search_path = public;
48+
49+
DROP TRIGGER IF EXISTS trg_refuse_iter_soa_registration ON public.registrations;
50+
CREATE TRIGGER trg_refuse_iter_soa_registration
51+
BEFORE INSERT OR UPDATE OF college, email ON public.registrations
52+
FOR EACH ROW EXECUTE FUNCTION public.refuse_iter_soa_registration();
53+
54+
-- Internal: only the trigger above uses it.
55+
REVOKE EXECUTE ON FUNCTION public.refuse_iter_soa_registration() FROM PUBLIC, anon, authenticated, service_role;

‎supabase/schema.sql‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ CREATE INDEX IF NOT EXISTS idx_registrations_user_id ON public.registrations(use
8080
CREATE INDEX IF NOT EXISTS idx_registrations_status ON public.registrations(status);
8181
CREATE INDEX IF NOT EXISTS idx_registrations_reg_id ON public.registrations(registration_id);
8282

83-
-- ITER - SOA students may register: drops the college CHECK that used to refuse them, on databases that still have it.
83+
-- The old ITER - SOA college CHECK: trg_refuse_iter_soa_registration (section 18) refuses them now, without blocking
84+
-- staff from reviewing a registration made before the ban.
8485
ALTER TABLE IF EXISTS public.registrations DROP CONSTRAINT IF EXISTS registrations_no_iter_soa;
8586

8687

@@ -1751,3 +1752,49 @@ CREATE TRIGGER trg_guard_registration_attempts
17511752
-- Internal: only the triggers above use these.
17521753
REVOKE EXECUTE ON FUNCTION public.move_registration_counter() FROM PUBLIC, anon, authenticated, service_role;
17531754
REVOKE EXECUTE ON FUNCTION public.guard_registration_attempts() FROM PUBLIC, anon, authenticated, service_role;
1755+
1756+
1757+
-- ==============================================================================
1758+
-- 18. NO ITER - SOA REGISTRATIONS
1759+
-- ==============================================================================
1760+
-- Students of ITER (Institute of Technical Education and Research) and SOA (Siksha 'O' Anusandhan) may not register.
1761+
-- True for any spelling of either name, whatever the case or punctuation: ITER, I.T.E.R., iter-soa, SOA University,
1762+
-- Institute of Technical Education & Research, Shiksha O Anushandhan... Must match isIterSoaCollege in
1763+
-- src/lib/validation.ts.
1764+
CREATE OR REPLACE FUNCTION public.is_iter_soa_college(p_college TEXT) RETURNS BOOLEAN AS $$
1765+
DECLARE
1766+
v_spaced TEXT;
1767+
BEGIN
1768+
IF p_college IS NULL THEN
1769+
RETURN FALSE;
1770+
END IF;
1771+
-- "I.T.E.R., S.O.A" -> "i t e r s o a": accents and full-width letters folded, everything but a-z and 0-9 a space.
1772+
v_spaced := btrim(regexp_replace(
1773+
lower(regexp_replace(normalize(p_college, NFKD), '[\u0300-\u036f]', '', 'g')) COLLATE "C",
1774+
'[^a-z0-9]+', ' ', 'g'
1775+
));
1776+
RETURN v_spaced ~ '(^| )(iter|soa|soau|i t e r|s o a|s o a u)( |$)'
1777+
OR replace(v_spaced, ' ', '') ~ '(itersoa|soaiter|technicaleducationa?n?d?research|sh?ik?s?h?a+o?a*nush?andh?a+n)';
1778+
END;
1779+
$$ LANGUAGE plpgsql IMMUTABLE SET search_path = public;
1780+
1781+
-- Refuses a registration from ITER - SOA, by its college or by an ITER / SOA email (@soa.ac.in, @iter.ac.in and their
1782+
-- subdomains, as isIterSoaEmail), whoever inserts: /api/register (service role) included. Registrations made before
1783+
-- the ban stay, and staff can still confirm or reject them: only a new row, or a change to its college or email, is
1784+
-- checked. /api/register looks for this message.
1785+
CREATE OR REPLACE FUNCTION public.refuse_iter_soa_registration() RETURNS TRIGGER AS $$
1786+
BEGIN
1787+
IF public.is_iter_soa_college(NEW.college) OR lower(btrim(COALESCE(NEW.email, ''))) ~ '@([a-z0-9-]+\.)*(soa|iter)\.' THEN
1788+
RAISE EXCEPTION 'ITER / SOA registrations are not allowed' USING ERRCODE = '23514';
1789+
END IF;
1790+
RETURN NEW;
1791+
END;
1792+
$$ LANGUAGE plpgsql SET search_path = public;
1793+
1794+
DROP TRIGGER IF EXISTS trg_refuse_iter_soa_registration ON public.registrations;
1795+
CREATE TRIGGER trg_refuse_iter_soa_registration
1796+
BEFORE INSERT OR UPDATE OF college, email ON public.registrations
1797+
FOR EACH ROW EXECUTE FUNCTION public.refuse_iter_soa_registration();
1798+
1799+
-- Internal: only the trigger above uses it.
1800+
REVOKE EXECUTE ON FUNCTION public.refuse_iter_soa_registration() FROM PUBLIC, anon, authenticated, service_role;

0 commit comments

Comments
 (0)