Skip to content

Commit

Permalink
feat: saml login no email, auth design fixups (#9507)
Browse files Browse the repository at this point in the history
* feat: saml login no email, auth design fixups

Signed-off-by: Matt Krick <[email protected]>

* fixup: remove console log

Signed-off-by: Matt Krick <[email protected]>

---------

Signed-off-by: Matt Krick <[email protected]>
  • Loading branch information
mattkrick authored Mar 6, 2024
1 parent 6762ebc commit 4ce391e
Show file tree
Hide file tree
Showing 22 changed files with 97 additions and 114 deletions.
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

0 comments on commit 4ce391e

Please sign in to comment.