/* eslint-disable no-console */ /* eslint-disable import/no-unresolved */ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 import React, { createContext, useContext, useState, useEffect } from 'react'; import { Auth } from '@aws-amplify/auth'; import { Credentials } from '@aws-amplify/core' import { useNotificationDispatch } from 'amazon-chime-sdk-component-library-react'; import appConfig from '../Config'; import AWS from 'aws-sdk'; const AuthContext = createContext(); const AuthProvider = ({ children }) => { const notificationDispatch = useNotificationDispatch(); // Member const [member, setMember] = useState({ username: '', userId: '', }); // Auth state const [isAuthenticated, setIsAuthenticated] = useState(false); // isAnonymous (anon does not have access to write to S3 for attachments) default to cognito flow const [isAnonymous, setAnonymous] = useState(false); // Using CognitoIdp (if true use Cognito IDP to search for users when adding members to a room, // else lookup using ListAppInstanceUsers API), default to Cognito flow const [useCognitoIdp, setUseCognitoIdp] = useState(true); const userSignOut = async () => { try { await Auth.signOut().then(() => { AWS.config.credentials = null; setIsAuthenticated(false); setMember({ username: '', userId: '' }); }); } catch (error) { console.log(`error signing out ${error}`); } }; const userSignUp = async (username, password) => { try { await Auth.signUp({ username, password, attributes: { // TODO: Utilize input field for email that way we can then have users self confirm after reg. email: 'email@me.com', profile: 'none', }, }); notificationDispatch({ type: 0, payload: { message: 'Your registration information has been set to your administrator. Contact them for additional instructions.', severity: 'success', }, }); } catch (error) { console.log('error signing up:', error); notificationDispatch({ type: 0, payload: { message: 'Registration failed.', severity: 'error', }, }); } }; const updateUserAttributes = async (userId) => { try { const user = await Auth.currentAuthenticatedUser(); await Auth.updateUserAttributes(user, { profile: userId, }); } catch (err) { console.log(err); } }; const getAwsCredentialsFromCognito = async () => { AWS.config.region = appConfig.region; const creds = await Credentials.get(); AWS.config.credentials = new AWS.Credentials( creds.accessKeyId, creds.secretAccessKey, creds.sessionToken); AWS.config.credentials.needsRefresh = function() { return Date.now() > creds.expiration; } AWS.config.credentials.refresh = function(cb) { console.log("Refresh Cognito IAM Creds"); Auth.currentUserCredentials().then(getAwsCredentialsFromCognito().then(cb())); } return creds; }; const setAuthenticatedUserFromCognito = () => { setUseCognitoIdp(true); Auth.currentUserInfo() .then(curUser => { setMember({ username: curUser.username, userId: curUser.id }); if (curUser.attributes?.profile === 'none') { updateUserAttributes(curUser.id); // Once we set attribute let's have user relogin to refresh SigninHookFn trigger. setIsAuthenticated(false); notificationDispatch({ type: 0, payload: { message: 'Your account is activated! Please sign in again to confirm.', severity: 'success', }, }); } else { setAnonymous(false); setIsAuthenticated(true); } }) .catch((err) => { console.log(`Failed to set authenticated user! ${err}`); }); getAwsCredentialsFromCognito(); }; const userSignIn = (username, password) => { Auth.signIn({ username, password }) .then(setAuthenticatedUserFromCognito) .catch((err) => { console.log(err); notificationDispatch({ type: 0, payload: { message: 'Your username and/or password is invalid!', severity: 'error', }, }); }); }; const setAuthenticatedUserFromCredentialExchangeService = (response) => { setUseCognitoIdp(false); setAnonymous(true) setMember({ username: response.ChimeDisplayName, userId: response.ChimeUserId }); const stsCredentials = response.ChimeCredentials; updateUserAttributes(response.ChimeUserId); AWS.config.region = appConfig.region; AWS.config.credentials = new AWS.Credentials( stsCredentials.AccessKeyId, stsCredentials.SecretAccessKey, stsCredentials.SessionToken); const credentialReceiveTime = Date.now(); // In template.yaml the credential role for anonymous is set to expire in 15 mins var CREDENTIAL_ROLE_EXPIRY_DURATION = 15 * 60 * 1000; AWS.config.credentials.needsRefresh = function() { return Date.now() - credentialReceiveTime > CREDENTIAL_ROLE_EXPIRY_DURATION } AWS.config.credentials.refresh = function(cb) { console.error("Credentials expired via anonymous token used for Demo. TODO: implement refresh for actual production use case"); // TODO, implement this for specific use case. In demo anonymous users are given a single one time // use token that can not be refreshed. An implementation here would likely call to your authorization // service to get new credentials cb(); } setIsAuthenticated(true); }; // Credential Exchange Service Code. Set Access Token on Authorization header using Bearer type. const userExchangeTokenForAwsCreds = accessToken => { fetch(`${appConfig.apiGatewayInvokeUrl}creds`, { method: 'POST', credentials: 'include', headers: new Headers({ Authorization: `Bearer ${btoa(accessToken)}` }) }).then(response => response.json()) .then(data => setAuthenticatedUserFromCredentialExchangeService(data)) .catch(err => { console.log(err); notificationDispatch({ type: 0, payload: { message: 'Your username and/or password is invalid!', severity: 'error', }, }); }); }; useEffect(() => { Auth.currentAuthenticatedUser() .then(setAuthenticatedUserFromCognito) .catch((err) => { console.log(err); setIsAuthenticated(false); }); }, [Auth]); const authFulfiller = { member, isAuthenticated, isAnonymous, useCognitoIdp, userSignOut, userSignUp, userSignIn, userExchangeTokenForAwsCreds: userExchangeTokenForAwsCreds }; return ( {children} ); }; const useAuthContext = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuthContext must be used within AuthProvider'); } return context; }; export { AuthProvider, useAuthContext };