Skip to content

refactor: extract ITokenService port to remove Fastify from AuthResolver - #20

Merged
mankatcheung merged 1 commit into
mainfrom
feat/token-service
Jul 20, 2026
Merged

mankatcheung merged 1 commit into
mainfrom
feat/token-service

Conversation

@mankatcheung

@mankatcheung mankatcheung commented Jul 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Introduces ITokenService port (use-cases/ports/ITokenService.ts) with sign() and verifyRefresh() methods
  • Implements FastifyJwtTokenService in the infrastructure layer (infrastructure/auth/FastifyJwtTokenService.ts) — the only place that touches fastify.jwt
  • Removes FastifyInstance from AuthResolver's dependencies; resolver now depends only on ITokenService, making it fully framework-agnostic
  • Updates container.ts to register tokenService as a singleton
  • Updates AuthResolver tests to mock ITokenService directly — no more fake Fastify object needed

Why

AuthResolver sat in the interface-adapter layer but was importing FastifyInstance to sign and verify JWTs — infrastructure work that belongs one layer out. This violates Clean Architecture: interface-adapters should orchestrate use cases, not reach into framework internals.

Test plan

  • pnpm --filter @job-finder/api typecheck — no errors
  • pnpm --filter @job-finder/api test — 189 tests pass
  • pnpm dev + manual login/register/refresh — verify tokens still work end-to-end

Summary by CodeRabbit

  • Security & Authentication

    • Improved access and refresh token handling across registration, login and token renewal.
    • Refresh tokens are now validated consistently, with invalid tokens returning an unauthorised error.
    • Token expiry behaviour is standardised for improved session security.
  • Reliability

    • Authentication flows now provide more consistent token responses and error handling.

AuthResolver was importing FastifyInstance directly to sign and verify JWTs,
coupling the interface-adapter layer to the framework. Introduce ITokenService
port and FastifyJwtTokenService infrastructure implementation so the resolver
has no framework dependency and is fully unit-testable via a plain mock.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Authentication token operations are now defined by an ITokenService port, implemented with Fastify JWT, injected through the container, and used by AuthResolver. Resolver tests now mock the token service for registration, login, and refresh flows.

Changes

Authentication token service

Layer / File(s) Summary
Token contract and JWT implementation
apps/api/src/use-cases/ports/ITokenService.ts, apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts
Defines token-pair and token-service contracts, then implements JWT signing and refresh-token verification.
Resolver token delegation
apps/api/src/interface-adapters/resolvers/AuthResolver.ts
Updates registration, login, and refresh flows to use the injected token service instead of Fastify JWT directly.
Dependency wiring and resolver validation
apps/api/src/http/container.ts, apps/api/src/__tests__/interface-adapters/resolvers/AuthResolver.test.ts
Registers the JWT token service as a singleton and updates resolver tests to mock signing and refresh verification.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

I’m a rabbit with tokens tucked under my ear,
Signing new carrots twice a day, crystal clear.
Refresh paws verify what the moonlight has seen,
While tests mock the burrow with stubs neat and clean.
Hop, hop—JWT flows now gleam!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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.
✅ 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 accurately summarises the main architectural change: introducing ITokenService and removing direct Fastify dependence from AuthResolver.
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 feat/token-service

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.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

🧹 Nitpick comments (1)
apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts (1)

8-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No direct unit test coverage for FastifyJwtTokenService.

The provided file set only shows AuthResolver.test.ts mocking ITokenService; no test exercises the real Fastify JWT signing/verification path (e.g. missing-secret behaviour, expiry, UNAUTHORIZED mapping). Worth adding a focused unit test for this class given it's the security-critical boundary.

🤖 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 `@apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts` around lines 8 -
32, The FastifyJwtTokenService security boundary lacks direct unit tests. Add
focused tests for FastifyJwtTokenService.sign and verifyRefresh, covering access
and refresh token creation, missing refresh-secret behavior, token expiry,
successful verification, and invalid-token mapping to an error with code
UNAUTHORIZED; mock or provide the Fastify JWT dependency and isolate environment
configuration.
🤖 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 `@apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts`:
- Around line 8-32: Update FastifyJwtTokenService’s constructor to read and
validate JWT_REFRESH_SECRET immediately, throwing when it is unset, then cache
the validated value in a private field. Replace the lazy
process.env.JWT_REFRESH_SECRET! reads in sign() and verifyRefresh() with that
field.

---

