Skip to content

Jwt#16

Open
Lavi2910 wants to merge 5 commits into
mainfrom
jwt
Open

Jwt#16
Lavi2910 wants to merge 5 commits into
mainfrom
jwt

Conversation

@Lavi2910

@Lavi2910 Lavi2910 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Description

jwt
Please include a summary of the changes and the related issue.

Related Issue(s)

Fixes # (issue number)

Checklist:

  • I have performed a self-review of my own code
  • My changes generate no new warnings

Screenshots (if appropriate):

Summary by CodeRabbit

  • New Features
    • Added Bearer token authentication middleware for selected protected API endpoints.
    • QR login now returns a generated session token (token) on successful verification.
  • Bug Fixes
    • Strengthened JWT-based token validation, including stricter QR token structure and token-type checking.
  • Configuration
    • Updated environment template to include JWT_SECRET; added required JWT dependencies.
  • API Changes
    • Made patientId optional for GET /api/patients/:patientId.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29628f6b-1e29-447e-8bd4-4884d6306a96

📥 Commits

Reviewing files that changed from the base of the PR and between 561962f and 6cb6f56.

📒 Files selected for processing (2)
  • src/app.ts
  • src/auth/jwt.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app.ts
  • src/auth/jwt.ts

📝 Walkthrough

Walkthrough

Adds JWT-based authentication helpers and middleware, migrates QR token signing and verification to JWTs, returns a session token from QR login, and updates route wiring. The env template and package manifest are updated with the JWT secret and dependencies.

Changes

JWT Authentication

Layer / File(s) Summary
JWT foundation
package.json, .env.example, src/auth/jwt.ts
Adds the JWT runtime and type dependencies, the JWT_SECRET env placeholder, and typed JWT helpers that validate the secret, sign payloads, and verify token types.
Authentication gate
src/constants/auth.constants.ts, src/auth/authenticate.ts, src/app.ts
Adds bearer-token and auth error constants, validates the authorization header and patient lookup in authenticate, and mounts that middleware between the public and protected route groups.
QR token migration
src/services/qr-token.service.ts
Defines JWT-backed QR token signing and verification, including the colon-delimited token format and signed-payload checks.
QR login session token
src/controllers/login/login.controller.ts
verifyQR signs a session token from the patient id and returns it in the success JSON as token.
Patient route path
src/routes/patient.routes.ts
Makes the patient GET route accept an optional patientId parameter while keeping the same handler.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I sniffed the code through moonlit dew,
Found tokens hopping, fresh and new.
Bearer trails and QR signs gleam,
A secret key powers the dream.
Hop hop — auth flows cleanly through!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete: it lacks a real change summary and does not provide the required related issue number. Fill in the summary, link the issue as "Fixes #", and keep the checklist/screenshots sections if applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague to identify the change and does not describe the JWT-related updates. Rename it to a concise summary of the JWT authentication changes, e.g. "Add JWT-based authentication and QR token signing".
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jwt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/auth/authenticate.ts`:
- Around line 4-17: The authenticate middleware only validates the token and
sets req.userId, but it does not enforce that the request’s patientId belongs to
that user, leaving patient-scoped routes vulnerable when handlers read patientId
from query or path. Update authenticate (or a shared patient-guard used by the
appointment, metrics, and patient routes) to derive the patient identity from
verifyToken and compare/bind it to req.userId before calling next(), so the
controller layer no longer needs to repeat the equality check.

In `@src/auth/jwt.ts`:
- Around line 15-20: The JWT helpers in issueToken and verifyToken are being
reused for both session auth and QR signatures, which lets a QR signature be
accepted as a Bearer token by authenticate(). Split the token flows so session
tokens and QR-signature tokens use separate helpers and/or separate
secrets/payload shapes, then update authenticate() to only accept session-token
verification and explicitly reject QR-token formats.

In `@src/services/qr-token.service.ts`:
- Line 20: The QR token flow is reusing the same JWT helper as session auth and
embedding password-bearing payloads directly into the QR value. Update the
qr-token.service logic around issueToken and authenticate to generate a separate
purpose-scoped, opaque one-time QR token or nonce, keep the password only on the
server, and ensure QR tokens cannot be accepted as Bearer/session JWTs. Make
protected routes and token validation accept only session-purpose tokens, and
adjust any QR payload/signature handling so the QR value never contains reusable
credentials.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61c2312d-e65a-46a3-9ed9-d1d50d32ec8f

📥 Commits

Reviewing files that changed from the base of the PR and between 8b21512 and a6fbd8d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .env.example
  • package.json
  • src/auth/authenticate.ts
  • src/auth/jwt.ts
  • src/constants/email/email.constants.ts
  • src/controllers/login/login.controller.ts
  • src/routes/appointments.routes.ts
  • src/routes/metrics.routes.ts
  • src/routes/patient.routes.ts
  • src/services/login.service.ts
  • src/services/qr-token.service.ts

Comment thread src/auth/authenticate.ts
Comment thread src/auth/jwt.ts Outdated
Comment thread src/services/qr-token.service.ts Outdated
.update(`${encodedPayload}.${getKey()}`)
.digest('base64url')
.slice(0, SIGNATURE_LENGTH);
return issueToken(encodedPayload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Separate QR proof tokens from session bearer tokens and avoid password-bearing QR payloads.

encodedPayload is embedded directly in the QR token and contains password; base64url is only encoding, so anyone with the QR value can decode the credential. Also, signature is produced by the same issueToken helper accepted by authenticate, so the QR signature segment is itself a valid Bearer JWT with userId = encodedPayload. Use an opaque one-time QR nonce or a purpose-scoped QR token, keep the password server-side, and make protected routes accept only session-purpose JWTs.

Also applies to: 35-35, 48-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/qr-token.service.ts` at line 20, The QR token flow is reusing
the same JWT helper as session auth and embedding password-bearing payloads
directly into the QR value. Update the qr-token.service logic around issueToken
and authenticate to generate a separate purpose-scoped, opaque one-time QR token
or nonce, keep the password only on the server, and ensure QR tokens cannot be
accepted as Bearer/session JWTs. Make protected routes and token validation
accept only session-purpose tokens, and adjust any QR payload/signature handling
so the QR value never contains reusable credentials.

