-
Notifications
You must be signed in to change notification settings - Fork 512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Credentials Management #888
Changes from 11 commits
409af50
51c094f
74ae1c3
1989d51
aadf828
cdfb8a9
102655c
b7b6445
5ed5f73
9fd898f
6a83b2e
b5abf17
2ee351e
dcb2d4c
f14b125
f47fad3
29c760f
ef8e981
2440bbf
004d6b9
7380cce
c848df8
23f6962
c34820f
150f199
4094ee4
4a0e321
676ae7b
3a7afa6
f7491f3
7712177
5025c19
6f33ac4
bc32e0d
b262bc7
6c76476
5f38544
ab3807e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
/*! | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* Copyright 2019-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import * as AWS from 'aws-sdk' | ||
import { CredentialsProvider } from './providers/credentialsProvider' | ||
import { asString, CredentialsProviderId } from './providers/credentialsProviderId' | ||
|
||
interface CredentialsData { | ||
export interface CachedCredentials { | ||
credentials: AWS.Credentials | ||
credentialsHashCode: number | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a hash of the contents that were used to produce the credentials. |
||
} | ||
|
@@ -14,7 +16,7 @@ interface CredentialsData { | |
* Simple cache for credentials | ||
*/ | ||
export class CredentialsStore { | ||
private readonly credentialsCache: { [key: string]: CredentialsData } | ||
private readonly credentialsCache: { [key: string]: CachedCredentials } | ||
|
||
public constructor() { | ||
this.credentialsCache = {} | ||
|
@@ -23,41 +25,36 @@ export class CredentialsStore { | |
/** | ||
* Returns undefined if credentials are not stored for given ID | ||
*/ | ||
public async getCredentials(credentialsId: string): Promise<AWS.Credentials | undefined> { | ||
return this.credentialsCache[credentialsId]?.credentials | ||
public async getCredentials(credentialsProviderId: CredentialsProviderId): Promise<CachedCredentials | undefined> { | ||
return this.credentialsCache[asString(credentialsProviderId)] | ||
} | ||
|
||
/** | ||
* If credentials are not stored, the provided create function is called. Created credentials are then stored. | ||
* If credentials are not stored, the credentialsProvider is used to produce credentials (which are then stored). | ||
*/ | ||
public async getCredentialsOrCreate( | ||
credentialsId: string, | ||
createCredentialsFn: (credentialsId: string) => Promise<CredentialsData> | ||
): Promise<AWS.Credentials> { | ||
const credentials = await this.getCredentials(credentialsId) | ||
|
||
if (credentials) { | ||
return credentials | ||
public async getOrCreateCredentials( | ||
credentialsProviderId: CredentialsProviderId, | ||
credentialsProvider: CredentialsProvider | ||
): Promise<CachedCredentials> { | ||
let credentials = await this.getCredentials(credentialsProviderId) | ||
|
||
if (!credentials) { | ||
credentials = { | ||
credentials: await credentialsProvider.getCredentials(), | ||
credentialsHashCode: credentialsProvider.getHashCode() | ||
} | ||
|
||
this.credentialsCache[asString(credentialsProviderId)] = credentials | ||
} | ||
|
||
const newCredentials = await createCredentialsFn(credentialsId) | ||
this.credentialsCache[credentialsId] = newCredentials | ||
|
||
return newCredentials.credentials | ||
} | ||
|
||
/** | ||
* Returns undefined if credentials are not stored for given ID | ||
*/ | ||
public getCredentialsHashCode(credentialsId: string): number | undefined { | ||
return this.credentialsCache[credentialsId]?.credentialsHashCode | ||
return credentials | ||
} | ||
|
||
/** | ||
* Evicts credentials from storage | ||
*/ | ||
public invalidateCredentials(credentialsId: string) { | ||
public invalidateCredentials(credentialsProviderId: CredentialsProviderId) { | ||
// tslint:disable-next-line:no-dynamic-delete | ||
delete this.credentialsCache[credentialsId] | ||
delete this.credentialsCache[asString(credentialsProviderId)] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,28 +3,32 @@ | |
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
export const CREDENTIALS_PROVIDER_ID_SEPARATOR = ':' | ||
const CREDENTIALS_PROVIDER_ID_SEPARATOR = ':' | ||
|
||
export interface CredentialsProviderIdComponents { | ||
credentialType: string | ||
credentialTypeId: string | ||
export interface CredentialsProviderId { | ||
readonly credentialType: string | ||
readonly credentialTypeId: string | ||
} | ||
|
||
export function makeCredentialsProviderIdComponents(credentialsProviderId: string): CredentialsProviderIdComponents { | ||
const chunks = credentialsProviderId.split(CREDENTIALS_PROVIDER_ID_SEPARATOR) | ||
export function asString(credentialsProviderId: CredentialsProviderId): string { | ||
return [credentialsProviderId.credentialType, credentialsProviderId.credentialTypeId].join( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just use a template literal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personal preference on this one, I find this more legible than |
||
CREDENTIALS_PROVIDER_ID_SEPARATOR | ||
) | ||
} | ||
|
||
if (chunks.length !== 2) { | ||
export function fromString(credentialsProviderId: string): CredentialsProviderId { | ||
const separatorPos = credentialsProviderId.indexOf(CREDENTIALS_PROVIDER_ID_SEPARATOR) | ||
|
||
if (separatorPos === -1) { | ||
throw new Error(`Unexpected credentialsProviderId format: ${credentialsProviderId}`) | ||
} | ||
|
||
return { | ||
credentialType: chunks[0], | ||
credentialTypeId: chunks[1] | ||
credentialType: credentialsProviderId.substring(0, separatorPos), | ||
credentialTypeId: credentialsProviderId.substring(separatorPos + 1) | ||
} | ||
} | ||
|
||
export function makeCredentialsProviderId(credentialsProviderIdComponents: CredentialsProviderIdComponents): string { | ||
return [credentialsProviderIdComponents.credentialType, credentialsProviderIdComponents.credentialTypeId].join( | ||
CREDENTIALS_PROVIDER_ID_SEPARATOR | ||
) | ||
export function isEqual(idA: CredentialsProviderId, idB: CredentialsProviderId): boolean { | ||
return idA.credentialType === idB.credentialType && idA.credentialTypeId === idB.credentialTypeId | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that this will make it more seamless for a user but I don't care to have this lying around if it's solely for that purpose. I'd say just invalidate the existing credentials and have the user log back in...it's fast enough that it shouldn't be an issue. At most, give them a one-version grace period and mark this for deletion in the next release afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed after a few versions. It makes for a better experience than seeing an "invalid credentials" notification, or a phantom log-out. It's low cost, the comment explains what is going on, and git history will tell us (if necessary) when this was added.