Nitpick comments:
In `@apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts`:
- Around line 8-32: The FastifyJwtTokenService security boundary lacks direct
unit tests. Add focused tests for FastifyJwtTokenService.sign and verifyRefresh,
covering access and refresh token creation, missing refresh-secret behavior,
token expiry, successful verification, and invalid-token mapping to an error
with code UNAUTHORIZED; mock or provide the Fastify JWT dependency and isolate
environment configuration.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb949b4c-1b21-4e63-a466-949e5b3c9203

📥 Commits

Reviewing files that changed from the base of the PR and between 50f1a59 and 1c9dd91.

📒 Files selected for processing (5)
  • apps/api/src/__tests__/interface-adapters/resolvers/AuthResolver.test.ts
  • apps/api/src/http/container.ts
  • apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts
  • apps/api/src/interface-adapters/resolvers/AuthResolver.ts
  • apps/api/src/use-cases/ports/ITokenService.ts

Comment on lines +8 to +32
export class FastifyJwtTokenService implements ITokenService {
constructor(private readonly deps: Deps) {}

sign(userId: string, email: string): TokenPair {
const accessToken = this.deps.fastify.jwt.sign(
{ sub: userId, email },
{ expiresIn: '15m' },
);
const refreshToken = this.deps.fastify.jwt.sign(
{ sub: userId, email },
{ key: process.env.JWT_REFRESH_SECRET!, expiresIn: '7d' },
);
return { accessToken, refreshToken };
}

verifyRefresh(token: string): { sub: string; email: string } {
try {
return this.deps.fastify.jwt.verify<{ sub: string; email: string }>(token, {
key: process.env.JWT_REFRESH_SECRET!,
});
} catch {
throw Object.assign(new Error('Invalid refresh token'), { code: 'UNAUTHORIZED' });
}
}
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file listing ==\n'
git ls-files 'apps/api/src/**/*' | sed -n '1,200p'

printf '\n== locate JWT_REFRESH_SECRET usage ==\n'
rg -n "JWT_REFRESH_SECRET|refresh secret|JWT_REFRESH" apps/api/src apps/api -g '!**/dist/**' -g '!**/build/**'

printf '\n== inspect target file ==\n'
cat -n apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts

printf '\n== inspect likely env/schema files ==\n'
fd -a -t f '.*(env|schema|config|validate).*' apps/api/src apps/api 2>/dev/null | sed -n '1,200p'

Repository: mankatcheung/job-finder

Length of output: 12864


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== search for env validation/config ==\n'
rg -n "process\.env|JWT_REFRESH_SECRET|zod.*env|env schema|dotenv|createEnv|safeEnv|validate.*env" \
  . \
  -g '!**/dist/**' -g '!**/build/**' -g '!**/node_modules/**'

printf '\n== inspect HTTP/container wiring around token service ==\n'
cat -n apps/api/src/http/container.ts | sed -n '1,260p'

printf '\n== inspect auth plugin ==\n'
cat -n apps/api/src/http/plugins/auth.plugin.ts | sed -n '1,220p'

Repository: mankatcheung/job-finder

Length of output: 18657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app bootstrap ==\n'
cat -n apps/api/src/app.ts | sed -n '1,240p'

printf '\n== server entrypoint ==\n'
cat -n apps/api/src/index.ts | sed -n '1,200p'

printf '\n== token service usages ==\n'
rg -n "tokenService|FastifyJwtTokenService|resolve\\(['\"]tokenService|cradle\\.tokenService|diContainer\\.resolve\\(['\"]tokenService" \
  apps/api/src -g '!**/dist/**' -g '!**/build/**'

Repository: mankatcheung/job-finder

Length of output: 6056


Validate JWT_REFRESH_SECRET in the constructor (apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts:8-32).

process.env.JWT_REFRESH_SECRET! is read lazily in sign() and verifyRefresh(), and this service is only resolved when auth flows run, so a missing secret lets the app boot and then breaks the first login/refresh request. Cache it in a private field and throw if it is unset.

🤖 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 `@apps/api/src/infrastructure/auth/FastifyJwtTokenService.ts` around lines 8 -
32, Update FastifyJwtTokenService’s constructor to read and validate
JWT_REFRESH_SECRET immediately, throwing when it is unset, then cache the
validated value in a private field. Replace the lazy
process.env.JWT_REFRESH_SECRET! reads in sign() and verifyRefresh() with that
field.

@mankatcheung
mankatcheung merged commit 59dcbec into main Jul 20, 2026
2 checks passed
@mankatcheung
mankatcheung deleted the feat/token-service branch July 26, 2026 22:14
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.

1 participant