-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
httpAuthSchemeMiddleware
to select an auth scheme (#929)
- Loading branch information
Steven Yuan
authored
Sep 20, 2023
1 parent
c346d59
commit 76e2ef3
Showing
15 changed files
with
554 additions
and
52 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,5 @@ | ||
--- | ||
"@smithy/experimental-identity-and-auth": patch | ||
--- | ||
|
||
Allow `DefaultIdentityProviderConfig` to accept `undefined` in the constructor |
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,5 @@ | ||
--- | ||
"@smithy/experimental-identity-and-auth": patch | ||
--- | ||
|
||
Add `httpAuthSchemeMiddleware` to select an auth scheme |
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,5 @@ | ||
--- | ||
"@smithy/experimental-identity-and-auth": patch | ||
--- | ||
|
||
Add `memoizeIdentityProvider()` |
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
87 changes: 87 additions & 0 deletions
87
packages/experimental-identity-and-auth/src/memoizeIdentityProvider.ts
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,87 @@ | ||
import { Identity, IdentityProvider } from "@smithy/types"; | ||
|
||
/** | ||
* @internal | ||
* This may need to be configurable in the future, but for now it is defaulted to 5min. | ||
*/ | ||
export const EXPIRATION_MS = 300_000; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const isIdentityExpired = (identity: Identity) => | ||
doesIdentityRequireRefresh(identity) && identity.expiration!.getTime() - Date.now() < EXPIRATION_MS; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const doesIdentityRequireRefresh = (identity: Identity) => identity.expiration !== undefined; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export interface MemoizedIdentityProvider<IdentityT extends Identity> { | ||
(options?: Record<string, any> & { forceRefresh?: boolean }): Promise<IdentityT>; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const memoizeIdentityProvider = <IdentityT extends Identity>( | ||
provider: IdentityT | IdentityProvider<IdentityT> | undefined, | ||
isExpired: (resolved: Identity) => boolean, | ||
requiresRefresh: (resolved: Identity) => boolean | ||
): MemoizedIdentityProvider<IdentityT> | undefined => { | ||
if (provider === undefined) { | ||
return undefined; | ||
} | ||
const normalizedProvider: IdentityProvider<IdentityT> = | ||
typeof provider !== "function" ? async () => Promise.resolve(provider) : provider; | ||
let resolved: IdentityT; | ||
let pending: Promise<IdentityT> | undefined; | ||
let hasResult: boolean; | ||
let isConstant = false; | ||
// Wrapper over supplied provider with side effect to handle concurrent invocation. | ||
const coalesceProvider: MemoizedIdentityProvider<IdentityT> = async (options) => { | ||
if (!pending) { | ||
pending = normalizedProvider(options); | ||
} | ||
try { | ||
resolved = await pending; | ||
hasResult = true; | ||
isConstant = false; | ||
} finally { | ||
pending = undefined; | ||
} | ||
return resolved; | ||
}; | ||
|
||
if (isExpired === undefined) { | ||
// This is a static memoization; no need to incorporate refreshing unless using forceRefresh; | ||
return async (options) => { | ||
if (!hasResult || options?.forceRefresh) { | ||
resolved = await coalesceProvider(options); | ||
} | ||
return resolved; | ||
}; | ||
} | ||
|
||
return async (options) => { | ||
if (!hasResult || options?.forceRefresh) { | ||
resolved = await coalesceProvider(options); | ||
} | ||
if (isConstant) { | ||
return resolved; | ||
} | ||
|
||
if (!requiresRefresh(resolved)) { | ||
isConstant = true; | ||
return resolved; | ||
} | ||
if (isExpired(resolved)) { | ||
await coalesceProvider(options); | ||
return resolved; | ||
} | ||
return resolved; | ||
}; | ||
}; |
30 changes: 30 additions & 0 deletions
30
...experimental-identity-and-auth/src/middleware-http-auth-scheme/getHttpAuthSchemePlugin.ts
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,30 @@ | ||
import { endpointMiddlewareOptions } from "@smithy/middleware-endpoint"; | ||
import { MetadataBearer, Pluggable, RelativeMiddlewareOptions, SerializeHandlerOptions } from "@smithy/types"; | ||
|
||
import { httpAuthSchemeMiddleware, PreviouslyResolved } from "./httpAuthSchemeMiddleware"; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const httpAuthSchemeMiddlewareOptions: SerializeHandlerOptions & RelativeMiddlewareOptions = { | ||
step: "serialize", | ||
tags: ["HTTP_AUTH_SCHEME"], | ||
name: "httpAuthSchemeMiddleware", | ||
override: true, | ||
relation: "before", | ||
toMiddleware: endpointMiddlewareOptions.name!, | ||
}; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const getHttpAuthSchemePlugin = < | ||
Input extends Record<string, unknown> = Record<string, unknown>, | ||
Output extends MetadataBearer = MetadataBearer | ||
>( | ||
config: PreviouslyResolved | ||
): Pluggable<Input, Output> => ({ | ||
applyToStack: (clientStack) => { | ||
clientStack.addRelativeTo(httpAuthSchemeMiddleware(config), httpAuthSchemeMiddlewareOptions); | ||
}, | ||
}); |
95 changes: 95 additions & 0 deletions
95
...xperimental-identity-and-auth/src/middleware-http-auth-scheme/httpAuthSchemeMiddleware.ts
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,95 @@ | ||
import { | ||
HandlerExecutionContext, | ||
MetadataBearer, | ||
SerializeHandler, | ||
SerializeHandlerArguments, | ||
SerializeHandlerOutput, | ||
SerializeMiddleware, | ||
SMITHY_CONTEXT_KEY, | ||
} from "@smithy/types"; | ||
import { getSmithyContext } from "@smithy/util-middleware"; | ||
|
||
import { HttpAuthScheme, HttpAuthSchemeId, SelectedHttpAuthScheme } from "../HttpAuthScheme"; | ||
import { HttpAuthSchemeParametersProvider, HttpAuthSchemeProvider } from "../HttpAuthSchemeProvider"; | ||
import { IdentityProviderConfig } from "../IdentityProviderConfig"; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export interface PreviouslyResolved { | ||
httpAuthSchemes: HttpAuthScheme[]; | ||
httpAuthSchemeProvider: HttpAuthSchemeProvider; | ||
httpAuthSchemeParametersProvider: HttpAuthSchemeParametersProvider; | ||
identityProviderConfig: IdentityProviderConfig; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
interface HttpAuthSchemeMiddlewareSmithyContext extends Record<string, unknown> { | ||
selectedHttpAuthScheme?: SelectedHttpAuthScheme; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
interface HttpAuthSchemeMiddlewareHandlerExecutionContext extends HandlerExecutionContext { | ||
[SMITHY_CONTEXT_KEY]?: HttpAuthSchemeMiddlewareSmithyContext; | ||
} | ||
|
||
/** | ||
* @internal | ||
* Later HttpAuthSchemes with the same HttpAuthSchemeId will overwrite previous ones. | ||
*/ | ||
function convertHttpAuthSchemesToMap(httpAuthSchemes: HttpAuthScheme[]): Map<HttpAuthSchemeId, HttpAuthScheme> { | ||
const map = new Map(); | ||
for (const scheme of httpAuthSchemes) { | ||
map.set(scheme.schemeId, scheme); | ||
} | ||
return map; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const httpAuthSchemeMiddleware = < | ||
Input extends Record<string, unknown> = Record<string, unknown>, | ||
Output extends MetadataBearer = MetadataBearer | ||
>( | ||
config: PreviouslyResolved | ||
): SerializeMiddleware<Input, Output> => ( | ||
next: SerializeHandler<Input, Output>, | ||
context: HttpAuthSchemeMiddlewareHandlerExecutionContext | ||
): SerializeHandler<Input, Output> => async ( | ||
args: SerializeHandlerArguments<Input> | ||
): Promise<SerializeHandlerOutput<Output>> => { | ||
const options = config.httpAuthSchemeProvider( | ||
await config.httpAuthSchemeParametersProvider(config, context, args.input) | ||
); | ||
const authSchemes = convertHttpAuthSchemesToMap(config.httpAuthSchemes); | ||
const smithyContext: HttpAuthSchemeMiddlewareSmithyContext = getSmithyContext(context); | ||
const failureReasons = []; | ||
for (const option of options) { | ||
const scheme = authSchemes.get(option.schemeId); | ||
if (!scheme) { | ||
failureReasons.push(`HttpAuthScheme \`${option.schemeId}\` was not enable for this service.`); | ||
continue; | ||
} | ||
const identityProvider = scheme.identityProvider(config.identityProviderConfig); | ||
if (!identityProvider) { | ||
failureReasons.push(`HttpAuthScheme \`${option.schemeId}\` did not have an IdentityProvider configured.`); | ||
continue; | ||
} | ||
const identity = await identityProvider(option.identityProperties || {}); | ||
smithyContext.selectedHttpAuthScheme = { | ||
httpAuthOption: option, | ||
identity, | ||
signer: scheme.signer, | ||
}; | ||
break; | ||
} | ||
if (!smithyContext.selectedHttpAuthScheme) { | ||
throw new Error(failureReasons.join("\n")); | ||
} | ||
return next(args); | ||
}; |
2 changes: 2 additions & 0 deletions
2
packages/experimental-identity-and-auth/src/middleware-http-auth-scheme/index.ts
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,2 @@ | ||
export * from "./httpAuthSchemeMiddleware"; | ||
export * from "./getHttpAuthSchemePlugin"; |
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
Oops, something went wrong.