Remove api keys from old world (#2548)
* Use apiKeyV2 for getApiKeys * Use apiKeyV2 for createApiKey * Use apiKeyV2 for getApiKey * Use apiKeyV2 to deleteapikey * Filter null revokedAt -> not working * Use apiKeyV2 to regenerate * Fix default values injected * Remove useless stuff * Fix type
This commit is contained in:
@ -1447,6 +1447,7 @@ export type Mutation = {
|
|||||||
deleteUserAccount: User;
|
deleteUserAccount: User;
|
||||||
deleteUserV2: UserV2;
|
deleteUserV2: UserV2;
|
||||||
deleteWorkspaceMember: WorkspaceMember;
|
deleteWorkspaceMember: WorkspaceMember;
|
||||||
|
generateApiKeyV2Token: ApiKeyToken;
|
||||||
impersonate: Verify;
|
impersonate: Verify;
|
||||||
renewToken: AuthTokens;
|
renewToken: AuthTokens;
|
||||||
revokeOneApiKey: ApiKey;
|
revokeOneApiKey: ApiKey;
|
||||||
@ -1602,6 +1603,11 @@ export type MutationDeleteWorkspaceMemberArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationGenerateApiKeyV2TokenArgs = {
|
||||||
|
data: ApiKeyCreateInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationImpersonateArgs = {
|
export type MutationImpersonateArgs = {
|
||||||
userId: Scalars['String'];
|
userId: Scalars['String'];
|
||||||
};
|
};
|
||||||
@ -3641,6 +3647,13 @@ export type DeleteOneApiKeyMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type DeleteOneApiKeyMutation = { __typename?: 'Mutation', revokeOneApiKey: { __typename?: 'ApiKey', id: string } };
|
export type DeleteOneApiKeyMutation = { __typename?: 'Mutation', revokeOneApiKey: { __typename?: 'ApiKey', id: string } };
|
||||||
|
|
||||||
|
export type GenerateOneApiKeyTokenMutationVariables = Exact<{
|
||||||
|
data: ApiKeyCreateInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GenerateOneApiKeyTokenMutation = { __typename?: 'Mutation', generateApiKeyV2Token: { __typename?: 'ApiKeyToken', token: string } };
|
||||||
|
|
||||||
export type InsertOneApiKeyMutationVariables = Exact<{
|
export type InsertOneApiKeyMutationVariables = Exact<{
|
||||||
data: ApiKeyCreateInput;
|
data: ApiKeyCreateInput;
|
||||||
}>;
|
}>;
|
||||||
@ -5650,6 +5663,39 @@ export function useDeleteOneApiKeyMutation(baseOptions?: Apollo.MutationHookOpti
|
|||||||
export type DeleteOneApiKeyMutationHookResult = ReturnType<typeof useDeleteOneApiKeyMutation>;
|
export type DeleteOneApiKeyMutationHookResult = ReturnType<typeof useDeleteOneApiKeyMutation>;
|
||||||
export type DeleteOneApiKeyMutationResult = Apollo.MutationResult<DeleteOneApiKeyMutation>;
|
export type DeleteOneApiKeyMutationResult = Apollo.MutationResult<DeleteOneApiKeyMutation>;
|
||||||
export type DeleteOneApiKeyMutationOptions = Apollo.BaseMutationOptions<DeleteOneApiKeyMutation, DeleteOneApiKeyMutationVariables>;
|
export type DeleteOneApiKeyMutationOptions = Apollo.BaseMutationOptions<DeleteOneApiKeyMutation, DeleteOneApiKeyMutationVariables>;
|
||||||
|
export const GenerateOneApiKeyTokenDocument = gql`
|
||||||
|
mutation GenerateOneApiKeyToken($data: ApiKeyCreateInput!) {
|
||||||
|
generateApiKeyV2Token(data: $data) {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type GenerateOneApiKeyTokenMutationFn = Apollo.MutationFunction<GenerateOneApiKeyTokenMutation, GenerateOneApiKeyTokenMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGenerateOneApiKeyTokenMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useGenerateOneApiKeyTokenMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGenerateOneApiKeyTokenMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [generateOneApiKeyTokenMutation, { data, loading, error }] = useGenerateOneApiKeyTokenMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGenerateOneApiKeyTokenMutation(baseOptions?: Apollo.MutationHookOptions<GenerateOneApiKeyTokenMutation, GenerateOneApiKeyTokenMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<GenerateOneApiKeyTokenMutation, GenerateOneApiKeyTokenMutationVariables>(GenerateOneApiKeyTokenDocument, options);
|
||||||
|
}
|
||||||
|
export type GenerateOneApiKeyTokenMutationHookResult = ReturnType<typeof useGenerateOneApiKeyTokenMutation>;
|
||||||
|
export type GenerateOneApiKeyTokenMutationResult = Apollo.MutationResult<GenerateOneApiKeyTokenMutation>;
|
||||||
|
export type GenerateOneApiKeyTokenMutationOptions = Apollo.BaseMutationOptions<GenerateOneApiKeyTokenMutation, GenerateOneApiKeyTokenMutationVariables>;
|
||||||
export const InsertOneApiKeyDocument = gql`
|
export const InsertOneApiKeyDocument = gql`
|
||||||
mutation InsertOneApiKey($data: ApiKeyCreateInput!) {
|
mutation InsertOneApiKey($data: ApiKeyCreateInput!) {
|
||||||
createOneApiKey(data: $data) {
|
createOneApiKey(data: $data) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
|
||||||
@ -41,16 +42,7 @@ export const useCreateOneObjectRecord = ({
|
|||||||
? async (input: Record<string, any>) => {
|
? async (input: Record<string, any>) => {
|
||||||
const createdObject = await mutate({
|
const createdObject = await mutate({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: { ...input, id: v4() },
|
||||||
...foundObjectMetadataItem.fields.reduce(
|
|
||||||
(result, field) => ({
|
|
||||||
...result,
|
|
||||||
[field.name]: defaultFieldValues[field.type],
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
...input,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,6 +52,7 @@ export const useCreateOneObjectRecord = ({
|
|||||||
`create${capitalize(foundObjectMetadataItem.nameSingular)}`
|
`create${capitalize(foundObjectMetadataItem.nameSingular)}`
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
return createdObject.data;
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GENERATE_ONE_API_KEY_TOKEN = gql`
|
||||||
|
mutation GenerateOneApiKeyToken($data: ApiKeyCreateInput!) {
|
||||||
|
generateApiKeyV2Token(data: $data) {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
|
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
|
||||||
import { GetApiKeysQuery } from '~/generated/graphql';
|
import { ApiKey } from '~/generated/graphql';
|
||||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||||
|
|
||||||
export const formatExpiration = (
|
export const formatExpiration = (
|
||||||
@ -18,9 +18,9 @@ export const formatExpiration = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const formatExpirations = (
|
export const formatExpirations = (
|
||||||
apiKeysQuery: GetApiKeysQuery,
|
apiKeys: Array<Pick<ApiKey, 'id' | 'name' | 'expiresAt'>>,
|
||||||
): ApiFieldItem[] => {
|
): ApiFieldItem[] => {
|
||||||
return apiKeysQuery.findManyApiKey.map(({ id, name, expiresAt }) => {
|
return apiKeys.map(({ id, name, expiresAt }) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
||||||
|
import { useFindOneObjectRecord } from '@/object-record/hooks/useFindOneObjectRecord';
|
||||||
|
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
|
||||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||||
@ -18,11 +21,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
|||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import {
|
import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
|
||||||
useDeleteOneApiKeyMutation,
|
|
||||||
useGetApiKeyQuery,
|
|
||||||
useInsertOneApiKeyMutation,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
|
|
||||||
const StyledInfo = styled.span`
|
const StyledInfo = styled.span`
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
@ -41,28 +40,29 @@ const StyledInputContainer = styled.div`
|
|||||||
export const SettingsDevelopersApiKeyDetail = () => {
|
export const SettingsDevelopersApiKeyDetail = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { apiKeyId = '' } = useParams();
|
const { apiKeyId = '' } = useParams();
|
||||||
const { triggerOptimisticEffects } = useOptimisticEffect('ApiKeyV2');
|
|
||||||
|
|
||||||
const setGeneratedApi = useGeneratedApiKeys();
|
const setGeneratedApi = useGeneratedApiKeys();
|
||||||
const [generatedApiKey] = useRecoilState(
|
const [generatedApiKey] = useRecoilState(
|
||||||
generatedApiKeyFamilyState(apiKeyId),
|
generatedApiKeyFamilyState(apiKeyId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [deleteApiKey] = useDeleteOneApiKeyMutation();
|
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
|
||||||
const [insertOneApiKey] = useInsertOneApiKeyMutation();
|
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({
|
||||||
const apiKeyData = useGetApiKeyQuery({
|
objectNamePlural: 'apiKeysV2',
|
||||||
variables: {
|
});
|
||||||
apiKeyId,
|
const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord({
|
||||||
},
|
objectNamePlural: 'apiKeysV2',
|
||||||
}).data?.findManyApiKey[0];
|
});
|
||||||
|
|
||||||
|
const { object: apiKeyData } = useFindOneObjectRecord({
|
||||||
|
objectNameSingular: 'apiKeyV2',
|
||||||
|
objectMetadataId: apiKeyId,
|
||||||
|
});
|
||||||
|
|
||||||
const deleteIntegration = async (redirect = true) => {
|
const deleteIntegration = async (redirect = true) => {
|
||||||
await deleteApiKey({
|
await updateApiKey?.({
|
||||||
variables: { apiKeyId },
|
idToUpdate: apiKeyId,
|
||||||
update: (cache) =>
|
input: { revokedAt: DateTime.now().toString() },
|
||||||
cache.evict({
|
|
||||||
id: cache.identify({ __typename: 'ApiKey', id: apiKeyId }),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
navigate('/settings/developers/api-keys');
|
navigate('/settings/developers/api-keys');
|
||||||
@ -73,19 +73,23 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
|||||||
name: string,
|
name: string,
|
||||||
newExpiresAt: string | null,
|
newExpiresAt: string | null,
|
||||||
) => {
|
) => {
|
||||||
return await insertOneApiKey({
|
const newApiKey = await createOneApiKey?.({
|
||||||
|
name: name,
|
||||||
|
expiresAt: newExpiresAt,
|
||||||
|
});
|
||||||
|
const tokenData = await generateOneApiKeyToken({
|
||||||
variables: {
|
variables: {
|
||||||
data: {
|
data: {
|
||||||
name: name,
|
id: newApiKey.createApiKeyV2.id,
|
||||||
expiresAt: newExpiresAt,
|
expiresAt: newApiKey.createApiKeyV2.expiresAt,
|
||||||
|
name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: (_cache, { data }) => {
|
|
||||||
if (data?.createOneApiKey) {
|
|
||||||
triggerOptimisticEffects('ApiKey', [data?.createOneApiKey]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
id: newApiKey.createApiKeyV2.id,
|
||||||
|
token: tokenData.data?.generateApiKeyV2Token.token,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const regenerateApiKey = async () => {
|
const regenerateApiKey = async () => {
|
||||||
@ -96,14 +100,9 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
|||||||
);
|
);
|
||||||
const apiKey = await createIntegration(apiKeyData.name, newExpiresAt);
|
const apiKey = await createIntegration(apiKeyData.name, newExpiresAt);
|
||||||
await deleteIntegration(false);
|
await deleteIntegration(false);
|
||||||
if (apiKey.data?.createOneApiKey) {
|
if (apiKey.token) {
|
||||||
setGeneratedApi(
|
setGeneratedApi(apiKey.id, apiKey.token);
|
||||||
apiKey.data.createOneApiKey.id,
|
navigate(`/settings/developers/api-keys/${apiKey.id}`);
|
||||||
apiKey.data.createOneApiKey.token,
|
|
||||||
);
|
|
||||||
navigate(
|
|
||||||
`/settings/developers/api-keys/${apiKey.data.createOneApiKey.id}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
|
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
||||||
import { objectSettingsWidth } from '@/settings/data-model/constants/objectSettings';
|
import { objectSettingsWidth } from '@/settings/data-model/constants/objectSettings';
|
||||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||||
import { getApiKeysOptimisticEffectDefinition } from '@/settings/developers/optimistic-effect-definitions/getApiKeysOptimisticEffectDefinition';
|
import { getApiKeysOptimisticEffectDefinition } from '@/settings/developers/optimistic-effect-definitions/getApiKeysOptimisticEffectDefinition';
|
||||||
|
import { ApiFieldItem } from '@/settings/developers/types/ApiFieldItem';
|
||||||
import { formatExpirations } from '@/settings/developers/utils/format-expiration';
|
import { formatExpirations } from '@/settings/developers/utils/format-expiration';
|
||||||
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
||||||
import { H1Title } from '@/ui/display/typography/components/H1Title';
|
import { H1Title } from '@/ui/display/typography/components/H1Title';
|
||||||
@ -14,7 +17,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
|
|||||||
import { Table } from '@/ui/layout/table/components/Table';
|
import { Table } from '@/ui/layout/table/components/Table';
|
||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { useGetApiKeysQuery } from '~/generated/graphql';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
@ -40,15 +42,26 @@ const StyledH1Title = styled(H1Title)`
|
|||||||
export const SettingsDevelopersApiKeys = () => {
|
export const SettingsDevelopersApiKeys = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { registerOptimisticEffect } = useOptimisticEffect('ApiKeyV2');
|
const { registerOptimisticEffect } = useOptimisticEffect('ApiKeyV2');
|
||||||
const apiKeysQuery = useGetApiKeysQuery({
|
const [apiKeys, setApiKeys] = useState<Array<ApiFieldItem>>([]);
|
||||||
onCompleted: () => {
|
useFindManyObjectRecords({
|
||||||
|
objectNamePlural: 'apiKeysV2',
|
||||||
|
/*filter: { revokedAt: { eq: null } },*/
|
||||||
|
onCompleted: (data) => {
|
||||||
|
setApiKeys(
|
||||||
|
formatExpirations(
|
||||||
|
data.edges.map((apiKey) => ({
|
||||||
|
id: apiKey.node.id,
|
||||||
|
name: apiKey.node.name,
|
||||||
|
expiresAt: apiKey.node.expiresAt,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
registerOptimisticEffect({
|
registerOptimisticEffect({
|
||||||
variables: {},
|
variables: {},
|
||||||
definition: getApiKeysOptimisticEffectDefinition,
|
definition: getApiKeysOptimisticEffectDefinition,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const apiKeys = apiKeysQuery.data ? formatExpirations(apiKeysQuery.data) : [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
@ -15,11 +15,10 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
|||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import { useInsertOneApiKeyMutation } from '~/generated/graphql';
|
import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
export const SettingsDevelopersApiKeysNew = () => {
|
export const SettingsDevelopersApiKeysNew = () => {
|
||||||
const [insertOneApiKey] = useInsertOneApiKeyMutation();
|
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
|
||||||
const { triggerOptimisticEffects } = useOptimisticEffect('ApiKeyV2');
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setGeneratedApi = useGeneratedApiKeys();
|
const setGeneratedApi = useGeneratedApiKeys();
|
||||||
const [formValues, setFormValues] = useState<{
|
const [formValues, setFormValues] = useState<{
|
||||||
@ -29,35 +28,36 @@ export const SettingsDevelopersApiKeysNew = () => {
|
|||||||
expirationDate: ExpirationDates[0].value,
|
expirationDate: ExpirationDates[0].value,
|
||||||
name: '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({
|
||||||
|
objectNamePlural: 'apiKeysV2',
|
||||||
|
});
|
||||||
const onSave = async () => {
|
const onSave = async () => {
|
||||||
const apiKey = await insertOneApiKey({
|
const expiresAt = formValues.expirationDate
|
||||||
|
? DateTime.now().plus({ days: formValues.expirationDate }).toString()
|
||||||
|
: null;
|
||||||
|
const newApiKey = await createOneApiKey?.({
|
||||||
|
name: formValues.name,
|
||||||
|
expiresAt,
|
||||||
|
});
|
||||||
|
const tokenData = await generateOneApiKeyToken({
|
||||||
variables: {
|
variables: {
|
||||||
data: {
|
data: {
|
||||||
name: formValues.name,
|
id: newApiKey.createApiKeyV2.id,
|
||||||
expiresAt: formValues.expirationDate
|
expiresAt: newApiKey.createApiKeyV2.expiresAt,
|
||||||
? DateTime.now()
|
name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here
|
||||||
.plus({ days: formValues.expirationDate })
|
|
||||||
.toString()
|
|
||||||
: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: (_cache, { data }) => {
|
|
||||||
if (data?.createOneApiKey) {
|
|
||||||
triggerOptimisticEffects('ApiKey', [data?.createOneApiKey]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (apiKey.data?.createOneApiKey) {
|
if (tokenData.data?.generateApiKeyV2Token) {
|
||||||
setGeneratedApi(
|
setGeneratedApi(
|
||||||
apiKey.data.createOneApiKey.id,
|
newApiKey.createApiKeyV2.id,
|
||||||
apiKey.data.createOneApiKey.token,
|
tokenData.data.generateApiKeyV2Token.token,
|
||||||
);
|
|
||||||
navigate(
|
|
||||||
`/settings/developers/api-keys/${apiKey.data.createOneApiKey.id}`,
|
|
||||||
);
|
);
|
||||||
|
navigate(`/settings/developers/api-keys/${newApiKey.createApiKeyV2.id}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const canSave = !!formValues.name;
|
const canSave = !!formValues.name && createOneApiKey;
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
|
|||||||
@ -42,6 +42,21 @@ export class ApiKeyResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => ApiKeyToken)
|
||||||
|
@UseGuards(AbilityGuard)
|
||||||
|
@CheckAbilities(CreateApiKeyAbilityHandler)
|
||||||
|
async generateApiKeyV2Token(
|
||||||
|
@Args()
|
||||||
|
args: CreateOneApiKeyArgs,
|
||||||
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
|
): Promise<Pick<ApiKeyToken, 'token'> | undefined> {
|
||||||
|
return await this.apiKeyService.generateApiKeyV2Token(
|
||||||
|
workspaceId,
|
||||||
|
args.data.id,
|
||||||
|
args.data.expiresAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => ApiKey)
|
@Mutation(() => ApiKey)
|
||||||
@UseGuards(AbilityGuard)
|
@UseGuards(AbilityGuard)
|
||||||
@CheckAbilities(UpdateApiKeyAbilityHandler)
|
@CheckAbilities(UpdateApiKeyAbilityHandler)
|
||||||
|
|||||||
@ -21,6 +21,34 @@ export class ApiKeyService {
|
|||||||
update = this.prismaService.client.apiKey.update;
|
update = this.prismaService.client.apiKey.update;
|
||||||
delete = this.prismaService.client.apiKey.delete;
|
delete = this.prismaService.client.apiKey.delete;
|
||||||
|
|
||||||
|
async generateApiKeyV2Token(
|
||||||
|
workspaceId: string,
|
||||||
|
apiKeyId?: string,
|
||||||
|
expiresAt?: Date | string,
|
||||||
|
): Promise<Pick<ApiKeyToken, 'token'> | undefined> {
|
||||||
|
if (!apiKeyId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const jwtPayload = {
|
||||||
|
sub: workspaceId,
|
||||||
|
};
|
||||||
|
const secret = this.environmentService.getAccessTokenSecret();
|
||||||
|
let expiresIn: string | number;
|
||||||
|
if (expiresAt) {
|
||||||
|
expiresIn = Math.floor(
|
||||||
|
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
expiresIn = this.environmentService.getApiTokenExpiresIn();
|
||||||
|
}
|
||||||
|
const token = this.jwtService.sign(jwtPayload, {
|
||||||
|
secret,
|
||||||
|
expiresIn,
|
||||||
|
jwtid: apiKeyId,
|
||||||
|
});
|
||||||
|
return { token };
|
||||||
|
}
|
||||||
|
|
||||||
async generateApiKeyToken(
|
async generateApiKeyToken(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user