Skip to content

Commit

Permalink
fix: notifications - avoid causing redirects + auto accept forced sso…
Browse files Browse the repository at this point in the history
… migration + remove auth - user dialog + avoid redirect from user settings page
  • Loading branch information
Anty0 committed Mar 4, 2025
1 parent f53bbbe commit fba9084
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class V2UserController(
summary = "Get organization which manages user",
description = "Returns the organization that manages a given user or null",
)
@BypassForcedSsoAuthentication
@OpenApiHideFromPublicDocs
fun getManagedBy(): PrivateOrganizationModel? {
val userAccount = authenticationFacade.authenticatedUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,14 @@ class AuthProviderChangeController(
}

@DeleteMapping("")
@Operation(summary = "Remove current third party authentication provider")
@Operation(summary = "Initiate provider change to remove current third party authentication provider")
@AllowApiAccess(AuthTokenType.ONLY_PAT)
@BypassForcedSsoAuthentication
@RequiresSuperAuthentication
@Transactional
fun deleteCurrentAuthProvider(): JwtAuthenticationResponse {
fun deleteCurrentAuthProvider() {
val user = authenticationFacade.authenticatedUserEntity
authProviderChangeService.removeCurrent(user)
userAccountService.invalidateTokens(user)
return JwtAuthenticationResponse(
jwtService.emitToken(authenticationFacade.authenticatedUser.id, true),
)
authProviderChangeService.initiateRemove(user)
}

@GetMapping("/change")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,13 @@ class AuthProviderChangeService(
self.apply(AuthProviderChangeRequest().from(user, data))
}

fun removeCurrent(user: UserAccount) {
if (user.password == null) {
throw BadRequestException(Message.USER_MISSING_PASSWORD)
}
fun initiateRemove(user: UserAccount) {
val data =
AuthProviderChangeData(
UserAccount.AccountType.LOCAL,
null,
)
self.apply(AuthProviderChangeRequest().from(user, data))
self.save(user, data)
}

@Transactional
Expand All @@ -120,7 +117,11 @@ class AuthProviderChangeService(
fun apply(req: AuthProviderChangeRequest) {
val userAccount = req.userAccount ?: return throw NotFoundException()
if (userAccount.accountType === UserAccount.AccountType.MANAGED) {
throw AuthenticationException(Message.OPERATION_UNAVAILABLE_FOR_ACCOUNT_TYPE)
throw BadRequestException(Message.OPERATION_UNAVAILABLE_FOR_ACCOUNT_TYPE)
}

if (req.accountType == UserAccount.AccountType.LOCAL && userAccount.password == null) {
throw BadRequestException(Message.USER_MISSING_PASSWORD)
}

userAccount.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export const NotificationsPopup: React.FC<NotificationsPopupProps> = ({
});
},
},
fetchOptions: {
disableAutoErrorHandle: true,
disableAuthRedirect: true,
},
});

const markSeenMutation = useApiMutation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const NotificationsTopBarButton: React.FC = () => {
staleTime: Infinity,
cacheTime: Infinity,
},
fetchOptions: {
disableAutoErrorHandle: true,
disableAuthRedirect: true,
disableErrorNotification: true,
},
});

const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
Expand Down
190 changes: 55 additions & 135 deletions webapp/src/component/security/AcceptAuthProviderChangeView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useHistory } from 'react-router-dom';
import { T, useTranslate } from '@tolgee/react';
import { Box, Paper, styled, Typography } from '@mui/material';
import { Box, styled } from '@mui/material';

import { LINKS } from 'tg.constants/links';
import { messageService } from 'tg.service/MessageService';
Expand All @@ -11,10 +11,9 @@ import { useWindowTitle } from 'tg.hooks/useWindowTitle';
import LoadingButton from 'tg.component/common/form/LoadingButton';
import { FullPageLoading } from 'tg.component/common/FullPageLoading';
import { TranslatedError } from 'tg.translationTools/TranslatedError';
import React from 'react';
import React, { Fragment, useEffect, useState } from 'react';
import { useIsSsoMigrationRequired } from 'tg.globalContext/helpers';

export const FULL_PAGE_BREAK_POINT = '(max-width: 700px)';
import { AuthProviderChangeBody } from 'tg.component/security/AuthProviderChangeBody';

