Skip to content

[137] Custom Backend JWT Issuer#65

Merged
AdriaCarrera merged 9 commits intomainfrom
apps/custom-issuer
Jan 14, 2026
Merged

[137] Custom Backend JWT Issuer#65
AdriaCarrera merged 9 commits intomainfrom
apps/custom-issuer

Conversation

@GuillemGarciaDev
Copy link
Collaborator

@GuillemGarciaDev GuillemGarciaDev commented Jan 13, 2026

[137]: Custom Backend JWT Issuer

Changes 🛠️

app/custom-issuer

  • custom-issuer service to validate and send issued jwt tokens to be used with fast-auth.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a Custom Issuer Service to validate incoming JWTs and issue signed tokens.
    • CLI helper to generate/send test JWTs to the service.
  • Infrastructure & DevOps

    • Docker image and Helm chart support for deploying the service.
    • CI/CD workflows updated to build and deploy the custom issuer.
  • Documentation

    • README and environment example added with setup and security guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
fast-auth-account-verifier Ready Ready Preview, Comment Jan 14, 2026 10:08am

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a new NestJS "custom-issuer" microservice that validates incoming JWTs against fetched public keys, extracts and validates claims, and issues RS256-signed tokens; includes config, key loading, global error filtering, Dockerfile, Helm chart, CI workflows, and helper scripts/docs.

Changes

Cohort / File(s) Summary
Project metadata & linting
apps/custom-issuer/package.json, apps/custom-issuer/.eslintrc.js, apps/custom-issuer/nest-cli.json, apps/custom-issuer/tsconfig.json, apps/custom-issuer/tsconfig.build.json
New project config, scripts, TypeScript and Nest CLI settings, and ESLint overrides for the custom-issuer app.
Repo scaffolding & docs
apps/custom-issuer/.env.example, apps/custom-issuer/.gitignore, apps/custom-issuer/README.md
Env template with required keys and notes, .gitignore rules, and README with setup/usage/security guidance.
Bootstrap & global config
apps/custom-issuer/src/main.ts, apps/custom-issuer/src/app.module.ts, apps/custom-issuer/src/config/index.ts, apps/custom-issuer/src/config/issuer.config.ts
NestJS bootstrap, global ValidationPipe/CORS, global HTTP exception filter registration, and issuer configuration loader with validation (base64 key and URL checks). Attention: config validation throws on missing/invalid envs.
HTTP error handling
apps/custom-issuer/src/common/filters/http-exception.filter.ts
New global exception filter implementing sensitive-data redaction (max depth 5), header sanitization, contextual logging, and structured client responses. Review for redaction coverage and logging sensitivity.
Issuer API & DTO
apps/custom-issuer/src/modules/issuer/issuer.controller.ts, apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts
Controller exposing POST /issuer/issue accepting IssueRequestDto (jwt string, max 10000 chars).
Issuer core: types, constants, errors, module
apps/custom-issuer/src/modules/issuer/issuer.types.ts, apps/custom-issuer/src/modules/issuer/issuer.constants.ts, apps/custom-issuer/src/modules/issuer/issuer.errors.ts, apps/custom-issuer/src/modules/issuer/issuer.module.ts
TokenClaims type, JWT_ALGORITHM (RS256) and time constant, new ErrorMessage enum including INVALID_ISSUER, and module wiring.
Issuer services (logic-heavy)
apps/custom-issuer/src/modules/issuer/issuer.service.ts, apps/custom-issuer/src/modules/issuer/key.service.ts
KeyService: decodes base64 private key, loads validation public keys from URL (JSON/PEM/Firebase-style), exposes signing/validation keys. IssuerService: verifies input JWT against keys, validates sub/iss/exp/nbf semantics, and issues RS256-signed token. Attention: complex parsing, time checks, and error messages—high review priority.
Utilities & scripts
apps/custom-issuer/scripts/generate-test-jwt.js
CLI script to POST a test JWT from file to the issuer service; includes input validation and HTTP/HTTPS handling.
Container & CI/CD
docker/custom-issuer.Dockerfile, .github/workflows/build-custom-issuer.yaml, .github/workflows/build-and-deploy.yaml, .github/workflows/pull-request.yml, .github/workflows/deploy.yaml, .github/workflows/manual-deploy.yml
Multi-stage Docker build and new GitHub Actions workflow/jobs for building and pushing custom-issuer image; deploy workflows updated to accept custom-issuer tag. Review secrets/args and build targets.
Kubernetes Helm chart & values
infra/chart/templates/custom-issuer.yaml, infra/chart/templates/ingress.yaml, infra/chart/values/staging.yaml
New Helm template (Service, Deployment, SecretProviderClass) guarded by .Values.customIssuer.enabled, ingress path /issuer, and staging values for secrets & resources. Review CSI/SecretProviderClass mappings and env/volume mounts.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as IssuerController
    participant Service as IssuerService
    participant KeySvc as KeyService
    participant ConfigSvc as ConfigService

    Client->>Controller: POST /issuer/issue { jwt }
    Controller->>Service: issueToken(jwt)
    Service->>KeySvc: getValidationPublicKeys()
    KeySvc-->>Service: publicKeys[]
    Service->>Service: verifyAndDecodeToken(jwt, publicKeys)
    Service->>Service: extractAndValidateClaims(decoded)
    Service->>ConfigSvc: read issuer config (issuerUrl, validationIssuerUrl)
    Service->>KeySvc: getSigningKey()
    KeySvc-->>Service: signingKey
    Service->>Service: createSignedToken(claims, signingKey)
    Service-->>Controller: { token }
    Controller-->>Client: 200 OK { token }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[137] Custom Backend JWT Issuer' clearly and concisely describes the main change: adding a new custom JWT issuer backend service.
Description check ✅ Passed The description follows the template structure with title and Changes section, but the Changes section lacks detail about the implementation specifics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.



📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11b5c26 and aae0e62.

📒 Files selected for processing (1)
  • infra/chart/values/staging.yaml

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

@socket-security
Copy link

socket-security bot commented Jan 13, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​nestjs/​schematics@​11.0.910010084870
Added@​types/​supertest@​6.0.31001007181100
Added@​types/​jest@​30.0.01001007781100
Updated@​types/​node@​24.9.1 ⏵ 25.0.81001008196100
Added@​nestjs/​cli@​11.0.14981008692100
Added@​nestjs/​common@​11.1.1110010010096100
Added@​nestjs/​testing@​11.1.1110010010096100

View full report

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In @apps/custom-issuer/eslint.config.mjs:
- Line 20: Update the ESLint config's parser option by changing the sourceType
value from 'commonjs' to 'module' in the eslint.config.mjs file (the sourceType
property currently set to 'commonjs'); this aligns ESLint parsing with the
project's ES module syntax and ensures the parser and rules correctly handle
import/export statements.

In @apps/custom-issuer/package.json:
- Around line 42-43: The package.json currently lists "jsonwebtoken" in
devDependencies but it's required at runtime by
apps/custom-issuer/src/modules/issuer/issuer.service.ts; move the "jsonwebtoken"
entry from devDependencies to dependencies in package.json so production builds
include it, then run npm/yarn install to update lockfile and ensure the runtime
import of jsonwebtoken in issuer.service.ts resolves in production.
- Line 47: Update the ts-jest dependency in package.json from "ts-jest":
"^29.2.5" to at least "ts-jest": "^29.4.0" (or newer) to ensure Jest 30
compatibility, then reinstall dependencies (npm/yarn/pnpm) to update lockfiles
and run the test suite/CI to verify no regressions.

In @apps/custom-issuer/scripts/generate-test-jwt.js:
- Around line 200-249: There is a duplicate definition of the function
sendJWTToService causing the second one to shadow the first; remove the
redundant copy (the later sendJWTToService definition) so only the original
implementation remains and ensure any references call the single
sendJWTToService function.

In @apps/custom-issuer/src/common/filters/http-exception.filter.ts:
- Around line 121-123: The query parameters are logged without redaction; apply
the same sanitization used for body/headers. Replace the raw request.query with
a redacted/sanitized version (e.g., pass request.query into redactSensitiveData
or a new sanitizeQuery helper) so the object logged under "query" is scrubbed of
tokens/PII; update any related imports/typing in http-exception.filter.ts to use
redactSensitiveData/sanitizeQuery consistently with the existing sanitizeHeaders
and redactSensitiveData usage.

In @apps/custom-issuer/src/modules/issuer/issuer.service.ts:
- Around line 36-46: jwt.verify currently performs exp/nbf validation and throws
before your custom validators run; change verifyAndDecodeToken to disable
default time checks by passing ignoreExpiration: true and ignoreNotBefore: true
in the jwt.verify options, then call your existing validateTimeClaims /
validateExpirationClaim / validateNotBeforeClaim on the returned payload and
throw the specific UnauthorizedException messages (TOKEN_EXPIRED,
TOKEN_NOT_YET_VALID, EXP_BEFORE_NBF) when those validators fail; keep the
algorithms option (JWT_ALGORITHM) and only fall back to a generic INVALID_TOKEN
error for non-time-related verification failures.

