Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: saml login no email, auth design fixups #9507

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/client/components/AuthenticationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import styled from '@emotion/styled'
import InviteDialog from './InviteDialog'

export const AUTH_DIALOG_WIDTH = 356
const AuthenticationDialog = styled(InviteDialog)({
alignItems: 'center',
paddingTop: 24,
paddingBottom: 24,
width: 356
width: AUTH_DIALOG_WIDTH
})

export default AuthenticationDialog
4 changes: 3 additions & 1 deletion packages/client/components/AuthenticationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import useAtmosphere from '../hooks/useAtmosphere'
import useDocumentTitle from '../hooks/useDocumentTitle'
import useRouter from '../hooks/useRouter'
import getValidRedirectParam from '../utils/getValidRedirectParam'
import {AUTH_DIALOG_WIDTH} from './AuthenticationDialog'
import GenericAuthentication, {AuthPageSlug, GotoAuthPage} from './GenericAuthentication'
import TeamInvitationWrapper from './TeamInvitationWrapper'

const CopyBlock = styled('div')({
marginBottom: 48,
width: 'calc(100vw - 16px)',
maxWidth: 500,
// must be no wider than the auth popup width to keep it looking clean
maxWidth: AUTH_DIALOG_WIDTH,
textAlign: 'center'
})

Expand Down
21 changes: 11 additions & 10 deletions packages/client/components/EmailPasswordAuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import RaisedButton from './RaisedButton'
import StyledTip from './StyledTip'

