Skip to content

Commit

Permalink
Merge pull request Sage-Bionetworks#1252 from jay-hodgson/SWC-6533
Browse files Browse the repository at this point in the history
SWC-6533
  • Loading branch information
jay-hodgson authored Oct 16, 2024
2 parents 8f14dbb + a96d1e8 commit 6ff532b
Show file tree
Hide file tree
Showing 44 changed files with 628 additions and 545 deletions.
22 changes: 15 additions & 7 deletions apps/SageAccountWeb/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SynapseClient,
SynapseContextConsumer,
SynapseContextType,
processRedirectURLInOneSage,
} from 'synapse-react-client'
import { WebhookManagementPage } from './components/WebhooksManagementPage'
import { getSearchParam } from './URLUtils'
Expand All @@ -30,6 +31,7 @@ import { OAuthClientManagementPage } from './components/OAuthClientManagementPag
import { ResetTwoFactorAuth } from './components/TwoFactorAuth/ResetTwoFactorAuth'
import { RESET_2FA_ROUTE } from './Constants'
import { ChangePasswordPage } from './components/ChangePasswordPage'
import { SignUpdatedTermsOfUsePage } from './components/SignUpdatedTermsOfUsePage'

function App() {
return (
Expand All @@ -45,19 +47,22 @@ function App() {
return (
<AppContextConsumer>
{appContext => {
// User is logged in and visiting the root, redirect (unless in the SSO Flow)
const isCodeSearchParam =
getSearchParam('code') !== undefined
const isProviderSearchParam =
getSearchParam('provider') !== undefined
const isInSSOFlow =
isCodeSearchParam && isProviderSearchParam
return (
<>
{appContext?.redirectURL &&
!isInSSOFlow &&
window.location.replace(appContext?.redirectURL)}
</>
)
if (!isInSSOFlow) {
// take user back to page they came from in the source app, if stored in a cookie
const isProcessed = processRedirectURLInOneSage()
if (!isProcessed && appContext?.redirectURL) {
// if not in the cookie, take them to
window.location.replace(appContext?.redirectURL)
}
}
return <></>
}}
</AppContextConsumer>
)
Expand Down Expand Up @@ -105,6 +110,9 @@ function App() {
<Route path={'/authenticated/signTermsOfUse'} exact>
<TermsOfUsePage />
</Route>
<Route path={'/authenticated/signUpdatedTermsOfUse'} exact>
<SignUpdatedTermsOfUsePage />
</Route>
<Route path={'/authenticated/myaccount'} exact>
<AccountSettings />
</Route>
Expand Down
42 changes: 35 additions & 7 deletions apps/SageAccountWeb/src/AppInitializer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React, { useEffect, useState } from 'react'
import { Redirect } from 'react-router-dom'
import { getSearchParam } from './URLUtils'
import { SignedTokenInterface } from '@sage-bionetworks/synapse-types'
import {
SignedTokenInterface,
TermsOfServiceState,
} from '@sage-bionetworks/synapse-types'
import {
storeLastPlace,
SynapseUtilityFunctions,
useApplicationSessionContext,
useFramebuster,
Expand All @@ -14,6 +18,9 @@ function AppInitializer(props: { children?: React.ReactNode }) {
const [signedToken, setSignedToken] = useState<
SignedTokenInterface | undefined
>()
const [skippedSigningUpdatedToS, setSkippedSigningUpdatedToS] = useState<
boolean | undefined
>()
const isFramed = useFramebuster()
const { appId, appURL } = useSourceApp()

Expand Down Expand Up @@ -57,10 +64,32 @@ function AppInitializer(props: { children?: React.ReactNode }) {
) as SignedTokenInterface
setSignedToken(localStorageParamToken)
}
}, [])

const { acceptsTermsOfUse } = useApplicationSessionContext()
// SignUpdatedTermsOfUsePage sets this session storage value if the user decided to skip
const sessionStorageSkippedToS = sessionStorage.getItem('skippedSigningToS')
setSkippedSigningUpdatedToS(sessionStorageSkippedToS === 'true')
}, [])

// Detect if terms of service are up to date. If not, route to either the Pledge or a page where the user can sign the updated terms.
// Note, if the status is "MUST_AGREE_SOON", then the new page will offer a "Skip" button
const { termsOfServiceStatus } = useApplicationSessionContext()
let redirectRoute = undefined
if (termsOfServiceStatus) {
if (
termsOfServiceStatus.usersCurrentTermsOfServiceState !=
TermsOfServiceState.UP_TO_DATE &&
skippedSigningUpdatedToS === false
) {
if (termsOfServiceStatus.lastAgreementDate == undefined) {
redirectRoute = '/authenticated/signTermsOfUse'
} else {
if (location.pathname != '/authenticated/signUpdatedTermsOfUse') {
redirectRoute = '/authenticated/signUpdatedTermsOfUse'
storeLastPlace()
}
}
}
}
return (
<AppContextProvider
appContext={{
Expand All @@ -69,10 +98,9 @@ function AppInitializer(props: { children?: React.ReactNode }) {
signedToken,
}}
>
{acceptsTermsOfUse === false &&
location.pathname != '/authenticated/signTermsOfUse' && (
<Redirect to="/authenticated/signTermsOfUse" />
)}
{!!redirectRoute && location.pathname != redirectRoute && (
<Redirect to={redirectRoute} />
)}
{!isFramed && props.children}
</AppContextProvider>
)
Expand Down
8 changes: 4 additions & 4 deletions apps/SageAccountWeb/src/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
StandaloneLoginForm,
SynapseConstants,
SystemUseNotification,
preparePostSSORedirect,
redirectAfterSSO,
storeLastPlace,
restoreLastPlace,
useLastLoginInfoState,
useApplicationSessionContext,
} from 'synapse-react-client'
Expand Down Expand Up @@ -83,15 +83,15 @@ function LoginPage(props: LoginPageProps) {
lastLoginSourceAppNameState.set(sourceApp?.friendlyName)
lastLoginSourceAppURLState.set(sourceApp?.appURL)
}
redirectAfterSSO(history, returnToUrl)
restoreLastPlace(history, returnToUrl)
// If we didn't redirect, refresh the session
refreshSession()
}}
registerAccountUrl={'/register1'}
resetPasswordUrl={'/resetPassword'}
onBeginOAuthSignIn={() => {
// save current route (so that we can go back here after SSO)
preparePostSSORedirect()
storeLastPlace()
}}
twoFactorAuthenticationRequired={twoFactorAuthSSOErrorResponse}
twoFactorAuthResetUri={`${window.location.origin}${RESET_2FA_ROUTE}?${RESET_2FA_SIGNED_TOKEN_PARAM}=`}
Expand Down
14 changes: 11 additions & 3 deletions apps/SageAccountWeb/src/components/AccountCreatedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { Link as RouterLink } from 'react-router-dom'
import { LeftRightPanel } from './LeftRightPanel'
import useMembershipInvitationTokenHandler from '../hooks/useMembershipInvitationTokenHandler'
import { useSourceApp } from './useSourceApp'
import { SynapseHookUtils } from 'synapse-react-client'
import {
SynapseHookUtils,
processRedirectURLInOneSage,
} from 'synapse-react-client'
import { sourceAppConfigTableID } from '../resources'

