CareCard Release 1.1.1 is the production baseline. Seeding with
--seed-release-data is complete. Production cluster data, database rows,
and stored user files will be preserved going forward. There will be no
complete user-data wipeout and re-creation.
Future production changes must use data-preserving migrations. Do not replay the completed initialization or run reset, rollback, or seed workflows as part of ordinary production deployment.
The completed seed inventory and verification record documents the accepted release baseline and verification limitations.
Non-negotiable test order invariance rule: Every test must pass independently of which tests run before or after it, and the suite must pass in every execution order. Each test must establish the state it needs, isolate mutable state, and clean up state it owns; it must never rely on another test's setup, mutations, or cleanup. Default test, CI, and Husky commands must use the test framework's ordinary ordering and must not force randomized ordering. Random-order execution is an explicit diagnostic only, and every failure it exposes must be fixed at the root cause.
Non-negotiable root-cause solution rule: Always identify and solve the verified root cause, use the stronger solution, and deliver a correct, durable, production-quality result. Never treat a temporary workaround, resource increase, retry, suppression, bypass, or symptom-only patch as completion. Validate the root-cause fix against the real failing workflow and prove the end state.
Utility package for reading, parsing, and verifying JWTs in the CareCard
ecosystem. It also provides the shared request middleware used by ms-*
services to accept either an ms-auth JWT or an opaque server-auth token
introspected by ms-auth.
Non-negotiable TDD rule: Always write the failing test first, run it to confirm it fails for the intended reason, then implement the code and rerun the test until it passes. Test Driven Development is required for all coding work and must not be skipped. For documentation- or skill-only edits, run the relevant focused non-test validation before changing the prose; do not add automated tests that inspect prose, files, or repository structure.
Non-negotiable repository isolation rule: Every repository must run its Husky hooks and tests using only files, code, fixtures, dependencies, and services contained within that repository. Tests and Husky scripts must not import, require, read, execute, or otherwise depend on sibling repositories or paths outside the repository root. app-e2e-tests is the only exception because cross-repository end-to-end testing is its explicit responsibility.
Non-negotiable error and warning rule: Never suppress, silence, hide, downgrade, filter, ignore, skip, or bypass errors or warnings from code, tests, tools, compilers, linters, or validation. Fix the root cause, then rerun the affected check and require a clean result. Expected error-path tests may assert errors, but must not conceal unexpected failures.
Non-negotiable TypeScript type rule: Never use the TypeScript type any; always use specific domain types, generics, existing project types, or unknown with explicit narrowing in all TypeScript-family files (.ts, .tsx, .mts, .cts, and .d.ts).
Non-negotiable code organization rule: Functions with the same or equivalent behavior must use the same or clearly corresponding descriptive names across CareCard repositories, and equivalent functionality must live in files with the same names within each repository's established architecture. No backward compatibility names, aliases, or duplicate locations are allowed.
- JWT Verification: Middleware-like utilities for signature and role verification.
- Express Integration: Designed to work seamlessly with Express
reqobjects. - Role Mapping: Simple utility for translating internal role codes to human-readable names.
- Claims Extraction: Easy extraction of
sub(clientId) and other JWT payload claims. - Expiration Management: Helpers to check if a JWT is expired and calculate its remaining TTL.
- Service JWTs: Helpers for verifying and extracting microservice-to-microservice JWTs with standard
iss,sub,aud,iat, andexpclaims. - JWT or Server Auth: Middleware helpers that verify normal JWTs locally and call a service-provided introspector for opaque server-auth tokens.
- Scoped User Authorization: Optional
X-Authorization-Contextverification attaches compact scoped authorization claims toreq.userAuthorizationwithout replacingreq.jwt.
npm install @carecard/jwt-readconst { verifyJwtAndRole, throwUsedTokenError } = require('@carecard/jwt-read');
const { parseJwtVerificationJwks } = require('@carecard/auth-util');
const verificationJwks = parseJwtVerificationJwks(process.env.MS_AUTH_JWT_VERIFICATION_JWKS);
const verifyAdmin = verifyJwtAndRole('admin', verificationJwks, throwUsedTokenError);
// In an Express controller/middleware
try {
await verifyAdmin(req, res, next);
// If successful, req.jwt contains { header, payload }
console.log(req.jwt.payload.sub);
} catch (error) {
// Handle verification error
}const { jwtVerify } = require('@carecard/jwt-read');
app.use(jwtVerify(verificationJwks));const { getNameOfRole, getCodeOfRole } = require('@carecard/jwt-read');
console.log(getNameOfRole('ad')); // Result: 'admin'
console.log(getCodeOfRole('super_admin')); // Result: 'su'ms-auth treats a JWT or server-auth payload containing roles: ["ad"] as the
auth-service super-admin signal for its RLS policies. Consumers may map ad to
UI/domain names such as super_admin, but middleware should preserve the
original roles array on the request context so services can make
database-context decisions consistently.
Docs that mention ms-auth controller internals should use concise action
names such as loginUser, registerUser, getUserDetail, and renewJwt.
Access level is conveyed by route middleware and endpoint placement, not by
public/protected/admin/Handler suffixes.
Use service JWT verification helpers for backend service calls. The sending
service signs the token with @carecard/auth-util. The receiving service uses
this package to verify the token by kid through the sending service's public
JWKS and check the expected issuer and audience.
const { jwtCreateServiceAuthorizationHeader } = require('@carecard/auth-util');
const { jwtVerifyService } = require('@carecard/jwt-read');
const authorization = jwtCreateServiceAuthorizationHeader({
issuer: 'ms-institutions',
audience: 'ms-auth',
signingJwk: institutionsSigningJwk,
});
app.use(
jwtVerifyService(
institutionsVerificationJwks,
'ms-institutions',
'ms-auth',
throwNotAuthorizedError,
),
);Service JWT payloads follow standard JWT semantics:
iss: sending servicesub: sending service identityaud: receiving serviceiat: issued-at NumericDateexp: expiration NumericDate
Use the OrServerAuth helpers on app-facing ms-* routes that should accept
both current authentication modes. The JWT path verifies locally with the
ms-auth public JWKS. The server-auth path calls the provided introspector,
which should send the opaque token to
POST /api/v1/ms-auth/server-auth/introspect with the receiving service's
service JWT.
const {
jwtGetRoleCode,
jwtVerifyOrServerAuth,
jwtVerifyOrServerAuthAndHasRole,
} = require('@carecard/jwt-read');
const verifyUser = jwtVerifyOrServerAuth(
msAuthVerificationJwks,
token => introspectServerAuthTokenWithMsAuth(token),
throwNotAuthorizedError,
);
const verifyAdmin = jwtVerifyOrServerAuthAndHasRole(
jwtGetRoleCode('admin'),
msAuthVerificationJwks,
token => introspectServerAuthTokenWithMsAuth(token),
throwNotAuthorizedError,
);The introspector must return claims for valid tokens. This package normalizes
those claims onto req.jwt.payload with authMode: "server-auth" and
auth_mode: "server-auth" so services can keep their existing JWT-backed
database context and role checks.
Server-auth email verification claims are copied only when present. The
emailVerified and email_verified names retain their exact values, and
omission remains omission.
Use jwtVerifyUserAuthorization when a route needs to verify only the compact
authorization-context token carried in X-Authorization-Context. The token is
read as a raw JWT header value, not as Bearer <token>.
The exported DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH is 2048; token
issuers and consumers should use that shared constant instead of duplicating a
local limit.
const { jwtVerifyUserAuthorization } = require('@carecard/jwt-read');
app.use(
jwtVerifyUserAuthorization(institutionsVerificationJwks, throwNotAuthorizedError, {
expectedType: 'carecard.authorization-context.scoped.v1',
expectedIssuer: 'ms-institutions',
expectedAudience: 'ms-documents',
}),
);Existing JWT verification helpers can also read the header by passing an
optional trailing options object. This preserves current req.jwt behavior and
adds decoded scoped claims to req.userAuthorization.
const verifyUser = jwtVerifyOrServerAuth(
msAuthVerificationJwks,
token => introspectServerAuthTokenWithMsAuth(token),
throwNotAuthorizedError,
{
userAuthorization: {
verificationJwks: institutionsVerificationJwks,
expectedType: 'carecard.authorization-context.scoped.v1',
expectedIssuer: 'ms-institutions',
expectedAudience: 'ms-documents',
},
},
);Every verification value must come from
parseJwtVerificationJwks(serializedJwks). A JWKS may contain active and
retiring public Ed25519 keys, so rotation adds the replacement verifier before
switching signers and removes the retiring key only after the maximum JWT
lifetime plus clock skew. Unknown or removed kid values fail closed.
When the optional reader is configured, a missing X-Authorization-Context
leaves req.userAuthorization as null. If the header is present but invalid,
throwing middleware fails closed. No-throw middleware clears
req.userAuthorization and continues.
jwtGetContext(req) preserves the normal database caller shape of user_id
and optional role. When a verified req.userAuthorization.payload is present,
it also returns authorizationContext with those compact claims and the original
userAuthorization object so service models can set app.authz_context for
RLS without rebuilding the full authorization graph.
Run tests using:
npm testTo run tests with coverage:
npm run test:coverageTo run type tests:
npm run test:typesThe package is organized into several modules:
jwtLib: Main logic for JWT verification, extraction, and Express integration.jwtRoles: Role mapping between internal codes and names.
All modules are exported through the main index.js.
The current package tests own no HTTP listener, database pool, Kafka client, background timer, or child process after completion. Mocha's test timeout fails a stalled async test, the suites run without bail or forced exit, and npm preserves each command's nonzero status. Keep natural process exit as the open handle regression check; validation must not hide failures with retries, forced success, skipped tests, or output suppression.
Do not add unpublished executable validation code to a pkg-* repository. If a
future test owns a long-lived resource or demonstrates a post-suite hang, add a
contract-tested process watchdog through the coordinated package version,
publish, and consumer propagation workflow. That watchdog must return
immediately when no helper remains, allow only a bounded 250 ms settlement
window for already-stopping helpers, fail persistent descendants, preserve
failures and output, use exit code 124 only for a real outer deadline, and
remain a final guard rather than a substitute for explicit cleanup.
Test Driven Development is a non-negotiable requirement.
The sole purpose of automated tests is to verify observable functionality and externally visible behavior. Tests must validate what the system does through its public interfaces and expected outcomes.
Tests must not assert, inspect, or depend on implementation details, including but not limited to:
- The existence of specific lines of code, statements, functions, classes, files, or modules.
- Specific algorithms, control flow, variable names, method calls, code snippets, or internal implementation choices.
- Any internal structure that can change without changing externally observable behavior.
A correct implementation may be completely rewritten or refactored without requiring changes to functional tests, provided its externally observable behavior remains unchanged.
Any test that fails solely because the implementation changed while the externally observable behavior remained correct is incorrectly designed and must be rewritten or removed.
This requirement is mandatory for all new tests and must be applied whenever existing tests are modified.