This service has been a blessing for me in terms of hiding credentials from being exposed to hackers. This is what Secrets Manager does from the official site.
“AWS Secrets Manager helps you protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. Users and applications retrieve secrets with a call to Secrets Manager APIs, eliminating the need to hardcode sensitive information in plain text. Secrets Manager offers secret rotation with built-in integration for Amazon RDS, Amazon Redshift, and Amazon DocumentDB. Also, the service is extensible to other types of secrets, including API keys and OAuth tokens. In addition, Secrets Manager enables you to control access to secrets using fine-grained permissions and audit secret rotation centrally for resources in the AWS Cloud, third-party services, and on-premises.”
How to implement:
Include this dependency in your pom file.
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-secretsmanager</artifactId> </dependency>
Configure an AWSSecretsManager bean or object.
@Configuration public class DevConfig { private Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired private AWSSecretsManagerUtils awsSecretsManagerUtils; @Bean public AWSCredentialsProvider amazonAWSCredentialsProvider() { return DefaultAWSCredentialsProviderChain.getInstance(); } @Bean public AWSSecretsManager awsSecretsManager() { AWSSecretsManager awsSecretsManager = AWSSecretsManagerClientBuilder .standard() .withCredentials(amazonAWSCredentialsProvider()) .withRegion(Regions.US_WEST_2) .build(); return awsSecretsManager; } @Bean public HikariDataSource dataSource() { //log.debug("DB SECRET: {}", dbSecret.toJson()); DbSecret dbSecret = awsSecretsManagerUtils.getDbSecret(); log.info("Configuring dev datasource..."); Integer port = 3306; String host = dbSecret.getHost(); String username = dbSecret.getUsername(); String password = dbSecret.getPassword(); String dbName = "springboot-course-tshirt"; String url = "jdbc:mysql://" + host + ":" + port + "/" + dbName + "?useUnicode=true&characterEncoding=utf8&useSSL=false"; HikariConfig config = new HikariConfig(); config.setJdbcUrl(url); config.setUsername(username); config.setPassword(password); HikariDataSource hds = new HikariDataSource(config); hds.setMaximumPoolSize(30); hds.setMinimumIdle(20); hds.setMaxLifetime(1800000); hds.setConnectionTimeout(30000); hds.setIdleTimeout(600000); return hds; } }
Use the service by retrieving a mysql server database.
@Component public class AWSSecretsManagerUtils { private Logger log = LoggerFactory.getLogger(AWSSecretsManagerUtils.class); @Value("${datasource.secret.name}") private String dataSourceSecretName; @Autowired private AWSSecretsManager awsSecretsManager; public DbSecret getDbSecret() { return DbSecret.fromJson(getCredentials(dataSourceSecretName)); } private String getCredentials(String secretId) { GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest(); getSecretValueRequest.setSecretId(secretId); GetSecretValueResult getSecretValueResponse = null; try { getSecretValueResponse = awsSecretsManager.getSecretValue(getSecretValueRequest); } catch (Exception e) { log.error("Exception, msg: ", e.getMessage()); } if (getSecretValueResponse == null) { return null; } ByteBuffer binarySecretData; String secret; // Decrypted secret using the associated KMS CMK // Depending on whether the secret was a string or binary, one of these fields // will be populated if (getSecretValueResponse.getSecretString() != null) { log.info("secret string"); secret = getSecretValueResponse.getSecretString(); } else { log.info("secret binary secret data"); binarySecretData = getSecretValueResponse.getSecretBinary(); secret = binarySecretData.toString(); } return secret; } }
public class DbSecret { private String username; private String password; private String engine; private String host; private String dbInstanceIdentifier; public DbSecret() { this(null,null,null,null,null); } public DbSecret(String username, String password, String engine, String host, String dbInstanceIdentifier) { super(); this.username = username; this.password = password; this.engine = engine; this.host = host; this.dbInstanceIdentifier = dbInstanceIdentifier; } // setters and getters public String toJson() { try { return ObjectUtils.getObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { System.out.println("JsonProcessingException, msg: " + e.getLocalizedMessage()); return "{}"; } } public static DbSecret fromJson(String json) { try { return ObjectUtils.getObjectMapper().readValue(json, DbSecret.class); } catch (IOException e) { System.out.println("From Json Exception: "+e.getLocalizedMessage()); return null; } } }
aws secretsmanager list-secrets --profile {profile-name}
aws secretsmanager get-secret-value --secret-id {secretName or ARN} --profile {profile-name}
Install Bootstrap
npm install --save bootstrap
Include bootstrap.css in the index.js
import 'bootstrap/dist/css/bootstrap.css';
React keys are useful when working with dynamically created components or when your lists are altered by the users. Setting the key value will keep your components uniquely identified after the change.
import React from 'react'; class App extends React.Component { constructor() { super(); this.state = { users:[ { name: 'Folau', id: 1 }, { name: 'Lisa', id: 2 } ] } } render() { return ( <div> <div> {this.state.data.map((user, i) => <h4>My name is {user.name}<h4/> )} </div> </div> ); } }
It’s strongly recommended that you assign proper keys whenever you build dynamic lists. If you don’t have an appropriate key, you may want to consider restructuring your data so that you do.
If no key is specified, React will present a warning and use the array index as a key by default. Using the array index as a key is problematic when trying to re-order a list’s items or inserting/removing list items. Explicitly passing key={i}
silences the warning but has the same problems as array indices and is not recommended in most cases.
Keys do not need to be globally unique; they only need to be unique between components and their siblings.
Install router
npm install –save react-router-dom
yarn add react-router-dom
In the index.js
import { BrowserRouter as Router, Route } from “react-router-dom”; ReactDOM.render( <Providerstore={createStore(allReducers, applyMiddleware(thunk))}> <React.StrictMode> <Router> <Routepath=”/”exactcomponent={Home}/> <Routepath=”/login”exactcomponent={Login}/> <Routepath=”/signup”exactcomponent={Signup}/> <Routepath=”/profile”exactcomponent={ProfilePage}/> <Routepath=”/about”exactcomponent={AboutPage}/> <Routepath=”/photos”exactcomponent={PhotosPage}/> <Routepath=”/friends”exactcomponent={FriendsPage}/> </Router> </React.StrictMode> </Provider>, document.getElementById(‘root’) );
Install redux
npm install react-redux
Add redux to index.js file
React Redux provides <Provider/>, which makes the Redux store available to the rest of your app
import React from 'react' import ReactDOM from 'react-dom' import thunk from 'redux-thunk' import {createStore, applyMiddleware} from 'redux' import { Provider } from 'react-redux' import store from './store' import allReducers from './redux/reducers/allReducer.js' import App from './App' const rootElement = document.getElementById('root') ReactDOM.render( <Provider store={createStore(allReducers, applyMiddleware(thunk))}> <App /> </Provider>, rootElement )
Connect Component to store
React Redux provides a connect function for you to connect your component to the store.
import React, { Component } from 'react'; import { connect } from 'react-redux'; import LandingPage from './landing_page'; import Header from './header/header'; import Posts from './post/posts'; import LeftPanel from './left_panel'; import RightPanel from './right_panel'; class Home extends Component { constructor(props) { super(props); this.state = { userAuthenticated: localStorage.getItem('userUuid') ? true : false } } componentDidMount() { } render() { return ( <div> <Header /> <main> <div className="main-wrapper pt-80"> <div className="container"> <div className="row"> <LeftPanel page="HOME"/> <Posts page="HOME"/> <RightPanel/> </div> </div> </div> </main> </div> ) } } const mapStateToProps = (state) => { return state; } export default connect(mapStateToProps, { })(Home);
Access data from the store
import React, { Component } from 'react'; import {connect} from 'react-redux'; import { loadNotifications } from '../redux/actions/NotificationAction'; class RecentNotifications extends Component { constructor(props) { super(props); console.log("RecentNotifications constructor()"); this.state = { } } componentDidMount(){ } render() { console.log("RecentNotifications render()"); console.log(this.props.ntcsState.notificationInfo.content); let listOfNotifications = {}; if(this.props.ntcsState.notificationInfo.content!==undefined && this.props.ntcsState.notificationInfo.content!==null){ listOfNotifications = this.props.ntcsState.notificationInfo.content.map((notification) => <li key={notification.uuid} className="unorder-list"> {/* profile picture end */} <div className="profile-thumb"> <a href="/"> <div className="profile-thumb-small"> <img src={notification.createdBy.profileImageUrl} alt="profile" /> </div> </a> </div> {/* profile picture end */} <div className="unorder-list-info"> <h3 className="list-title"><a href="/">Any one can join with us if you want</a></h3> <p className="list-subtitle">5 min ago</p> </div> </li> ); }else{ listOfNotifications = <li className="unorder-list"></li> } return ( <div className="card widget-item"> <h4 className="widget-title">Recent Notifications</h4> <div className="widget-body"> <ul className="like-page-list-wrapper"> {listOfNotifications} </ul> </div> </div> ) } } const mapStateToProps = (state) => { return state; } export default connect(mapStateToProps,{ loadNotifications: loadNotifications })(RecentNotifications);
You can access data from the store by connecting to the store and using props like this.props.ntcsState.
NotificationReducer.js
const notificationInitialState = { notificationInfo: {} }; let notificationInfo; export const NotificationStateReducer = (state = notificationInitialState, action) => { switch (action.type) { case 'LOAD_NOTIFICATIONS': notificationInfo = action.payload; //console.log("LOAD_NOTIFICATIONS"); //console.log(notificationInfo.data); return Object.assign({}, state, { notificationInfo: notificationInfo.data }); default: return state; } }
NotificationAction.js
import NotificationAPI from '../../api/notification'; export const loadNotifications = () => { let userUuid = localStorage.getItem("userUuid"); let pageSize = 10; let pageNumber = 0; //console.log("LOAD_NOTIFICATIONS"); return async function(dispatch, getState) { const response = await NotificationAPI.getNotifications(userUuid, pageSize, pageNumber); dispatch({ type: "LOAD_NOTIFICATIONS", payload: response }); }; };
Trigger actions in the store
import React, { Component } from 'react'; import Avatar from '../images/avatar.png'; import UserAPI from '../api/user'; import { connect } from 'react-redux'; import { setUserSession } from '../redux/actions/UserAction'; class ProfileUpdate extends Component { constructor(props) { super(props); this.state = { profile: { firstName: "", lastName: "", email:"", phoneNumber:"", gender: "", maritalStatus: "", profileImageUrl: props.userState.userProfileImageUrl!== null ? props.userState.userProfileImageUrl : Avatar, coverImageUrl: props.userState.userCoverImageUrl!== null ? props.userState.userCoverImageUrl : Avatar, uploadImage:"" } } this.loadProfile = this.loadProfile.bind(this); this.handleInputChange = this.handleInputChange.bind(this); this.updateProfile = this.updateProfile.bind(this); } componentDidMount() { } handleInputChange(event) { console.log("handleInputChange"); const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; if(name==="profileImageFile"){ console.log("upload profile image"); const formData = new FormData(); formData.append( "file", event.target.files[0], event.target.files[0].name ); let uuid = localStorage.getItem("userUuid"); UserAPI.uploadProfileImage(uuid, formData) .then(response => { console.log("response"); console.log(response.data); let user = response.data; this.setState({profile:user},()=>{}); this.props.setUserSession(user); }).catch(error => { console.log("error"); console.log(error.response.data); }); return; } if(name==="coverImageFile"){ console.log("upload cover image"); const formData = new FormData(); formData.append( "file", event.target.files[0], event.target.files[0].name ); let uuid = localStorage.getItem("userUuid"); UserAPI.uploadCoverImage(uuid, formData) .then(response => { console.log("response"); console.log(response.data); let user = response.data; this.setState({profile:user},()=>{}); this.props.setUserSession(user); }).catch(error => { console.log("error"); console.log(error.response.data); }); return; } let updatedState = { profile: this.state.profile } updatedState['profile'][name] = value; console.log("updated state"); console.log(updatedState); this.setState(updatedState, function () { //console.log(this.state); }); } loadProfile(event){ let uuid = localStorage.getItem("userUuid"); UserAPI.getProfile(uuid) .then(response => { console.log("response"); console.log(response.data); let user = response.data; this.setState({profile: user}); this.props.setUserSession(user); }).catch(error => { console.log("error"); console.log(error.response.data); }); } updateProfile(event){ console.log(this.state.profile); UserAPI.updateProfile(this.state.profile) .then(response => { console.log("response"); console.log(response.data); let user = response.data; this.setState({profile: user}); this.props.setUserSession(user); }).catch(error => { console.log("error"); console.log(error.response.data); }); } render() { return ( <div className="col-lg-2 col-md-3 d-none d-md-block"> <div className="profile-edit-panel"> <button onClick={this.loadProfile} className="btn-adda edit-btn" data-toggle="modal" data-target="#profileUpdateModal" >edit profile</button> </div> <div className="modal fade in" id="profileUpdateModal" aria-labelledby="profileUpdateModal"> <div className="modal-dialog"> <div className="modal-content"> <div className="modal-header"> <h5 className="modal-title">Update Profile</h5> <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> </div> <div className="modal-body custom-scroll"> <div className="row"> <div className="col-sm-12 text-center"> <img src={this.state.profile.profileImageUrl} className="rounded" alt="..."/> </div> </div> <div className="row"> <div className="col-sm-6 offset-sm-3"> <div className="form-group text-center"> <input type="file" className="form-control-file" name="profileImageFile" onChange={this.handleInputChange} /> </div> </div> </div> <div className="row"> <div className="col-sm-12 text-center"> <img src={this.state.profile.coverImageUrl} className="rounded" alt="..."/> </div> </div> <div className="row"> <div className="col-sm-6 offset-sm-3"> <div className="form-group text-center"> <input type="file" className="form-control-file" name="coverImageFile" onChange={this.handleInputChange} /> </div> </div> </div> <div className="row"> <div className="col-sm-6"> <div className="form-group"> <label>First Name</label> <input type="text" className="form-control" aria-describedby="firstNameHelp" placeholder="Enter first name" name="firstName" onChange={this.handleInputChange} value={this.state.profile.firstName}/> </div> </div> <div className="col-sm-6"> <div className="form-group"> <label>Last Name</label> <input type="text" className="form-control" aria-describedby="lastNameHelp" placeholder="Enter last name" name="lastName" onChange={this.handleInputChange} value={this.state.profile.lastName} /> </div> </div> </div> <div className="row"> <div className="col-sm-6"> <div className="form-group"> <label>Email</label> <input type="email" className="form-control" aria-describedby="emailHelp" placeholder="Enter email" name="email" onChange={this.handleInputChange} value={this.state.profile.email} /> </div> </div> <div className="col-sm-6"> <div className="form-group"> <label>Phone</label> <input type="tel" className="form-control" aria-describedby="phoneNumberHelp" placeholder="Enter phone number" name="phoneNumber" onChange={this.handleInputChange} value={this.state.profile.phoneNumber} /> </div> </div> </div> <div className="row"> <div className="col-sm-6"> <div className="form-group"> <label>Gender</label> <select className="form-control" name="gender" onChange={this.handleInputChange} value={this.state.profile.gender}> <option value="MALE">Male</option> <option value="FEMALE">Female</option> <option value="TRANGENDER">Transgender</option> </select> </div> </div> <div className="col-sm-6"> <div className="form-group"> <label>Marital Status</label> <select className="form-control" name="maritalStatus" onChange={this.handleInputChange} value={this.state.profile.maritalStatus}> <option value="SINGLE">Single</option> <option value="MARRIED">Married</option> </select> </div> </div> </div> </div> <div className="modal-footer"> <button type="button" className="btn-adda post-share-btn" data-dismiss="modal">cancel</button> <button onClick={this.updateProfile} type="button" data-dismiss="modal" className="btn-adda post-share-btn">Update</button> </div> </div> </div> </div> </div> ) } } const mapStateToProps = (state) => { //console.log("home all states", state) return state; } export default connect(mapStateToProps, { setUserSession: setUserSession })(ProfileUpdate);
You use props to call actions like this
this.props.setUserSession(user);