In @apps/custom-issuer/src/modules/issuer/key.service.ts:
- Around line 56-73: The validateKeyPath function uses a startsWith check that
can be bypassed by sibling names; replace that logic in validateKeyPath so you
compute allowedBase via path.resolve(this.allowedBasePath) and normalizedPath
via path.normalize(keyPath) (and resolve it to absolute), then use
path.relative(allowedBase, normalizedPath) and throw if the relative path
startsWith('../') or isAbsolute(relative); keep the subsequent absolute-path
validation to ensure normalizedPath is absolute before proceeding.
- Around line 62-64: Replace the detailed throw new Error that exposes resolved
filesystem paths with a generic message: remove inclusion of normalizedPath and
allowedBase from the exception thrown in the path-traversal check (the throw new
Error call handling keyPath/normalizedPath/allowedBase in key.service.ts) and
instead throw a non-revealing error like "Invalid key path" or "Path not
allowed"; keep the path-check logic intact but ensure logs or errors do not
include actual filesystem paths or directory names.
🧹 Nitpick comments (11)
apps/custom-issuer/tsconfig.json (1)

21-23: Consider enabling stricter type-checking for a security-critical service.

For a JWT issuer service handling authentication, relaxed type-checking options increase the risk of subtle bugs:

  • noImplicitAny: false allows untyped variables, which can mask type errors in token validation/signing logic.
  • noFallthroughCasesInSwitch: false can lead to unintended fallthrough bugs.

Consider enabling these for better compile-time safety:

-    "noImplicitAny": false,
-    "strictBindCallApply": false,
-    "noFallthroughCasesInSwitch": false
+    "noImplicitAny": true,
+    "strictBindCallApply": true,
+    "noFallthroughCasesInSwitch": true
apps/custom-issuer/package.json (1)

2-7: Consider adding project metadata.

The description and author fields are empty. While not blocking, filling these improves project documentation and package registry discoverability if ever published.

apps/custom-issuer/README.md (1)

16-22: Misleading key generation example for testing.

The example generates a single key pair and uses it for both signing and validation. This works for local testing but misrepresents the service's intended use case—validating tokens from an external issuer while signing with a different key.

Consider clarifying this in the documentation:

