Conversation
| catchInternal((req, res) => controller.verifyRegistrationEmail(req, res)), | ||
| authRouter.route('/verify-email-code').post( | ||
| validate(AuthValidator.verifyEmailCodeSchema, 'body'), | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailCode(req, res)), |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
To fix this, we should add rate limiting middleware to the sensitive authorization routes in this router, particularly /verify-email-code (and likely other similar endpoints like /verify-email-token and /resend-verification-email). The standard way in an Express/TypeScript project is to use a well‑known library such as express-rate-limit, configure a limiter with sensible defaults (e.g., small number of attempts per time window for code verification), and plug it into the route’s middleware chain before the expensive controller logic, without changing any existing controller behavior.
Concretely, in server/routes/auth.ts:
- Add an import for
express-rate-limit. - Define one or more limiter instances, e.g.
emailCodeLimiter, configured for short windows and lowmaxto prevent brute‑force on email verification codes, and possibly a more generalauthLimiterfor other auth endpoints. - Apply
emailCodeLimiterto the/verify-email-coderoute by inserting it into the.post()middleware list betweenvalidate(...)andcatchInternal(...)(or immediately aftervalidate, order between those two doesn’t materially change behavior). For consistency and improved security, we can also apply an appropriate limiter to/verify-email-tokenand/resend-verification-email, but we will not alter the controllers themselves or their return values.
This requires only adding the express-rate-limit import and definitions at the top of this file, and updating the route definitions where they are already shown.
| @@ -11,6 +11,7 @@ | ||
| } from '../middleware'; | ||
| import { catchInternal } from '../helpers'; | ||
| import bodyParser from 'body-parser'; | ||
| import rateLimit from 'express-rate-limit'; | ||
|
|
||
| const authRouter = express.Router(); | ||
| const controller = new AuthController(); | ||
| @@ -21,7 +22,27 @@ | ||
| const CAS_BRIDGE_SERVER_URL = (_casPrefix.startsWith('https://') || _casPrefix.startsWith('http://')) ? _casPrefix : `https://${_casPrefix}`; | ||
| const SELF_URL = `${_selfDomainSafe}/api/v1/auth/cas-bridge`; | ||
|
|
||
| const emailCodeLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 10, // limit each IP to 10 verification attempts per window | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }); | ||
|
|
||
| const emailTokenLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, | ||
| max: 30, | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }); | ||
|
|
||
| const resendVerificationEmailLimiter = rateLimit({ | ||
| windowMs: 60 * 60 * 1000, // 1 hour | ||
| max: 5, // limit resends to 5 per hour | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }); | ||
|
|
||
| authRouter.route('/register').post( | ||
| validate(AuthValidator.registerSchema, 'body'), | ||
| catchInternal((req, res) => controller.register(req, res)), | ||
| @@ -29,16 +49,19 @@ | ||
|
|
||
| authRouter.route('/verify-email-code').post( | ||
| validate(AuthValidator.verifyEmailCodeSchema, 'body'), | ||
| emailCodeLimiter, | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailCode(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/verify-email-token').post( | ||
| validate(AuthValidator.verifyEmailTokenSchema, 'body'), | ||
| emailTokenLimiter, | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailToken(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/resend-verification-email').post( | ||
| validate(AuthValidator.resendVerificationEmailSchema, 'body'), | ||
| resendVerificationEmailLimiter, | ||
| catchInternal((req, res) => controller.resendVerificationEmail(req, res)), | ||
| ); | ||
|
|
| @@ -69,7 +69,8 @@ | ||
| "vite": "^6.4.1", | ||
| "vue": "^3.2.45", | ||
| "vue-i18n": "^11.1.10", | ||
| "yargs": "^17.7.2" | ||
| "yargs": "^17.7.2", | ||
| "express-rate-limit": "^8.2.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@aws-sdk/types": "^3.329.0", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.2.1 | None |
|
|
||
| authRouter.route('/verify-email-token').post( | ||
| validate(AuthValidator.verifyEmailTokenSchema, 'body'), | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailToken(req, res)), |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, the fix is to introduce an Express‑compatible rate‑limiting middleware (for example, via express-rate-limit) and apply it to sensitive routes that perform authentication/authorization or other expensive operations. This middleware intercepts requests before they reach the controller, tracks request counts per client (typically keyed by IP), and rejects or delays excessive requests, mitigating denial‑of‑service and brute‑force attacks.
For this file, the least intrusive fix without changing existing functionality is:
- Import a well‑known rate‑limiter (
express-rate-limit). - Define one or more limiter instances with sensible defaults (e.g., separate, stricter limits for auth/verification endpoints).
- Apply the limiter middleware to the affected routes; at minimum the route CodeQL flagged (
/verify-email-token), and, following the same rationale, to adjacent registration/verification endpoints (/register,/verify-email-code,/resend-verification-email,/complete-registration,/external-provision), since they are also auth‑related and may be expensive.
Concretely in server/routes/auth.ts:
-
Add an import for
express-rate-limitnear the top, without modifying existing imports. -
After router/controller initialization, create a limiter, e.g.:
const authRateLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, standardHeaders: true, legacyHeaders: false });
These values are examples and can be tuned later; they preserve existing behavior for normal users while limiting abuse.
-
Insert
authRateLimiterin the middleware chains of the relevant routes, especially the flagged/verify-email-tokenhandler, ensuring it appears beforecatchInternalso that rate‑limit responses are generated by the limiter itself and existing controller logic is untouched.
No existing controller logic, validation schemas, or helper functions need changes; we’re only extending middleware chains.
| @@ -11,10 +11,18 @@ | ||
| } from '../middleware'; | ||
| import { catchInternal } from '../helpers'; | ||
| import bodyParser from 'body-parser'; | ||
| import rateLimit from 'express-rate-limit'; | ||
|
|
||
| const authRouter = express.Router(); | ||
| const controller = new AuthController(); | ||
|
|
||
| const authRateLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // limit each IP to 100 requests per windowMs | ||
| standardHeaders: true, | ||
| legacyHeaders: false, | ||
| }); | ||
|
|
||
| const _selfDomain = process.env.DOMAIN || 'localhost:5001'; | ||
| const _selfDomainSafe = _selfDomain.startsWith('https://') ? _selfDomain : `https://${_selfDomain}`; | ||
| const _casPrefix = process.env.CAS_BRIDGE_SERVER_URL || 'http://localhost:8443/cas'; | ||
| @@ -23,32 +27,38 @@ | ||
|
|
||
|
|
||
| authRouter.route('/register').post( | ||
| authRateLimiter, | ||
| validate(AuthValidator.registerSchema, 'body'), | ||
| catchInternal((req, res) => controller.register(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/verify-email-code').post( | ||
| authRateLimiter, | ||
| validate(AuthValidator.verifyEmailCodeSchema, 'body'), | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailCode(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/verify-email-token').post( | ||
| authRateLimiter, | ||
| validate(AuthValidator.verifyEmailTokenSchema, 'body'), | ||
| catchInternal((req, res) => controller.verifyRegistrationEmailToken(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/resend-verification-email').post( | ||
| authRateLimiter, | ||
| validate(AuthValidator.resendVerificationEmailSchema, 'body'), | ||
| catchInternal((req, res) => controller.resendVerificationEmail(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/complete-registration').post( | ||
| authRateLimiter, | ||
| verifyAPIAuthentication, | ||
| validate(AuthValidator.completeRegistrationSchema, 'body'), | ||
| catchInternal((req, res) => controller.completeRegistration(req, res)), | ||
| ); | ||
|
|
||
| authRouter.route('/external-provision').post( | ||
| authRateLimiter, | ||
| verifyAPIAuthentication, | ||
| ensureActorIsAPIUser, | ||
| ensureAPIUserHasPermission(['users:write']), |
| @@ -69,7 +69,8 @@ | ||
| "vite": "^6.4.1", | ||
| "vue": "^3.2.45", | ||
| "vue-i18n": "^11.1.10", | ||
| "yargs": "^17.7.2" | ||
| "yargs": "^17.7.2", | ||
| "express-rate-limit": "^8.2.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@aws-sdk/types": "^3.329.0", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.2.1 | None |
There was a problem hiding this comment.
Pull request overview
This pull request adds token-based email verification links alongside the existing code-based verification system for user registration. Users can now verify their email addresses either by entering a 6-digit code or by clicking a link in their verification email.
Changes:
- Added new API endpoints for token-based email verification and resending verification emails
- Updated the EmailVerification model to include token field and removed the email field
- Modified user registration flow to send both verification code and link in emails
- Added new UI page for handling email verification via token link
- Blocked login for users with unverified emails
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| server/validators/auth.ts | Added validation schemas for token-based verification and resend endpoints |
| server/types/auth.ts | Renamed and added type definitions for new verification methods |
| server/routes/auth.ts | Added routes for verify-email-token and resend-verification-email endpoints |
| server/models/User.ts | Added email_verified boolean field to track verification status |
| server/models/EmailVerification.ts | Removed email field, added token field for link-based verification |
| server/controllers/EmailVerificationController.ts | Added token generation and verification methods, updated email template |
| server/controllers/AuthController.ts | Added new verification handlers and login blocking for unverified users |
| server/tests/auth.spec.ts | Updated test endpoints to use new verify-email-code route |
| pages/verify-email/+onBeforeRender.ts | New server-side page handler to extract token from URL |
| pages/verify-email/+Page.vue | New UI page for token-based verification with resend functionality |
| pages/register/+Page.vue | Renamed VerifyEmail component to VerifyEmailForm for clarity |
| locales/en-us.json | Added localization strings for email verification page |
| components/registration/VerifyEmailForm.vue | Updated API endpoint to use verify-email-code |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| message: 'We need to verify your email address before you can continue. Please use the link below to begin the verification process.', | ||
| autoRedirect: true, | ||
| links: { | ||
| 'Go': `${SELF_BASE}/api/v1/auth/login?${redirectParams.toString()}`, |
There was a problem hiding this comment.
The redirect link for unverified users points back to the login endpoint, which will likely result in an infinite redirect loop. The link should probably direct users to a page where they can request a new verification email or enter their verification code, not back to the login page that will block them again.
| 'Go': `${SELF_BASE}/api/v1/auth/login?${redirectParams.toString()}`, | |
| 'Go': `${SELF_BASE}/verify-email?${redirectParams.toString()}`, |
109ead0 to
328639e
Compare
No description provided.