Skip to content

Commit 6293c33

Browse files
authored
chore: add refresh token and error to user's external auth page (#13380)
* chore: add story for failed refresh error * chore: add refresh icon to tokens that can refresh
1 parent 5b78ec9 commit 6293c33

File tree

2 files changed

+101
-13
lines changed

2 files changed

+101
-13
lines changed

site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.stories.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,33 @@ export const Unauthenticated: Story = {
6060
},
6161
},
6262
};
63+
64+
export const Failed: Story = {
65+
args: {
66+
...meta.args,
67+
auths: {
68+
providers: [MockGithubExternalProvider],
69+
links: [
70+
{
71+
...MockGithubAuthLink,
72+
validate_error: "Failed to refresh token",
73+
},
74+
],
75+
},
76+
},
77+
};
78+
79+
export const NoRefresh: Story = {
80+
args: {
81+
...meta.args,
82+
auths: {
83+
providers: [MockGithubExternalProvider],
84+
links: [
85+
{
86+
...MockGithubAuthLink,
87+
has_refresh_token: false,
88+
},
89+
],
90+
},
91+
},
92+
};

site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx

+71-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import { useTheme } from "@emotion/react";
2+
import AutorenewIcon from "@mui/icons-material/Autorenew";
13
import LoadingButton from "@mui/lab/LoadingButton";
4+
import Badge from "@mui/material/Badge";
25
import Divider from "@mui/material/Divider";
6+
import { styled } from "@mui/material/styles";
37
import Table from "@mui/material/Table";
48
import TableBody from "@mui/material/TableBody";
59
import TableCell from "@mui/material/TableCell";
610
import TableContainer from "@mui/material/TableContainer";
711
import TableHead from "@mui/material/TableHead";
812
import TableRow from "@mui/material/TableRow";
13+
import Tooltip from "@mui/material/Tooltip";
914
import visuallyHidden from "@mui/utils/visuallyHidden";
1015
import { type FC, useState, useCallback, useEffect } from "react";
1116
import { useQuery } from "react-query";
@@ -104,13 +109,33 @@ interface ExternalAuthRowProps {
104109
onValidateExternalAuth: () => void;
105110
}
106111

112+
const StyledBadge = styled(Badge)(({ theme }) => ({
113+
"& .MuiBadge-badge": {
114+
// Make a circular background for the icon. Background provides contrast, with a thin
115+
// border to separate it from the avatar image.
116+
backgroundColor: `${theme.palette.background.paper}`,
117+
borderStyle: "solid",
118+
borderColor: `${theme.palette.secondary.main}`,
119+
borderWidth: "thin",
120+
121+
// Override the default minimum sizes, as they are larger than what we want.
122+
minHeight: "0px",
123+
minWidth: "0px",
124+
// Override the default "height", which is usually set to some constant value.
125+
height: "auto",
126+
// Padding adds some room for the icon to live in.
127+
padding: "0.1em",
128+
},
129+
}));
130+
107131
const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
108132
app,
109133
unlinked,
110134
link,
111135
onUnlinkExternalAuth,
112136
onValidateExternalAuth,
113137
}) => {
138+
const theme = useTheme();
114139
const name = app.display_name || app.id || app.type;
115140
const authURL = "/external-auth/" + app.id;
116141

@@ -125,22 +150,55 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
125150
? externalAuth.authenticated
126151
: link?.authenticated ?? false;
127152

153+
let avatar = app.display_icon ? (
154+
<Avatar src={app.display_icon} variant="square" fitImage size="sm" />
155+
) : (
156+
<Avatar>{name}</Avatar>
157+
);
158+
159+
// If the link is authenticated and has a refresh token, show that it will automatically
160+
// attempt to authenticate when the token expires.
161+
if (link?.has_refresh_token && authenticated) {
162+
avatar = (
163+
<StyledBadge
164+
anchorOrigin={{
165+
vertical: "bottom",
166+
horizontal: "right",
167+
}}
168+
color="default"
169+
overlap="circular"
170+
badgeContent={
171+
<Tooltip
172+
title="Authentication token will automatically refresh when expired."
173+
placement="right"
174+
>
175+
<AutorenewIcon
176+
sx={{
177+
fontSize: "1em",
178+
}}
179+
/>
180+
</Tooltip>
181+
}
182+
>
183+
{avatar}
184+
</StyledBadge>
185+
);
186+
}
187+
128188
return (
129189
<TableRow key={app.id}>
130190
<TableCell>
131-
<AvatarData
132-
title={name}
133-
avatar={
134-
app.display_icon && (
135-
<Avatar
136-
src={app.display_icon}
137-
variant="square"
138-
fitImage
139-
size="sm"
140-
/>
141-
)
142-
}
143-
/>
191+
<AvatarData title={name} avatar={avatar} />
192+
{link?.validate_error && (
193+
<>
194+
<span
195+
css={{ paddingLeft: "1em", color: theme.palette.error.light }}
196+
>
197+
Error:{" "}
198+
</span>
199+
{link?.validate_error}
200+
</>
201+
)}
144202
</TableCell>
145203
<TableCell css={{ textAlign: "right" }}>
146204
<LoadingButton

0 commit comments

Comments
 (0)