@carecard/jwt-read 3.1.15 → 3.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/carecard-workspace-standards/SKILL.md +35 -13
- package/.agents/skills/pkg-jwt-read-jwt-middleware-library/SKILL.md +32 -10
- package/index.d.ts +60 -0
- package/index.js +3 -0
- package/lib/jwtLib.js +113 -0
- package/package.json +1 -1
- package/readme.md +46 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: carecard-workspace-standards
|
|
3
|
-
description: Follow the shared SO_CareCardCa/CareCard workspace coding, testing, repository, dependency, shared package, frontend, database, API response, and security standards. Use before modifying, testing, reviewing, or debugging any
|
|
3
|
+
description: Follow the shared SO_CareCardCa/CareCard workspace coding, testing, repository, dependency, shared package, frontend, database, API response, and security standards. Use before modifying, testing, reviewing, or debugging any ms-*, pkg-*, app-*, website, dashboard, or other CareCard repository in this workspace, especially when choosing validation commands, package boundaries, TypeScript types, dependencies, API contracts, database logic, service patterns, or frontend architecture.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# CareCard Workspace Standards
|
|
@@ -23,7 +23,7 @@ Use before modifying, testing, reviewing, or debugging any CareCard workspace re
|
|
|
23
23
|
|
|
24
24
|
- `.agents/config.toml`
|
|
25
25
|
- `.agents/skills`
|
|
26
|
-
- `
|
|
26
|
+
- `ms-*` services
|
|
27
27
|
- `pkg-*` packages
|
|
28
28
|
- `app-*` frontends
|
|
29
29
|
- `.husky` and `.junie` validation guidance
|
|
@@ -52,7 +52,7 @@ Use before modifying, testing, reviewing, or debugging any CareCard workspace re
|
|
|
52
52
|
|
|
53
53
|
Treat `/Users/pankajpriscilla/SO_CareCardCa` as a collection of independent Git
|
|
54
54
|
repositories, not as one monorepo. Work from the specific child repository
|
|
55
|
-
being changed. Each `
|
|
55
|
+
being changed. Each `ms-*`, `pkg-*`, and `app-*` directory has its own package
|
|
56
56
|
scripts, Git status, test commands, style, naming, structure, test framework,
|
|
57
57
|
and Husky hooks.
|
|
58
58
|
|
|
@@ -183,11 +183,11 @@ build artifacts, logs, or `.DS_Store`.
|
|
|
183
183
|
`notFound404`, `appErrorHandler`, error throw helpers, case converters, and
|
|
184
184
|
`ApiErrorType`.
|
|
185
185
|
- Do not create or maintain duplicated common response or error helpers inside
|
|
186
|
-
each `
|
|
186
|
+
each `ms-*` service.
|
|
187
187
|
- Keep service-local response code limited to service-specific mapping or
|
|
188
188
|
wiring.
|
|
189
189
|
- Add broadly useful shared functionality to the relevant `pkg-*` package
|
|
190
|
-
instead of duplicating it locally in an `
|
|
190
|
+
instead of duplicating it locally in an `ms-*` or `app-*` project.
|
|
191
191
|
- When changing a `pkg-*` package, write package tests first, increase that
|
|
192
192
|
package's minor version in `package.json`, run `npm install` in the package,
|
|
193
193
|
update consuming projects to the new package version, run `npm install` in
|
|
@@ -206,10 +206,10 @@ build artifacts, logs, or `.DS_Store`.
|
|
|
206
206
|
|
|
207
207
|
## Backend Microservices
|
|
208
208
|
|
|
209
|
-
Most JavaScript `
|
|
209
|
+
Most JavaScript `ms-*` services use CommonJS, Mocha, Supertest, Docker Compose
|
|
210
210
|
database tests, `@carecard/*` packages, and `sub-apps`
|
|
211
|
-
controller/router/model patterns. TypeScript services such as `
|
|
212
|
-
and `
|
|
211
|
+
controller/router/model patterns. TypeScript services such as `ms-contact-us`
|
|
212
|
+
and `ms-template-ts` use Jest or TypeScript tooling and should keep their
|
|
213
213
|
existing TypeScript style.
|
|
214
214
|
|
|
215
215
|
- Keep environment-specific files explicit: `.env.development`, `.env.test`,
|
|
@@ -283,9 +283,9 @@ existing TypeScript style.
|
|
|
283
283
|
- Keep tests readable and domain-specific.
|
|
284
284
|
- Tests must cover desired or happy paths and prevention or rejection of
|
|
285
285
|
undesired behavior.
|
|
286
|
-
- JavaScript `
|
|
286
|
+
- JavaScript `ms-*` services usually use Mocha, Supertest,
|
|
287
287
|
`test/index.test.js`, and Docker-backed PostgreSQL scripts.
|
|
288
|
-
- TypeScript `
|
|
288
|
+
- TypeScript `ms-*` services usually use Jest and `tests/index.test.ts`.
|
|
289
289
|
- `pkg-*` packages usually use Mocha plus TypeScript type tests where present.
|
|
290
290
|
- `app-dashboard` uses Vitest, React Testing Library, mock API tests, and
|
|
291
291
|
Selenium for end-to-end flows.
|
|
@@ -306,8 +306,8 @@ existing TypeScript style.
|
|
|
306
306
|
## Dashboard Frontend
|
|
307
307
|
|
|
308
308
|
`app-dashboard` is a Next.js App Router TypeScript app using MUI, React Query,
|
|
309
|
-
`next-intl`, and shared CareCard utilities. It consumes `
|
|
310
|
-
`
|
|
309
|
+
`next-intl`, and shared CareCard utilities. It consumes `ms-auth`,
|
|
310
|
+
`ms-institutions`, `ms-contact-us`, and `ms-user-profiles` through service
|
|
311
311
|
modules.
|
|
312
312
|
|
|
313
313
|
- Keep backend URL definitions centralized in `src/services/api.routes.ts`.
|
|
@@ -366,12 +366,34 @@ the authenticated dashboard.
|
|
|
366
366
|
- Keep CareCard package usage consistent with the service or app being changed.
|
|
367
367
|
- When standardizing response or error behavior, prefer `@carecard/common-util`
|
|
368
368
|
`3.1.15` because it contains response and error functions aligned with
|
|
369
|
-
`
|
|
369
|
+
`ms-auth`.
|
|
370
370
|
- If package version changes are required, update lockfiles and verify affected
|
|
371
371
|
services or apps.
|
|
372
372
|
- Avoid broad dependency upgrades as part of feature or refactor work unless
|
|
373
373
|
the task is specifically about dependencies.
|
|
374
374
|
|
|
375
|
+
## Auth Service RLS Contract
|
|
376
|
+
|
|
377
|
+
- `ms-auth` follows the shared PostgreSQL/RLS pattern from `ms-template-js`: auth tables live in the `carecard` schema, RLS is enabled and forced on every auth table, and application runtime queries use the unprivileged database role.
|
|
378
|
+
- Auth table policies allow normal JWT or server-auth users to access only
|
|
379
|
+
self-owned rows. Do not add redundant `user_id = <jwt sub>` SQL predicates to
|
|
380
|
+
duplicate self-row checks when RLS owns the authorization decision.
|
|
381
|
+
- A JWT or server-auth payload containing `roles: ["ad"]` is the auth-service
|
|
382
|
+
super-admin signal and can perform any action on auth tables. Dashboard code
|
|
383
|
+
may map that role to `super_admin`, but backend auth RLS must not require a
|
|
384
|
+
separate database role row for that bypass.
|
|
385
|
+
- Public auth flows such as registration, login, server-auth session create and
|
|
386
|
+
introspection, confirmation, recovery, visitor creation, and service user
|
|
387
|
+
lookup must use narrow system contexts (`system_create`, `system_login`,
|
|
388
|
+
`system_confirm`, `system_recovery`, `system_visitor`, `system_service`)
|
|
389
|
+
instead of privileged runtime queries.
|
|
390
|
+
|
|
391
|
+
- `ms-auth` controller exports use concise action names such as `loginUser`,
|
|
392
|
+
`registerUser`, `getUserDetail`, and `renewJwt`; route middleware and router
|
|
393
|
+
placement express whether a flow is public, authenticated, admin-only, or
|
|
394
|
+
service-only, so avoid `public`, `protected`, `admin`, or `Handler` suffixes
|
|
395
|
+
in new controller names.
|
|
396
|
+
|
|
375
397
|
## Security Requirements
|
|
376
398
|
|
|
377
399
|
- Treat authentication, authorization, JWT, password, email confirmation,
|
|
@@ -7,11 +7,13 @@ description: Use when changing pkg-jwt-read JWT parsing, middleware, visitor tok
|
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
9
9
|
|
|
10
|
-
CareCard JWT read package for parsing, request attachment, visitor tokens, role mapping, authorization middleware, exports, and tests.
|
|
10
|
+
CareCard JWT read package for parsing, request attachment, visitor tokens, role mapping, JWT-or-server-auth authorization middleware, exports, and tests.
|
|
11
11
|
|
|
12
12
|
## When To Use
|
|
13
13
|
|
|
14
|
-
- Use when changing pkg-jwt-read JWT parsing,
|
|
14
|
+
- Use when changing pkg-jwt-read JWT parsing, server-auth introspection
|
|
15
|
+
middleware, visitor tokens, role checks, auth context, package exports, or
|
|
16
|
+
tests.
|
|
15
17
|
- Pair with `carecard-workspace-standards` when the task affects shared CareCard conventions or cross-repository contracts.
|
|
16
18
|
|
|
17
19
|
## When Not To Use
|
|
@@ -53,7 +55,9 @@ CareCard JWT read package for parsing, request attachment, visitor tokens, role
|
|
|
53
55
|
Use this skill when working inside `pkg-jwt-read`, the `@carecard/jwt-read`
|
|
54
56
|
package. It provides utilities for reading, parsing, verifying, and attaching
|
|
55
57
|
JWT data in the CareCard ecosystem. It depends on `@carecard/auth-util` for
|
|
56
|
-
low-level cryptographic operations.
|
|
58
|
+
low-level cryptographic operations. It also exposes middleware helpers that
|
|
59
|
+
allow `ms-*` services to accept either an `ms-auth` JWT or an opaque server-auth
|
|
60
|
+
token introspected by `ms-auth`.
|
|
57
61
|
|
|
58
62
|
Use `$carecard-workspace-standards` for shared workspace, dependency, package,
|
|
59
63
|
testing, and security rules. Legacy `pkg-jwt-read/.codex` and
|
|
@@ -95,9 +99,14 @@ depend on those folders being present.
|
|
|
95
99
|
- Middleware-like functions for Express, such as `verifyJwtAndRole`.
|
|
96
100
|
- Service-to-service JWT verification and extraction helpers:
|
|
97
101
|
`jwtValidateAndExtractService` and `jwtVerifyService`.
|
|
102
|
+
- JWT-or-server-auth helpers: `jwtValidateAndExtractOrServerAuth`,
|
|
103
|
+
`jwtVerifyOrServerAuth`, and `jwtVerifyOrServerAuthAndHasRole`.
|
|
98
104
|
- Extraction of `sub`/clientId and other claims from JWT objects.
|
|
99
105
|
- Expiration checks and TTL calculations.
|
|
100
106
|
- Request attachment behavior for authenticated JWT objects and visitor tokens.
|
|
107
|
+
- Server-auth request attachment behavior that normalizes introspected claims
|
|
108
|
+
into `req.jwt.payload` with `authMode: "server-auth"` and
|
|
109
|
+
`auth_mode: "server-auth"`.
|
|
101
110
|
- Integration with `@carecard/common-util` for standardized login and
|
|
102
111
|
authorization errors.
|
|
103
112
|
|
|
@@ -108,6 +117,11 @@ JWT creation functions do not belong in this package. Service-to-service token
|
|
|
108
117
|
creation belongs in `@carecard/auth-util` via `jwtCreateServiceToken` and
|
|
109
118
|
`jwtCreateServiceAuthorizationHeader`.
|
|
110
119
|
|
|
120
|
+
Opaque server-auth token creation, hashing, persistence, and introspection
|
|
121
|
+
belong in `ms-auth`. This package only accepts a caller-provided introspector
|
|
122
|
+
function and normalizes valid introspection claims into the existing request
|
|
123
|
+
JWT context.
|
|
124
|
+
|
|
111
125
|
Service JWTs must follow standard JWT claim semantics. They use `iss` for the
|
|
112
126
|
sending service, `sub` for the sending service identity, `aud` for the
|
|
113
127
|
receiving service, and NumericDate `iat`, `exp`, and optional `nbf` claims.
|
|
@@ -126,6 +140,10 @@ role names, such as `ad` and `admin`.
|
|
|
126
140
|
by focused tests.
|
|
127
141
|
- Preserve existing role semantics unless a task explicitly changes
|
|
128
142
|
authorization behavior.
|
|
143
|
+
- Preserve the original JWT `roles` array on request context. `ms-auth` RLS
|
|
144
|
+
treats a payload containing `ad` as the auth-service super-admin signal;
|
|
145
|
+
dashboard code may map that to `super_admin`, but middleware must not hide,
|
|
146
|
+
rename, or drop the raw role payload needed by backend database contexts.
|
|
129
147
|
|
|
130
148
|
## NoThrow And Error Behavior
|
|
131
149
|
|
|
@@ -141,17 +159,20 @@ role names, such as `ad` and `admin`.
|
|
|
141
159
|
|
|
142
160
|
## Security Rules
|
|
143
161
|
|
|
144
|
-
- Treat JWT parsing, signature verification,
|
|
145
|
-
roles, and request context as
|
|
162
|
+
- Treat JWT parsing, signature verification, server-auth introspection,
|
|
163
|
+
visitor tokens, authorization roles, and request context as
|
|
164
|
+
security-sensitive.
|
|
146
165
|
- Do not log JWTs, token fragments, public/private keys, decoded payloads,
|
|
147
166
|
authorization headers, visitor headers, or sensitive request data.
|
|
148
|
-
- Keep missing headers, invalid signatures, expired tokens,
|
|
149
|
-
|
|
167
|
+
- Keep missing headers, invalid signatures, expired tokens, revoked or invalid
|
|
168
|
+
server-auth tokens, role failures, and used-token errors behaviorally
|
|
169
|
+
distinct where existing APIs do so.
|
|
150
170
|
|
|
151
171
|
## Types And API Contracts
|
|
152
172
|
|
|
153
|
-
- Model JWT header, payload,
|
|
154
|
-
context shapes explicitly in
|
|
173
|
+
- Model JWT header, payload, server-auth introspection claims, request
|
|
174
|
+
attachment, visitor attachment, role, and context shapes explicitly in
|
|
175
|
+
`index.d.ts`.
|
|
155
176
|
- Prefer `AuthenticatedRequest`, `JwtHeader`, `JwtPayload`, `JwtParts`,
|
|
156
177
|
`JwtRequestObject`, `VisitorRequestObject`, and `JwtContext` over loose
|
|
157
178
|
request objects.
|
|
@@ -171,7 +192,8 @@ role names, such as `ad` and `admin`.
|
|
|
171
192
|
- `test/types.test.ts` verifies TypeScript declarations with `tsc`.
|
|
172
193
|
- Add focused tests for valid JWTs, invalid JWTs, missing headers, role checks,
|
|
173
194
|
visitor token extraction, expiration behavior, request attachment behavior,
|
|
174
|
-
NoThrow behavior, and
|
|
195
|
+
server-auth introspection success/failure, NoThrow behavior, and
|
|
196
|
+
context-bound helpers when those areas change.
|
|
175
197
|
- Set `NODE_ENV=test` where tests or scripts require it.
|
|
176
198
|
- Keep tests deterministic and avoid real external services.
|
|
177
199
|
|
package/index.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface JwtPayload {
|
|
|
32
32
|
sub?: string;
|
|
33
33
|
/** Roles assigned to the user. */
|
|
34
34
|
roles?: string[];
|
|
35
|
+
/** Authentication mode used by app-facing services. */
|
|
36
|
+
authMode?: 'jwt' | 'server-auth' | string;
|
|
37
|
+
/** Server-auth session identifier when an opaque server-auth token was used. */
|
|
38
|
+
sessionId?: string;
|
|
35
39
|
/** Any other custom payload fields. */
|
|
36
40
|
[key: string]: any;
|
|
37
41
|
}
|
|
@@ -76,6 +80,30 @@ export interface AuthenticatedRequest extends Request {
|
|
|
76
80
|
visitor?: VisitorRequestObject | null;
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
export interface ServerAuthIntrospectionClaims {
|
|
84
|
+
valid?: boolean;
|
|
85
|
+
sub?: string;
|
|
86
|
+
userId?: string;
|
|
87
|
+
user_id?: string;
|
|
88
|
+
email?: string;
|
|
89
|
+
emailVerified?: boolean;
|
|
90
|
+
email_verified?: boolean;
|
|
91
|
+
emailConfirmed?: boolean;
|
|
92
|
+
email_confirmed?: boolean;
|
|
93
|
+
roles?: string[];
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
session_id?: string;
|
|
96
|
+
exp?: number | string;
|
|
97
|
+
expiresAt?: string;
|
|
98
|
+
expires_at?: string;
|
|
99
|
+
[key: string]: any;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type ServerAuthIntrospector = (
|
|
103
|
+
token: string,
|
|
104
|
+
req: AuthenticatedRequest,
|
|
105
|
+
) => Promise<ServerAuthIntrospectionClaims> | ServerAuthIntrospectionClaims;
|
|
106
|
+
|
|
79
107
|
/**
|
|
80
108
|
* Returns a middleware that verifies a JWT from the 'Authorization: Bearer <token>' header
|
|
81
109
|
* and extracts it into req.jwt. Throws an error if invalid.
|
|
@@ -147,6 +175,27 @@ export function jwtVerifyAndHasRole(
|
|
|
147
175
|
customErrorFunction?: () => void,
|
|
148
176
|
): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
|
|
149
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Returns middleware that accepts either an ms-auth JWT or an opaque server-auth token.
|
|
180
|
+
* Server-auth tokens are validated by the supplied introspector on every request.
|
|
181
|
+
*/
|
|
182
|
+
export function jwtVerifyOrServerAuth(
|
|
183
|
+
publicKey: string,
|
|
184
|
+
serverAuthIntrospector: ServerAuthIntrospector,
|
|
185
|
+
customErrorFunction?: () => void,
|
|
186
|
+
): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Returns middleware that accepts either an ms-auth JWT or server-auth token and
|
|
190
|
+
* then checks that the authenticated user has the required role.
|
|
191
|
+
*/
|
|
192
|
+
export function jwtVerifyOrServerAuthAndHasRole(
|
|
193
|
+
userRole: string,
|
|
194
|
+
publicKey: string,
|
|
195
|
+
serverAuthIntrospector: ServerAuthIntrospector,
|
|
196
|
+
customErrorFunction?: () => void,
|
|
197
|
+
): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
|
|
198
|
+
|
|
150
199
|
/**
|
|
151
200
|
* Gets the full name of a role from its code (e.g., 'ad' -> 'admin').
|
|
152
201
|
*/
|
|
@@ -189,6 +238,17 @@ export function jwtValidateAndExtractService(
|
|
|
189
238
|
customErrorFunction?: () => void,
|
|
190
239
|
): void;
|
|
191
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Validates the Authorization header as either an ms-auth JWT or an opaque
|
|
243
|
+
* server-auth token and extracts the result into req.jwt.
|
|
244
|
+
*/
|
|
245
|
+
export function jwtValidateAndExtractOrServerAuth(
|
|
246
|
+
req: AuthenticatedRequest,
|
|
247
|
+
publicKey: string,
|
|
248
|
+
serverAuthIntrospector: ServerAuthIntrospector,
|
|
249
|
+
customErrorFunction?: () => void,
|
|
250
|
+
): Promise<void>;
|
|
251
|
+
|
|
192
252
|
/**
|
|
193
253
|
* Validates the JWT from a custom header and extracts it into req.jwt.
|
|
194
254
|
*/
|
package/index.js
CHANGED
|
@@ -17,11 +17,14 @@ module.exports = {
|
|
|
17
17
|
jwtGetContext: jwtRoles.getContext,
|
|
18
18
|
jwtValidateAndExtract: jwtLib.validateAndExtractJwtObject,
|
|
19
19
|
jwtValidateAndExtractService: jwtLib.validateAndExtractServiceJwtObject,
|
|
20
|
+
jwtValidateAndExtractOrServerAuth: jwtLib.validateAndExtractJwtOrServerAuthObject,
|
|
20
21
|
jwtValidateAndExtractWebToken: jwtLib.validateAndExtractWebToken,
|
|
21
22
|
jwtValidateAndExtractNoThrow: jwtLib.validateAndExtractJwtObjectNoThrow,
|
|
22
23
|
jwtValidateAndExtractWebTokenNoThrow: jwtLib.validateAndExtractWebTokenObjectNoThrow,
|
|
23
24
|
jwtValidateAndExtractVisitorNoThrow: jwtLib.validateAndExtractVisitorObjectNoThrow,
|
|
24
25
|
jwtVerifyService: jwtLib.verifyServiceJwt,
|
|
26
|
+
jwtVerifyOrServerAuth: jwtLib.verifyJwtOrServerAuth,
|
|
27
|
+
jwtVerifyOrServerAuthAndHasRole: jwtLib.verifyJwtOrServerAuthAndHasRole,
|
|
25
28
|
|
|
26
29
|
/** @deprecated use jwtVerify */
|
|
27
30
|
verifyJwt: jwtLib.verifyJwt,
|
package/lib/jwtLib.js
CHANGED
|
@@ -122,6 +122,15 @@ function validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expe
|
|
|
122
122
|
return req;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
async function validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction) {
|
|
126
|
+
if (tryValidateAndExtractJwtObject(req, publicKey)) return req;
|
|
127
|
+
|
|
128
|
+
const serverAuthToken = _validateServerAuthToken(req, customErrorFunction);
|
|
129
|
+
const claims = await introspectServerAuthToken(serverAuthIntrospector, serverAuthToken, req, customErrorFunction);
|
|
130
|
+
attachServerAuthClaims(req, claims, customErrorFunction);
|
|
131
|
+
return req;
|
|
132
|
+
}
|
|
133
|
+
|
|
125
134
|
function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName) {
|
|
126
135
|
return _validateAndExtractGenericNoThrow(req, publicKey, r => _validateWebTokenNoThrow(r, headerName), _extractJwtObjectNoThrow, 'jwt');
|
|
127
136
|
}
|
|
@@ -165,6 +174,30 @@ function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErr
|
|
|
165
174
|
};
|
|
166
175
|
}
|
|
167
176
|
|
|
177
|
+
function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFunction) {
|
|
178
|
+
return async function (req, res, next) {
|
|
179
|
+
try {
|
|
180
|
+
await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction);
|
|
181
|
+
next();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
next(err);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function verifyJwtOrServerAuthAndHasRole(role, publicKey, serverAuthIntrospector, customErrorFunction) {
|
|
189
|
+
return async function (req, res, next) {
|
|
190
|
+
try {
|
|
191
|
+
await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction);
|
|
192
|
+
const isRoleExist = doesJwtUserHasRole(req, role);
|
|
193
|
+
_isLoginRequired(isRoleExist, customErrorFunction);
|
|
194
|
+
next();
|
|
195
|
+
} catch (err) {
|
|
196
|
+
next(err);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
168
201
|
function verifyWebToken(publicKey, headerName, customErrorFunction) {
|
|
169
202
|
return function (req, res, next) {
|
|
170
203
|
try {
|
|
@@ -222,6 +255,80 @@ function _isLoginRequired(hasRequiredRole, customErrorFunction) {
|
|
|
222
255
|
}
|
|
223
256
|
}
|
|
224
257
|
|
|
258
|
+
function tryValidateAndExtractJwtObject(req, publicKey) {
|
|
259
|
+
const jwtString = _validateJwtNoThrow(req);
|
|
260
|
+
if (!jwtString || !isJwtString(jwtString)) return false;
|
|
261
|
+
|
|
262
|
+
const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
|
|
263
|
+
if (!isJwtSignatureValid) {
|
|
264
|
+
if (req) req.jwt = null;
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_extractJwtObjectNoThrow(req, jwtString);
|
|
269
|
+
return Boolean(req?.jwt);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function _validateServerAuthToken(req, customErrorFunction) {
|
|
273
|
+
const authorizationHeader = req?.get?.('Authorization') || req?.get?.('authorization');
|
|
274
|
+
const token = _extractJwtNoThrow(authorizationHeader);
|
|
275
|
+
if (token) return token;
|
|
276
|
+
|
|
277
|
+
throwError(customErrorFunction);
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function introspectServerAuthToken(serverAuthIntrospector, token, req, customErrorFunction) {
|
|
282
|
+
if (typeof serverAuthIntrospector !== 'function') {
|
|
283
|
+
throwError(customErrorFunction);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const claims = await serverAuthIntrospector(token, req);
|
|
287
|
+
if (!claims || claims.valid === false) {
|
|
288
|
+
throwError(customErrorFunction);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return claims;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function attachServerAuthClaims(req, claims, customErrorFunction) {
|
|
295
|
+
const payload = createServerAuthPayload(claims);
|
|
296
|
+
if (!payload.sub) {
|
|
297
|
+
if (req) req.jwt = null;
|
|
298
|
+
throwError(customErrorFunction);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
req.jwt = {
|
|
302
|
+
header: {
|
|
303
|
+
alg: 'opaque',
|
|
304
|
+
typ: 'ServerAuth',
|
|
305
|
+
},
|
|
306
|
+
payload,
|
|
307
|
+
};
|
|
308
|
+
_attachJwtMethods(req.jwt);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function createServerAuthPayload(claims) {
|
|
312
|
+
return {
|
|
313
|
+
sub: claims.sub || claims.userId || claims.user_id,
|
|
314
|
+
email: claims.email || '',
|
|
315
|
+
email_verified: claims.emailVerified || claims.email_verified || claims.emailConfirmed || claims.email_confirmed || false,
|
|
316
|
+
roles: Array.isArray(claims.roles) ? claims.roles : [],
|
|
317
|
+
authMode: 'server-auth',
|
|
318
|
+
auth_mode: 'server-auth',
|
|
319
|
+
sessionId: claims.sessionId || claims.session_id,
|
|
320
|
+
session_id: claims.session_id || claims.sessionId,
|
|
321
|
+
exp: readEpochSeconds(claims.exp || claims.expiresAt || claims.expires_at),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function readEpochSeconds(value) {
|
|
326
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
327
|
+
if (typeof value === 'number') return normalizeSeconds(value);
|
|
328
|
+
const millis = Date.parse(value);
|
|
329
|
+
return Number.isFinite(millis) ? Math.floor(millis / 1000) : undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
225
332
|
function normalizeSeconds(value) {
|
|
226
333
|
if (!Number.isFinite(value)) return null;
|
|
227
334
|
return value > 1000000000000 ? Math.floor(value / 1000) : Math.floor(value);
|
|
@@ -434,6 +541,7 @@ module.exports = {
|
|
|
434
541
|
_extractJwtObject,
|
|
435
542
|
validateAndExtractJwtObject,
|
|
436
543
|
validateAndExtractServiceJwtObject,
|
|
544
|
+
validateAndExtractJwtOrServerAuthObject,
|
|
437
545
|
validateAndExtractWebToken,
|
|
438
546
|
jwtAgeInSeconds,
|
|
439
547
|
isJwtExpired,
|
|
@@ -442,6 +550,8 @@ module.exports = {
|
|
|
442
550
|
visitorClientId,
|
|
443
551
|
verifyJwtAndRole,
|
|
444
552
|
verifyServiceJwt,
|
|
553
|
+
verifyJwtOrServerAuth,
|
|
554
|
+
verifyJwtOrServerAuthAndHasRole,
|
|
445
555
|
verifyJwt,
|
|
446
556
|
verifyWebTokenNoThrow,
|
|
447
557
|
verifyWebToken,
|
|
@@ -461,6 +571,9 @@ module.exports = {
|
|
|
461
571
|
_extractVisitorObjectNoThrow,
|
|
462
572
|
_extractWebToken,
|
|
463
573
|
_isLoginRequired,
|
|
574
|
+
tryValidateAndExtractJwtObject,
|
|
575
|
+
attachServerAuthClaims,
|
|
576
|
+
createServerAuthPayload,
|
|
464
577
|
_isServiceJwtFor,
|
|
465
578
|
_attachJwtMethods,
|
|
466
579
|
_attachVisitorMethods,
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
Utility package for reading, parsing, and verifying JWTs in the CareCard
|
|
6
|
+
Utility package for reading, parsing, and verifying JWTs in the CareCard
|
|
7
|
+
ecosystem. It also provides the shared request middleware used by `ms-*`
|
|
8
|
+
services to accept either an `ms-auth` JWT or an opaque server-auth token
|
|
9
|
+
introspected by `ms-auth`.
|
|
7
10
|
|
|
8
11
|
## Features
|
|
9
12
|
|
|
@@ -13,6 +16,8 @@ Utility package for reading, parsing, and verifying JWTs in the CareCard ecosyst
|
|
|
13
16
|
- **Claims Extraction**: Easy extraction of `sub` (clientId) and other JWT payload claims.
|
|
14
17
|
- **Expiration Management**: Helpers to check if a JWT is expired and calculate its remaining TTL.
|
|
15
18
|
- **Service JWTs**: Helpers for verifying and extracting microservice-to-microservice JWTs with standard `iss`, `sub`, `aud`, `iat`, and `exp` claims.
|
|
19
|
+
- **JWT or Server Auth**: Middleware helpers that verify normal JWTs locally and
|
|
20
|
+
call a service-provided introspector for opaque server-auth tokens.
|
|
16
21
|
|
|
17
22
|
## Installation
|
|
18
23
|
|
|
@@ -60,6 +65,19 @@ console.log(getNameOfRole('ad')); // Result: 'admin'
|
|
|
60
65
|
console.log(getCodeOfRole('super_admin')); // Result: 'su'
|
|
61
66
|
```
|
|
62
67
|
|
|
68
|
+
### Auth RLS Role Semantics
|
|
69
|
+
|
|
70
|
+
`ms-auth` treats a JWT or server-auth payload containing `roles: ["ad"]` as the
|
|
71
|
+
auth-service super-admin signal for its RLS policies. Consumers may map `ad` to
|
|
72
|
+
UI/domain names such as `super_admin`, but middleware should preserve the
|
|
73
|
+
original roles array on the request context so services can make
|
|
74
|
+
database-context decisions consistently.
|
|
75
|
+
|
|
76
|
+
Docs that mention `ms-auth` controller internals should use concise action
|
|
77
|
+
names such as `loginUser`, `registerUser`, `getUserDetail`, and `renewJwt`.
|
|
78
|
+
Access level is conveyed by route middleware and endpoint placement, not by
|
|
79
|
+
`public`/`protected`/`admin`/`Handler` suffixes.
|
|
80
|
+
|
|
63
81
|
### Service-To-Service JWTs
|
|
64
82
|
|
|
65
83
|
Use service JWT verification helpers for backend service calls. The sending
|
|
@@ -88,6 +106,33 @@ Service JWT payloads follow standard JWT semantics:
|
|
|
88
106
|
- `iat`: issued-at NumericDate
|
|
89
107
|
- `exp`: expiration NumericDate
|
|
90
108
|
|
|
109
|
+
### JWT Or Server-Auth Middleware
|
|
110
|
+
|
|
111
|
+
Use the `OrServerAuth` helpers on app-facing `ms-*` routes that should accept
|
|
112
|
+
both current authentication modes. The JWT path verifies locally with the
|
|
113
|
+
`ms-auth` public key. The server-auth path calls the provided introspector,
|
|
114
|
+
which should send the opaque token to
|
|
115
|
+
`POST /api/v1/ms-auth/server-auth/introspect` with the receiving service's
|
|
116
|
+
service JWT.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const { jwtGetRoleCode, jwtVerifyOrServerAuth, jwtVerifyOrServerAuthAndHasRole } = require('@carecard/jwt-read');
|
|
120
|
+
|
|
121
|
+
const verifyUser = jwtVerifyOrServerAuth(msAuthPublicKey, token => introspectServerAuthTokenWithMsAuth(token), throwNotAuthorizedError);
|
|
122
|
+
|
|
123
|
+
const verifyAdmin = jwtVerifyOrServerAuthAndHasRole(
|
|
124
|
+
jwtGetRoleCode('admin'),
|
|
125
|
+
msAuthPublicKey,
|
|
126
|
+
token => introspectServerAuthTokenWithMsAuth(token),
|
|
127
|
+
throwNotAuthorizedError,
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The introspector must return claims for valid tokens. This package normalizes
|
|
132
|
+
those claims onto `req.jwt.payload` with `authMode: "server-auth"` and
|
|
133
|
+
`auth_mode: "server-auth"` so services can keep their existing JWT-backed
|
|
134
|
+
database context and role checks.
|
|
135
|
+
|
|
91
136
|
## Testing
|
|
92
137
|
|
|
93
138
|
Run tests using:
|