Skip to content

chores: implemented jwt authentication middleware#21

Open
KodeSage wants to merge 2 commits into
ShadeProtocol:mainfrom
KodeSage:feat/jwt_auth
Open

chores: implemented jwt authentication middleware#21
KodeSage wants to merge 2 commits into
ShadeProtocol:mainfrom
KodeSage:feat/jwt_auth

Conversation

@KodeSage

@KodeSage KodeSage commented Jun 27, 2026

Copy link
Copy Markdown

JWT authentication middleware for merchant-facing routes(CLOSES #8)

Summary

Merchant-facing endpoints (invoices, registration, and future dashboard/profile/API-key routes) need to be gated behind a valid access token. The codebase already issued JWT access tokens (jwt.sign({ sub: merchant.id, address }, JWT_SECRET) in auth.services.ts), but the middleware never actually verified them — it did a refreshToken database session lookup instead. That meant the signed access token was issued but never checked anywhere.

This PR makes authenticateMerchant a real JWT guard: it verifies the Bearer token against JWT_SECRET, loads the merchant referenced by the token's sub claim, attaches it to req.merchant, and returns clear, distinct 401s for every failure mode.

What changed

File Change
src/middlewares/auth.middleware.ts Rewrote to verify the JWT, decode sub, load the merchant, and attach req.merchant; spec-compliant 401 messages
tests/unit/auth.middleware.test.ts New — 7 unit tests covering every acceptance criterion
tests/integration/invoice.routes.test.ts Authenticate with a real signed JWT + mock merchant.findUnique (was mocking the old refresh-token lookup)
tests/integration/merchant.register.test.ts Same JWT-based auth update + assert the new 401 messages

Behavior

Case Response
Missing or non-Bearer Authorization header 401 { "error": "Authentication required" }
Malformed / expired / wrong-secret JWT 401 { "error": "Invalid or expired token" }
Valid JWT but merchant no longer in DB 401 { "error": "Invalid or expired token" }
Valid JWT + merchant exists req.merchant populated, next() called

The middleware is applied at the router levelinvoice.routes.ts uses router.use(authenticateMerchant), and the protected merchant /register route guards via the same middleware — so new protected route groups only need a single router.use(authenticateMerchant) to opt in.

Notes / deviations

  • Path convention: the task description referenced src/middleware/ (singular), but the project already uses src/middlewares/ (plural) and every route imports from there, so the existing file was updated rather than forking a parallel path.
  • Types: src/types/express.d.ts already augments Express.Request with merchant?: Merchant, so req.merchant is typed project-wide; no type changes were needed.
  • Out of scope: the refreshToken table and refresh-token rotation (exchanging a refresh token for a new 15-minute access token) are untouched — the middleware only validates access tokens. A /auth/refresh endpoint can follow separately.

Testing

Test Suites: 12 passed, 12 total
Tests:       112 passed, 112 total
  • Added 7 focused unit tests (valid token, missing header, non-Bearer scheme, malformed token, expired token, forged-secret token, deleted merchant).
  • npx tsc --noEmit is clean for all src/ code. (Pre-existing, unrelated: missing @types/urijs declarations inside node_modules/@stellar/stellar-sdk.)

Summary by CodeRabbit

  • Bug Fixes
    • Updated authentication to use signed Bearer access tokens instead of session/refresh-token validation.
    • Clearer unauthenticated error handling: missing/invalid Bearer header now returns 401 { error: 'Authentication required' } or 401 { error: 'Invalid or expired token' }.
    • Authenticated requests now reliably resolve the merchant before granting access.
  • Tests
    • Integration and unit tests now generate and use real JWTs and assert the updated authentication error messages and merchant lookup behavior.

@coderabbitai

coderabbitai Bot commented Jun 27, 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 Plus

Run ID: e9f731c7-b088-4531-bbff-f99d55e41ae8

📥 Commits

Reviewing files that changed from the base of the PR and between 6925628 and 8700c13.

📒 Files selected for processing (1)
  • tests/integration/merchant.register.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/merchant.register.test.ts

📝 Walkthrough

Walkthrough

authenticateMerchant now verifies Bearer JWTs with jwtSecret, loads the merchant by token subject, and returns specific 401 errors for missing or invalid tokens. Unit and integration tests were updated to use signed JWTs and the new authentication responses.

Changes

JWT Auth Middleware Rewrite

Layer / File(s) Summary
JWT middleware implementation
src/middlewares/auth.middleware.ts
Adds the JWT payload type, updates middleware docs, and replaces refresh-token/session validation with Bearer parsing, jwt.verify, merchant lookup by payload.sub, and distinct 401 responses for missing versus invalid tokens.
Unit tests for authenticateMerchant
tests/unit/auth.middleware.test.ts
Adds coverage for valid JWT authentication, missing and non-Bearer headers, malformed and expired tokens, wrong-secret tokens, and a missing merchant record.
Integration test updates
tests/integration/invoice.routes.test.ts, tests/integration/merchant.register.test.ts
Switches integration tests to signed JWTs using environment.jwtSecret, mocks merchant.findUnique, and updates expected authentication error payloads and authorized requests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ShadeProtocol/shade-backend#17: Uses authenticated merchant context in invoice-related routes and tests, which is directly affected by the JWT middleware change.
  • ShadeProtocol/shade-backend#18: Changes the same authenticateMerchant path, previously using refresh-token/session lookup instead of JWT verification.

Suggested reviewers

  • codebestia

🐇 Bearer hops and secrets sing,
JWTs now guard each merchant thing.
If tokens fade or merchants flee,
The gate says “no” quite clearly.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning [#8] The JWT guard work is present, but router-level application and typed req.merchant support are not evidenced in the changed files. Apply the middleware to the protected router groups and extend Express Request typings so req.merchant is typed as Merchant everywhere.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: replacing merchant auth with JWT middleware.
Out of Scope Changes check ✅ Passed The changes stay focused on JWT auth middleware and related tests, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

tests/integration/merchant.register.test.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): tests/integration/merchant.register.test.ts


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: 1

🧹 Nitpick comments (1)
tests/integration/merchant.register.test.ts (1)

53-58: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Return the auth header from authenticateAs.

Keeping authHeader fixed to baseMerchant lets the token subject drift from the mocked merchant when tests pass a different merchant. Make the helper produce both the mock and matching header.

♻️ Proposed refactor
 const authenticateAs = (merchant: Record<string, unknown>) => {
   prismaMock.merchant.findUnique.mockResolvedValue(merchant as any);
+  return `Bearer ${tokenFor(merchant)}`;
 };
 
-const authHeader = `Bearer ${tokenFor(baseMerchant)}`;
+const authHeader = authenticateAs(baseMerchant);
🤖 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 `@tests/integration/merchant.register.test.ts` around lines 53 - 58, The
authentication helper is only mocking the merchant lookup and the auth header is
still always generated from baseMerchant, so the token subject can mismatch the
merchant under test. Update authenticateAs in merchant.register.test.ts to
return the matching Bearer header for the provided merchant while also setting
prismaMock.merchant.findUnique, and use that returned header at call sites
instead of the fixed authHeader constant.
🤖 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/middlewares/auth.middleware.ts`:
- Line 42: The auth middleware currently verifies tokens with
environment.jwtSecret, which can fall back to the development secret if
JWT_SECRET is unset. Update the configuration path used by auth.middleware.ts
and the environment.jwtSecret source so production startup fails fast when
JWT_SECRET is missing instead of allowing the fallback; keep jwt.verify in
AuthMiddleware using only a validated secret and ensure the config check happens
before the middleware can accept requests.

---

Nitpick comments:
In `@tests/integration/merchant.register.test.ts`:
- Around line 53-58: The authentication helper is only mocking the merchant
lookup and the auth header is still always generated from baseMerchant, so the
token subject can mismatch the merchant under test. Update authenticateAs in
merchant.register.test.ts to return the matching Bearer header for the provided
merchant while also setting prismaMock.merchant.findUnique, and use that
returned header at call sites instead of the fixed authHeader constant.
🪄 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 Plus

Run ID: a908776e-0343-4407-9c77-01ed9eea5fd5

📥 Commits

Reviewing files that changed from the base of the PR and between c0e30a5 and 6925628.

📒 Files selected for processing (4)
  • src/middlewares/auth.middleware.ts
  • tests/integration/invoice.routes.test.ts
  • tests/integration/merchant.register.test.ts
  • tests/unit/auth.middleware.test.ts

Comment thread src/middlewares/auth.middleware.ts
@codebestia

Copy link
Copy Markdown
Contributor

@KodeSage
Great job so far.

Please add the authentication mock to the tests for email otp.
This was merged into your branch.

Ensure to pull from the branch before you continue.

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.

Auth Middleware (JWT Guard for Protected Routes)

2 participants