Skip to content

Commit

Permalink
Merge pull request #108 from osstotalsoft/axa-fr-update
Browse files Browse the repository at this point in the history
`@axa-fr/react-oidc` update to v6.x
  • Loading branch information
DCosti committed Jul 7, 2023
2 parents 3fcd493 + 9e5c6a1 commit b7288cc
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 172 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"javascript.format.enable": true,
"typescript.format.enable": false,
"cSpell.words": [
"Oidc",
"totalsoft"
],
}
3 changes: 2 additions & 1 deletion generators/app/templates/infrastructure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"author": "",
"license": "ISC",
"scripts": {
"prestart": "node setOidcDomains.js",
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
Expand All @@ -22,7 +23,7 @@
},
"dependencies": {
"@apollo/client": "^3.7.15",
"@axa-fr/react-oidc-context": "3.1.6",
"@axa-fr/react-oidc": "6.19.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2023 cdiaconita
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Add below trusted domains, access tokens will automatically injected to be send to
// trusted domain can also be a path like https://www.myapi.com/users,
// then all subroute like https://www.myapi.com/useers/1 will be authorized to send access_token to.

// Service worker will continue to give access token to the JavaScript client
// Ideal to hide refresh token from client JavaScript, but to retrieve access_token for some
// scenarios which require it. For example, to send it via websocket connection.

const REACT_APP_URL = `${oidc.REACT_APP_GQL_HTTP_PROTOCOL}://${oidc.REACT_APP_GQL}/graphql`

// Domains used by OIDC server must be also declared here
const defaultTrustedDomains = {
config_show_access_token: {
domains: [oidc.REACT_APP_IDENTITY_AUTHORITY, REACT_APP_URL],
showAccessToken: true
}
}

const handler = {
get(target, name) {
if (name in target) {
return target[name]
}
return {
domains: [oidc.REACT_APP_IDENTITY_AUTHORITY, REACT_APP_URL],
showAccessToken: true
}
}
}

const trustedDomains = new Proxy(defaultTrustedDomains, handler)

51 changes: 51 additions & 0 deletions generators/app/templates/infrastructure/setOidcDomains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 cdiaconita
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const fs = require('fs')
const dotenv = require('dotenv')

const injectedObjectRegEx = /(const oidc = {[^}]*})/

fs.readFile('public/OidcTrustedDomains.js', 'utf8', function (err, data) {
if (err) {
console.error(err)
return
}
const result = dotenv.config()
if (result.error) {
const path = `.env`
dotenv.config({ path })
}

const { REACT_APP_IDENTITY_AUTHORITY, REACT_APP_GQL_HTTP_PROTOCOL, REACT_APP_GQL } = process.env

const injectedValue =
'const oidc = ' +
JSON.stringify({
REACT_APP_IDENTITY_AUTHORITY,
REACT_APP_GQL_HTTP_PROTOCOL,
REACT_APP_GQL
})

if (data.match(injectedObjectRegEx)) {
data = data.replace(injectedObjectRegEx, injectedValue)
} else {
data = injectedValue + data
}

fs.writeFile('public/OidcTrustedDomains.js', data, 'utf8', function (err) {
if (err) throw err

console.log('The file was saved!')
})
})
31 changes: 31 additions & 0 deletions generators/app/templates/infrastructure/setenv.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const crypto = require('crypto')
const path = require('path')

const envFileRegEx = /env.([a-z0-9]*.)?js/g
const injectedObjectRegEx = /(const oidc = {[^}]*})/
const buildDir = './build'

// Generate the configuration script from environment variables
Expand All @@ -29,6 +30,34 @@ function saveConfigScript(scriptContents, scriptName) {
})
}

function setOidcDomains(scriptContents, trustedDomainsFile) {
const scriptPath = path.join(buildDir, trustedDomainsFile)
fs.readFile(scriptPath, 'utf8', function (err, data) {
if (err) {
console.error(err)
return
}
const { REACT_APP_IDENTITY_AUTHORITY, REACT_APP_GQL_HTTP_PROTOCOL, REACT_APP_GQL } = process.env

const injectedValue =
'const oidc = ' +
JSON.stringify({
REACT_APP_IDENTITY_AUTHORITY,
REACT_APP_GQL_HTTP_PROTOCOL,
REACT_APP_GQL
})

data = data.replace(injectedObjectRegEx, injectedValue)

console.log('Saving file to: ' + scriptPath)
fs.writeFile(scriptPath, data, 'utf8', function (err) {
if (err) throw err

console.log('The file was saved!')
})
})
}