Comment thread src/services/qr-token.service.ts
Comment thread src/auth/authenticate.ts
Comment thread src/auth/authenticate.ts
Comment thread src/auth/authenticate.ts Outdated
userId?: string;
}

export function authenticate(req: AuthRequest, res: Response, next: NextFunction): void {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with export const authenticate = () ...

Comment thread src/auth/authenticate.ts Outdated
export function authenticate(req: AuthRequest, res: Response, next: NextFunction): void {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
res.status(401).json({ error: 'missing token' });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that we have library for status codes instead of that 401, also the error message and the Bearer should be saved inside constants and not hardcoded like this

Comment thread src/auth/authenticate.ts Outdated
return;
}
try {
const { userId } = verifyToken(header.slice(7));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save 7 into constant variable, no magic numbers

Comment thread src/auth/authenticate.ts
Comment thread src/auth/authenticate.ts Outdated
req.userId = userId;
next();
} catch {
res.status(401).json({ error: 'invalid or expired token' });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here status codes library and no hardcoded values

Comment thread src/auth/jwt.ts Outdated

const TOKEN_TTL = '2h';

export function issueToken(userId: string) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is a bit confusing, maybe change it into signToken or something that will tell us "this function sighns the token", also lets go with function expression and not function

Comment thread src/auth/jwt.ts Outdated
Comment thread src/routes/appointments.routes.ts Outdated

const router = Router();

router.use(authenticate);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why must you need to use this in every router? cant we use it globally ?

Comment thread src/services/login.service.ts Outdated

export const findPatientById = (userId: string): Patient | null => {
return patients.find((patient) => patient.patientId === userId) ?? null;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find is returning undefined if nothing was found, isnt it enough? why do you need to explicitly return null? this counts as bad practice

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i forgot to remove this function i didnt use it

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/controllers/login/login.controller.ts (1)

67-69: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Update the QR verify response schema to include token.

This endpoint now returns token: sessionToken, but src/routes/login.routes.ts still documents the 200 response as only message and user, so generated API docs/client expectations drift from the actual payload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/login/login.controller.ts` around lines 67 - 69, The QR
verify endpoint in login.controller now returns a token field, but the
documented 200 response in login.routes is still missing it. Update the response
schema used by the QR verify route to include token alongside message and user,
and keep the documented payload aligned with the actual return shape from the
login controller.
🧹 Nitpick comments (1)
src/app.ts (1)

42-46: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Make the protected route boundary explicit per route.

The bare app.use(authenticate) on Line 42 is order-sensitive. Mounting authenticate directly on the protected routes makes future public/protected route additions harder to misplace.

♻️ Proposed refactor
-app.use(authenticate);
-
-app.use('/api/patients', patientRoutes);
-app.use('/api/appointment', appointmentRoutes);
-app.use('/api/metrics/', metricsRoutes);
+app.use('/api/patients', authenticate, patientRoutes);
+app.use('/api/appointment', authenticate, appointmentRoutes);
+app.use('/api/metrics/', authenticate, metricsRoutes);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app.ts` around lines 42 - 46, The route protection in
app.use(authenticate) is implicit and order-sensitive, so make the protected
boundary explicit by attaching authenticate directly to each protected mount
point in app.ts. Update the app.use calls for patientRoutes, appointmentRoutes,
and metricsRoutes so each one is wrapped or chained with authenticate, and
remove the standalone global authenticate middleware to avoid accidentally
protecting future public routes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/auth/authenticate.ts`:
- Around line 18-35: The authenticate flow is conflating JWT validation failures
with database lookup failures, so any dbService.get or patientModel access error
is incorrectly returned as INVALID_OR_EXPIRED_TOKEN. Update authenticate to
catch verifyToken errors separately from the patient lookup around
dbService.get(patientModel, { patientId: userId }), keep the 401 response only
for missing/invalid tokens, and send lookup/service errors to the next error
handler instead of swallowing them in the same catch.

---

Outside diff comments:
In `@src/controllers/login/login.controller.ts`:
- Around line 67-69: The QR verify endpoint in login.controller now returns a
token field, but the documented 200 response in login.routes is still missing
it. Update the response schema used by the QR verify route to include token
alongside message and user, and keep the documented payload aligned with the
actual return shape from the login controller.

---

Nitpick comments:
In `@src/app.ts`:
- Around line 42-46: The route protection in app.use(authenticate) is implicit
and order-sensitive, so make the protected boundary explicit by attaching
authenticate directly to each protected mount point in app.ts. Update the
app.use calls for patientRoutes, appointmentRoutes, and metricsRoutes so each
one is wrapped or chained with authenticate, and remove the standalone global
authenticate middleware to avoid accidentally protecting future public routes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d79460d4-1dc9-4142-9f1f-ff4ce53d2ff8

📥 Commits

Reviewing files that changed from the base of the PR and between a6fbd8d and 0659003.

📒 Files selected for processing (6)
  • src/app.ts
  • src/auth/authenticate.ts
  • src/auth/jwt.ts
  • src/constants/auth.constants.ts
  • src/controllers/login/login.controller.ts
  • src/services/qr-token.service.ts
✅ Files skipped from review due to trivial changes (1)
  • src/constants/auth.constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/qr-token.service.ts

Comment thread src/auth/authenticate.ts
Comment on lines +18 to +35
try {
const { userId } = verifyToken(header.slice(BEARER_PREFIX.length));
const [patient] = await dbService.get(patientModel, { patientId: userId });

if (!patient) {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
return;
}

req.userId = userId;
next();
} catch {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don’t return token-invalid 401s for DB lookup failures.

Line 20 is inside the same catch as JWT verification, so a database/service error is reported as INVALID_OR_EXPIRED_TOKEN. Catch verification errors separately and pass lookup failures to the error handler.

🐛 Proposed fix
-  try {
-    const { userId } = verifyToken(header.slice(BEARER_PREFIX.length));
-    const [patient] = await dbService.get(patientModel, { patientId: userId });
+  let userId: string;
+
+  try {
+    ({ userId } = verifyToken(header.slice(BEARER_PREFIX.length)));
+  } catch {
+    res
+      .status(StatusCodes.UNAUTHORIZED)
+      .json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
+    return;
+  }
+
+  try {
+    const [patient] = await dbService.get(patientModel, { patientId: userId });
 
     if (!patient) {
       res
         .status(StatusCodes.UNAUTHORIZED)
         .json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
@@
 
     req.userId = userId;
     next();
-  } catch {
-    res
-      .status(StatusCodes.UNAUTHORIZED)
-      .json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
+  } catch (error) {
+    next(error);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const { userId } = verifyToken(header.slice(BEARER_PREFIX.length));
const [patient] = await dbService.get(patientModel, { patientId: userId });
if (!patient) {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
return;
}
req.userId = userId;
next();
} catch {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
}
let userId: string;
try {
({ userId } = verifyToken(header.slice(BEARER_PREFIX.length)));
} catch {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
return;
}
try {
const [patient] = await dbService.get(patientModel, { patientId: userId });
if (!patient) {
res
.status(StatusCodes.UNAUTHORIZED)
.json({ error: AUTH_ERROR_MESSAGES.INVALID_OR_EXPIRED_TOKEN });
return;
}
req.userId = userId;
next();
} catch (error) {
next(error);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth/authenticate.ts` around lines 18 - 35, The authenticate flow is
conflating JWT validation failures with database lookup failures, so any
dbService.get or patientModel access error is incorrectly returned as
INVALID_OR_EXPIRED_TOKEN. Update authenticate to catch verifyToken errors
separately from the patient lookup around dbService.get(patientModel, {
patientId: userId }), keep the 401 response only for missing/invalid tokens, and
send lookup/service errors to the next error handler instead of swallowing them
in the same catch.

@Lavi2910 Lavi2910 requested review from GilHeller and Tamir198 June 25, 2026 08:43
Comment thread src/auth/authenticate.ts
Tamir198
Tamir198 previously approved these changes Jun 25, 2026
Comment thread src/app.ts Outdated
Comment on lines 39 to 40
app.use('/api/tips', tipsRoutes);
app.use('/api/metrics/', metricsRoutes);
app.use('/api/email', emailRoutes);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tips and email route should be also protected by this middleware

return parsePayload(encodedPayload);
} catch {
} catch (error) {
console.error('Failed to verify QR token:', error);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have a logger for this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i couldt find a logger for this

Comment thread src/auth/jwt.ts Outdated

export const JWT_SECRET = process.env.JWT_SECRET;

const TOKEN_TTL = '2h';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider to make it an env variable so we can easily change it by the hospital request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants