mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
fix: limit creation of other users PATs (adapting) (#3019)
https://linear.app/unleash/issue/2-656/limit-the-ability-of-creating-a-token-on-behalf-of-another-user Adapts to the refactor that reverts the initial experimental idea of Service Accounts before they existed in the current implementation: Managing other user's PATs.
This commit is contained in:
parent
0a8330f55d
commit
054c590813
@ -29,7 +29,7 @@ import {
|
||||
IPersonalAPITokenFormErrors,
|
||||
PersonalAPITokenForm,
|
||||
} from 'component/user/Profile/PersonalAPITokensTab/CreatePersonalAPIToken/PersonalAPITokenForm/PersonalAPITokenForm';
|
||||
import { usePersonalAPITokensApi } from 'hooks/api/actions/usePersonalAPITokensApi/usePersonalAPITokensApi';
|
||||
import { useServiceAccountTokensApi } from 'hooks/api/actions/useServiceAccountTokensApi/useServiceAccountTokensApi';
|
||||
import { INewPersonalAPIToken } from 'interfaces/personalAPIToken';
|
||||
import { ServiceAccountTokens } from './ServiceAccountTokens/ServiceAccountTokens';
|
||||
import { IServiceAccount } from 'interfaces/service-account';
|
||||
@ -127,7 +127,7 @@ export const ServiceAccountModal = ({
|
||||
const { serviceAccounts, roles, refetch } = useServiceAccounts();
|
||||
const { addServiceAccount, updateServiceAccount, loading } =
|
||||
useServiceAccountsApi();
|
||||
const { createUserPersonalAPIToken } = usePersonalAPITokensApi();
|
||||
const { createServiceAccountToken } = useServiceAccountTokensApi();
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { uiConfig } = useUiConfig();
|
||||
|
||||
@ -190,7 +190,7 @@ export const ServiceAccountModal = ({
|
||||
getServiceAccountPayload()
|
||||
);
|
||||
if (tokenGeneration === TokenGeneration.NOW) {
|
||||
const token = await createUserPersonalAPIToken(id, {
|
||||
const token = await createServiceAccountToken(id, {
|
||||
description: patDescription,
|
||||
expiresAt: patExpiresAt,
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
IPersonalAPITokenFormErrors,
|
||||
PersonalAPITokenForm,
|
||||
} from 'component/user/Profile/PersonalAPITokensTab/CreatePersonalAPIToken/PersonalAPITokenForm/PersonalAPITokenForm';
|
||||
import { ICreatePersonalApiTokenPayload } from 'hooks/api/actions/usePersonalAPITokensApi/usePersonalAPITokensApi';
|
||||
import { ICreateServiceAccountTokenPayload } from 'hooks/api/actions/useServiceAccountTokensApi/useServiceAccountTokensApi';
|
||||
import { IPersonalAPIToken } from 'interfaces/personalAPIToken';
|
||||
|
||||
const DEFAULT_EXPIRATION = ExpirationOption['30DAYS'];
|
||||
@ -15,7 +15,7 @@ interface IServiceAccountCreateTokenDialogProps {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
tokens: IPersonalAPIToken[];
|
||||
onCreateClick: (newToken: ICreatePersonalApiTokenPayload) => void;
|
||||
onCreateClick: (newToken: ICreateServiceAccountTokenPayload) => void;
|
||||
}
|
||||
|
||||
export const ServiceAccountCreateTokenDialog = ({
|
||||
|
@ -16,7 +16,7 @@ import { HighlightCell } from 'component/common/Table/cells/HighlightCell/Highli
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { PAT_LIMIT } from '@server/util/constants';
|
||||
import { usePersonalAPITokens } from 'hooks/api/getters/usePersonalAPITokens/usePersonalAPITokens';
|
||||
import { useServiceAccountTokens } from 'hooks/api/getters/useServiceAccountTokens/useServiceAccountTokens';
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import {
|
||||
INewPersonalAPIToken,
|
||||
@ -32,9 +32,9 @@ import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColum
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import {
|
||||
ICreatePersonalApiTokenPayload,
|
||||
usePersonalAPITokensApi,
|
||||
} from 'hooks/api/actions/usePersonalAPITokensApi/usePersonalAPITokensApi';
|
||||
ICreateServiceAccountTokenPayload,
|
||||
useServiceAccountTokensApi,
|
||||
} from 'hooks/api/actions/useServiceAccountTokensApi/useServiceAccountTokensApi';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { IServiceAccount } from 'interfaces/service-account';
|
||||
@ -90,12 +90,12 @@ export const ServiceAccountTokens = ({
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const { tokens = [], refetchTokens } = usePersonalAPITokens(
|
||||
const { tokens = [], refetchTokens } = useServiceAccountTokens(
|
||||
serviceAccount.id
|
||||
);
|
||||
const { refetch } = useServiceAccounts();
|
||||
const { createUserPersonalAPIToken, deleteUserPersonalAPIToken } =
|
||||
usePersonalAPITokensApi();
|
||||
const { createServiceAccountToken, deleteServiceAccountToken } =
|
||||
useServiceAccountTokensApi();
|
||||
|
||||
const [initialState] = useState(() => ({
|
||||
sortBy: readOnly ? [{ id: 'seenAt' }] : [defaultSort],
|
||||
@ -108,9 +108,11 @@ export const ServiceAccountTokens = ({
|
||||
const [newToken, setNewToken] = useState<INewPersonalAPIToken>();
|
||||
const [selectedToken, setSelectedToken] = useState<IPersonalAPIToken>();
|
||||
|
||||
const onCreateClick = async (newToken: ICreatePersonalApiTokenPayload) => {
|
||||
const onCreateClick = async (
|
||||
newToken: ICreateServiceAccountTokenPayload
|
||||
) => {
|
||||
try {
|
||||
const token = await createUserPersonalAPIToken(
|
||||
const token = await createServiceAccountToken(
|
||||
serviceAccount.id,
|
||||
newToken
|
||||
);
|
||||
@ -131,7 +133,7 @@ export const ServiceAccountTokens = ({
|
||||
const onDeleteClick = async () => {
|
||||
if (selectedToken) {
|
||||
try {
|
||||
await deleteUserPersonalAPIToken(
|
||||
await deleteServiceAccountToken(
|
||||
serviceAccount.id,
|
||||
selectedToken?.id
|
||||
);
|
||||
|
@ -36,6 +36,3 @@ export const DELETE_SEGMENT = 'DELETE_SEGMENT';
|
||||
export const APPLY_CHANGE_REQUEST = 'APPLY_CHANGE_REQUEST';
|
||||
export const APPROVE_CHANGE_REQUEST = 'APPROVE_CHANGE_REQUEST';
|
||||
export const SKIP_CHANGE_REQUEST = 'SKIP_CHANGE_REQUEST';
|
||||
export const READ_USER_PAT = 'READ_USER_PAT';
|
||||
export const CREATE_USER_PAT = 'CREATE_USER_PAT';
|
||||
export const DELETE_USER_PAT = 'DELETE_USER_PAT';
|
||||
|
@ -0,0 +1,56 @@
|
||||
import { INewPersonalAPIToken } from 'interfaces/personalAPIToken';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
export interface ICreateServiceAccountTokenPayload {
|
||||
description: string;
|
||||
expiresAt: Date;
|
||||
}
|
||||
|
||||
export const useServiceAccountTokensApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
|
||||
const createServiceAccountToken = async (
|
||||
serviceAccountId: number,
|
||||
payload: ICreateServiceAccountTokenPayload
|
||||
): Promise<INewPersonalAPIToken> => {
|
||||
const req = createRequest(
|
||||
`api/admin/service-account/${serviceAccountId}/token`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
);
|
||||
try {
|
||||
const response = await makeRequest(req.caller, req.id);
|
||||
return await response.json();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteServiceAccountToken = async (
|
||||
serviceAccountId: number,
|
||||
id: string
|
||||
) => {
|
||||
const req = createRequest(
|
||||
`api/admin/service-account/${serviceAccountId}/token/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
}
|
||||
);
|
||||
try {
|
||||
await makeRequest(req.caller, req.id);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
createServiceAccountToken,
|
||||
deleteServiceAccountToken,
|
||||
errors,
|
||||
loading,
|
||||
};
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import { IPersonalAPIToken } from 'interfaces/personalAPIToken';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||
|
||||
export interface IUseServiceAccountTokensOutput {
|
||||
tokens?: IPersonalAPIToken[];
|
||||
refetchTokens: () => void;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export const useServiceAccountTokens = (id: number) => {
|
||||
const { uiConfig, isEnterprise } = useUiConfig();
|
||||
|
||||
const { data, error, mutate } = useConditionalSWR(
|
||||
Boolean(uiConfig.flags.serviceAccounts) && isEnterprise(),
|
||||
{ tokens: [] },
|
||||
formatApiPath(`api/admin/service-account/${id}/token`),
|
||||
fetcher
|
||||
);
|
||||
|
||||
return {
|
||||
tokens: data ? data.pats : undefined,
|
||||
loading: !error && !data,
|
||||
refetchTokens: () => mutate(),
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
.then(handleErrorResponses('Service Account Tokens'))
|
||||
.then(res => res.json());
|
||||
};
|
@ -42,6 +42,3 @@ export const DELETE_SEGMENT = 'DELETE_SEGMENT';
|
||||
export const APPROVE_CHANGE_REQUEST = 'APPROVE_CHANGE_REQUEST';
|
||||
export const APPLY_CHANGE_REQUEST = 'APPLY_CHANGE_REQUEST';
|
||||
export const SKIP_CHANGE_REQUEST = 'SKIP_CHANGE_REQUEST';
|
||||
export const READ_USER_PAT = 'READ_USER_PAT';
|
||||
export const CREATE_USER_PAT = 'CREATE_USER_PAT';
|
||||
export const DELETE_USER_PAT = 'DELETE_USER_PAT';
|
||||
|
21
src/migrations/20230130113337-revert-user-pat-permissions.js
Normal file
21
src/migrations/20230130113337-revert-user-pat-permissions.js
Normal file
@ -0,0 +1,21 @@
|
||||
exports.up = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
DELETE FROM permissions WHERE permission = 'READ_USER_PAT';
|
||||
DELETE FROM permissions WHERE permission = 'CREATE_USER_PAT';
|
||||
DELETE FROM permissions WHERE permission = 'DELETE_USER_PAT';
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
INSERT INTO permissions (permission, display_name, type) VALUES ('READ_USER_PAT', 'Read PATs for a specific user', 'root');
|
||||
INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_USER_PAT', 'Create a PAT for a specific user', 'root');
|
||||
INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_USER_PAT', 'Delete a PAT for a specific user', 'root');
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user