Finish v1 Azure Key Vault integration

This commit is contained in:
Tuan Dang
2023-02-06 17:57:47 +07:00
parent 1bd180596e
commit 68bf0b9efe
21 changed files with 560 additions and 49 deletions
+2 -2
View File
@@ -199,13 +199,13 @@ const Layout = ({ children }: LayoutProps) => {
.split('/')
[router.asPath.split('/').length - 1].split('?')[0];
if (!['heroku', 'vercel', 'github', 'netlify'].includes(intendedWorkspaceId)) {
if (!['heroku', 'vercel', 'github', 'netlify', 'azure-key-vault'].includes(intendedWorkspaceId)) {
localStorage.setItem('projectData.id', intendedWorkspaceId);
}
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if (
!['heroku', 'vercel', 'github', 'netlify'].includes(intendedWorkspaceId) &&
!['heroku', 'vercel', 'github', 'netlify', 'azure-key-vault'].includes(intendedWorkspaceId) &&
!userWorkspaces
.map((workspace: { _id: string }) => workspace._id)
.includes(intendedWorkspaceId)
@@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
import { faArrowRight, faRotate, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// TODO: This needs to be moved from public folder
import { contextNetlifyMapping, reverseContextNetlifyMapping } from 'public/data/frequentConstants';
import { contextNetlifyMapping, integrationSlugNameMapping, reverseContextNetlifyMapping } from 'public/data/frequentConstants';
import Button from '@app/components/basic/buttons/Button';
import ListBox from '@app/components/basic/Listbox';
@@ -52,6 +52,7 @@ const IntegrationTile = ({
environments = [],
handleDeleteIntegration
}: Props) => {
// set initial environment. This find will only execute when component is mounting
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props['environments'][0]>(
environments.find(({ slug }) => slug === integration.environment) || {
@@ -72,7 +73,14 @@ const IntegrationTile = ({
});
setApps(tempApps);
setIntegrationApp(integration.app ? integration.app : tempApps[0].name);
if (integration?.app) {
setIntegrationApp(integration.app);
} else if (tempApps.length > 0) {
setIntegrationApp(tempApps[0].name)
} else {
setIntegrationApp('');
}
switch (integration.integration) {
case 'vercel':
@@ -174,7 +182,7 @@ const IntegrationTile = ({
return <div />;
};
if (!integrationApp || apps.length === 0) return <div />;
if (!integrationApp) return <div />;
return (
<div className="max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between">
@@ -201,7 +209,8 @@ const IntegrationTile = ({
<div className="mr-2">
<p className="text-gray-400 text-xs font-semibold mb-2">INTEGRATION</p>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300">
{integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)}
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
{integrationSlugNameMapping[integration.integration]}
</div>
</div>
<div className="mr-2">
@@ -35,7 +35,7 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
// ws empty means user has no access to the ws
// push to the first workspace
if (!isLoading && !value?.currentWorkspace?._id) {
router.push(`/dashboard/${value.workspaces?.[0]?._id}`);
// router.push(`/dashboard/${value.workspaces?.[0]?._id}`);
}
}, [value?.currentWorkspace?._id, isLoading, value.workspaces?.[0]?._id, router.pathname]);
@@ -26,7 +26,7 @@ const AuthorizeIntegration = ({ workspaceId, code, integration }: Props) =>
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
return (res.json());
}
console.log('Failed to authorize the integration');
return undefined;
+166
View File
@@ -0,0 +1,166 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import queryString from 'query-string';
import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps';
import {
Button,
Card,
CardTitle,
FormControl,
Input,
Select,
SelectItem
} from '../components/v2';
import AuthorizeIntegration from './api/integrations/authorizeIntegration';
import updateIntegration from './api/integrations/updateIntegration';
import getAWorkspace from './api/workspace/getAWorkspace';
interface Integration {
_id: string;
isActive: boolean;
app: string | null;
appId: string | null;
createdAt: string;
updatedAt: string;
environment: string;
integration: string;
targetEnvironment: string;
workspace: string;
integrationAuth: string;
}
export default function AzureKeyVault() {
const router = useRouter();
// query-string variables
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
const {code} = parsedUrl;
const {state} = parsedUrl;
const [integration, setIntegration] = useState<Integration | null>(null);
const [environments, setEnvironments] = useState<
{
name: string;
slug: string;
}[]
>([]);
const [environment, setEnvironment] = useState('');
const [vaultBaseUrl, setVaultBaseUrl] = useState('');
const [vaultBaseUrlErrorText, setVaultBaseUrlErrorText] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
const integrationDetails = await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id') as string,
code: code as string,
integration: 'azure-key-vault',
});
setIntegration(integrationDetails.integration);
const workspaceId = localStorage.getItem('projectData.id');
if (!workspaceId) return;
const workspace = await getAWorkspace(workspaceId);
setEnvironment(workspace.environments[0].slug);
setEnvironments(workspace.environments);
}
} catch (error) {
console.error('Azure Key Vault integration error: ', error);
}
})();
}, []);
const handleButtonClick = async () => {
try {
if (vaultBaseUrl.length === 0) {
setVaultBaseUrlErrorText('Vault URI cannot be blank');
return;
}
if (
!vaultBaseUrl.startsWith('https://')
|| !vaultBaseUrl.endsWith('vault.azure.net')
) {
setVaultBaseUrlErrorText('Vault URI must be like https://<vault_name>.vault.azure.net');
return;
}
if (!integration) return;
setIsLoading(true);
await updateIntegration({
integrationId: integration._id,
isActive: true,
environment,
app: vaultBaseUrl,
appId: null,
targetEnvironment: null,
owner: null
});
setIsLoading(false);
router.push(
`/integrations/${localStorage.getItem('projectData.id')}`
);
} catch (err) {
console.error(err);
}
}
return (integration && environments.length > 0) ? (
<div className="h-full w-full flex justify-center items-center">
<Card className="max-w-md p-8 rounded-md">
<CardTitle className='text-center'>Azure Key Vault Integration</CardTitle>
<FormControl
label="Project Environment"
className='mt-4'
>
<Select
value={environment}
onValueChange={(val) => setEnvironment(val)}
className='w-full border border-mineshaft-500'
>
{environments.map((e) => (
<SelectItem value={e.slug} key={`azure-key-vault-environment-${e.slug}`}>
{e.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl
label="Vault URI"
errorText={vaultBaseUrlErrorText}
isError={vaultBaseUrlErrorText !== '' ?? false}
>
<Input
placeholder='https://example.vault.azure.net'
value={vaultBaseUrl}
onChange={(e) => setVaultBaseUrl(e.target.value)}
/>
</FormControl>
<Button
onClick={handleButtonClick}
color="mineshaft"
className='mt-4'
isLoading={isLoading}
>
Create Integration
</Button>
</Card>
</div>
) : <div />
}
AzureKeyVault.requireAuth = true;
export const getServerSideProps = getTranslatedServerSideProps(['integrations']);
+1 -1
View File
@@ -25,7 +25,7 @@ export default function Github() {
integration: 'github',
});
router.push(
`/integrations/${ localStorage.getItem('projectData.id')}`
`/integrations/${localStorage.getItem('projectData.id')}`
);
}
} catch (error) {
+14 -4
View File
@@ -195,6 +195,11 @@ export default function Integrations() {
localStorage.setItem('latestCSRFToken', state);
switch (integrationOption.slug) {
case 'azure-key-vault':
window.location.assign(
`https://login.microsoftonline.com/${integrationOption.tenantId}/oauth2/v2.0/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${window.location.origin}/azure-key-vault&response_mode=query&scope=https://vault.azure.net/.default openid offline_access&state=${state}`
);
break;
case 'heroku':
window.location.assign(
`https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`
@@ -275,10 +280,15 @@ export default function Integrations() {
// case: integration has been authorized before
// -> create new integration
const integration = await createIntegration({
integrationAuthId: integrationAuthX._id
});
setIntegrations([...integrations, integration]);
if (!['azure-key-vault'].includes(integrationOption.slug)) {
const integration = await createIntegration({
integrationAuthId: integrationAuthX._id
});
setIntegrations([...integrations, integration]);
} else {
handleIntegrationOption({ integrationOption });
}
} catch (err) {
console.error(err);
}