Merge pull request #592 from ONLYOFFICE/bugfix/oauth2-scope

Bugfix/oauth2 scope
This commit is contained in:
Alexey Bannov 2024-08-20 10:32:37 +03:00 committed by GitHub
commit a77fd92d09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 137 additions and 25 deletions

View File

@ -242,7 +242,6 @@ const StyledProperties = styled.div`
gap: 4px; gap: 4px;
.property-tag { .property-tag {
background: red;
max-width: 195px; max-width: 195px;
margin: 0; margin: 0;
background: ${(props) => props.theme.infoPanel.details.tagBackground}; background: ${(props) => props.theme.infoPanel.details.tagBackground};

View File

@ -56,6 +56,8 @@ const ScopesBlock = ({
) => { ) => {
const isChecked = checkedScopes.includes(name); const isChecked = checkedScopes.includes(name);
const isWrite = type === "write";
if (!isChecked) { if (!isChecked) {
setFilteredScopes((val) => { setFilteredScopes((val) => {
val[group].isChecked = true; val[group].isChecked = true;
@ -88,6 +90,8 @@ const ScopesBlock = ({
setCheckedScopes((val) => val.filter((v) => v !== name)); setCheckedScopes((val) => val.filter((v) => v !== name));
} }
if (isWrite) onAddScope("scopes", name.replace("write", "read"));
onAddScope("scopes", name); onAddScope("scopes", name);
}; };

View File

@ -25,6 +25,7 @@ import { TTranslation } from "@docspace/shared/types";
import { ContextMenuModel } from "@docspace/shared/components/context-menu"; import { ContextMenuModel } from "@docspace/shared/components/context-menu";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore"; import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { Tag } from "@docspace/shared/components/tag";
const StyledContainer = styled.div<{ const StyledContainer = styled.div<{
showDescription: boolean; showDescription: boolean;
@ -118,6 +119,23 @@ const StyledContainer = styled.div<{
background: ${(props) => props.theme.oauth.infoDialog.separatorColor}; background: ${(props) => props.theme.oauth.infoDialog.separatorColor};
} }
} }
.property-tag_list {
display: flex;
flex-wrap: wrap;
gap: 4px;
.property-tag {
max-width: 195px;
margin: 0;
background: ${(props) => props.theme.infoPanel.details.tagBackground};
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
`; `;
StyledContainer.defaultProps = { theme: Base }; StyledContainer.defaultProps = { theme: Base };
@ -288,7 +306,6 @@ const InfoDialog = ({
)} )}
</> </>
)} )}
<Text <Text
className="block-header" className="block-header"
fontSize="14px" fontSize="14px"
@ -299,7 +316,6 @@ const InfoDialog = ({
> >
{t("Common:Website")} {t("Common:Website")}
</Text> </Text>
<Link <Link
fontSize="13px" fontSize="13px"
lineHeight="15px" lineHeight="15px"
@ -311,7 +327,21 @@ const InfoDialog = ({
> >
{client?.websiteUrl} {client?.websiteUrl}
</Link> </Link>
<Text
className="block-header"
fontSize="14px"
lineHeight="16px"
fontWeight="600"
noSelect
truncate
>
{t("Scopes")}
</Text>{" "}
<ScopeList
selectedScopes={client?.scopes || []}
scopes={scopeList || []}
t={t}
/>
<Text <Text
className="block-header" className="block-header"
fontSize="14px" fontSize="14px"
@ -322,11 +352,16 @@ const InfoDialog = ({
> >
{t("Access")} {t("Access")}
</Text> </Text>
<ScopeList <div className="property-tag_list">
selectedScopes={client?.scopes || []} {client?.scopes.map((scope) => (
scopes={scopeList || []} <Tag
t={t} key={scope}
/> tag={scope}
className="property-tag"
label={scope}
/>
))}
</div>
{isProfile && ( {isProfile && (
<> <>
<Text <Text
@ -351,7 +386,6 @@ const InfoDialog = ({
</Text> </Text>
</> </>
)} )}
<Text <Text
className="block-header" className="block-header"
fontSize="14px" fontSize="14px"
@ -362,7 +396,6 @@ const InfoDialog = ({
> >
{t("SupportAndLegalInfo")} {t("SupportAndLegalInfo")}
</Text> </Text>
<Text <Text
className="privacy-block" className="privacy-block"
fontSize="13px" fontSize="13px"

View File

@ -27,6 +27,8 @@ const StyledImage = styled.img`
height: 32px; height: 32px;
border-radius: 3px; border-radius: 3px;
object-fit: cover;
`; `;
interface NameCellProps { interface NameCellProps {

View File

@ -21,9 +21,11 @@ import {
import { UserStore } from "@docspace/shared/store/UserStore"; import { UserStore } from "@docspace/shared/store/UserStore";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore"; import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { introspectDeveloperToken } from "@docspace/shared/api/oauth";
import { FieldContainer } from "@docspace/shared/components/field-container";
const StyledContainer = styled.div` const StyledContainer = styled.div`
p { .warning-text {
margin-bottom: 16px; margin-bottom: 16px;
} }
`; `;
@ -42,11 +44,15 @@ const GenerateDeveloperTokenDialog = ({
// const {} = useTranslation(["OAuth", "Common"]); // const {} = useTranslation(["OAuth", "Common"]);
const [token, setToken] = React.useState(""); const [token, setToken] = React.useState("");
const [isValidToken, setIsValidToken] = React.useState(false);
const [tokenError, setTokenError] = React.useState("");
const [requestRunning, setRequestRunning] = React.useState(false); const [requestRunning, setRequestRunning] = React.useState(false);
const timerRef = React.useRef<null | NodeJS.Timeout>(null);
const onRevoke = async () => { const onRevoke = async () => {
if (!token || !client || requestRunning) return; if (!token || !isValidToken || !client || requestRunning) return;
try { try {
const { clientId, clientSecret } = client; const { clientId, clientSecret } = client;
@ -66,9 +72,42 @@ const GenerateDeveloperTokenDialog = ({
} }
}; };
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
if (timerRef.current) {
clearTimeout(timerRef.current);
}
if (!value) {
setIsValidToken(false);
setTokenError("");
setToken("");
return;
}
timerRef.current = setTimeout(async () => {
try {
const data = await introspectDeveloperToken(value);
if (!data) return;
const { active, client_id: clientId } = data;
if (active && clientId === client?.clientId) {
setIsValidToken(true);
setTokenError("");
return;
}
setIsValidToken(false);
setTokenError("Invalid token");
} catch (err) {
setIsValidToken(false);
setTokenError("Invalid token");
}
}, 200);
setToken(value); setToken(value);
}; };
@ -89,16 +128,23 @@ const GenerateDeveloperTokenDialog = ({
<ModalDialog.Header>Revoke developer token</ModalDialog.Header> <ModalDialog.Header>Revoke developer token</ModalDialog.Header>
<ModalDialog.Body> <ModalDialog.Body>
<StyledContainer> <StyledContainer>
<Text>Warning text</Text> <Text className="warning-text">Warning text</Text>
<TextInput <FieldContainer
value={token} hasError={!!tokenError}
scale errorMessage={tokenError}
placeholder="Enter developer token" removeMargin
type={InputType.text} >
size={InputSize.base} <TextInput
onChange={onChange} value={token}
maxLength={10000} scale
/> placeholder="Enter developer token"
type={InputType.text}
size={InputSize.base}
onChange={onChange}
maxLength={10000}
hasError={!!tokenError}
/>
</FieldContainer>
</StyledContainer> </StyledContainer>
</ModalDialog.Body> </ModalDialog.Body>
<ModalDialog.Footer> <ModalDialog.Footer>
@ -107,7 +153,7 @@ const GenerateDeveloperTokenDialog = ({
primary primary
scale scale
onClick={onRevoke} onClick={onRevoke}
isDisabled={!token} isDisabled={!token || !isValidToken}
isLoading={requestRunning} isLoading={requestRunning}
size={ButtonSize.small} size={ButtonSize.small}
/> />

View File

@ -12,6 +12,7 @@ import {
TConsentData, TConsentData,
TConsentList, TConsentList,
TGenerateDeveloperToken, TGenerateDeveloperToken,
TIntrospectDeveloperToken,
} from "../../utils/oauth/types"; } from "../../utils/oauth/types";
export const getClient = async (clientId: string): Promise<IClientProps> => { export const getClient = async (clientId: string): Promise<IClientProps> => {
@ -273,3 +274,14 @@ export const revokeDeveloperToken = (
true, true,
); );
}; };
export const introspectDeveloperToken = (token: string) => {
const params = new URLSearchParams();
params.append("token", token);
return request<TIntrospectDeveloperToken>(
{ method: "post", url: "/oauth2/introspect", data: params },
false,
true,
);
};

View File

@ -157,3 +157,19 @@ export type TGenerateDeveloperToken = {
scope: string; scope: string;
token_type: string; token_type: string;
}; };
export type TIntrospectDeveloperToken = {
active: boolean;
sub: string;
aud: string[];
nbf: string;
scope: string;
iss: string;
exp: number;
iat: number;
jti: string;
tid: number;
cid: string;
client_id: string;
token_type: string;
};