-### 2. Generate Test Keys (Optional)
+### 2. Generate Test Keys (For Local Testing Only)

 ```bash
-# Generate RSA key pair for testing
+# For local testing, the same key pair is used for both operations.
+# In production, use separate key pairs: the external issuer's public key
+# for validation and your own private key for signing.
 mkdir -p keys
apps/custom-issuer/scripts/generate-test-jwt.js (1)

36-50: Consider explicit handling when user passes 0.

Using || 1 after parseInt treats user-supplied 0 as invalid and defaults to 1. While --count 0 is likely not meaningful, --nbf 0 is explicitly allowed as default. The pattern is inconsistent.

For --count, this is fine since 0 doesn't make sense. For --exp and --nbf, consider whether 0 should be preserved:

     case '--exp':
     case '-e':
-      options.exp = parseInt(args[++i], 10) || 3600;
+      const expArg = parseInt(args[++i], 10);
+      options.exp = isNaN(expArg) ? 3600 : expArg;
       break;
apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)

49-53: Sensitive field matching may produce false positives.

Using lowerKey.includes(field) means keys like "tokenizer" or "passwordPolicy" would be redacted. This is generally safe (erring on the side of caution), but be aware it may over-redact legitimate non-sensitive fields.

If more precise matching is needed later, consider using word boundaries or exact matches.

apps/custom-issuer/src/modules/issuer/key.service.ts (2)

67-72: Redundant absolute path check.

path.normalize(path.resolve(keyPath)) is always absolute. The path.resolve call on line 51 already guarantees an absolute path, making this check unnecessary.


27-29: Consider validating KEYS_BASE_PATH format.

If KEYS_BASE_PATH is set to an invalid or relative path, the path traversal protection may not work as intended. Consider validating it's an absolute path.

Proposed improvement
    // Set allowed base path to prevent path traversal
    // Note: KEYS_BASE_PATH is optional and not part of typed config, so access directly from env
-   this.allowedBasePath = process.env.KEYS_BASE_PATH || process.cwd();
+   const basePath = process.env.KEYS_BASE_PATH || process.cwd();
+   this.allowedBasePath = path.resolve(basePath);
+   
+   if (!path.isAbsolute(this.allowedBasePath)) {
+     throw new Error('KEYS_BASE_PATH must be an absolute path');
+   }
apps/custom-issuer/src/app.module.ts (1)

10-14: Minor: Simplify configuration loader.

The wrapper function is unnecessary since configuration is already a function.

Proposed simplification
    ConfigModule.forRoot({
-     load: [() => configuration()],
+     load: [configuration],
      isGlobal: true,
      envFilePath: ['.env'],
    }),
apps/custom-issuer/src/modules/issuer/issuer.constants.ts (1)

2-2: Rename constant for clarity.

The constant name SECONDS_IN_MILLISECOND is inverted. The value 1000 represents milliseconds per second, not seconds per millisecond. Consider renaming to MILLISECONDS_PER_SECOND or MS_PER_SECOND to avoid confusion when reading conversion code.

✏️ Suggested rename
 export const JWT_ALGORITHM = 'RS256' as const;
-export const SECONDS_IN_MILLISECOND = 1000;
+export const MILLISECONDS_PER_SECOND = 1000;
apps/custom-issuer/src/modules/issuer/issuer.service.ts (2)

30-34: Consider removing async if not needed.

The method is marked async but contains no await expressions. All called methods are synchronous. If this is intentional for future compatibility (e.g., anticipating async key retrieval), consider adding a comment to clarify. Otherwise, the signature could be simplified to issueToken(inputJwt: string): string.


36-46: Consider validating aud and/or iss claims on input token.

The input token is verified against the validation public key, but there's no check for aud (audience) or iss (issuer) claims. If the same key could sign tokens intended for different services, this could allow unintended token reuse.

If the validation key is unique to a single trusted issuer and this service is the only intended audience, this may be acceptable. Otherwise, consider adding validation:

🔒 Optional: Add issuer/audience validation
     try {
       return jwt.verify(inputJwt, validationPublicKey, {
         algorithms: [JWT_ALGORITHM],
+        issuer: this.configService.get('issuer.expectedInputIssuer'),
+        audience: this.configService.get('issuer.expectedAudience'),
       }) as jwt.JwtPayload;
     } catch (error) {
       throw new UnauthorizedException(ErrorMessage.INVALID_TOKEN);
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60cacd6 and ded2050.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • apps/custom-issuer/.env.example
  • apps/custom-issuer/.gitignore
  • apps/custom-issuer/.prettierrc
  • apps/custom-issuer/README.md
  • apps/custom-issuer/eslint.config.mjs
  • apps/custom-issuer/nest-cli.json
  • apps/custom-issuer/package.json
  • apps/custom-issuer/scripts/generate-test-jwt.js
  • apps/custom-issuer/src/app.module.ts
  • apps/custom-issuer/src/common/filters/http-exception.filter.ts
  • apps/custom-issuer/src/config/index.ts
  • apps/custom-issuer/src/config/issuer.config.ts
  • apps/custom-issuer/src/main.ts
  • apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts
  • apps/custom-issuer/src/modules/issuer/issuer.constants.ts
  • apps/custom-issuer/src/modules/issuer/issuer.controller.ts
  • apps/custom-issuer/src/modules/issuer/issuer.errors.ts
  • apps/custom-issuer/src/modules/issuer/issuer.module.ts
  • apps/custom-issuer/src/modules/issuer/issuer.service.ts
  • apps/custom-issuer/src/modules/issuer/issuer.types.ts
  • apps/custom-issuer/src/modules/issuer/key.service.ts
  • apps/custom-issuer/tsconfig.build.json
  • apps/custom-issuer/tsconfig.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-10T13:23:58.048Z
Learnt from: AgustinMJ
Repo: Peersyst/near-mobile PR: 571
File: packages/shared/api/src/index.ts:33-33
Timestamp: 2025-09-10T13:23:58.048Z
Learning: The packages/shared/api package exports source files directly (./src/index.ts) rather than compiled output because it's auto-generated by openapi-typescript-codegen and this export strategy is intentional for this shared package.

Applied to files:

  • apps/custom-issuer/tsconfig.json
📚 Learning: 2025-08-14T14:53:11.507Z
Learnt from: AdriaCarrera
Repo: Peersyst/near-mobile PR: 520
File: turbo.json:0-0
Timestamp: 2025-08-14T14:53:11.507Z
Learning: In turbo.json, package.json files can be legitimate outputs when build tasks modify them during execution (e.g., version updates, metadata injection). In such cases, including package.json in the outputs array ensures proper cache restoration of the modified files.

Applied to files:

  • apps/custom-issuer/.gitignore
🧬 Code graph analysis (8)
apps/custom-issuer/src/modules/issuer/issuer.controller.ts (2)
apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts (1)
  • IssueRequestDto (3-8)
apps/custom-issuer/scripts/generate-test-jwt.js (1)
  • token (138-140)
apps/custom-issuer/src/config/index.ts (1)
apps/custom-issuer/src/config/issuer.config.ts (2)
  • IssuerConfig (1-5)
  • IssuerConfig (7-35)
apps/custom-issuer/src/modules/issuer/issuer.module.ts (1)
apps/custom-issuer/src/app.module.ts (1)
  • Module (8-24)
apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)
apps/custom-issuer/scripts/generate-test-jwt.js (1)
  • response (263-263)
apps/custom-issuer/src/app.module.ts (1)
apps/custom-issuer/src/modules/issuer/issuer.module.ts (1)
  • Module (6-10)
apps/custom-issuer/src/modules/issuer/key.service.ts (3)
apps/custom-issuer/src/modules/issuer/issuer.service.ts (1)
  • Injectable (10-144)
apps/custom-issuer/src/config/index.ts (2)
  • Config (3-5)
  • Config (7-11)
apps/custom-issuer/scripts/generate-test-jwt.js (2)
  • keyPath (94-94)
  • path (19-19)
apps/custom-issuer/src/config/issuer.config.ts (1)
apps/custom-issuer/scripts/generate-test-jwt.js (1)
  • keyPath (94-94)
apps/custom-issuer/scripts/generate-test-jwt.js (2)
packages/shared/cli/src/cli.mjs (1)
  • __dirname (12-12)
apps/attester/src/services/logger.service.ts (1)
  • error (18-20)
🪛 Biome (2.1.2)
apps/custom-issuer/scripts/generate-test-jwt.js

[error] 201-201: Shouldn't redeclare 'sendJWTToService'. Consider to delete it or rename it.

'sendJWTToService' is defined here:

(lint/suspicious/noRedeclare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-base / Build
  • GitHub Check: integration-packages / Build
🔇 Additional comments (28)
apps/custom-issuer/tsconfig.build.json (1)

1-4: LGTM!

Standard build configuration that correctly extends the base tsconfig and excludes test files, specs, and build artifacts from compilation.

apps/custom-issuer/.prettierrc (1)

1-4: LGTM!

Standard Prettier configuration aligned with common NestJS project conventions.

apps/custom-issuer/nest-cli.json (1)

1-8: LGTM!

Standard NestJS CLI configuration with appropriate settings for a new application.

apps/custom-issuer/.gitignore (1)

58-59: Good security practice.

Excluding the keys/ directory prevents accidental commits of private cryptographic keys used for JWT signing. This aligns with the commit history showing testing keys were previously removed.

apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts (1)

1-8: LGTM!

Well-structured DTO with appropriate validation. The MaxLength(10000) constraint is reasonable for JWT tokens while preventing oversized payloads, and the custom error message improves API usability.

apps/custom-issuer/src/config/issuer.config.ts (2)

1-5: LGTM!

The type definition is clean and well-structured, defining all required configuration fields for the issuer service.


7-35: LGTM!

The validation logic is solid:

  • Checks for both missing and whitespace-only values
  • Provides clear, actionable error messages
  • Returns trimmed values to prevent issues from accidental whitespace
apps/custom-issuer/src/config/index.ts (1)

1-11: LGTM!

Clean config aggregator following NestJS conventions. Provides a centralized entry point for configuration that can be easily extended as more config sections are added.

apps/custom-issuer/.env.example (1)

1-35: LGTM!

Well-documented environment template with clear separation of required and optional variables. The inline security note about path traversal protection for KEYS_BASE_PATH is a good practice.

apps/custom-issuer/README.md (1)

92-99: LGTM!

Good security considerations section covering the key aspects: input validation, request limits, CORS, path traversal protection, and data redaction. The recommendation for rate limiting in production is helpful.

apps/custom-issuer/scripts/generate-test-jwt.js (3)

93-102: LGTM!

Good defensive check for key file existence with a clear error message guiding users on how to resolve the issue.


118-143: LGTM!

Clean JWT generation logic with proper timestamp handling and conditional claim inclusion.


251-283: LGTM!

The main execution loop handles success and error cases well, with helpful output formatting and a specific hint for connection refused errors.

apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)

87-151: LGTM — solid exception filter with security-conscious logging.

The filter correctly handles both HttpException and unknown exceptions, logs appropriately at different severity levels, and sanitizes sensitive data before logging.

apps/custom-issuer/eslint.config.mjs (1)

1-35: ESLint configuration looks reasonable.

Good integration of TypeScript-aware linting with Prettier. The type-checked rules provide additional safety for async code.

apps/custom-issuer/src/app.module.ts (1)

8-23: LGTM — clean module setup.

Good use of global configuration, proper module organization, and centralized exception handling.

apps/custom-issuer/src/main.ts (3)

30-33: CORS configuration may be too restrictive.

The allowedHeaders only includes Content-Type and Authorization. Common headers like Accept, Origin, and X-Requested-With are not included, which may cause preflight failures for some clients. Also, HEAD and PATCH methods are missing.

Verify if the current CORS configuration meets the API consumers' requirements. If broader compatibility is needed:

Proposed update
  app.enableCors({
    origin: allowedOrigins.length > 0 ? allowedOrigins : false,
    credentials: true,
-   methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
-   allowedHeaders: ['Content-Type', 'Authorization'],
+   methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'],
+   allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'],
  });

20-22: enableImplicitConversion can cause unexpected type coercion.

With enableImplicitConversion: true, strings like "123" are automatically converted to numbers if the DTO property is typed as number. This can lead to subtle bugs if query parameters or body fields are unexpectedly coerced.

Ensure this behavior is intentional for your DTOs. If strict typing is preferred, consider removing this option.


7-36: Bootstrap implementation is solid.

Good security defaults with body size limits, input validation, and configurable CORS.

apps/custom-issuer/src/modules/issuer/issuer.module.ts (1)

1-10: LGTM!

The module is correctly structured following NestJS conventions. Providers and controller are properly registered, and the module integrates cleanly with the AppModule as shown in the relevant context.

apps/custom-issuer/src/modules/issuer/issuer.types.ts (1)

1-6: LGTM!

The type correctly represents standard JWT claims with sub as required and temporal claims (exp, nbf) as optional numbers for Unix timestamps. This aligns well with the validation logic implied by the error messages in issuer.errors.ts.

apps/custom-issuer/src/modules/issuer/issuer.controller.ts (1)

5-15: LGTM!

The controller follows NestJS conventions correctly. The @HttpCode(HttpStatus.OK) is appropriate since this is a token exchange operation rather than resource creation. Input validation is properly delegated to the DTO with reasonable constraints (max 10000 chars).

apps/custom-issuer/src/modules/issuer/issuer.errors.ts (1)

1-9: LGTM!

The error enum provides clear, centralized messages for JWT validation failures. The messages are appropriately informative without leaking sensitive implementation details, and they cover the standard temporal validation scenarios including the edge case of exp being before nbf.

apps/custom-issuer/src/modules/issuer/issuer.service.ts (5)

10-28: LGTM!

The constructor properly validates required configuration at startup (fail-fast pattern), and the typed ConfigService<Config> provides good type safety. Throwing an error for missing configuration ensures the service won't start in an invalid state.


48-61: LGTM!

The claim extraction and subject validation logic is well-structured. The TypeScript assertion function (asserts sub is string) provides proper type narrowing. The check for both existence and type is appropriate.


63-109: Comprehensive time claim validation logic.

The validation logic is thorough and correct:

  • exp > now ensures the token hasn't expired
  • nbf <= now ensures the token is already valid
  • exp > nbf ensures logical consistency

The assertion functions provide proper type narrowing for TypeScript. This code will work correctly once the jwt.verify options are updated to disable automatic time validation (per the earlier comment).


111-117: LGTM!

The helper methods are correct and well-named. isValidInteger properly checks both type and integer property, and getCurrentTimestamp correctly produces Unix timestamps in seconds using the standard pattern.


128-143: Verify intent: output token inherits input token's expiration.

The output token copies exp and nbf directly from the input token. This means the newly issued token will have the same expiration time as the original. If there's any processing delay, the output token could be very close to expiration or even expired by the time it's used.

Is this the intended behavior? An alternative approach would be to issue the output token with a fresh iat and a new exp calculated from the current time (e.g., now + configuredTTL).

...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check package.json for module type and tsconfig for module settings
cat apps/custom-issuer/package.json | jq '.type // "not set"'
cat apps/custom-issuer/tsconfig.json | jq '.compilerOptions.module // "not set"'

Repository: Peersyst/fast-auth

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Search for import/export statements in the custom-issuer app
echo "=== Checking for import statements ==="
rg "^import\s+" apps/custom-issuer/src --type ts --type js -c | head -20

echo -e "\n=== Checking for export statements ==="
rg "^export\s+" apps/custom-issuer/src --type ts --type js -c | head -20

echo -e "\n=== Checking current sourceType setting ==="
cat apps/custom-issuer/eslint.config.mjs | grep -A 2 -B 2 "sourceType"

Repository: Peersyst/fast-auth

Length of output: 1439


Change sourceType from 'commonjs' to 'module'.

The codebase extensively uses ES module syntax (import/export statements) throughout the source files, but ESLint's sourceType is configured as 'commonjs'. This mismatch causes the parser to analyze code with incorrect syntax expectations, which can lead to missed linting errors or incorrect rule behavior. Update to sourceType: 'module' to align with the project's actual module system.

🤖 Prompt for AI Agents
In @apps/custom-issuer/eslint.config.mjs at line 20, Update the ESLint config's
parser option by changing the sourceType value from 'commonjs' to 'module' in
the eslint.config.mjs file (the sourceType property currently set to
'commonjs'); this aligns ESLint parsing with the project's ES module syntax and
ensures the parser and rules correctly handle import/export statements.

Comment on lines +42 to +43
"jest": "^30.0.0",
"jsonwebtoken": "^9.0.2",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "require\(['\"]jsonwebtoken['\"]|from ['\"]jsonwebtoken['\"]" apps/custom-issuer/src/

Repository: Peersyst/fast-auth

Length of output: 156


Move jsonwebtoken from devDependencies to dependencies.

jsonwebtoken is imported at runtime in apps/custom-issuer/src/modules/issuer/issuer.service.ts and will be unavailable in production builds when placed in devDependencies.

🤖 Prompt for AI Agents
In @apps/custom-issuer/package.json around lines 42 - 43, The package.json
currently lists "jsonwebtoken" in devDependencies but it's required at runtime
by apps/custom-issuer/src/modules/issuer/issuer.service.ts; move the
"jsonwebtoken" entry from devDependencies to dependencies in package.json so
production builds include it, then run npm/yarn install to update lockfile and
ensure the runtime import of jsonwebtoken in issuer.service.ts resolves in
production.

"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat apps/custom-issuer/package.json | grep -E '"jest":|"ts-jest":'

Repository: Peersyst/fast-auth

Length of output: 122


🌐 Web query:

ts-jest 29 compatibility Jest 30

💡 Result:

Yes — ts-jest added Jest 30 support in the 29.x line. Upgrade to ts-jest >= 29.4.0 to use it with Jest 30. [1][2]

Sources:
[1] ts-jest v29.4.0 release notes (feat: support Jest 30). [2] ts-jest version/compatibility docs (29.x).


Upgrade ts-jest to 29.4.0 or higher for Jest 30 compatibility.

ts-jest 29.2.5 does not support Jest 30. Jest 30 support was added in ts-jest 29.4.0. Upgrade to ts-jest >= 29.4.0 to resolve compatibility issues.

🤖 Prompt for AI Agents
In @apps/custom-issuer/package.json at line 47, Update the ts-jest dependency in
package.json from "ts-jest": "^29.2.5" to at least "ts-jest": "^29.4.0" (or
newer) to ensure Jest 30 compatibility, then reinstall dependencies
(npm/yarn/pnpm) to update lockfiles and run the test suite/CI to verify no
regressions.

Comment on lines 121 to 123
body: request.body ? redactSensitiveData(request.body) : undefined,
headers: sanitizeHeaders(request.headers),
query: request.query,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Query parameters are not redacted.

The request body and headers are sanitized before logging, but query parameters (request.query) are logged as-is. Sensitive data like tokens can appear in query strings (e.g., ?token=...).

Proposed fix
         // Sanitize request body and headers
         body: request.body ? redactSensitiveData(request.body) : undefined,
         headers: sanitizeHeaders(request.headers),
-        query: request.query,
+        query: request.query ? redactSensitiveData(request.query) : undefined,
📝 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
body: request.body ? redactSensitiveData(request.body) : undefined,
headers: sanitizeHeaders(request.headers),
query: request.query,
body: request.body ? redactSensitiveData(request.body) : undefined,
headers: sanitizeHeaders(request.headers),
query: request.query ? redactSensitiveData(request.query) : undefined,
🤖 Prompt for AI Agents
In @apps/custom-issuer/src/common/filters/http-exception.filter.ts around lines
121 - 123, The query parameters are logged without redaction; apply the same
sanitization used for body/headers. Replace the raw request.query with a
redacted/sanitized version (e.g., pass request.query into redactSensitiveData or
a new sanitizeQuery helper) so the object logged under "query" is scrubbed of
tokens/PII; update any related imports/typing in http-exception.filter.ts to use
redactSensitiveData/sanitizeQuery consistently with the existing sanitizeHeaders
and redactSensitiveData usage.

Comment on lines 36 to 46
private verifyAndDecodeToken(inputJwt: string): jwt.JwtPayload {
const validationPublicKey = this.keyService.getValidationPublicKey();

try {
return jwt.verify(inputJwt, validationPublicKey, {
algorithms: [JWT_ALGORITHM],
}) as jwt.JwtPayload;
} catch (error) {
throw new UnauthorizedException(ErrorMessage.INVALID_TOKEN);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

jsonwebtoken jwt.verify ignoreExpiration ignoreNotBefore default behavior

💡 Result:

By default jwt.verify validates both exp and nbf: ignoreExpiration and ignoreNotBefore default to false (so expiration and not-before checks are enforced). The verifier’s current time defaults to the system time (clockTimestamp = now) and clockTolerance defaults to 0 seconds. [1][2]

Sources:

  • jsonwebtoken options (ignoreExpiration / ignoreNotBefore, clockTimestamp, clockTolerance). [1]
  • jsonwebtoken README (options list). [2]

🏁 Script executed:

cat -n apps/custom-issuer/src/modules/issuer/issuer.service.ts | head -120

Repository: Peersyst/fast-auth

Length of output: 4413


Custom time claim validation is unreachable due to jwt.verify default behavior.

The jsonwebtoken library's verify() function validates exp and nbf claims by default. If a token is expired, it throws TokenExpiredError; if not yet valid, it throws NotBeforeError. These errors are caught at line 44 and converted to a generic INVALID_TOKEN message.

This means validateTimeClaims, validateExpirationClaim, and validateNotBeforeClaim (lines 63–109) will never execute for time-based failures—the verify() call will have already thrown. Your custom error messages like TOKEN_EXPIRED, TOKEN_NOT_YET_VALID, and EXP_BEFORE_NBF are unreachable.

To use your custom time validation with specific error messages, disable the default checks:

🔧 Proposed fix
     try {
       return jwt.verify(inputJwt, validationPublicKey, {
         algorithms: [JWT_ALGORITHM],
+        ignoreExpiration: true,
+        ignoreNotBefore: true,
       }) as jwt.JwtPayload;
     } catch (error) {
       throw new UnauthorizedException(ErrorMessage.INVALID_TOKEN);
     }
📝 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
private verifyAndDecodeToken(inputJwt: string): jwt.JwtPayload {
const validationPublicKey = this.keyService.getValidationPublicKey();
try {
return jwt.verify(inputJwt, validationPublicKey, {
algorithms: [JWT_ALGORITHM],
}) as jwt.JwtPayload;
} catch (error) {
throw new UnauthorizedException(ErrorMessage.INVALID_TOKEN);
}
}
private verifyAndDecodeToken(inputJwt: string): jwt.JwtPayload {
const validationPublicKey = this.keyService.getValidationPublicKey();
try {
return jwt.verify(inputJwt, validationPublicKey, {
algorithms: [JWT_ALGORITHM],
ignoreExpiration: true,
ignoreNotBefore: true,
}) as jwt.JwtPayload;
} catch (error) {
throw new UnauthorizedException(ErrorMessage.INVALID_TOKEN);
}
}
🤖 Prompt for AI Agents
In @apps/custom-issuer/src/modules/issuer/issuer.service.ts around lines 36 -
46, jwt.verify currently performs exp/nbf validation and throws before your
custom validators run; change verifyAndDecodeToken to disable default time
checks by passing ignoreExpiration: true and ignoreNotBefore: true in the
jwt.verify options, then call your existing validateTimeClaims /
validateExpirationClaim / validateNotBeforeClaim on the returned payload and
throw the specific UnauthorizedException messages (TOKEN_EXPIRED,
TOKEN_NOT_YET_VALID, EXP_BEFORE_NBF) when those validators fail; keep the
algorithms option (JWT_ALGORITHM) and only fall back to a generic INVALID_TOKEN
error for non-time-related verification failures.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/custom-issuer/src/modules/issuer/key.service.ts`:
- Around line 71-109: The fetch in loadPublicKeysFromUrl can hang indefinitely;
wrap the fetch with an AbortController and a configurable timeout (e.g., 5-10s),
pass controller.signal to fetch, and set a timer to call controller.abort() if
the timeout elapses; ensure you clear the timer after fetch completes and handle
AbortError by throwing a clear timeout-specific Error from loadPublicKeysFromUrl
so startup fails fast instead of hanging.
♻️ Duplicate comments (1)
apps/custom-issuer/src/modules/issuer/issuer.service.ts (1)

