Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fix alias check on account creation with Google
  • Loading branch information
nickgros committed Jan 22, 2025
1 parent bee8f32 commit f26b337
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 64 deletions.
167 changes: 103 additions & 64 deletions apps/SageAccountWeb/src/components/RegisterAccount1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ import {
LastLoginInfo,
RegisterPageLogoutPrompt,
SynapseClient,
SynapseClientError,
SynapseConstants,
useApplicationSessionContext,
useLastLoginInfo,
useSynapseContext,
} from 'synapse-react-client'
import { useAppContext } from '../AppContext'
import GoogleLogo from '../assets/g-logo.png'
import {
VALID_USERNAME_DESCRIPTION,
validateAlias,
} from '../utils/validateAlias'
import { BackButton } from './BackButton'
import { EmailConfirmationPage } from './EmailConfirmationPage'
import { SourceAppLogo } from './SourceApp'
Expand All @@ -42,6 +47,34 @@ export enum Pages {
GOOGLE_REGISTRATION,
}

function BackButtonForPage(props: {
page: Pages
setPage: (page: Pages) => void
}) {
const { page, setPage } = props
switch (page) {
case Pages.CHOOSE_REGISTRATION:
return <BackButton to={'/authenticated/myaccount'} />
case Pages.EMAIL_REGISTRATION:
case Pages.GOOGLE_REGISTRATION:
return <BackButton onClick={() => setPage(Pages.CHOOSE_REGISTRATION)} />
default:
return <></>
}
}

function handleError(e: unknown) {
if (e instanceof SynapseClientError) {
displayToast(e.reason, 'danger')
} else if (e instanceof Error) {
displayToast(e.message, 'danger')
} else {
// This should never happen
console.error(e)
displayToast(JSON.stringify(e), 'danger')
}
}