interface Props {
// used to determine the coordinates of the auth popup
getOffsetTop?: () => number
email: string
invitationToken: string | undefined | null
// is the primary login action (not secondary to Google Oauth)
Expand All @@ -38,9 +40,6 @@ interface Props {
goToPage?: (page: AuthPageSlug, params: string) => void
}

const FieldGroup = styled('div')({
margin: '16px 0'
})
const FieldBlock = styled('div')<{isSSO?: boolean}>(({isSSO}) => ({
margin: '0 0 1.25rem',
visibility: isSSO ? 'hidden' : undefined
Expand Down Expand Up @@ -90,7 +89,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
const isInternalAuthEnabled = window.__ACTION__.AUTH_INTERNAL_ENABLED
const isSSOAuthEnabled = window.__ACTION__.AUTH_SSO_ENABLED

const {isPrimary, isSignin, invitationToken, email, goToPage} = props
const {getOffsetTop, isPrimary, isSignin, invitationToken, email, goToPage} = props
const {location} = useRouter()
const params = new URLSearchParams(location.search)
const isSSODefault = isSSOAuthEnabled && Boolean(params.get('sso'))
Expand All @@ -105,7 +104,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
const {fields, onChange, setDirtyField, validateField} = useForm({
email: {
getDefault: () => email,
validate: validateEmail
validate: signInWithSSOOnly ? undefined : validateEmail
},
password: {
getDefault: () => '',
Expand Down Expand Up @@ -150,6 +149,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
const domain = getSSODomainFromEmail(email)!
const validSSOURL = domain === ssoDomain && ssoURL
const isProbablySSO = isSSO || !fields.password.value || validSSOURL
const top = getOffsetTop?.() || 56
let optimisticPopup
if (isProbablySSO) {
// Safari blocks all calls to window.open that are not triggered SYNCHRONOUSLY from an event
Expand All @@ -164,7 +164,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
optimisticPopup = window.open(
'',
'SSO',
getOAuthPopupFeatures({width: 385, height: 550, top: 64})
getOAuthPopupFeatures({width: 385, height: 576, top})
)
}
const url = validSSOURL || (await getSSOUrl(atmosphere, email))
Expand All @@ -173,7 +173,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
return false
}
submitMutation()
const response = await getTokenFromSSO(url)
const response = await getTokenFromSSO(url, top)
if ('error' in response) {
onError(new Error(response.error || 'Error logging in'))
return true
Expand All @@ -198,6 +198,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (submitting) return
onCompleted()
setDirtyField()
const {email: emailRes, password: passwordRes} = validateField()
if (emailRes.error) return
Expand Down Expand Up @@ -244,8 +245,8 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
<Form onSubmit={onSubmit}>
{error && <ErrorAlert message={error.message} />}
{isSSO && submitting && <HelpMessage>Continue through the login popup</HelpMessage>}
<FieldGroup>
<FieldBlock>
<div className={signInWithSSOOnly ? 'hidden' : 'mt-4 mb-4'}>
<FieldBlock isSSO={signInWithSSOOnly}>
<EmailInputField
autoFocus={!hasEmail}
{...fields.email}
Expand All @@ -263,7 +264,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
/>
</FieldBlock>
)}
</FieldGroup>
</div>
<Button size='medium' disabled={false} waiting={submitting}>
{isSignin ? SIGNIN_LABEL : CREATE_ACCOUNT_BUTTON_LABEL}
{signInWithSSOOnly ? ' with SSO' : ''}
Expand Down
23 changes: 16 additions & 7 deletions packages/client/components/GenericAuthentication.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {
SIGNIN_LABEL,
SIGNIN_SLUG
} from '../utils/constants'
import AuthenticationDialog from './AuthenticationDialog'
import AuthPrivacyFooter from './AuthPrivacyFooter'
import AuthenticationDialog from './AuthenticationDialog'
import DialogTitle from './DialogTitle'
import EmailPasswordAuthForm from './EmailPasswordAuthForm'
import ForgotPasswordPage from './ForgotPasswordPage'
import GoogleOAuthButtonBlock from './GoogleOAuthButtonBlock'
import MicrosoftOAuthButtonBlock from './MicrosoftOAuthButtonBlock'
import HorizontalSeparator from './HorizontalSeparator/HorizontalSeparator'
import MicrosoftOAuthButtonBlock from './MicrosoftOAuthButtonBlock'
import PlainButton from './PlainButton/PlainButton'
import SubmittedForgotPasswordPage from './SubmittedForgotPasswordPage'

Expand Down Expand Up @@ -70,12 +70,12 @@ const GenericAuthentication = (props: Props) => {
const {location} = useRouter()
const params = new URLSearchParams(location.search)
const email = params.get('email')

const authDialogRef = useRef<HTMLDivElement>(null)
const getOffsetTop = () => authDialogRef.current?.offsetTop || 0
const isGoogleAuthEnabled = window.__ACTION__.AUTH_GOOGLE_ENABLED
const isMicrosoftAuthEnabled = window.__ACTION__.AUTH_MICROSOFT_ENABLED
const isInternalAuthEnabled = window.__ACTION__.AUTH_INTERNAL_ENABLED
const isSSOAuthEnabled = window.__ACTION__.AUTH_SSO_ENABLED

if (page === 'forgot-password') {
return <ForgotPasswordPage goToPage={goToPage} />
}
Expand All @@ -97,7 +97,7 @@ const GenericAuthentication = (props: Props) => {
goToPage('forgot-password', `?email=${emailRef.current?.email()}`)
}
return (
<AuthenticationDialog>
<AuthenticationDialog ref={authDialogRef}>
<DialogTitle>{title}</DialogTitle>
<DialogSubTitle>
<span>{actionCopy}</span>
Expand All @@ -106,10 +106,18 @@ const GenericAuthentication = (props: Props) => {
</BrandedLink>
</DialogSubTitle>
{isGoogleAuthEnabled && (
<GoogleOAuthButtonBlock isCreate={isCreate} invitationToken={invitationToken} />
<GoogleOAuthButtonBlock
isCreate={isCreate}
invitationToken={invitationToken}
getOffsetTop={getOffsetTop}
/>
)}
{isMicrosoftAuthEnabled && (
<MicrosoftOAuthButtonBlock isCreate={isCreate} invitationToken={invitationToken} />
<MicrosoftOAuthButtonBlock
isCreate={isCreate}
invitationToken={invitationToken}
getOffsetTop={getOffsetTop}
/>
)}
{(isGoogleAuthEnabled || isMicrosoftAuthEnabled) &&
(isInternalAuthEnabled || isSSOAuthEnabled) && (
Expand All @@ -121,6 +129,7 @@ const GenericAuthentication = (props: Props) => {
isSignin={!isCreate}
invitationToken={invitationToken}
ref={emailRef}
getOffsetTop={getOffsetTop}
goToPage={goToPage}
/>
)}
Expand Down
12 changes: 7 additions & 5 deletions packages/client/components/GoogleOAuthButtonBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import styled from '@emotion/styled'
import clsx from 'clsx'
import React from 'react'
import useAtmosphere from '../hooks/useAtmosphere'
import useMutationProps from '../hooks/useMutationProps'
import useRouter from '../hooks/useRouter'
import logo from '../styles/theme/images/graphics/google.svg'
import GoogleClientManager from '../utils/GoogleClientManager'
import RaisedButton from './RaisedButton'
import StyledError from './StyledError'
import StyledTip from './StyledTip'
import logo from '../styles/theme/images/graphics/google.svg'
import RaisedButton from './RaisedButton'
import clsx from 'clsx'

interface Props {
invitationToken?: string
isCreate?: boolean
loginHint?: string
getOffsetTop?: () => number
}

const helpText = {
Expand All @@ -30,7 +31,7 @@ const HelpMessage = styled(StyledTip)({
})

const GoogleOAuthButtonBlock = (props: Props) => {
const {invitationToken, isCreate, loginHint} = props
const {invitationToken, isCreate, loginHint, getOffsetTop} = props
const {onError, error, submitting, onCompleted, submitMutation} = useMutationProps()
const atmosphere = useAtmosphere()
const {history, location} = useRouter()
Expand All @@ -43,7 +44,8 @@ const GoogleOAuthButtonBlock = (props: Props) => {
history,
location.search,
invitationToken,
loginHint
loginHint,
getOffsetTop
)
}
return (
Expand Down
6 changes: 4 additions & 2 deletions packages/client/components/MicrosoftOAuthButtonBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Props {
invitationToken?: string
isCreate?: boolean
loginHint?: string
getOffsetTop?: () => number
}

const helpText = {
Expand All @@ -30,7 +31,7 @@ const HelpMessage = styled(StyledTip)({
})

const MicrosoftOAuthButtonBlock = (props: Props) => {
const {invitationToken, isCreate, loginHint} = props
const {invitationToken, isCreate, loginHint, getOffsetTop} = props
const {onError, error, submitting, onCompleted, submitMutation} = useMutationProps()
const atmosphere = useAtmosphere()
const {history, location} = useRouter()
Expand All @@ -43,7 +44,8 @@ const MicrosoftOAuthButtonBlock = (props: Props) => {
history,
location.search,
invitationToken,
loginHint
loginHint,
getOffsetTop
)
}
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import styled from '@emotion/styled'
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import useDocumentTitle from '../hooks/useDocumentTitle'
import {TeamInvitationEmailCreateAccount_verifiedInvitation$key} from '../__generated__/TeamInvitationEmailCreateAccount_verifiedInvitation.graphql'
import useDocumentTitle from '../hooks/useDocumentTitle'
import AuthPrivacyFooter from './AuthPrivacyFooter'
import {AUTH_DIALOG_WIDTH} from './AuthenticationDialog'
import DialogContent from './DialogContent'
import DialogTitle from './DialogTitle'
import EmailPasswordAuthForm from './EmailPasswordAuthForm'
Expand All @@ -18,7 +19,7 @@ interface Props {
}

const StyledDialog = styled(InviteDialog)({
maxWidth: 356
maxWidth: AUTH_DIALOG_WIDTH
})

const TeamName = styled('span')({
Expand Down
3 changes: 2 additions & 1 deletion packages/client/components/TeamInvitationEmailSignin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import ForgotPasswordOneClick from './ForgotPasswordOneClick'
import InvitationCenteredCopy from './InvitationCenteredCopy'
import InvitationDialogCopy from './InvitationDialogCopy'
import InviteDialog from './InviteDialog'
import {AUTH_DIALOG_WIDTH} from './AuthenticationDialog'

interface Props {
invitationToken: string
verifiedInvitation: TeamInvitationEmailSignin_verifiedInvitation$key
}

const StyledDialog = styled(InviteDialog)({
maxWidth: 356
maxWidth: AUTH_DIALOG_WIDTH
})

const TeamName = styled('span')({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import InvitationCenteredCopy from './InvitationCenteredCopy'
import InvitationDialogCopy from './InvitationDialogCopy'
import InviteDialog from './InviteDialog'
import PlainButton from './PlainButton/PlainButton'
import {AUTH_DIALOG_WIDTH} from './AuthenticationDialog'

interface Props {
invitationToken: string
verifiedInvitation: TeamInvitationGoogleCreateAccount_verifiedInvitation$key
}

const StyledDialog = styled(InviteDialog)({
maxWidth: 356
maxWidth: AUTH_DIALOG_WIDTH
})

const StyledContent = styled(DialogContent)({
Expand Down
29 changes: 3 additions & 26 deletions packages/client/components/TeamInvitationWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,17 @@
import styled from '@emotion/styled'
import React, {ReactNode} from 'react'
import {PALETTE} from '../styles/paletteV3'
import Header from './AuthPage/Header'

const PageContainer = styled('div')({
alignItems: 'center',
backgroundColor: PALETTE.SLATE_200,
color: PALETTE.SLATE_700,
display: 'flex',
flexDirection: 'column',
maxWidth: '100%',
minHeight: '100vh'
})

const CenteredBlock = styled('div')({
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
flex: 1,
justifyContent: 'center',
maxWidth: '100%',
padding: '2rem 1rem',
width: '100%'
})

interface Props {
children: ReactNode
}

function TeamInvitationWrapper(props: Props) {
const {children} = props
return (
<PageContainer>
<div className='flex min-h-screen max-w-full flex-col items-center bg-slate-200 text-slate-700'>
<Header />
<CenteredBlock>{children}</CenteredBlock>
</PageContainer>
<div className='maxw-full flex w-full flex-1 flex-col items-center py-8 px-4'>{children}</div>
</div>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const OrgAuthenticationMetadata = (props: Props) => {
const optimisticPopup = window.open(
'',
'SSO',
getOAuthPopupFeatures({width: 385, height: 550, top: 64})
getOAuthPopupFeatures({width: 385, height: 576, top: 64})
)

// Get the Sign-on URL, which includes metadataURL in the RelayState
Expand Down
Loading
Loading