const StyledContainer = styled(Box)`
display: grid;
Expand All @@ -28,18 +27,6 @@ const StyledContent = styled(Box)`
gap: 32px;
`;

const StyledPaper = styled(Paper)`
padding: 60px;
display: grid;
gap: 32px;
background: ${({ theme }) => theme.palette.tokens.background['paper-1']};
@media ${FULL_PAGE_BREAK_POINT} {
padding: 10px;
box-shadow: none;
background: transparent;
}
`;

const AcceptAuthProviderChangeView: React.FC = () => {
const history = useHistory();
const { t } = useTranslate();
Expand Down Expand Up @@ -130,136 +117,69 @@ const AcceptAuthProviderChangeView: React.FC = () => {
}

const accountType = authProviderChangeInfo.data?.accountType;
const authType = authProviderChangeInfo.data?.authType ?? 'NONE';
const authTypeOld = authProviderCurrentInfo.data?.authType ?? 'NONE';
const ssoDomain = authProviderChangeInfo.data?.ssoDomain ?? '';
const params = {
authType,
authTypeOld,
ssoDomain,
b: <b />,
br: <br />,
};

const willBeManaged = accountType === 'MANAGED';

let titleText: React.ReactNode | null;
let infoText: React.ReactNode;

switch (true) {
case willBeManaged && isSsoMigrationRequired:
// Migrating to SSO; migration is forced
titleText = null;
infoText = (
<T
keyName="accept_auth_provider_change_description_managed_sso"
params={params}
/>
);
break;
case willBeManaged:
// Migrating to SSO; migration is voluntary
titleText = null;
infoText = (
<T
keyName="accept_auth_provider_change_description_managed_sso_optional"
params={params}
/>
);
break;
case authTypeOld === 'NONE':
// Currently user has no third-party provider
titleText = (
<T
keyName="accept_auth_provider_change_title_no_existing_provider"
params={params}
/>
);
infoText = (
<T
keyName="accept_auth_provider_change_description_no_existing_provider"
params={params}
/>
);
break;
case authType === 'NONE':
// User is removing third-party provider
titleText = (
<T
keyName="accept_auth_provider_change_title_remove_existing_provider"
params={params}
/>
);
infoText = (
<T
keyName="accept_auth_provider_change_description_remove_existing_provider"
params={params}
/>
);
break;
default:
// From one third-party provider to another third-party provider
titleText = (
<T keyName="accept_auth_provider_change_title" params={params} />
);
infoText = (
<T keyName="accept_auth_provider_change_description" params={params} />
);
break;
}
const [autoAccepted, setAutoAccepted] = useState(false);
useEffect(() => {
// Auto-accept forced sso migration to avoid second confirmation dialog
if (
authProviderChangeInfo.data &&
willBeManaged &&
isSsoMigrationRequired &&
!autoAccepted
) {
setAutoAccepted(true);
handleAccept();
}
}, [
authProviderChangeInfo,
willBeManaged,
isSsoMigrationRequired,
autoAccepted,
]);

if (!authProviderChangeInfo.data || authProviderCurrentInfo.isLoading) {
return <FullPageLoading />;
}

const buttons = (
<Fragment>
<LoadingButton
loading={acceptChange.isLoading || rejectChange.isLoading}
variant="contained"
color="primary"
onClick={handleAccept}
data-cy="accept-auth-provider-change-accept"
>
{willBeManaged
? t('accept_auth_provider_change_accept')
: t('accept_auth_provider_change_accept_non_managed')}
</LoadingButton>
{(!willBeManaged || !isSsoMigrationRequired) && (
<LoadingButton
loading={acceptChange.isLoading || rejectChange.isLoading}
variant="outlined"
onClick={handleReject}
data-cy="accept-auth-provider-change-decline"
>
{t('accept_auth_provider_change_decline')}
</LoadingButton>
)}
</Fragment>
);

return (
<DashboardPage hideQuickStart>
<StyledContainer>
<StyledContent>
<StyledPaper>
{titleText && (
<Typography variant="h3" sx={{ textAlign: 'center' }}>
{titleText}
</Typography>
)}

<Box display="grid" gap="24px" justifyItems="center">
<Box
textAlign="center"
data-cy="accept-auth-provider-change-info-text"
>
{infoText}
</Box>
<Box
display="flex"
gap={3}
flexWrap="wrap"
justifyContent="center"
>
<LoadingButton
loading={acceptChange.isLoading || rejectChange.isLoading}
variant="contained"
color="primary"
onClick={handleAccept}
data-cy="accept-auth-provider-change-accept"
>
{willBeManaged
? t('accept_auth_provider_change_accept')
: t('accept_auth_provider_change_accept_non_managed')}
</LoadingButton>
{(!willBeManaged || !isSsoMigrationRequired) && (
<LoadingButton
loading={acceptChange.isLoading || rejectChange.isLoading}
variant="outlined"
onClick={handleReject}
data-cy="accept-auth-provider-change-decline"
>
{t('accept_auth_provider_change_decline')}
</LoadingButton>
)}
</Box>
</Box>
</StyledPaper>
<AuthProviderChangeBody
willBeManaged={willBeManaged}
authType={authProviderChangeInfo.data?.authType ?? 'NONE'}
authTypeOld={authProviderCurrentInfo.data?.authType ?? 'NONE'}
ssoDomain={authProviderChangeInfo.data?.ssoDomain ?? ''}
>
{buttons}
</AuthProviderChangeBody>
</StyledContent>
</StyledContainer>
</DashboardPage>
Expand Down
Loading

0 comments on commit fba9084

Please sign in to comment.