export type AccountCreatedPageProps = {}
Expand Down Expand Up @@ -63,8 +66,13 @@ export const AccountCreatedPage = (props: AccountCreatedPageProps) => {
variant="contained"
sx={{ padding: '10px', height: '100%' }}
onClick={() => {
appContext?.redirectURL &&
window.location.assign(appContext.redirectURL)
// take user back to page they came from in the source app, if stored in a cookie
const isProcessed = processRedirectURLInOneSage()
if (!isProcessed) {
// if not processed, fall back to the source app config redirect URL
appContext?.redirectURL &&
window.location.assign(appContext.redirectURL)
}
}}
>
Take me to {sourceApp?.friendlyName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export const onBindToORCiD = (
// after binding, go to ???
if (redirectAfter) {
localStorage.setItem(
SynapseConstants.POST_SSO_REDIRECT_URL_LOCALSTORAGE_KEY,
SynapseConstants.LAST_PLACE_LOCALSTORAGE_KEY,
redirectAfter,
)
} else {
localStorage.setItem(
SynapseConstants.POST_SSO_REDIRECT_URL_LOCALSTORAGE_KEY,
SynapseConstants.LAST_PLACE_LOCALSTORAGE_KEY,
`${SynapseClient.getRootURL()}authenticated/validate?step=${
ValidationWizardStep.VERIFY_IDENTITY
}`,
Expand Down
2 changes: 1 addition & 1 deletion apps/SageAccountWeb/src/components/RegisterAccount1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const RegisterAccount1 = () => {
// redirect to Google login, passing the username through via the state param.
// Send us back to the special oauth2 account creation step2 path (which is ignored by our AppInitializer)
localStorage.setItem(
SynapseConstants.POST_SSO_REDIRECT_URL_LOCALSTORAGE_KEY,
SynapseConstants.LAST_PLACE_LOCALSTORAGE_KEY,
`${SynapseClient.getRootURL()}authenticated/signTermsOfUse`,
)
const redirectUrl = `${SynapseClient.getRootURL()}?provider=${
Expand Down
115 changes: 115 additions & 0 deletions apps/SageAccountWeb/src/components/SignUpdatedTermsOfUsePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useState } from 'react'
import {
displayToast,
SynapseContextUtils,
SynapseQueries,
GovernanceMarkdownGithub,
restoreLastPlace,
} from 'synapse-react-client'
import {
Box,
Button,
Checkbox,
Container,
FormControlLabel,
Paper,
} from '@mui/material'
import { StyledOuterContainer } from './StyledComponents'
import { TermsOfServiceState } from '@sage-bionetworks/synapse-types'

export type SignUpdatedTermsOfUsePageProps = {}

export const SignUpdatedTermsOfUsePage = (
props: SignUpdatedTermsOfUsePageProps,
) => {
const [isLoading, setIsLoading] = useState(false)
const [isCheckboxSelected, setIsCheckboxSelected] = useState(false)
const { accessToken } = SynapseContextUtils.useSynapseContext()
const { mutate: signTermsOfService } = SynapseQueries.useSignTermsOfService()

const { data: tosInfo } = SynapseQueries.useTermsOfServiceInfo()
const { data: tosStatus } = SynapseQueries.useTermsOfServiceStatus()

const isSkipAvailable =
tosStatus?.usersCurrentTermsOfServiceState ==
TermsOfServiceState.MUST_AGREE_SOON
const onSignTermsOfUse = async (event: React.SyntheticEvent) => {
event.preventDefault()
setIsLoading(true)
try {
if (accessToken) {
signTermsOfService(
{
accessToken,
termsOfServiceVersion: tosInfo?.latestTermsOfServiceVersion!,
},
{
onSuccess: () => {
restoreLastPlace()
},
onError: err => {
displayToast(err.reason as string, 'danger')
},
},
)
}
} catch (err: any) {
displayToast(err.reason as string, 'danger')
} finally {
setIsLoading(false)
}
}

return (
<StyledOuterContainer className="SignUpdatedTermsOfUsePage">
<Container sx={{ maxWidth: '800px' }}>
<Paper>
<GovernanceMarkdownGithub
repoOwner="Sage-Bionetworks"
repoName="Sage-Governance-Documents"
filePath="Terms.md"
/>
<FormControlLabel
control={<Checkbox />}
sx={{ p: '30px 45px' }}
label="I agree to the terms of service"
checked={isCheckboxSelected}
onChange={() => {
setIsCheckboxSelected(!isCheckboxSelected)
}}
/>
<Box
sx={{
display: 'grid',
gridTemplateColumns: isSkipAvailable ? 'repeat(2, 1fr)' : '1fr', // either 2 fr (fraction unit) or 1
gap: '10px',
p: '0px 45px 45px 45px',
}}
>
<Button
variant="contained"
sx={{ width: '100%' }}
onClick={onSignTermsOfUse}
disabled={isLoading || !isCheckboxSelected}
>
Agree and Continue
</Button>
{isSkipAvailable && (
<Button
variant="outlined"
sx={{ width: '100%' }}
onClick={() => {
sessionStorage.setItem('skippedSigningToS', 'true')
restoreLastPlace()
}}
disabled={isLoading}
>
Skip for Now
</Button>
)}
</Box>
</Paper>
</Container>
</StyledOuterContainer>
)
}
28 changes: 19 additions & 9 deletions apps/SageAccountWeb/src/components/TermsOfUsePage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react'
import {
displayToast,
SynapseClient,
TermsAndConditions,
IconSvg,
SynapseContextUtils,
SynapseQueries,
} from 'synapse-react-client'
import { SourceAppLogo } from './SourceApp'
import { Box, Button, useTheme } from '@mui/material'
Expand All @@ -23,18 +23,28 @@ export const TermsOfUsePage = (props: TermsOfUsePageProps) => {
const { accessToken } = SynapseContextUtils.useSynapseContext()
const sourceApp = useSourceApp()

const { mutate: signTermsOfService } = SynapseQueries.useSignTermsOfService()
const { data: tosInfo } = SynapseQueries.useTermsOfServiceInfo()

const onSignTermsOfUse = async (event: React.SyntheticEvent) => {
event.preventDefault()
setIsLoading(true)
try {
if (accessToken) {
SynapseClient.signSynapseTermsOfUse(accessToken)
.then(() => {
setIsDone(true)
})
.catch((err: any) => {
displayToast(err.reason as string, 'danger')
})
signTermsOfService(
{
accessToken,
termsOfServiceVersion: tosInfo?.latestTermsOfServiceVersion!,
},
{
onSuccess: () => {
setIsDone(true)
},
onError: err => {
displayToast(err.reason as string, 'danger')
},
},
)
}
} catch (err: any) {
displayToast(err.reason as string, 'danger')
Expand Down Expand Up @@ -95,7 +105,7 @@ export const TermsOfUsePage = (props: TermsOfUsePageProps) => {
onClick={onSignTermsOfUse}
disabled={isLoading || !isFormComplete}
>
Accept and Continue <IconSvg icon="arrowForward" />
Agree and Continue <IconSvg icon="arrowForward" />
</Button>
<TermsAndConditionsLink sx={buttonSx} />
</div>
Expand Down
Loading

0 comments on commit 6ff532b

Please sign in to comment.