Skip to content

Commit f6b64c9

Browse files
authored
Merge pull request #150 from ModusCreateOrg/NO-TICKET-FIX-AUTH
feat: add Cognito Identity client and enhance DirectCognitoAuthService for token management and AWS credentials retrieval
2 parents b2c5bec + 1725a5a commit f6b64c9

File tree

3 files changed

+197
-18
lines changed

3 files changed

+197
-18
lines changed

frontend/package-lock.json

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
},
4141
"dependencies": {
4242
"@aws-sdk/client-bedrock-runtime": "^3.799.0",
43+
"@aws-sdk/client-cognito-identity": "^3.799.0",
4344
"@aws-sdk/client-cognito-identity-provider": "^3.799.0",
4445
"@aws-sdk/client-s3": "^3.802.0",
4546
"@aws-sdk/s3-request-presigner": "^3.802.0",

frontend/src/common/services/auth/direct-cognito-auth-service.ts

Lines changed: 145 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import {
1616
ConfirmSignUpCommandOutput,
1717
ResendConfirmationCodeCommandOutput,
1818
} from '@aws-sdk/client-cognito-identity-provider';
19-
import { COGNITO_CONFIG } from '../../config/aws-config';
19+
import {
20+
CognitoIdentityClient,
21+
GetIdCommand,
22+
GetCredentialsForIdentityCommand,
23+
} from '@aws-sdk/client-cognito-identity';
24+
import { COGNITO_CONFIG, REGION } from '../../config/aws-config';
2025
import { UserTokens } from '../../models/auth';
2126
import { CognitoUser } from '../../models/user';
2227

@@ -42,6 +47,7 @@ interface CognitoSession {
4247
*/
4348
export class DirectCognitoAuthService {
4449
private static client = new CognitoIdentityProviderClient({ region: 'us-east-1' });
50+
private static identityClient = new CognitoIdentityClient({ region: REGION });
4551
private static clientId = COGNITO_CONFIG.USER_POOL_WEB_CLIENT_ID;
4652
private static userPoolId = COGNITO_CONFIG.USER_POOL_ID;
4753

@@ -413,35 +419,156 @@ export class DirectCognitoAuthService {
413419
}
414420

415421
/**
416-
* Fetches the current authentication session, similar to Amplify's fetchAuthSession
422+
* Refreshes the token using the refresh token if available
423+
* @param tokens Current tokens
424+
* @returns Updated tokens if refresh succeeded, original tokens otherwise
425+
*/
426+
private static async refreshTokensIfNeeded(tokens: UserTokens): Promise<UserTokens> {
427+
// Check if token is about to expire (within 5 minutes)
428+
const needsRefresh = tokens.expires_at
429+
? new Date(tokens.expires_at).getTime() - Date.now() < 5 * 60 * 1000
430+
: false;
431+
432+
// If token doesn't need refresh or we don't have a refresh token, return original tokens
433+
if (!needsRefresh || !tokens.refresh_token) {
434+
return tokens;
435+
}
436+
437+
try {
438+
const refreshParams = {
439+
AuthFlow: 'REFRESH_TOKEN_AUTH' as AuthFlowType,
440+
ClientId: this.clientId,
441+
AuthParameters: {
442+
REFRESH_TOKEN: tokens.refresh_token,
443+
},
444+
};
445+
446+
const refreshCommand = new InitiateAuthCommand(refreshParams);
447+
const refreshResponse = await this.client.send(refreshCommand);
448+
449+
if (!refreshResponse.AuthenticationResult) {
450+
return tokens; // No auth result, return original tokens
451+
}
452+
453+
const { IdToken, AccessToken, ExpiresIn } = refreshResponse.AuthenticationResult;
454+
455+
if (!IdToken || !AccessToken) {
456+
return tokens; // Missing tokens, return original tokens
457+
}
458+
459+
// Update tokens in local storage
460+
localStorage.setItem('cognito_id_token', IdToken);
461+
localStorage.setItem('cognito_access_token', AccessToken);
462+
463+
// Return updated tokens
464+
return {
465+
...tokens,
466+
id_token: IdToken,
467+
access_token: AccessToken,
468+
expires_in: ExpiresIn || 3600,
469+
expires_at: new Date(Date.now() + (ExpiresIn || 3600) * 1000).toISOString(),
470+
};
471+
} catch (error) {
472+
console.warn('Token refresh failed, proceeding with existing token:', error);
473+
return tokens; // Return original tokens on error
474+
}
475+
}
476+
477+
/**
478+
* Get Cognito Identity ID
479+
* @param tokens User tokens
480+
* @returns The Identity ID
481+
*/
482+
private static async getIdentityId(tokens: UserTokens): Promise<string> {
483+
const identityPoolId = COGNITO_CONFIG.IDENTITY_POOL_ID;
484+
485+
if (!identityPoolId) {
486+
throw new Error('Identity Pool ID is not configured');
487+
}
488+
489+
const getIdParams = {
490+
IdentityPoolId: identityPoolId,
491+
Logins: {
492+
[`cognito-idp.${REGION}.amazonaws.com/${this.userPoolId}`]: tokens.id_token,
493+
},
494+
};
495+
496+
const getIdCommand = new GetIdCommand(getIdParams);
497+
const identityResponse = await this.identityClient.send(getIdCommand);
498+
499+
if (!identityResponse.IdentityId) {
500+
throw new Error('Failed to obtain identity ID');
501+
}
502+
503+
return identityResponse.IdentityId;
504+
}
505+
506+
/**
507+
* Get AWS credentials using Identity ID
508+
* @param identityId The Identity ID
509+
* @param idToken The ID token
510+
* @returns AWS credentials
511+
*/
512+
private static async getAWSCredentials(
513+
identityId: string,
514+
idToken: string,
515+
): Promise<AWSCredentials> {
516+
const credentialsParams = {
517+
IdentityId: identityId,
518+
Logins: {
519+
[`cognito-idp.${REGION}.amazonaws.com/${this.userPoolId}`]: idToken,
520+
},
521+
};
522+
523+
const credentialsCommand = new GetCredentialsForIdentityCommand(credentialsParams);
524+
const credentialsResponse = await this.identityClient.send(credentialsCommand);
525+
526+
if (!credentialsResponse.Credentials) {
527+
throw new Error('Failed to obtain AWS credentials');
528+
}
529+
530+
const { AccessKeyId, SecretKey, SessionToken, Expiration } = credentialsResponse.Credentials;
531+
532+
if (!AccessKeyId || !SecretKey || !SessionToken || !Expiration) {
533+
throw new Error('Incomplete AWS credentials received');
534+
}
535+
536+
return {
537+
accessKeyId: AccessKeyId,
538+
secretAccessKey: SecretKey,
539+
sessionToken: SessionToken,
540+
expiration: Expiration,
541+
};
542+
}
543+
544+
/**
545+
* Fetches the current authentication session, including AWS credentials
417546
* @returns Promise with session information including credentials
418547
*/
419548
static async fetchAuthSession(): Promise<{ credentials: AWSCredentials }> {
420549
try {
550+
// Get the current tokens
421551
const tokens = this.getTokens();
422-
if (!tokens || !tokens.access_token) {
552+
if (!tokens || !tokens.id_token) {
423553
throw new Error('No active session found');
424554
}
425555

426-
// In a full implementation, you would exchange these tokens for AWS credentials
427-
// using Cognito Identity Pool. For now, we'll return a simplified structure.
556+
// Refresh tokens if needed
557+
const refreshedTokens = await this.refreshTokensIfNeeded(tokens);
428558

429-
// Calculate an expiration time based on the token
430-
const expiresAt = tokens.expires_at
431-
? new Date(tokens.expires_at)
432-
: new Date(Date.now() + 3600 * 1000);
559+
// Get identity ID
560+
const identityId = await this.getIdentityId(refreshedTokens);
433561

434-
return {
435-
credentials: {
436-
accessKeyId: process.env.AWS_ACCESS_KEY_ID || 'DEMO_ACCESS_KEY_ID',
437-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || 'DEMO_SECRET_ACCESS_KEY',
438-
sessionToken: tokens.id_token,
439-
expiration: expiresAt,
440-
},
441-
};
562+
// Get AWS credentials
563+
const credentials = await this.getAWSCredentials(identityId, refreshedTokens.id_token);
564+
565+
return { credentials };
442566
} catch (error) {
443567
console.error('Error fetching auth session:', error);
444-
throw error;
568+
throw new Error(
569+
'Failed to get authentication session: ' +
570+
(error instanceof Error ? error.message : String(error)),
571+
);
445572
}
446573
}
447574

0 commit comments

Comments
 (0)