fhirstarterjs 1.0.6 → 1.0.7
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/README.md +14 -0
- package/dist/fhirstarter.js +16 -1
- package/package.json +1 -1
- package/ts/fhirstarter.ts +21 -1
package/README.md
CHANGED
|
@@ -81,9 +81,23 @@ const res = await fetch(url, {
|
|
|
81
81
|
| `tokenResponse()` | `LiveTokenResponse` | Getter-backed token response for `fhirclient` |
|
|
82
82
|
| `onRefresh(callback)` | `() => void` | Subscribe to token updates — returns unsubscribe |
|
|
83
83
|
| `getJwks()` | `Promise<JwkSet>` | Public JWKS derived from the private key |
|
|
84
|
+
| `fhirStarter.thumbprint(privateKey)` | `string` | RFC 7638 JWK Thumbprint (base64url SHA-256) |
|
|
84
85
|
|
|
85
86
|
`getJwks()` strips private key material — host the output JSON at your registered JWKS URL and pass that URL as `jwksUrl` so the JWT `jku` header is set automatically.
|
|
86
87
|
|
|
88
|
+
## Thumbprint
|
|
89
|
+
|
|
90
|
+
Derive a deterministic `kid` from a private key without instantiating the class:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import fhirStarter from "fhirstarterjs"
|
|
94
|
+
|
|
95
|
+
const kid = fhirStarter.thumbprint("./privatekey.pem")
|
|
96
|
+
console.log(kid) // base64url SHA-256 of the canonical RSA public JWK
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This implements RFC 7638 — the SHA-256 of the sorted canonical JWK members `{e, kty, n}`, base64url-encoded. Use it as the `keyId` when registering your JWKS.
|
|
100
|
+
|
|
87
101
|
## JWKS
|
|
88
102
|
|
|
89
103
|
Some SMART Backend Services registrations require a public JWKS URL when using `jku`. Generate it from the same private key you use for auth:
|
package/dist/fhirstarter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { importPKCS8, exportJWK, SignJWT, } from "jose";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createHash, createPrivateKey, createPublicKey, randomUUID } from "node:crypto";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
/**
|
|
5
5
|
* SMART Backend Services auth provider.
|
|
@@ -298,4 +298,19 @@ export default class fhirStarter {
|
|
|
298
298
|
throw new Error(`AuthConfig: privateKey must be PEM text, a Buffer, or a readable file path: ${trimmed}`);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Derives a deterministic key identifier from a private key using an
|
|
303
|
+
* RFC 7638 JWK Thumbprint (SHA-256 of canonical RSA public JWK members, base64url).
|
|
304
|
+
*
|
|
305
|
+
* @param privateKey - RSA PKCS#8 PEM text, a Buffer, or a readable file path.
|
|
306
|
+
* @returns A base64url-encoded SHA-256 thumbprint string.
|
|
307
|
+
* @throws If the key is not RSA or cannot be parsed.
|
|
308
|
+
*/
|
|
309
|
+
static thumbprint(privateKey) {
|
|
310
|
+
const pem = fhirStarter.resolvePrivateKey(privateKey), key = createPrivateKey(pem);
|
|
311
|
+
if (key.asymmetricKeyType !== "rsa")
|
|
312
|
+
throw new Error(`thumbprint: expected RSA key, got ${key.asymmetricKeyType}`);
|
|
313
|
+
const pub = createPublicKey(key).export({ format: "jwk" }), canonical = JSON.stringify({ e: pub.e, kty: "RSA", n: pub.n });
|
|
314
|
+
return createHash("sha256").update(canonical).digest("base64url");
|
|
315
|
+
}
|
|
301
316
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhirstarterjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "SMART Backend Services auth lifecycle for any FHIR client",
|
|
5
5
|
"author": "Joshua Faulkenberry <j@joshuafaulkenberry.com> (https://joshuafaulkenberry.com/)",
|
|
6
6
|
"license": "Kopimi",
|
package/ts/fhirstarter.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
SignJWT,
|
|
5
5
|
type JWTHeaderParameters,
|
|
6
6
|
} from "jose";
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, createPrivateKey, createPublicKey, randomUUID } from "node:crypto";
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -348,4 +348,24 @@ export default class fhirStarter implements Provider {
|
|
|
348
348
|
);
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Derives a deterministic key identifier from a private key using an
|
|
354
|
+
* RFC 7638 JWK Thumbprint (SHA-256 of canonical RSA public JWK members, base64url).
|
|
355
|
+
*
|
|
356
|
+
* @param privateKey - RSA PKCS#8 PEM text, a Buffer, or a readable file path.
|
|
357
|
+
* @returns A base64url-encoded SHA-256 thumbprint string.
|
|
358
|
+
* @throws If the key is not RSA or cannot be parsed.
|
|
359
|
+
*/
|
|
360
|
+
static thumbprint(privateKey: string | Buffer): string {
|
|
361
|
+
const
|
|
362
|
+
pem = fhirStarter.resolvePrivateKey(privateKey),
|
|
363
|
+
key = createPrivateKey(pem)
|
|
364
|
+
if (key.asymmetricKeyType !== "rsa")
|
|
365
|
+
throw new Error(`thumbprint: expected RSA key, got ${key.asymmetricKeyType}`)
|
|
366
|
+
const
|
|
367
|
+
pub = createPublicKey(key).export({ format: "jwk" }) as { e?: string, n?: string, kty?: string },
|
|
368
|
+
canonical = JSON.stringify({ e: pub.e, kty: "RSA", n: pub.n })
|
|
369
|
+
return createHash("sha256").update(canonical).digest("base64url")
|
|
370
|
+
}
|
|
351
371
|
}
|