56-58: Custom time validation is unreachable—already flagged in previous review.

As noted in the prior review, jwt.verify validates exp and nbf by default, throwing TokenExpiredError or NotBeforeError before your custom validateTimeClaims methods can execute. The fix requires adding ignoreExpiration: true and ignoreNotBefore: true to the verify options.

🧹 Nitpick comments (3)
apps/custom-issuer/src/config/issuer.config.ts (1)

38-45: URL validation is inconsistent across fields.

validationPublicKeyUrl is validated as a proper URL, but validationIssuerUrl and issuerUrl (lines 26-36) are only checked for presence. Per the PR objective to "validate issuer URL," consider applying URL format validation to all URL fields for consistency.

♻️ Proposed fix to validate all URL fields
  if (!issuerUrl || issuerUrl.trim() === '') {
    throw new Error(
      'Missing required environment variable: ISSUER_URL. Please set ISSUER_URL to the URL of the issuer service.',
    );
  }

  // Validate URL format
  try {
    new URL(validationPublicKeyUrl.trim());
  } catch (error) {
    throw new Error(
      `Invalid VALIDATION_PUBLIC_KEY_URL: "${validationPublicKeyUrl}" is not a valid URL.`,
    );
  }

+ // Validate VALIDATION_ISSUER_URL format
+ try {
+   new URL(validationIssuerUrl.trim());
+ } catch (error) {
+   throw new Error(
+     `Invalid VALIDATION_ISSUER_URL: "${validationIssuerUrl}" is not a valid URL.`,
+   );
+ }
+
+ // Validate ISSUER_URL format
+ try {
+   new URL(issuerUrl.trim());
+ } catch (error) {
+   throw new Error(
+     `Invalid ISSUER_URL: "${issuerUrl}" is not a valid URL.`,
+   );
+ }
+
  // Validate base64 format and ensure it decodes to a valid PEM key
