Skip to content

Commit

Permalink
feat: introduces usePii hook and withPii HOC to pass vendor-specific …
Browse files Browse the repository at this point in the history
…masking HTML attributes
  • Loading branch information
adamstankiewicz committed Aug 10, 2024
1 parent c878cce commit e5e6bcc
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 199 deletions.
20 changes: 20 additions & 0 deletions env.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
// Also note that in an actual application this file would be added to .gitignore.
const config = {
JS_FILE_VAR: 'JS_FILE_VAR_VALUE_FOR_EXAMPLE_APP',
piiAttributeMappings: {
vendors: {
datadog: {
mappings: {
username: {
attribute: 'data-dd-privacy',
value: 'mask',
},
},
},
hotjar: {
mappings: {
username: {
attribute: 'data-hj-suppress',
value: '',
},
},
},
},
},
};

export default config;
1 change: 1 addition & 0 deletions example/ExamplePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ExamplePage extends Component {
<p>EXAMPLE_VAR env var came through: <strong>{getConfig().EXAMPLE_VAR}</strong></p>
<p>JS_FILE_VAR var came through: <strong>{getConfig().JS_FILE_VAR}</strong></p>
<p>Visit <Link to="/authenticated">authenticated page</Link>.</p>
<p>Visit <Link to="/pii">PII page</Link>.</p>
<p>Visit <Link to="/error_example">error page</Link>.</p>
</div>
);
Expand Down
71 changes: 71 additions & 0 deletions example/PiiPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { forwardRef, useContext, useRef } from 'react';
import PropTypes from 'prop-types';

import { AppContext, usePii, withPii } from '@edx/frontend-platform/react';

// Example via `usePii` (hook)
function UsernameWithPii({ children, ...rest }) {
const piiProps = usePii('username', rest);
return <span {...piiProps}>{children}</span>;
}
UsernameWithPii.propTypes = {
children: PropTypes.node.isRequired,
};

// Example via `withPii` (HOC)
const Username = forwardRef(({ children, ...rest }, ref) => (
<span ref={ref} {...rest}>{children}</span>
));
Username.displayName = 'Username';
Username.propTypes = {
children: PropTypes.node.isRequired,
};
const UsernameWithPii2 = withPii('username')(Username);

export default function PiiPage() {
const { authenticatedUser, config } = useContext(AppContext);
const usernameRef = useRef(null);
const { piiAttributeMappings } = config;
return (
<div>
<h1>Privacy options to obfuscate PII</h1>

<h2>Current PII configuration</h2>
{piiAttributeMappings ? (
<pre>
{JSON.stringify(piiAttributeMappings, null, 2)}
</pre>
) : (
<p>
No PII-related configuration found. Consider updating this application&apos;s <code>env.config.js</code> to
configure any vendor-specific PII-related attributes/values.
</p>
)}

<h2>Examples</h2>
<p>
The following examples below demonstrate using <code>usePii</code> and <code>withPii</code> to
obfuscate PII (e.g., username) based on the application&apos;s configuration. Inspect the DOM
elements for the rendered usernames below to observe the configured PII attributes/values.
</p>

{/* Example 1 */}
<h3><code>usePii</code> (hook)</h3>
<p>
<i>Username:</i>{' '}
<UsernameWithPii>
{authenticatedUser.username}
</UsernameWithPii>
</p>

{/* Example 2 */}
<h3><code>withPii</code> (HOC)</h3>
<p>
<i>Username:</i>{' '}
<UsernameWithPii2 ref={usernameRef}>
{authenticatedUser.username}
</UsernameWithPii2>
</p>
</div>
);
}
49 changes: 49 additions & 0 deletions example/PrivacyOptionsExample.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useContext, forwardRef, useRef } from 'react';
import PropTypes from 'prop-types';

import { AppContext, usePii, withPii } from '@edx/frontend-platform/react';

// Example via `usePii` (hook)
function UsernameWithPii({ children, ...rest }) {
const piiProps = usePii('username', rest);
return <span {...piiProps}>{children}</span>;
}
UsernameWithPii.propTypes = {
children: PropTypes.node.isRequired,
};

// Example via `withPii` (HOC)
const Username = forwardRef(({ children, ...rest }, ref) => (
<span ref={ref} {...rest}>{children}</span>
));
Username.displayName = 'Username';
Username.propTypes = {
children: PropTypes.node.isRequired,
};
const UsernameWithPii2 = withPii('username')(Username);

export default function PrivacyOptionsExample() {
const { authenticatedUser } = useContext(AppContext);
const usernameRef = useRef(null);
return (
<section>
<h2>Privacy options for logging and analytics</h2>

{/* Example 1 */}
<h3><code>usePii</code> (hook)</h3>
<p>
<UsernameWithPii>
{authenticatedUser.username}
</UsernameWithPii>
</p>

{/* Example 2 */}
<h3><code>withPii</code> (HOC)</h3>
<p>
<UsernameWithPii2 piiSelector="username" ref={usernameRef}>
{authenticatedUser.username}
</UsernameWithPii2>
</p>
</section>
);
}
2 changes: 2 additions & 0 deletions example/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Routes, Route } from 'react-router-dom';
import './index.scss';
import ExamplePage from './ExamplePage';
import AuthenticatedPage from './AuthenticatedPage';
import PiiPage from './PiiPage';

subscribe(APP_READY, () => {
ReactDOM.render(
Expand All @@ -27,6 +28,7 @@ subscribe(APP_READY, () => {
element={<PageWrap><ErrorPage message="Test error message" /></PageWrap>}
/>
<Route path="/authenticated" element={<AuthenticatedPageRoute><AuthenticatedPage /></AuthenticatedPageRoute>} />
<Route path="/pii" element={<AuthenticatedPageRoute><PiiPage /></AuthenticatedPageRoute>} />
</Routes>
</AppProvider>,
document.getElementById('root'),
Expand Down
Loading

0 comments on commit e5e6bcc

Please sign in to comment.