// Update configuration script name in index.html
function updateIndex(scriptName) {
const indexPath = path.join(buildDir, 'index.html')
Expand Down Expand Up @@ -78,7 +107,9 @@ console.log('Setting runtime envoronment..')
let scriptContents = generateConfigScript()
let scriptHash = crypto.createHash('sha256').update(scriptContents).digest('hex')
let scriptName = `env.${scriptHash}.js`
const trustedDomainsFile = 'OidcTrustedDomains.js'

saveConfigScript(scriptContents, scriptName)
setOidcDomains(scriptContents, trustedDomainsFile)
updateIndex(scriptName)
deleteOldScript(scriptName)
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import { ApolloProvider } from '@apollo/client';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { AuthenticationContext } from '@axa-fr/react-oidc-context';
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { ApolloProvider } from '@apollo/client'
import { useOidcUser, OidcUserStatus, useOidcAccessToken } from '@axa-fr/react-oidc'

import { getApolloClient } from './client';
import { getApolloClient, getAccessToken } from './client'
import { getOidcConfigName } from "utils/functions"

export function AuthApolloProvider({ children }) {
const oidc = useContext(AuthenticationContext);
const { oidcUserLoadingState } = useOidcUser(getOidcConfigName())
const { accessToken } = useOidcAccessToken(getOidcConfigName())

if (oidc.isLoading) {
return (<>auth loading</>)
}
useEffect(() => {
setAccessToken(accessToken)
}, [accessToken])

return (
<ApolloProvider client={getApolloClient()}>
{children}
</ApolloProvider>)
if (oidcUserLoadingState === OidcUserStatus.Loading) {
return <>auth loading</>
}

return <ApolloProvider client={getApolloClient()}>{children}</ApolloProvider>
}

AuthApolloProvider.propTypes = {
children: PropTypes.element.isRequired
children: PropTypes.element.isRequired
}

export default AuthApolloProvider;
export default AuthApolloProvider
33 changes: 10 additions & 23 deletions generators/app/templates/infrastructure/src/apollo/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,28 @@ import { setContext } from "@apollo/client/link/context"
import { env } from "../utils/env"
import { createUploadLink } from 'apollo-upload-client'
import omitDeep from 'omit-deep-lodash'
import { getUserManager } from "@axa-fr/react-oidc-core";
import { emptyObject } from 'utils/constants'

let access_token

<%_ if (withSubscription) { _%>
// Create a WebSocket link:
let wsLink
const getWsLink = ()=> {
const getWsLink = () => {
let activeSocket, currentAccessToken
if (!wsLink) {
const wsClient = createClient({
url: `${env.REACT_APP_GQL_WS_PROTOCOL}://${env.REACT_APP_GQL}/graphql`,
keepAlive: 10000, // ping server every 10 seconds,
connectionParams: async () => {
const userManager = getUserManager()
const { access_token } = await userManager.getUser() ?? emptyObject
currentAccessToken = access_token;

return {
authorization: access_token ? `Bearer ${access_token}` : ""
authorization: access_token ? `Bearer ${access_token}` : ''
}
},
on: {
connected: socket => (activeSocket = socket),
ping: async received => {
if (!received) {
const userManager = getUserManager()
const { access_token } = (await userManager.getUser()) ?? emptyObject
if (activeSocket.readyState === WebSocket.OPEN && currentAccessToken !== access_token) {
setTimeout(() => {
activeSocket.close(1000, "Access token silent renew")
Expand Down Expand Up @@ -78,18 +73,6 @@ const httpLink = createUploadLink({
}),
})

const authLink = setContext(async (_, { headers }) => {
const userManager = getUserManager();
const { access_token } = await userManager.getUser() ?? emptyObject

return {
headers: {
...headers,
authorization: access_token ? `Bearer ${access_token}` : ""
},
}
})

const omitTypenameLink = new ApolloLink((operation, forward) => {
if (operation.variables) {
operation.variables = omitDeep(operation.variables, ['__typename'])
Expand Down Expand Up @@ -120,7 +103,7 @@ const retryLink = new RetryLink({
}
});

const myAppLink = () => ApolloLink.from([omitTypenameLink, retryLink, authLink.concat(<% if (withSubscription) { %> link() <% } else { %> httpLink <%} %>)])
const myAppLink = () => ApolloLink.from([omitTypenameLink, retryLink, <% if (withSubscription) { %> link() <% } else { %> httpLink <%} %>])

const cache = new InMemoryCache({
typePolicies: {
Expand All @@ -131,13 +114,17 @@ const cache = new InMemoryCache({
}
})

export function setAccessToken(accessToken) {
access_token = accessToken
}

let apolloClient
export const getApolloClient = () => {
if (!apolloClient) {
apolloClient = new ApolloClient({
link: myAppLink(),
cache
});
})
}
return apolloClient
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import { List } from './MenuStyle';
<%_ if (withRights) { _%>
import { isEmpty } from 'ramda';
import { emptyArray } from 'utils/constants';
import { useReactOidc } from '@axa-fr/react-oidc-context';
import { useOidcUser } from '@axa-fr/react-oidc';
import { useUserData } from 'hooks/rights';
import { intersect } from 'utils/functions';
import { intersect } from 'utils/functions';
import { getOidcConfigName } from "utils/functions"
<%_ } _%>

function Menu({ drawerOpen, withGradient }) {
const location = useLocation();
<%_ if (withRights){ _%>
const { oidcUser } = useReactOidc();
const { oidcUser } = useOidcUser(getOidcConfigName());

const userRoles = oidcUser?.profile?.role || emptyArray;<%_ } _%>

const activeRoute = useCallback(routeName => location.pathname.indexOf(routeName) > -1, [location.pathname])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useCallback<% if (withMultiTenancy) { %>, useEffect, u
import PropTypes from 'prop-types'
import { useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useReactOidc } from '@axa-fr/react-oidc-context'
import { useOidcUser, useOidc } from '@axa-fr/react-oidc'

import { Tooltip } from '@mui/material'
import { PowerSettingsNew } from '@mui/icons-material'
Expand All @@ -25,7 +25,7 @@ import {
StyledSelector
} from './UserMenuStyle'
import UserMenuItem from './UserMenuItem'

import { getOidcConfigName } from "utils/functions"
<%_ if (withMultiTenancy) { _%>
import { useLazyQuery } from '@apollo/client';
import { TenantContext } from 'providers/TenantAuthenticationProvider'
Expand All @@ -43,8 +43,9 @@ function UserMenu({ drawerOpen, avatar, language, changeLanguage, withGradient }
const [openAvatar, setOpenAvatar] = useState(false);
const { t } = useTranslation();
const location = useLocation();
const { oidcUser, logout } = useReactOidc();

const { oidcUser } = useOidcUser(getOidcConfigName());
const { logout } = useOidc(getOidcConfigName());

<%_ if (withRights){ _%>
const userRoles = oidcUser?.profile?.role || emptyArray;<%_ } _%>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import { Container } from "./CustomRouteStyle";
import { <% if (withRights) { %>useReactOidc, <% } %> withOidcSecure } from '@axa-fr/react-oidc-context';
import { getOidcConfigName } from "utils/functions";
import { <% if (withRights) { %>useOidcUser, <% } %> withOidcSecure } from '@axa-fr/react-oidc';
<%_ if (withRights) { _%>
import { emptyArray } from "utils/constants";
import { isEmpty } from "ramda";
import { useUserData } from "hooks/rights";
import { FakeText, Forbidden } from '@totalsoft/rocket-ui';
import { intersect } from "utils/functions";
<% } %>
function PrivateRoute({ component: Component, <% if (withRights) { %>roles, rights, <%}%> }) {
const SecuredComponent = useMemo(() => withOidcSecure(Component), [Component]);

function PrivateRoute({ component: Component, <% if (withRights) { %>roles, rights, <%}%> }) {
const SecuredComponent = useMemo(() => withOidcSecure(Component, undefined, undefined, getOidcConfigName()), [Component]);

<%_ if (withRights) { _%>
const { oidcUser } = useReactOidc();
const { oidcUser } = useOidcUser(getOidcConfigName());
const userRoles = oidcUser?.profile?.role || emptyArray;
const { userData, loading } = useUserData();
const userRights = userData?.rights || emptyArray
Expand Down
Loading

0 comments on commit b7288cc

Please sign in to comment.