Skip to content
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

chore(auth): general clean up for credentialsProvider and also rename Credentials from sdk #12021

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { formLoginsMap } from './credentialsProvider';
import { AuthError } from '../../../errors/AuthError';
import { IdentityIdStore } from './types';
import { getRegionFromIdentityPoolId } from '../utils/clients/CognitoIdentityProvider/utils';
import { Identity } from '@aws-amplify/core';

const logger = new Logger('CognitoIdentityIdProvider');

Expand All @@ -18,9 +19,9 @@ const logger = new Logger('CognitoIdentityIdProvider');
*
* @param tokens - The AuthTokens received after SignIn
* @returns string
* @throws internal: {@link AuthError }
* @throws configuration excpetions: {@link InvalidIdentityPoolIdException }
* - Auth errors that may arise from misconfiguration.
*
* @throws service excpetions: {@link GetIdException }
*/
export async function cognitoIdentityIdProvider({
tokens,
Expand All @@ -32,10 +33,13 @@ export async function cognitoIdentityIdProvider({
identityIdStore: IdentityIdStore;
}): Promise<string> {
identityIdStore.setAuthConfig({ Cognito: authConfig });
let identityId = await identityIdStore.loadIdentityId();

// will return null only if there is no identityId cached or if there is an error retrieving it
let identityId: Identity | null = await identityIdStore.loadIdentityId();

// Tokens are available so return primary identityId
if (tokens) {
// Tokens are available so return primary identityId
// If there is existing primary identityId in-memory return that
if (identityId && identityId.type === 'primary') {
return identityId.id;
} else {
Expand All @@ -46,10 +50,8 @@ export async function cognitoIdentityIdProvider({
const generatedIdentityId = await generateIdentityId(logins, authConfig);

if (identityId && identityId.id === generatedIdentityId) {
// if guestIdentity is found and used by GetCredentialsForIdentity
// it will be linked to the logins provided, and disqualified as an unauth identity
logger.debug(
`The guest identity ${identityId.id} has become the primary identity`
`The guest identity ${identityId.id} has become the primary identity.`
);
}
identityId = {
Expand All @@ -58,7 +60,7 @@ export async function cognitoIdentityIdProvider({
};
}
} else {
// Tokens are avaliable so return guest identityId
// If there is existing guest identityId cached return that
if (identityId && identityId.type === 'guest') {
return identityId.id;
} else {
Expand All @@ -69,9 +71,8 @@ export async function cognitoIdentityIdProvider({
}
}

// Store in-memory or local storage
// Store in-memory or local storage depending on guest or primary identityId
identityIdStore.storeIdentityId(identityId);
logger.debug(`The identity being returned ${identityId.id}`);
return identityId.id;
}

Expand All @@ -80,7 +81,6 @@ async function generateIdentityId(
authConfig: CognitoIdentityPoolConfig
): Promise<string> {
const identityPoolId = authConfig?.identityPoolId;

const region = getRegionFromIdentityPoolId(identityPoolId);

// IdentityId is absent so get it using IdentityPoolId with Cognito's GetId API
Expand All @@ -100,8 +100,8 @@ async function generateIdentityId(
).IdentityId;
if (!idResult) {
throw new AuthError({
name: 'IdentityIdResponseException',
message: 'Did not receive an identityId from Cognito identity pool',
name: 'GetIdResponseException',
message: 'Received undefined response from getId operation',
recoverySuggestion:
'Make sure to pass a valid identityPoolId in the configuration.',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from '@aws-amplify/core';
import { assertIdentityPooIdConfig } from '@aws-amplify/core/internals/utils';
import { IdentityIdStorageKeys, IdentityIdStore } from './types';
import { AuthError } from '../../../errors/AuthError';
import { getAuthStorageKeys } from '../tokenProvider/TokenStore';
import { AuthKeys } from '../tokenProvider/types';

Expand All @@ -33,18 +32,9 @@ export class DefaultIdentityIdStore implements IdentityIdStore {
this.keyValueStorage = keyValueStorage;
}

async loadIdentityId(): Promise<Identity | undefined> {
async loadIdentityId(): Promise<Identity | null> {
assertIdentityPooIdConfig(this.authConfig?.Cognito);
if (this.keyValueStorage === undefined) {
throw new AuthError({
message: 'No KeyValueStorage available',
name: 'KeyValueStorageNotFound',
recoverySuggestion:
'Make sure to set the keyValueStorage before using this method',
});
}
// TODO(v6): migration logic should be here
// Reading V5 tokens old format
try {
if (!!this._primaryIdentityId) {
return {
Expand All @@ -61,30 +51,16 @@ export class DefaultIdentityIdStore implements IdentityIdStore {
type: 'guest',
};
}
return null;
}
} catch (err) {
// TODO(v6): validate partial results with mobile implementation
throw new Error(`Error loading identityId from storage: ${err}`);
return null;
}
}

async storeIdentityId(identity: Identity): Promise<void> {
assertIdentityPooIdConfig(this.authConfig?.Cognito);
if (identity === undefined) {
throw new AuthError({
message: 'Invalid Identity parameter',
name: 'InvalidAuthIdentity',
recoverySuggestion: 'Make sure a valid Identity object is passed',
});
}
if (this.keyValueStorage === undefined) {
throw new AuthError({
message: 'No KeyValueStorage available',
name: 'KeyValueStorageNotFound',
recoverySuggestion:
'Make sure to set the keyValueStorage before using this method',
});
}

if (identity.type === 'guest') {
this.keyValueStorage.setItem(this._authKeys.identityId, identity.id);
Expand All @@ -99,9 +75,7 @@ export class DefaultIdentityIdStore implements IdentityIdStore {

async clearIdentityId(): Promise<void> {
this._primaryIdentityId = undefined;
await Promise.all([
this.keyValueStorage.removeItem(this._authKeys.identityId),
]);
await this.keyValueStorage.removeItem(this._authKeys.identityId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { assertIdTokenInAuthTokens } from '../utils/types';

const logger = new Logger('CognitoCredentialsProvider');
const CREDENTIALS_TTL = 50 * 60 * 1000; // 50 min, can be modified on config if required in the future

export class CognitoAWSCredentialsAndIdentityIdProvider
implements AWSCredentialsAndIdentityIdProvider
{
Expand Down Expand Up @@ -75,21 +74,13 @@ export class CognitoAWSCredentialsAndIdentityIdProvider
identityIdStore: this._identityIdStore,
});

if (!identityId) {
throw new AuthError({
name: 'IdentityIdConfigException',
message: 'No Cognito Identity Id provided',
recoverySuggestion: 'Make sure to pass a valid identityId.',
});
}

// Clear cached credentials when forceRefresh is true OR the cache token has changed
if (forceRefresh || tokenHasChanged) {
this.clearCredentials();
}
if (!isAuthenticated) {
return this.getGuestCredentials(identityId, authConfig.Cognito);
} else {
// Tokens will always be present if getCredentialsOptions.authenticated is true as dictated by the type
assertIdTokenInAuthTokens(tokens);
return this.credsForOIDCTokens(authConfig.Cognito, tokens, identityId);
}
Expand All @@ -99,6 +90,7 @@ export class CognitoAWSCredentialsAndIdentityIdProvider
identityId: string,
authConfig: CognitoIdentityPoolConfig
): Promise<AWSCredentialsAndIdentityId> {
// Return existing in-memory cached credentials only if it exists, is not past it's lifetime and is unauthenticated credentials
if (
this._credentialsAndIdentityId &&
!this.isPastTTL() &&
Expand All @@ -113,12 +105,12 @@ export class CognitoAWSCredentialsAndIdentityIdProvider
// Clear to discard if any authenticated credentials are set and start with a clean slate
this.clearCredentials();

const region = getRegionFromIdentityPoolId(authConfig.identityPoolId);

// use identityId to obtain guest credentials
// save credentials in-memory
// No logins params should be passed for guest creds:
// https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
const region = getRegionFromIdentityPoolId(authConfig.identityPoolId);

const clientResult = await getCredentialsForIdentity(
{ region },
{
Expand Down Expand Up @@ -157,7 +149,7 @@ export class CognitoAWSCredentialsAndIdentityIdProvider
return res;
} else {
throw new AuthError({
name: 'CredentialsException',
name: 'CredentialsNotFoundException',
message: `Cognito did not respond with either Credentials, AccessKeyId or SecretKey.`,
});
}
Expand All @@ -166,15 +158,15 @@ export class CognitoAWSCredentialsAndIdentityIdProvider
private async credsForOIDCTokens(
authConfig: CognitoIdentityPoolConfig,
authTokens: AuthTokens,
identityId?: string
identityId: string
): Promise<AWSCredentialsAndIdentityId> {
if (
this._credentialsAndIdentityId &&
!this.isPastTTL() &&
this._credentialsAndIdentityId.isAuthenticatedCreds === true
) {
logger.debug(
'returning stored credentials as they neither past TTL nor expired'
'returning stored credentials as they neither past TTL nor expired.'
);
return this._credentialsAndIdentityId;
}
Expand Down Expand Up @@ -256,7 +248,7 @@ export function formLoginsMap(idToken: string) {
if (!issuer) {
throw new AuthError({
name: 'InvalidIdTokenException',
message: 'Invalid Idtoken',
message: 'Invalid Idtoken.',
});
}
let domainName: string = issuer.replace(/(^\w+:|^)\/\//, '');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { LocalStorage } from '@aws-amplify/core';
* Cognito specific implmentation of the CredentialsProvider interface
* that manages setting and getting of AWS Credentials.
*
* @throws internal: {@link AuthError }
* @throws configuration expections: {@link InvalidIdentityPoolIdException }
* - Auth errors that may arise from misconfiguration.
* @throws service expections: {@link GetCredentialsForIdentityException}, {@link GetIdException}
*
*/
export const cognitoCredentialsProvider =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const IdentityIdStorageKeys = {

export interface IdentityIdStore {
setAuthConfig(authConfigParam: AuthConfig): void;
loadIdentityId(): Promise<Identity | undefined>;
loadIdentityId(): Promise<Identity | null>;
storeIdentityId(identity: Identity): Promise<void>;
clearIdentityId(): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function getRegion(userPoolId?: string): string {
export function getRegionFromIdentityPoolId(identityPoolId?: string): string {
if (!identityPoolId || !identityPoolId.includes(':')) {
throw new AuthError({
name: 'InvalidIdentityPoolId',
name: 'InvalidIdentityPoolIdException',
message: 'Invalid identity pool id provided.',
recoverySuggestion:
'Make sure a valid identityPoolId is given in the config.',
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/clients/types/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { MetadataBearer } from '@aws-sdk/types';
import { Endpoint } from './core';
import { HttpResponse } from './http';

export type { Credentials, MetadataBearer } from '@aws-sdk/types';
export type {
AwsCredentialIdentity as Credentials,
MetadataBearer,
} from '@aws-sdk/types';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we doing the thing that Allan did on v5 to generate those and have @aws-sdk/types as dev dependency

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we can create a follow up PR for this. Thanks


export type SourceData = string | ArrayBuffer | ArrayBufferView;

Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/singleton/Auth/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export function assertIdentityPooIdConfig(
): asserts cognitoConfig is CognitoIdentityPoolConfig {
const validConfig = !!cognitoConfig?.identityPoolId;
return asserts(validConfig, {
name: 'AuthIdentityPoolIdException',
message: 'Auth IdentityPoolId not configured',
name: 'InvalidIdentityPoolIdException',
message: 'Invalid identity pool id provided.',
recoverySuggestion:
'Make sure to call Amplify.configure in your app with a valid IdentityPoolId',
'Make sure a valid identityPoolId is given in the config.',
});
}

Expand Down
Loading