-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
218 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* TokenContext represents the data associated with an issued token. | ||
* NOTE: In a production environment, this should also include session binding | ||
* to prevent token reuse across different user sessions. | ||
*/ | ||
export interface TokenContext { | ||
id_token_hash: string | ||
scope: string | ||
patient?: string | ||
user: string | ||
client_id: string | ||
context: Record<string, any> | ||
exp: number | ||
} | ||
|
||
/** | ||
* A simple in-memory cache for token contexts. | ||
* | ||
* SECURITY NOTE: This is a reference implementation. | ||
* In a production environment, you should: | ||
* 1. Bind tokens to user sessions to prevent unauthorized reuse | ||
* 2. Use a persistent storage mechanism | ||
*/ | ||
export class TokenCache { | ||
private cache: Map<string, TokenContext> = new Map(); | ||
private readonly maxSize: number = 1000; | ||
|
||
public set(context: TokenContext): void { | ||
// If we're at capacity, remove oldest entries | ||
while (this.cache.size >= this.maxSize) { | ||
const firstKey = this.cache.keys().next().value; | ||
this.cache.delete(firstKey); | ||
} | ||
|
||
this.cache.set(context.id_token_hash, context); | ||
} | ||
|
||
public get(idTokenHash: string): TokenContext | undefined { | ||
const context = this.cache.get(idTokenHash); | ||
if (context && | ||
Date.now() < context.exp * 1000) { | ||
return context; | ||
} | ||
// Clean up expired entries | ||
this.cache.delete(idTokenHash); | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Request, Response } from "express" | ||
import LaunchOptions from "../../../src/isomorphic/LaunchOptions" | ||
import { InvalidRequestError } from "../../errors" | ||
|
||
function formatClientName(clientId: string): string { | ||
return clientId | ||
.split(/[-_.]/) | ||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) | ||
.join(' ') | ||
} | ||
|
||
export default function getClientRegistration(req: Request, res: Response) { | ||
try { | ||
const launchOptions = new LaunchOptions(req.params.sim || "") | ||
const requestedClientId = req.params.clientId | ||
|
||
// Validate client_id matches if specified in launch options | ||
if (launchOptions.client_id && launchOptions.client_id !== requestedClientId) { | ||
return res.status(404).json({ | ||
error: "not_found", | ||
error_description: "Client not found" | ||
}) | ||
} | ||
|
||
// Build response following OAuth 2.0 Dynamic Client Registration spec | ||
const clientMetadata: Record<string, any> = { | ||
client_id: requestedClientId, | ||
client_id_issued_at: Math.floor(Date.now() / 1000), | ||
|
||
// Client identity | ||
client_name: formatClientName(requestedClientId), | ||
client_uri: `https://example.com/apps/${requestedClientId}`, | ||
logo_uri: `https://via.placeholder.com/150?text=${encodeURIComponent(formatClientName(requestedClientId))}`, | ||
tos_uri: `https://example.com/apps/${requestedClientId}/tos`, | ||
policy_uri: `https://example.com/apps/${requestedClientId}/privacy`, | ||
contacts: [`support@${requestedClientId}.example.com`], | ||
|
||
// OAuth capabilities | ||
grant_types: ["authorization_code", "refresh_token"], | ||
response_types: ["code"], | ||
token_endpoint_auth_method: "none", // default for public clients | ||
scope: launchOptions.scope || "launch/patient offline_access openid fhirUser" | ||
} | ||
|
||
// Add redirect URIs if specified | ||
if (launchOptions.redirect_uris) { | ||
clientMetadata.redirect_uris = launchOptions.redirect_uris.split(/\s*,\s*/) | ||
} | ||
|
||
// Add asymmetric auth properties | ||
if (launchOptions.client_type === "confidential-asymmetric") { | ||
clientMetadata.token_endpoint_auth_method = "private_key_jwt" | ||
if (launchOptions.jwks_url) { | ||
clientMetadata.jwks_uri = launchOptions.jwks_url | ||
} | ||
if (launchOptions.jwks) { | ||
clientMetadata.jwks = JSON.parse(launchOptions.jwks) | ||
} | ||
} | ||
|
||
// Add backend service properties | ||
if (launchOptions.client_type === "backend-service") { | ||
clientMetadata.grant_types = ["client_credentials"] | ||
clientMetadata.token_endpoint_auth_method = "private_key_jwt" | ||
} | ||
|
||
// Pretty print the response | ||
res.setHeader('Content-Type', 'application/json') | ||
res.send(JSON.stringify(clientMetadata, null, 2)) | ||
|
||
} catch (error) { | ||
throw new InvalidRequestError("Invalid launch options: " + error) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.