Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds 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
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 }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
✏️ Tip: You can disable this entire section by setting Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
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: falseallows untyped variables, which can mask type errors in token validation/signing logic.noFallthroughCasesInSwitch: falsecan lead to unintended fallthrough bugs.Consider enabling these for better compile-time safety:
- "noImplicitAny": false, - "strictBindCallApply": false, - "noFallthroughCasesInSwitch": false + "noImplicitAny": true, + "strictBindCallApply": true, + "noFallthroughCasesInSwitch": trueapps/custom-issuer/package.json (1)
2-7: Consider adding project metadata.The
descriptionandauthorfields 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 keysapps/custom-issuer/scripts/generate-test-jwt.js (1)
36-50: Consider explicit handling when user passes0.Using
|| 1afterparseInttreats user-supplied0as invalid and defaults to1. While--count 0is likely not meaningful,--nbf 0is explicitly allowed as default. The pattern is inconsistent.For
--count, this is fine since0doesn't make sense. For--expand--nbf, consider whether0should 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. Thepath.resolvecall on line 51 already guarantees an absolute path, making this check unnecessary.
27-29: Consider validatingKEYS_BASE_PATHformat.If
KEYS_BASE_PATHis 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
configurationis 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_MILLISECONDis inverted. The value1000represents milliseconds per second, not seconds per millisecond. Consider renaming toMILLISECONDS_PER_SECONDorMS_PER_SECONDto 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 removingasyncif not needed.The method is marked
asyncbut contains noawaitexpressions. 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 toissueToken(inputJwt: string): string.
36-46: Consider validatingaudand/orissclaims on input token.The input token is verified against the validation public key, but there's no check for
aud(audience) oriss(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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
apps/custom-issuer/.env.exampleapps/custom-issuer/.gitignoreapps/custom-issuer/.prettierrcapps/custom-issuer/README.mdapps/custom-issuer/eslint.config.mjsapps/custom-issuer/nest-cli.jsonapps/custom-issuer/package.jsonapps/custom-issuer/scripts/generate-test-jwt.jsapps/custom-issuer/src/app.module.tsapps/custom-issuer/src/common/filters/http-exception.filter.tsapps/custom-issuer/src/config/index.tsapps/custom-issuer/src/config/issuer.config.tsapps/custom-issuer/src/main.tsapps/custom-issuer/src/modules/issuer/dto/issue-request.dto.tsapps/custom-issuer/src/modules/issuer/issuer.constants.tsapps/custom-issuer/src/modules/issuer/issuer.controller.tsapps/custom-issuer/src/modules/issuer/issuer.errors.tsapps/custom-issuer/src/modules/issuer/issuer.module.tsapps/custom-issuer/src/modules/issuer/issuer.service.tsapps/custom-issuer/src/modules/issuer/issuer.types.tsapps/custom-issuer/src/modules/issuer/key.service.tsapps/custom-issuer/tsconfig.build.jsonapps/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_PATHis 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
HttpExceptionand 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
allowedHeadersonly includesContent-TypeandAuthorization. Common headers likeAccept,Origin, andX-Requested-Withare not included, which may cause preflight failures for some clients. Also,HEADandPATCHmethods 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:enableImplicitConversioncan cause unexpected type coercion.With
enableImplicitConversion: true, strings like"123"are automatically converted to numbers if the DTO property is typed asnumber. 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
subas required and temporal claims (exp,nbf) as optional numbers for Unix timestamps. This aligns well with the validation logic implied by the error messages inissuer.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
expbeing beforenbf.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 > nowensures the token hasn't expirednbf <= nowensures the token is already validexp > nbfensures logical consistencyThe assertion functions provide proper type narrowing for TypeScript. This code will work correctly once the
jwt.verifyoptions are updated to disable automatic time validation (per the earlier comment).
111-117: LGTM!The helper methods are correct and well-named.
isValidIntegerproperly checks both type and integer property, andgetCurrentTimestampcorrectly produces Unix timestamps in seconds using the standard pattern.
128-143: Verify intent: output token inherits input token's expiration.The output token copies
expandnbfdirectly 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
iatand a newexpcalculated from the current time (e.g.,now + configuredTTL).
apps/custom-issuer/eslint.config.mjs
Outdated
| ...globals.node, | ||
| ...globals.jest, | ||
| }, | ||
| sourceType: 'commonjs', |
There was a problem hiding this comment.
🧩 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.
| "jest": "^30.0.0", | ||
| "jsonwebtoken": "^9.0.2", |
There was a problem hiding this comment.
🧩 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", |
There was a problem hiding this comment.
🧩 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.
| body: request.body ? redactSensitiveData(request.body) : undefined, | ||
| headers: sanitizeHeaders(request.headers), | ||
| query: request.query, |
There was a problem hiding this comment.
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.
| 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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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 -120Repository: 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.
| 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.
There was a problem hiding this comment.
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.verifyvalidatesexpandnbfby default, throwingTokenExpiredErrororNotBeforeErrorbefore your customvalidateTimeClaimsmethods can execute. The fix requires addingignoreExpiration: trueandignoreNotBefore: trueto the verify options.
🧹 Nitpick comments (3)
apps/custom-issuer/src/config/issuer.config.ts (1)
38-45: URL validation is inconsistent across fields.
validationPublicKeyUrlis validated as a proper URL, butvalidationIssuerUrlandissuerUrl(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 keyapps/custom-issuer/src/modules/issuer/issuer.service.ts (2)
52-63: Unused variablelastErroris dead code.The
lastErrorvariable 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 addingiat(issued-at) claim to signed tokens.The new token omits the
iatclaim. While not strictly required, including it is a common best practice for auditing and debugging token issuance times. Note thatjwt.signdoes not automatically addiatwhen 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
📒 Files selected for processing (7)
apps/custom-issuer/.env.exampleapps/custom-issuer/README.mdapps/custom-issuer/scripts/generate-test-jwt.jsapps/custom-issuer/src/config/issuer.config.tsapps/custom-issuer/src/modules/issuer/issuer.errors.tsapps/custom-issuer/src/modules/issuer/issuer.service.tsapps/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
Errorfor 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.verifyoptions are updated per the prior review comment, these will function as intended. Note thatvalidateTimeClaimConsistency(exp ≤ nbf check) is already reachable sincejwt.verifydoesn'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.
| 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'); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the key.service.ts file
find . -type f -name "key.service.ts" | head -5Repository: 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.tsRepository: 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 -20Repository: 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.tsRepository: 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 3Repository: 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 -60Repository: 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.tsRepository: 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.
There was a problem hiding this comment.
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.verifyfunction validatesexpandnbfclaims by default, throwingTokenExpiredErrororNotBeforeErrorbefore your custom validation methods can execute. The detailed error messages invalidateTimeClaims,validateExpirationClaim, andvalidateNotBeforeClaim(lines 85-125) are unreachable for time-based failures.To enable custom time validation with specific error messages, pass
ignoreExpiration: trueandignoreNotBefore: truetojwt.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 keepingno-consoleenabled.Disabling
no-consoleglobally may allow accidentalconsole.logstatements in production. Since this service already uses NestJS'sLogger(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-consolefor 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 liketokenCount,secretId, orpasswordPolicy. 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
validationPublicKeyUrlis validated as a URL. ThevalidationIssuerUrlandissuerUrlfields 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-1is hardcoded. Consider parameterizing this via.Valuesfor 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.ymlapps/custom-issuer/.eslintrc.jsapps/custom-issuer/package.jsonapps/custom-issuer/scripts/generate-test-jwt.jsapps/custom-issuer/src/app.module.tsapps/custom-issuer/src/common/filters/http-exception.filter.tsapps/custom-issuer/src/config/index.tsapps/custom-issuer/src/config/issuer.config.tsapps/custom-issuer/src/main.tsapps/custom-issuer/src/modules/issuer/dto/issue-request.dto.tsapps/custom-issuer/src/modules/issuer/issuer.constants.tsapps/custom-issuer/src/modules/issuer/issuer.controller.tsapps/custom-issuer/src/modules/issuer/issuer.errors.tsapps/custom-issuer/src/modules/issuer/issuer.module.tsapps/custom-issuer/src/modules/issuer/issuer.service.tsapps/custom-issuer/src/modules/issuer/issuer.types.tsapps/custom-issuer/src/modules/issuer/key.service.tsdocker/custom-issuer.Dockerfileinfra/chart/templates/custom-issuer.yamlinfra/chart/templates/ingress.yamlinfra/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.tsapps/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
TokenClaimstype 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-issuerjob 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: integrationandpush: 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
tagCustomIssuerparameter follows the established pattern fortagRelayerandtagAttester, ensuring consistent image tagging during deployment..github/workflows/manual-deploy.yml (2)
15-18: LGTM!The new
custom-issuer-taginput 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 tagCustomIssuerflag, consistent with the existing tag parameters.apps/custom-issuer/src/modules/issuer/issuer.constants.ts (1)
1-1: LGTM!Using
as constfor 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: trueprovides good input sanitization- CORS configuration properly handles the optional
ALLOWED_ORIGINSenvironment variableapps/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 theIssueRequestDto.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
customIssuerblock follows the same structure asrelayerwith 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
validateSubjectandvalidateIssuermethods use proper type narrowing with assertion signatures and provide specific error messages. The issuer validation correctly compares against the configuredvalidationIssuerUrl.
135-159: Token signing implementation is correct.The token creation flow properly:
- Retrieves the signing key from
KeyService- Builds payload with required
subandissclaims- Optionally includes
expandnbffrom 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.
| 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 }} |
There was a problem hiding this comment.
🧩 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.
| 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; | |||
There was a problem hiding this comment.
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.
| 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" ] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the Dockerfile structure and build steps
cat -n docker/custom-issuer.DockerfileRepository: Peersyst/fast-auth
Length of output: 1430
🏁 Script executed:
# Check NestJS build configuration
cat apps/custom-issuer/tsconfig.json | head -30Repository: 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 -10Repository: 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 -10Repository: 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 -5Repository: 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.
| 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.
| jmesPath: | ||
| {{- range $k, $v := .Values.customIssuer.secretProviderClass.secrets }} | ||
| - path: {{ $v }} | ||
| objectAlias: {{ $k }} | ||
| {{ end }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there are other SecretProviderClass examples in the repo for comparison
rg -n "jmesPath" --type yaml -C5Repository: 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 -lRepository: 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 -lRepository: 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.yamlRepository: Peersyst/fast-auth
Length of output: 4125
🏁 Script executed:
#!/bin/bash
# Find all values files
fd "values" --type f | head -10Repository: 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 -A10Repository: 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 yamlRepository: 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:
pathshould reference the JSON key in the AWS secret (e.g.,keyBase64)objectAliasshould 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.
[137]: Custom Backend JWT Issuer
Changes 🛠️
app/custom-issuer
custom-issuerservice to validate and send issued jwt tokens to be used with fast-auth.Summary by CodeRabbit
Release Notes
New Features
Infrastructure & DevOps
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.