apps/custom-issuer/src/modules/issuer/issuer.service.ts (2)

52-63: Unused variable lastError is dead code.

The lastError variable is assigned on each iteration but never used after the loop ends. Either remove it or use it to provide additional context in the final error (e.g., logging).

♻️ Option 1: Remove unused variable
     // Try each public key until one succeeds (early exit)
-    let lastError: Error | null = null;
-    
     for (const publicKey of validationPublicKeys) {
       try {
         return jwt.verify(inputJwt, publicKey, {
           algorithms: [JWT_ALGORITHM],
         }) as jwt.JwtPayload;
-      } catch (error) {
-        // Store the error but continue to next key
-        lastError = error instanceof Error ? error : new Error(String(error));
+      } catch {
+        // Continue to next key
         continue;
       }
     }

161-176: Consider adding iat (issued-at) claim to signed tokens.

The new token omits the iat claim. While not strictly required, including it is a common best practice for auditing and debugging token issuance times. Note that jwt.sign does not automatically add iat when passing an object payload.

♻️ Proposed enhancement
   private buildTokenPayload(claims: TokenClaims): jwt.JwtPayload {
     const payload: jwt.JwtPayload = {
       sub: claims.sub,
       iss: this.issuerUrl,
+      iat: this.getCurrentTimestamp(),
     };

     if (claims.exp !== undefined) {
       payload.exp = claims.exp;
     }

     if (claims.nbf !== undefined) {
       payload.nbf = claims.nbf;
     }

     return payload;
   }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ded2050 and 07c82eb.

📒 Files selected for processing (7)
  • apps/custom-issuer/.env.example
  • apps/custom-issuer/README.md
  • apps/custom-issuer/scripts/generate-test-jwt.js
  • apps/custom-issuer/src/config/issuer.config.ts
  • apps/custom-issuer/src/modules/issuer/issuer.errors.ts
  • apps/custom-issuer/src/modules/issuer/issuer.service.ts
  • apps/custom-issuer/src/modules/issuer/key.service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/custom-issuer/README.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-12T07:12:41.650Z
Learnt from: JordiParraCrespo
Repo: Peersyst/bitcoin-light PR: 77
File: packages/frontend/token/src/ui/queries/token.queries.ts:11-14
Timestamp: 2025-09-12T07:12:41.650Z
Learning: In packages/frontend/token/src/ui/queries/token.queries.ts, the detail function accepts a chain parameter but does not include it in the query key, which could lead to cache key collisions if currencyId is not globally unique across chains.

Applied to files:

  • apps/custom-issuer/src/modules/issuer/issuer.service.ts
📚 Learning: 2025-09-02T10:52:36.185Z
Learnt from: GuillemGarciaDev
Repo: Peersyst/fast-auth PR: 34
File: contracts/auth0-guard/src/lib.rs:67-76
Timestamp: 2025-09-02T10:52:36.185Z
Learning: The init signature changes in contracts/auth0-guard/src/lib.rs have already been properly handled in both test files - contracts/auth0-guard/tests/test_integration.rs includes issuer values like "https://dev-gb1h5yrp85jsty.us.auth0.com/" and examples/jwt-rs256-guard/tests/test_integration.rs includes issuer as an empty string variable in all init calls.

Applied to files:

  • apps/custom-issuer/src/config/issuer.config.ts
🧬 Code graph analysis (1)
apps/custom-issuer/src/modules/issuer/key.service.ts (2)
apps/custom-issuer/src/modules/issuer/issuer.service.ts (1)
  • Injectable (10-177)