export const RegisterAccount1 = () => {
const { accessToken } = useSynapseContext()
const isSignedIn = !!accessToken
Expand All @@ -51,6 +84,7 @@ export const RegisterAccount1 = () => {
const [isLoading, setIsLoading] = useState(false)
const [email, setEmail] = useState('')
const [username, setUsername] = useState('')
const [isUsernameValid, setIsUsernameValid] = useState(true)
const [page, setPage] = useState(Pages.CHOOSE_REGISTRATION)
const { appId: sourceAppId, friendlyName: sourceAppName } = useSourceApp()
const [membershipInvitationEmail, setMembershipInvitationEmail] =
Expand Down Expand Up @@ -106,18 +140,6 @@ export const RegisterAccount1 = () => {
borderColor: '#EAECEE',
}

const BackButtonForPage = () => {
switch (page) {
case Pages.CHOOSE_REGISTRATION:
return <BackButton to={'/authenticated/myaccount'} />
case Pages.EMAIL_REGISTRATION:
case Pages.GOOGLE_REGISTRATION:
return <BackButton onClick={() => setPage(Pages.CHOOSE_REGISTRATION)} />
default:
return <></>
}
}

const onSendRegistrationInfo = async (event: SyntheticEvent) => {
event.preventDefault()
if (!email) {
Expand All @@ -129,8 +151,8 @@ export const RegisterAccount1 = () => {
const callbackUrl = `${window.location.protocol}//${window.location.host}/register2?emailValidationSignedToken=`
await SynapseClient.registerAccountStep1({ email }, callbackUrl)
setPage(Pages.EMAIL_REGISTRATION_THANK_YOU)
} catch (err: any) {
displayToast(err.reason as string, 'danger')
} catch (e: unknown) {
handleError(e)
} finally {
setIsLoading(false)
}
Expand All @@ -148,36 +170,31 @@ export const RegisterAccount1 = () => {
alias: username,
type: AliasType.USER_NAME,
})
if (!aliasCheckResponse.available) {
displayToast('Sorry, that username has already been taken.', 'danger')
} else if (!aliasCheckResponse.valid) {
displayToast('Sorry, that username is not valid.', 'danger')
} else {
// Looks good! Go to Google oauth account creation flow
// 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.LAST_PLACE_LOCALSTORAGE_KEY,
`${SynapseClient.getRootURL()}authenticated/signTermsOfUse`,
)
const redirectUrl = `${SynapseClient.getRootURL()}?provider=${
SynapseConstants.OAUTH2_PROVIDERS.GOOGLE
}`
SynapseClient.oAuthUrlRequest(
SynapseConstants.OAUTH2_PROVIDERS.GOOGLE,
redirectUrl,
{ registrationUsername: username },
)
.then((data: any) => {
const authUrl = data.authorizationUrl
window.location.assign(authUrl)
})
.catch((err: any) => {
displayToast(err.reason as string, 'danger')
})
try {
validateAlias(aliasCheckResponse)
} catch (e) {
setIsUsernameValid(false)
// rethrow for generic error handler
throw e
}
} catch (err: any) {
displayToast(err.reason as string, 'danger')
// Looks good! Go to Google oauth account creation flow
// 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.LAST_PLACE_LOCALSTORAGE_KEY,
`${SynapseClient.getRootURL()}authenticated/signTermsOfUse`,
)
const redirectUrl = `${SynapseClient.getRootURL()}?provider=${
SynapseConstants.OAUTH2_PROVIDERS.GOOGLE
}`
const { authorizationUrl } = await SynapseClient.oAuthUrlRequest(
SynapseConstants.OAUTH2_PROVIDERS.GOOGLE,
redirectUrl,
{ registrationUsername: username },
)
window.location.assign(authorizationUrl)
} catch (e: unknown) {
handleError(e)
} finally {
setIsLoading(false)
}
Expand All @@ -194,7 +211,7 @@ export const RegisterAccount1 = () => {
<Box mx={'auto'} mt={15} width={'fit-content'}>
<RegisterPageLogoutPrompt
onLogout={() => {
sessionContext.refreshSession()
void sessionContext.refreshSession()
}}
logo={<SourceAppLogo sx={{ width: '100%' }} />}
/>
Expand All @@ -210,7 +227,7 @@ export const RegisterAccount1 = () => {
{page !== Pages.EMAIL_REGISTRATION_THANK_YOU && (
<>
<Box sx={{ py: 10, px: 8, height: '100%', position: 'relative' }}>
<BackButtonForPage />
<BackButtonForPage page={page} setPage={setPage} />
<Box
display="flex"
flexDirection="column"
Expand Down Expand Up @@ -269,9 +286,9 @@ export const RegisterAccount1 = () => {
)
}
value={email || ''}
onKeyPress={(e: any) => {
onKeyDown={e => {
if (e.key === 'Enter') {
onSendRegistrationInfo(e)
void onSendRegistrationInfo(e)
}
}}
/>
Expand All @@ -290,9 +307,11 @@ export const RegisterAccount1 = () => {
<Button
sx={buttonSx}
variant="contained"
onClick={onSendRegistrationInfo}
onClick={e => {
void onSendRegistrationInfo(e)
}}
type="button"
disabled={email && !isLoading ? false : true}
disabled={!(email && !isLoading)}
>
Continue
</Button>
Expand All @@ -312,21 +331,32 @@ export const RegisterAccount1 = () => {
id="username"
name="username"
required
onChange={e => setUsername(e.target.value)}
error={!isUsernameValid}
helperText={
isUsernameValid
? undefined
: VALID_USERNAME_DESCRIPTION
}
onChange={e => {
setUsername(e.target.value)
}}
value={username || ''}
onKeyPress={(e: any) => {
onKeyDown={e => {
if (e.key === 'Enter') {
onSignUpWithGoogle(e)
setIsUsernameValid(true)
void onSignUpWithGoogle(e)
}
}}
/>
</StyledFormControl>
<Button
sx={buttonSx}
variant="contained"
onClick={onSignUpWithGoogle}
onClick={e => {
void onSignUpWithGoogle(e)
}}
type="button"
disabled={username && !isLoading ? false : true}
disabled={!(username && !isLoading)}
>
Continue
</Button>
Expand All @@ -343,25 +373,34 @@ export const RegisterAccount1 = () => {
<Typography variant="headline2" sx={{ marginTop: '95px' }}>
Create an Account
</Typography>
{sourceAppId != SYNAPSE_SOURCE_APP_ID && (
<Typography variant="body1" sx={{ marginBottom: '20px' }}>
Your <strong>{sourceAppName}</strong> account is also a{' '}
<strong>Synapse account</strong>. You can also use it to
access many other resources from Sage Bionetworks.
</Typography>
{page !== Pages.GOOGLE_REGISTRATION && (
<>
{sourceAppId != SYNAPSE_SOURCE_APP_ID && (
<Typography variant="body1" sx={{ marginBottom: '20px' }}>
Your <strong>{sourceAppName}</strong> account is also a{' '}
<strong>Synapse account</strong>. You can also use it to
access many other resources from Sage Bionetworks.
</Typography>
)}
{sourceAppId === SYNAPSE_SOURCE_APP_ID && (
<Typography variant="body1" sx={{ marginBottom: '20px' }}>
Your <strong>Synapse</strong> account can also be used
to access many other resources from Sage Bionetworks.
</Typography>
)}
</>
)}
{sourceAppId === SYNAPSE_SOURCE_APP_ID && (
{page === Pages.GOOGLE_REGISTRATION && (
<Typography variant="body1" sx={{ marginBottom: '20px' }}>
Your <strong>Synapse</strong> account can also be used to
access many other resources from Sage Bionetworks.
{VALID_USERNAME_DESCRIPTION}
</Typography>
)}
<Link
color="primary"
component={RouterLink}
to="/sageresources"
>
More about Synapse account
More about Synapse accounts
</Link>
</Box>
</>
Expand Down
24 changes: 24 additions & 0 deletions apps/SageAccountWeb/src/utils/validateAlias.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { validateAlias } from './validateAlias'

describe('validateAlias', () => {
it('does not throw if alias is valid', () => {
validateAlias({ valid: true, available: true })
})
it('throws if alias is taken', () => {
expect(() => validateAlias({ valid: true, available: false })).toThrowError(
'Sorry, that username has already been taken.',
)
})
it('throws if alias is invalid', () => {
expect(() => validateAlias({ valid: false, available: true })).toThrowError(
'Sorry, that username is not valid. User names can contain letters, numbers, dot (.), dash (-) and underscore (_) and must be at least 3 characters long.',
)
})
it('throws if invalid and unavailable, warning about validity', () => {
expect(() =>
validateAlias({ valid: false, available: false }),
).toThrowError(
'Sorry, that username is not valid. User names can contain letters, numbers, dot (.), dash (-) and underscore (_) and must be at least 3 characters long.',
)
})
})
18 changes: 18 additions & 0 deletions apps/SageAccountWeb/src/utils/validateAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AliasCheckResponse } from '@sage-bionetworks/synapse-types'

// Requirements for a valid username (set by backend)
// https://github.com/Sage-Bionetworks/Synapse-Repository-Services/blob/f54c903e6e724b9567ebfa365200ef7f69b807a0/lib/models/src/main/java/org/sagebionetworks/repo/model/principal/AliasEnum.java#L15

export const VALID_USERNAME_DESCRIPTION =
'User names can contain letters, numbers, dot (.), dash (-) and underscore (_) and must be at least 3 characters long.'

export function validateAlias(aliasCheckResponse: AliasCheckResponse) {
if (!aliasCheckResponse.valid) {
throw new Error(
`Sorry, that username is not valid. ${VALID_USERNAME_DESCRIPTION}`,
)
}
if (!aliasCheckResponse.available) {
throw new Error('Sorry, that username has already been taken.')
}
}

0 comments on commit f26b337

Please sign in to comment.