apps/custom-issuer/src/config/index.ts (2)
  • Config (3-5)
  • Config (7-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-base / Build
  • GitHub Check: integration-packages / Build
🔇 Additional comments (8)
apps/custom-issuer/scripts/generate-test-jwt.js (1)

1-201: LGTM!

The script is well-structured with clear documentation, proper argument parsing, and appropriate error handling. The duplicate function issue from the previous review has been resolved.

apps/custom-issuer/src/modules/issuer/issuer.errors.ts (1)

1-10: LGTM!

Clean and comprehensive error message enum covering all standard JWT validation scenarios.

apps/custom-issuer/.env.example (1)

1-37: LGTM!

Well-documented environment example with clear separation of required and optional variables, helpful encoding instructions, and relevant examples.

apps/custom-issuer/src/modules/issuer/key.service.ts (1)

174-190: LGTM!

The getter methods properly guard against uninitialized state with clear error messages pointing users to startup logs for troubleshooting.

apps/custom-issuer/src/modules/issuer/issuer.service.ts (4)

15-40: LGTM!

Constructor validation is well-implemented with clear, actionable error messages that guide developers to the required environment variables. Using plain Error for startup configuration failures is appropriate since these aren't HTTP request-level exceptions.


70-94: LGTM!

Claim extraction and validation logic is well-structured with clear type assertions. The issuer validation correctly enforces exact match against the configured validation URL.


96-146: Time validation logic is sound.

The validation methods correctly handle type checking, expiration, not-before, and consistency between claims. Once the jwt.verify options are updated per the prior review comment, these will function as intended. Note that validateTimeClaimConsistency (exp ≤ nbf check) is already reachable since jwt.verify doesn't validate this condition.


152-159: LGTM!

Token signing is correctly implemented with the configured algorithm and signing key from KeyService.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 71 to 109
private async loadPublicKeysFromUrl(url: string): Promise<string[]> {
try {
const response = await fetch(url);

if (!response.ok) {
throw new Error(
`Failed to fetch public keys from URL: ${response.status} ${response.statusText}`,
);
}

const contentType = response.headers.get('content-type') || '';
const responseText = await response.text();

// Firebase typically returns JSON with certificates
// Format: https://www.googleapis.com/robot/v1/metadata/x509/{email}
if (contentType.includes('application/json')) {
return this.parseFirebasePublicKeys(responseText);
}

// If it's already PEM format, return as single-item array
if (responseText.includes('-----BEGIN')) {
return [responseText];
}

// Try to parse as JSON and extract keys
try {
return this.parseFirebasePublicKeys(responseText);
} catch {
throw new Error(
`Unable to parse public keys from URL. Expected PEM format or Firebase certificate format.`,
);
}
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to load public keys from URL: ${error.message}`);
}
throw new Error('Failed to load public keys from URL: Unknown error');
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the key.service.ts file
find . -type f -name "key.service.ts" | head -5

Repository: Peersyst/fast-auth

Length of output: 116


🏁 Script executed:

# Once we find it, let's read the entire file to understand the context
cat -n apps/custom-issuer/src/modules/issuer/key.service.ts

Repository: Peersyst/fast-auth

Length of output: 8157


🏁 Script executed:

# Check for any timeout configurations in the project (package.json, environment configs, etc.)
fd -t f -e json -e ts -e yaml | xargs grep -l "timeout" 2>/dev/null | head -20

Repository: Peersyst/fast-auth

Length of output: 275


🏁 Script executed:

# Check Node.js version requirement in package.json
cat apps/custom-issuer/package.json 2>/dev/null || cat package.json 2>/dev/null | grep -A 5 '"engines"'

Repository: Peersyst/fast-auth

Length of output: 2053


🏁 Script executed:

cat -n packages/shared/utils/src/timeout-promise.ts

Repository: Peersyst/fast-auth

Length of output: 878


🏁 Script executed:

# Check if timeout-promise is used in other services
rg "timeout-promise\|timeoutPromise" --type ts -A 3

Repository: Peersyst/fast-auth

Length of output: 44


🏁 Script executed:

# Check for other fetch calls in the codebase to see how they handle timeouts
rg "fetch\(" --type ts -B 2 -A 5 | head -60

Repository: Peersyst/fast-auth

Length of output: 5079


🏁 Script executed:

# Check if with-retries.ts provides timeout patterns
cat -n packages/shared/utils/src/with-retries.ts

Repository: Peersyst/fast-auth

Length of output: 1123


Add timeout to prevent indefinite hangs during startup.

The fetch call at line 73 has no timeout configured. If the validation key URL is slow or unresponsive, the application startup will hang indefinitely, potentially causing deployment failures or health check timeouts.

🐛 Proposed fix using AbortController
  private async loadPublicKeysFromUrl(url: string): Promise<string[]> {
    try {
-     const response = await fetch(url);
+     const controller = new AbortController();
+     const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
+     
+     const response = await fetch(url, { signal: controller.signal });
+     clearTimeout(timeoutId);
      
      if (!response.ok) {
        throw new Error(
          `Failed to fetch public keys from URL: ${response.status} ${response.statusText}`,
        );
      }
🤖 Prompt for AI Agents
In `@apps/custom-issuer/src/modules/issuer/key.service.ts` around lines 71 - 109,
The fetch in loadPublicKeysFromUrl can hang indefinitely; wrap the fetch with an
AbortController and a configurable timeout (e.g., 5-10s), pass controller.signal
to fetch, and set a timer to call controller.abort() if the timeout elapses;
ensure you clear the timer after fetch completes and handle AbortError by
throwing a clear timeout-specific Error from loadPublicKeysFromUrl so startup
fails fast instead of hanging.

AdriaCarrera
AdriaCarrera previously approved these changes Jan 14, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @.github/workflows/build-custom-issuer.yaml:
- Around line 47-58: Replace the outdated action reference "uses:
docker/build-push-action@v4" with the latest stable release "uses:
docker/build-push-action@v6.18.0" in the workflow block that configures the
Docker build (the block referencing file: docker/custom-issuer.Dockerfile and
inputs like target, push, tags, build-args, secrets); update only the version
specifier for docker/build-push-action to v6.18.0 and verify the existing keys
(context, file, target, push, tags, build-args, secrets) remain valid with v6.

In `@apps/custom-issuer/src/modules/issuer/issuer.constants.ts`:
- Line 2: The constant SECONDS_IN_MILLISECOND is misnamed: its value 1000
represents milliseconds per second, not seconds per millisecond; rename the
constant to MILLISECONDS_IN_SECOND (or MS_PER_SECOND) and update all
references/usages of SECONDS_IN_MILLISECOND across the codebase (imports, tests,
and any calculations) to use the new name so semantics match the value.

In `@docker/custom-issuer.Dockerfile`:
- Line 37: The Dockerfile CMD currently attempts to execute the JS output
directly (CMD [ "/app/dist/src/main" ]), which fails because the Node runtime
isn't invoked; update the CMD to explicitly invoke the Node executable and pass
the built entry file (e.g., use node with "/app/dist/src/main" as the argument)
to match the package.json "start:prod": "node dist/main" pattern so the
container runs with the Node runtime.

In `@infra/chart/templates/custom-issuer.yaml`:
- Around line 88-92: The jmesPath mapping is inverted: swap the values so the
JSON key from the secret becomes path and the output filename/alias becomes
objectAlias; update the jmesPath loop that iterates over
.Values.customIssuer.secretProviderClass.secrets (currently using $k and $v) to
assign path from the JSON key and objectAlias from the alias (i.e., swap $k and
$v in the jmesPath entries) so path references the secret JSON key and
objectAlias is the output name.
♻️ Duplicate comments (2)
apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)

103-103: Query parameters are not redacted.

This was flagged in a previous review. Sensitive data like tokens can appear in query strings (e.g., ?token=...).

Proposed fix
         headers: sanitizeHeaders(request.headers),
-        query: request.query,
+        query: request.query ? redactSensitiveData(request.query) : undefined,
apps/custom-issuer/src/modules/issuer/issuer.service.ts (1)

44-57: Custom time validation remains unreachable — previous feedback not yet addressed.

The jwt.verify function validates exp and nbf claims by default, throwing TokenExpiredError or NotBeforeError before your custom validation methods can execute. The detailed error messages in validateTimeClaims, validateExpirationClaim, and validateNotBeforeClaim (lines 85-125) are unreachable for time-based failures.

To enable custom time validation with specific error messages, pass ignoreExpiration: true and ignoreNotBefore: true to jwt.verify.

🧹 Nitpick comments (5)
apps/custom-issuer/scripts/generate-test-jwt.js (1)

118-170: Consider adding a request timeout.

The HTTP request has no timeout configured, which could cause the script to hang indefinitely if the server doesn't respond.

♻️ Suggested improvement
         const req = client.request(requestOptions, (res) => {
             let data = "";
 
             res.on("data", (chunk) => {
                 data += chunk;
             });
 
             res.on("end", () => {
                 try {
                     const parsed = JSON.parse(data);
                     resolve({
                         statusCode: res.statusCode,
                         statusMessage: res.statusMessage,
                         headers: res.headers,
                         body: parsed,
                     });
                 } catch (_) {
                     resolve({
                         statusCode: res.statusCode,
                         statusMessage: res.statusMessage,
                         headers: res.headers,
                         body: data,
                     });
                 }
             });
         });
 
+        req.setTimeout(30000, () => {
+            req.destroy(new Error('Request timeout after 30 seconds'));
+        });
+
         req.on("error", (error) => {
             reject(error);
         });
apps/custom-issuer/.eslintrc.js (1)

9-9: Consider keeping no-console enabled.

Disabling no-console globally may allow accidental console.log statements in production. Since this service already uses NestJS's Logger (as seen in the exception filter), keeping this rule enabled would help enforce consistent structured logging.

If console usage is intentional in specific places, consider using // eslint-disable-next-line no-console for those lines instead.

apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)

42-52: Consider more precise field matching to reduce false positives.

Using includes() may over-redact benign fields like tokenCount, secretId, or passwordPolicy. While this errs on the side of caution, it could obscure useful debugging information.

Consider exact matching or word-boundary checks if this becomes problematic:

Alternative approach
const isSensitive = SENSITIVE_FIELDS.some(
    (field) => lowerKey === field || lowerKey.endsWith(`_${field}`) || lowerKey.startsWith(`${field}_`)
);
apps/custom-issuer/src/config/issuer.config.ts (1)

36-41: Consider validating all URL fields.

Only validationPublicKeyUrl is validated as a URL. The validationIssuerUrl and issuerUrl fields are also URLs by their naming and purpose, and should likely be validated for format as well.

Suggested enhancement
     // Validate URL format
     try {
         new URL(validationPublicKeyUrl.trim());
     } catch (_) {
         throw new Error(`Invalid VALIDATION_PUBLIC_KEY_URL: "${validationPublicKeyUrl}" is not a valid URL.`);
     }
+
+    try {
+        new URL(validationIssuerUrl.trim());
+    } catch (_) {
+        throw new Error(`Invalid VALIDATION_ISSUER_URL: "${validationIssuerUrl}" is not a valid URL.`);
+    }
+
+    try {
+        new URL(issuerUrl.trim());
+    } catch (_) {
+        throw new Error(`Invalid ISSUER_URL: "${issuerUrl}" is not a valid URL.`);
+    }
infra/chart/templates/custom-issuer.yaml (1)

84-84: Hardcoded AWS region reduces portability.

The region us-east-1 is hardcoded. Consider parameterizing this via .Values for multi-region deployments.

Suggested fix
   parameters:
-    region: us-east-1
+    region: {{ .Values.customIssuer.secretProviderClass.region | default "us-east-1" }}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07c82eb and 11b5c26.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .github/workflows/build-and-deploy.yaml
  • .github/workflows/build-custom-issuer.yaml
  • .github/workflows/deploy.yaml
  • .github/workflows/manual-deploy.yml
  • .github/workflows/pull-request.yml
  • apps/custom-issuer/.eslintrc.js
  • apps/custom-issuer/package.json
  • apps/custom-issuer/scripts/generate-test-jwt.js
  • apps/custom-issuer/src/app.module.ts
  • apps/custom-issuer/src/common/filters/http-exception.filter.ts
  • apps/custom-issuer/src/config/index.ts
  • apps/custom-issuer/src/config/issuer.config.ts
  • apps/custom-issuer/src/main.ts
  • apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts
  • apps/custom-issuer/src/modules/issuer/issuer.constants.ts
  • apps/custom-issuer/src/modules/issuer/issuer.controller.ts
  • apps/custom-issuer/src/modules/issuer/issuer.errors.ts
  • apps/custom-issuer/src/modules/issuer/issuer.module.ts
  • apps/custom-issuer/src/modules/issuer/issuer.service.ts
  • apps/custom-issuer/src/modules/issuer/issuer.types.ts
  • apps/custom-issuer/src/modules/issuer/key.service.ts
  • docker/custom-issuer.Dockerfile
  • infra/chart/templates/custom-issuer.yaml
  • infra/chart/templates/ingress.yaml
  • infra/chart/values/staging.yaml
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/custom-issuer/package.json
  • apps/custom-issuer/src/modules/issuer/key.service.ts
  • apps/custom-issuer/src/app.module.ts
  • apps/custom-issuer/src/config/index.ts
  • apps/custom-issuer/src/modules/issuer/issuer.errors.ts
  • apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts
  • apps/custom-issuer/src/modules/issuer/issuer.module.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-02T10:52:36.185Z
Learnt from: GuillemGarciaDev
Repo: Peersyst/fast-auth PR: 34
File: contracts/auth0-guard/src/lib.rs:67-76
Timestamp: 2025-09-02T10:52:36.185Z
Learning: The init signature changes in contracts/auth0-guard/src/lib.rs have already been properly handled in both test files - contracts/auth0-guard/tests/test_integration.rs includes issuer values like "https://dev-gb1h5yrp85jsty.us.auth0.com/" and examples/jwt-rs256-guard/tests/test_integration.rs includes issuer as an empty string variable in all init calls.

Applied to files:

  • apps/custom-issuer/src/config/issuer.config.ts
📚 Learning: 2025-09-12T07:12:41.650Z
Learnt from: JordiParraCrespo
Repo: Peersyst/bitcoin-light PR: 77
File: packages/frontend/token/src/ui/queries/token.queries.ts:11-14
Timestamp: 2025-09-12T07:12:41.650Z
Learning: In packages/frontend/token/src/ui/queries/token.queries.ts, the detail function accepts a chain parameter but does not include it in the query key, which could lead to cache key collisions if currencyId is not globally unique across chains.

Applied to files:

  • apps/custom-issuer/src/common/filters/http-exception.filter.ts
  • apps/custom-issuer/src/modules/issuer/issuer.service.ts
🧬 Code graph analysis (3)
apps/custom-issuer/src/modules/issuer/issuer.controller.ts (1)
apps/custom-issuer/src/modules/issuer/dto/issue-request.dto.ts (1)
  • IssueRequestDto (3-8)
apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)
apps/custom-issuer/scripts/generate-test-jwt.js (1)
  • response (181-181)
apps/custom-issuer/src/modules/issuer/issuer.service.ts (4)
apps/custom-issuer/src/modules/issuer/key.service.ts (1)
  • Injectable (5-173)
apps/custom-issuer/src/config/index.ts (2)
  • Config (3-5)
  • Config (7-11)
apps/custom-issuer/src/modules/issuer/issuer.constants.ts (2)
  • JWT_ALGORITHM (1-1)
  • SECONDS_IN_MILLISECOND (2-2)
apps/custom-issuer/src/modules/issuer/issuer.types.ts (1)
  • TokenClaims (1-6)
🪛 actionlint (1.7.10)
.github/workflows/build-custom-issuer.yaml

47-47: the runner of "docker/build-push-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 YAMLlint (1.37.1)
infra/chart/templates/custom-issuer.yaml

[error] 1-1: syntax error: expected the node content, but found '-'

(syntax)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-relayer / Build
🔇 Additional comments (22)
infra/chart/templates/ingress.yaml (1)

16-19: LGTM!

The custom-issuer ingress configuration follows the same pattern as the existing relayer setup. The conditional gating with .Values.customIssuer.enabled, service naming convention, and ALB action annotation are correctly implemented.

Also applies to: 36-44

apps/custom-issuer/src/modules/issuer/issuer.types.ts (1)

1-6: LGTM!

The TokenClaims type is well-defined with appropriate required (sub) and optional (exp, nbf, iss) fields that align with standard JWT claims (RFC 7519).

apps/custom-issuer/scripts/generate-test-jwt.js (1)

1-199: Overall implementation looks good.

The script provides a clean CLI interface for testing the custom-issuer service with proper argument parsing, file validation, JWT format checking, and error handling.

docker/custom-issuer.Dockerfile (1)

1-28: Build pipeline structure looks good.

The multi-stage build correctly separates integration (lint, type-check, test, build) from the production release stage. Using Turbo with mounted secrets and pnpm deploy for production isolation follows best practices.

.github/workflows/build-custom-issuer.yaml (1)

1-44: Workflow structure looks good.

The workflow correctly sets up QEMU and Buildx for multi-platform builds, authenticates with DockerHub, and constructs appropriate image tags. Secrets are properly handled.

.github/workflows/build-and-deploy.yaml (1)

40-50: LGTM!

The new build-custom-issuer job follows the established pattern of sibling build jobs (build-relayer, build-attester) with consistent inputs (push: true, target: release, base_image). The deploy job dependency is correctly updated to wait for all builds.

.github/workflows/pull-request.yml (1)

38-45: LGTM!

The PR build job correctly mirrors the sibling jobs with target: integration and push: false, ensuring the custom-issuer is validated during PR checks without pushing images.

apps/custom-issuer/src/common/filters/http-exception.filter.ts (1)

76-129: Well-structured exception filter.

The filter correctly:

  • Differentiates between HttpException and other errors
  • Logs at appropriate levels (error for 5xx with stack trace, warn for 4xx)
  • Sanitizes body and headers before logging
  • Provides consistent response formatting

Once the query parameter redaction is addressed, this implementation provides good security hygiene.

.github/workflows/deploy.yaml (1)

100-100: LGTM!

The tagCustomIssuer parameter follows the established pattern for tagRelayer and tagAttester, ensuring consistent image tagging during deployment.

.github/workflows/manual-deploy.yml (2)

15-18: LGTM!

The new custom-issuer-tag input follows the established pattern for other tag inputs (relayer-tag, attester-tag), maintaining consistency across the workflow.


104-104: LGTM!

The Helm deployment command is correctly extended with the new --set-string tagCustomIssuer flag, consistent with the existing tag parameters.

apps/custom-issuer/src/modules/issuer/issuer.constants.ts (1)

1-1: LGTM!

Using as const for the algorithm string literal is correct and ensures type safety when used with JWT libraries.

apps/custom-issuer/src/main.ts (1)

7-36: LGTM!

The bootstrap configuration is well-structured:

  • Body size limits prevent oversized payloads
  • ValidationPipe with forbidNonWhitelisted: true provides good input sanitization
  • CORS configuration properly handles the optional ALLOWED_ORIGINS environment variable
apps/custom-issuer/src/config/issuer.config.ts (2)

1-6: LGTM!

Clean type definition for the issuer configuration.


43-53: LGTM!

Good validation that the base64-encoded key decodes to a PEM private key format. This catches configuration errors at startup rather than at runtime.

apps/custom-issuer/src/modules/issuer/issuer.controller.ts (1)

5-15: LGTM!

Clean controller implementation. The explicit @HttpCode(HttpStatus.OK) appropriately returns 200 for this token exchange operation rather than the default 201 for POST requests. Input validation is properly delegated to the IssueRequestDto.

infra/chart/templates/custom-issuer.yaml (2)

1-1: Static analysis false positive — Helm templating is valid.

The YAMLlint error is expected since this is a Helm template file with Go templating syntax ({{- if ... }}), not pure YAML. The template is correctly structured.


2-101: Overall Helm template structure looks good.

The template correctly:

  • Conditionally deploys based on .Values.customIssuer.enabled
  • Uses appropriate naming conventions with environment suffix
  • Configures CSI volume for secrets injection
  • Sets up Prometheus scrape annotations for metrics
infra/chart/values/staging.yaml (1)

30-51: Configuration structure is consistent with existing patterns.

The customIssuer block follows the same structure as relayer with appropriate service ports, deployment specs, and secretProviderClass configuration. Resource allocations are reasonable for a token validation/issuance service.

apps/custom-issuer/src/modules/issuer/issuer.service.ts (3)

15-36: Good defensive configuration validation.

The constructor properly validates that required configuration values are present and throws descriptive errors with environment variable hints if missing. This fail-fast approach prevents runtime issues.


69-83: Claim validation methods are well-structured.

The validateSubject and validateIssuer methods use proper type narrowing with assertion signatures and provide specific error messages. The issuer validation correctly compares against the configured validationIssuerUrl.


135-159: Token signing implementation is correct.

The token creation flow properly:

  • Retrieves the signing key from KeyService
  • Builds payload with required sub and iss claims
  • Optionally includes exp and nbf from the original token
  • Signs with the configured RS256 algorithm

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +47 to +58
uses: docker/build-push-action@v4
with:
context: .
file: docker/custom-issuer.Dockerfile
target: ${{ inputs.target }}
push: ${{ inputs.push }}
tags: ${{ env.DOCKER_IMAGE_NAME }}
build-args: |
BASE_IMAGE=${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}-base:${{ inputs.base_image }}
TURBO_TEAM=peersyst
secrets: |
turbo_token=${{ secrets.TURBO_TOKEN }}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

docker/build-push-action latest version releases GitHub Actions

💡 Result:

Latest stable major release: v6. Latest published package version: v6.18.0 (current latest). [1][2]

Sources:

  • docker/build-push-action repo (README showing v6 as stable). [1]
  • Package versions page listing v6.18.0 as the latest. [2]

Update docker/build-push-action to v6.

The docker/build-push-action@v4 is outdated. Update to v6.18.0 (the latest stable version).

♻️ Proposed update
       - name: Build docker image
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v6
         with:
           context: .
           file: docker/custom-issuer.Dockerfile
📝 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
uses: docker/build-push-action@v4
with:
context: .
file: docker/custom-issuer.Dockerfile
target: ${{ inputs.target }}
push: ${{ inputs.push }}
tags: ${{ env.DOCKER_IMAGE_NAME }}
build-args: |
BASE_IMAGE=${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}-base:${{ inputs.base_image }}
TURBO_TEAM=peersyst
secrets: |
turbo_token=${{ secrets.TURBO_TOKEN }}
uses: docker/build-push-action@v6
with:
context: .
file: docker/custom-issuer.Dockerfile
target: ${{ inputs.target }}
push: ${{ inputs.push }}
tags: ${{ env.DOCKER_IMAGE_NAME }}
build-args: |
BASE_IMAGE=${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}-base:${{ inputs.base_image }}
TURBO_TEAM=peersyst
secrets: |
turbo_token=${{ secrets.TURBO_TOKEN }}
🧰 Tools
🪛 actionlint (1.7.10)

47-47: the runner of "docker/build-push-action@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/build-custom-issuer.yaml around lines 47 - 58, Replace the
outdated action reference "uses: docker/build-push-action@v4" with the latest
stable release "uses: docker/build-push-action@v6.18.0" in the workflow block
that configures the Docker build (the block referencing file:
docker/custom-issuer.Dockerfile and inputs like target, push, tags, build-args,
secrets); update only the version specifier for docker/build-push-action to
v6.18.0 and verify the existing keys (context, file, target, push, tags,
build-args, secrets) remain valid with v6.

@@ -0,0 +1,2 @@
export const JWT_ALGORITHM = "RS256" as const;
export const SECONDS_IN_MILLISECOND = 1000;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inverted constant name.

The constant name SECONDS_IN_MILLISECOND is semantically incorrect. The value 1000 represents milliseconds per second, not seconds per millisecond.

Suggested fix
-export const SECONDS_IN_MILLISECOND = 1000;
+export const MILLISECONDS_PER_SECOND = 1000;
📝 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
export const SECONDS_IN_MILLISECOND = 1000;
export const MILLISECONDS_PER_SECOND = 1000;
🤖 Prompt for AI Agents
In `@apps/custom-issuer/src/modules/issuer/issuer.constants.ts` at line 2, The
constant SECONDS_IN_MILLISECOND is misnamed: its value 1000 represents
milliseconds per second, not seconds per millisecond; rename the constant to
MILLISECONDS_IN_SECOND (or MS_PER_SECOND) and update all references/usages of
SECONDS_IN_MILLISECOND across the codebase (imports, tests, and any
calculations) to use the new name so semantics match the value.

WORKDIR /app
COPY --from=integration /artifacts/dist /app/dist
COPY --from=integration /artifacts/node_modules /app/node_modules
CMD [ "/app/dist/src/main" ]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the Dockerfile structure and build steps
cat -n docker/custom-issuer.Dockerfile

Repository: Peersyst/fast-auth

Length of output: 1430


🏁 Script executed:

# Check NestJS build configuration
cat apps/custom-issuer/tsconfig.json | head -30

Repository: Peersyst/fast-auth

Length of output: 738


🏁 Script executed:

# Check package.json for build configuration
cat apps/custom-issuer/package.json | jq '.scripts // empty'

Repository: Peersyst/fast-auth

Length of output: 374


🏁 Script executed:

# Look for the actual build output or reference files
fd -t f "main" apps/custom-issuer --type f 2>/dev/null | head -10

Repository: Peersyst/fast-auth

Length of output: 92


🏁 Script executed:

# Check package.json in custom-issuer to see if it declares ESM
cat apps/custom-issuer/package.json | jq '.type // empty'

Repository: Peersyst/fast-auth

Length of output: 44


🏁 Script executed:

# Check what the actual build output looks like by looking at the nest.json or checking build config
cat apps/custom-issuer/nest-cli.json 2>/dev/null || echo "No nest-cli.json"

Repository: Peersyst/fast-auth

Length of output: 232


🏁 Script executed:

# Look for any package.json at root level to understand monorepo structure
cat package.json | jq '.type // empty'

Repository: Peersyst/fast-auth

Length of output: 70


🏁 Script executed:

# Check if there's any executable configuration in custom-issuer's build output
ls -la apps/custom-issuer/dist 2>/dev/null | head -20 || echo "dist not available in repo (expected)"

Repository: Peersyst/fast-auth

Length of output: 44


🏁 Script executed:

# Check if root package.json declares ESM
cat package.json | jq '.type // "not_specified"'

Repository: Peersyst/fast-auth

Length of output: 70


🏁 Script executed:

# Look at NestJS build output structure - check how NestJS outputs files
# The start:prod script uses "node dist/main" which suggests the output is dist/main (no .js extension)
# Let's check if there's documentation or examples in the codebase
find . -name "Dockerfile*" -type f | head -10

Repository: Peersyst/fast-auth

Length of output: 74


🏁 Script executed:

# Check if dist/main exists as executable or if it's a JavaScript file
# Search for any other Dockerfile patterns to see how other services are started
grep -r "CMD\|ENTRYPOINT" docker/ --include="*.Dockerfile" --include="Dockerfile"

Repository: Peersyst/fast-auth

Length of output: 224


🏁 Script executed:

# Check if there are any build outputs or scripts that show what main is
find apps/custom-issuer -name "main*" -type f 2>/dev/null | grep -E "(dist|build)" || echo "No compiled main found (expected in repo view)"

Repository: Peersyst/fast-auth

Length of output: 108


🏁 Script executed:

# Check if there's a shebang in the source or if main is meant to be executable
cat apps/custom-issuer/src/main.ts | head -5

Repository: Peersyst/fast-auth

Length of output: 299


🏁 Script executed:

# Verify how NestJS compilation works with nodenext module setting
# Check if there are any build configuration files that affect the output
cat apps/custom-issuer/.nxrc* 2>/dev/null || cat apps/custom-issuer/project.json 2>/dev/null | head -50 || echo "No nx config"

Repository: Peersyst/fast-auth

Length of output: 44


Add node executable to the CMD entrypoint.

The CMD should explicitly invoke the Node.js runtime; otherwise the container attempts to execute a JavaScript file as a binary. The .js extension is not required—this is an ESM project ("type": "module" in package.json), and the build output with nodenext module resolution handles extension resolution automatically. The package.json script already uses this pattern: "start:prod": "node dist/main" without an extension.

🐛 Proposed fix
-CMD [ "/app/dist/src/main" ]
+CMD [ "node", "/app/dist/src/main" ]
📝 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
CMD [ "/app/dist/src/main" ]
CMD [ "node", "/app/dist/src/main" ]
🤖 Prompt for AI Agents
In `@docker/custom-issuer.Dockerfile` at line 37, The Dockerfile CMD currently
attempts to execute the JS output directly (CMD [ "/app/dist/src/main" ]), which
fails because the Node runtime isn't invoked; update the CMD to explicitly
invoke the Node executable and pass the built entry file (e.g., use node with
"/app/dist/src/main" as the argument) to match the package.json "start:prod":
"node dist/main" pattern so the container runs with the Node runtime.

Comment on lines +88 to +92
jmesPath:
{{- range $k, $v := .Values.customIssuer.secretProviderClass.secrets }}
- path: {{ $v }}
objectAlias: {{ $k }}
{{ end }}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are other SecretProviderClass examples in the repo for comparison
rg -n "jmesPath" --type yaml -C5

Repository: Peersyst/fast-auth

Length of output: 2466


🏁 Script executed:

#!/bin/bash
# Look at the full custom-issuer.yaml file to understand context
fd "custom-issuer.yaml" --type f | head -1 | xargs wc -l

Repository: Peersyst/fast-auth

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Check the values file to understand the structure
fd "values.yaml" --type f | head -5 | xargs wc -l

Repository: Peersyst/fast-auth

Length of output: 63


🏁 Script executed:

#!/bin/bash
# Read the full custom-issuer.yaml file
cat -n infra/chart/templates/custom-issuer.yaml

Repository: Peersyst/fast-auth

Length of output: 4125


🏁 Script executed:

#!/bin/bash
# Find all values files
fd "values" --type f | head -10

Repository: Peersyst/fast-auth

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for how customIssuer.secretProviderClass.secrets is structured in values
rg "customIssuer" --type yaml -A10

Repository: Peersyst/fast-auth

Length of output: 8980


🏁 Script executed:

#!/bin/bash
# Look at the staging values file for customIssuer secrets
cat infra/chart/values/staging.yaml | grep -A20 "customIssuer:" | grep -A10 "secretProviderClass"

Repository: Peersyst/fast-auth

Length of output: 396


🏁 Script executed:

#!/bin/bash
# Search for all values files to find customIssuer secrets structure
fd "values" --type f -x grep -l "secretProviderClass" {}

Repository: Peersyst/fast-auth

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Get complete customIssuer secretProviderClass section from values files
rg "secretProviderClass:" -A15 --type yaml

Repository: Peersyst/fast-auth

Length of output: 8185


🌐 Web query:

AWS Secrets Store CSI provider jmesPath path vs objectAlias semantics documentation

💡 Result:

Short answer — semantics:

  • path: a JMESPath expression (against the JSON value of the secret) that selects the value to extract. It's the lookup expression.
  • objectAlias: the filename (the key under the mounted volume) that the selected value will be written to in the pod.

Notes/behaviour:

  • jmesPath is used only for JSON-formatted secret values and each jmesPath entry must include both path and objectAlias. [1][2][3]
  • Each jmesPath entry creates a separate file named by objectAlias containing the extracted value; objectAlias must be unique for mounts that would otherwise collide (otherwise later entries override earlier ones). [1][2]
  • If a hyphen appears in path or objectAlias, escape it with single quotes (e.g. path: '"hyphenated-path"'). [1][3]
  • Optional filePermission can be set per jmesPath entry; otherwise the parent object's permission is used. [1][3]

Sources: AWS provider README (aws/secrets-store-csi-driver-provider-aws) and AWS docs/blog on the Secrets Store CSI integration. [1][2][3]


Fix the inverted jmesPath mapping.

The path and objectAlias fields in the jmesPath entries are inverted. According to AWS Secrets Store CSI provider semantics:

  • path should reference the JSON key in the AWS secret (e.g., keyBase64)
  • objectAlias should be the output filename/alias for the extracted value (e.g., KEY_BASE_64)

The current mapping uses environment variable names as path and JSON keys as objectAlias, which is backwards. Swap $k and $v in the jmesPath section:

Corrected mapping
jmesPath:
{{- range $k, $v := .Values.customIssuer.secretProviderClass.secrets }}
  - path: {{ $k }}
    objectAlias: {{ $v }}
{{ end }}
🤖 Prompt for AI Agents
In `@infra/chart/templates/custom-issuer.yaml` around lines 88 - 92, The jmesPath
mapping is inverted: swap the values so the JSON key from the secret becomes
path and the output filename/alias becomes objectAlias; update the jmesPath loop
that iterates over .Values.customIssuer.secretProviderClass.secrets (currently
using $k and $v) to assign path from the JSON key and objectAlias from the alias
(i.e., swap $k and $v in the jmesPath entries) so path references the secret
JSON key and objectAlias is the output name.

@AdriaCarrera AdriaCarrera merged commit 8ab2881 into main Jan 14, 2026
8 of 9 checks passed
@AdriaCarrera AdriaCarrera deleted the apps/custom-issuer branch January 14, 2026 10:09
@coderabbitai coderabbitai bot mentioned this pull request Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants