mirror of
https://github.com/Infisical/infisical.git
synced 2026-05-02 03:02:03 -04:00
Merge pull request #1102 from G3root/hasura-cloud
feat: add hasura cloud integration
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,8 @@ import {
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HASURA_CLOUD_API_URL,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
@@ -63,6 +65,10 @@ import { Octokit } from "@octokit/rest";
|
||||
import _ from "lodash";
|
||||
import sodium from "libsodium-wrappers";
|
||||
import { standardRequest } from "../config/request";
|
||||
import {
|
||||
ZGetTenantEnv,
|
||||
ZUpdateTenantEnv
|
||||
} from "../validation/hasuraCloudIntegration";
|
||||
|
||||
const getSecretKeyValuePair = (
|
||||
secrets: Record<string, { value: string | null; comment?: string } | null>
|
||||
@@ -95,7 +101,7 @@ const syncSecrets = async ({
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
switch (integration.integration) {
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
@@ -306,6 +312,14 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
|
||||
case INTEGRATION_HASURA_CLOUD:
|
||||
await syncSecretsHasuraCloud({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -963,8 +977,9 @@ const syncSecretsVercel = async ({
|
||||
: {}),
|
||||
...(integration?.path
|
||||
? {
|
||||
gitBranch: integration?.path
|
||||
} : {})
|
||||
gitBranch: integration?.path
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
const vercelSecrets: VercelSecret[] = (
|
||||
@@ -992,7 +1007,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const res: { [key: string]: VercelSecret } = {};
|
||||
|
||||
for await (const vercelSecret of vercelSecrets) {
|
||||
@@ -1352,7 +1367,7 @@ const syncSecretsGitHub = async ({
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
interface GitHubRepoKey {
|
||||
key_id: string;
|
||||
@@ -1395,14 +1410,23 @@ const syncSecretsGitHub = async ({
|
||||
{}
|
||||
);
|
||||
|
||||
encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: {
|
||||
[key: string]: GitHubSecret;
|
||||
}, key) => {
|
||||
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) {
|
||||
result[key] = encryptedSecrets[key];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
encryptedSecrets = Object.keys(encryptedSecrets).reduce(
|
||||
(
|
||||
result: {
|
||||
[key: string]: GitHubSecret;
|
||||
},
|
||||
key
|
||||
) => {
|
||||
if (
|
||||
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
|
||||
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
|
||||
) {
|
||||
result[key] = encryptedSecrets[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
Object.keys(encryptedSecrets).map(async (key) => {
|
||||
if (!(key in secrets)) {
|
||||
@@ -2095,7 +2119,7 @@ const syncSecretsCheckly = async ({
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
let getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
|
||||
@@ -2113,14 +2137,23 @@ const syncSecretsCheckly = async ({
|
||||
{}
|
||||
);
|
||||
|
||||
getSecretsRes = Object.keys(getSecretsRes).reduce((result: {
|
||||
[key: string]: string;
|
||||
}, key) => {
|
||||
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) {
|
||||
result[key] = getSecretsRes[key];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
getSecretsRes = Object.keys(getSecretsRes).reduce(
|
||||
(
|
||||
result: {
|
||||
[key: string]: string;
|
||||
},
|
||||
key
|
||||
) => {
|
||||
if (
|
||||
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
|
||||
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
|
||||
) {
|
||||
result[key] = getSecretsRes[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// add secrets
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
@@ -2195,18 +2228,20 @@ const syncSecretsQovery = async ({
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, {
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
).data.results.reduce(
|
||||
(obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.key]: {"id": secret.id, "value": secret.value}
|
||||
[secret.key]: { id: secret.id, value: secret.value }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
@@ -3076,4 +3111,111 @@ const syncSecretsNorthflank = async ({
|
||||
);
|
||||
};
|
||||
|
||||
/** Sync/push [secrets] to Hasura Cloud
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessToken - access token for Hasura Cloud integration
|
||||
*/
|
||||
const syncSecretsHasuraCloud = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const res = await standardRequest.post(
|
||||
INTEGRATION_HASURA_CLOUD_API_URL,
|
||||
{
|
||||
query:
|
||||
"query MyQuery($tenantId: uuid!) { getTenantEnv(tenantId: $tenantId) { hash envVars } }",
|
||||
variables: {
|
||||
tenantId: integration.appId
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `pat ${accessToken}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
data: {
|
||||
getTenantEnv: { hash, envVars }
|
||||
}
|
||||
} = ZGetTenantEnv.parse(res.data);
|
||||
|
||||
let currentHash = hash;
|
||||
|
||||
const secretsToUpdate = Object.keys(secrets).map((key) => {
|
||||
return ({
|
||||
key,
|
||||
value: secrets[key].value
|
||||
});
|
||||
});
|
||||
|
||||
if (secretsToUpdate.length) {
|
||||
// update secrets
|
||||
|
||||
const addRequest = await standardRequest.post(
|
||||
INTEGRATION_HASURA_CLOUD_API_URL,
|
||||
{
|
||||
query:
|
||||
"mutation MyQuery($currentHash: String!, $envs: [UpdateEnvObject!]!, $tenantId: uuid!) { updateTenantEnv(currentHash: $currentHash, envs: $envs, tenantId: $tenantId) { hash envVars} }",
|
||||
variables: {
|
||||
currentHash,
|
||||
envs: secretsToUpdate,
|
||||
tenantId: integration.appId
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `pat ${accessToken}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const addRequestResponse = ZUpdateTenantEnv.safeParse(addRequest.data);
|
||||
if (addRequestResponse.success) {
|
||||
currentHash = addRequestResponse.data.data.updateTenantEnv.hash;
|
||||
}
|
||||
}
|
||||
|
||||
const secretsToDelete = envVars.environment
|
||||
? Object.keys(envVars.environment).filter((key) => !(key in secrets))
|
||||
: [];
|
||||
|
||||
if (secretsToDelete.length) {
|
||||
await standardRequest.post(
|
||||
INTEGRATION_HASURA_CLOUD_API_URL,
|
||||
{
|
||||
query: `
|
||||
mutation deleteTenantEnv($id: uuid!, $currentHash: String!, $env: [String!]!) {
|
||||
deleteTenantEnv(tenantId: $id, currentHash: $currentHash, deleteEnvs: $env) {
|
||||
hash
|
||||
envVars
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: integration.appId,
|
||||
currentHash,
|
||||
env: secretsToDelete
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `pat ${accessToken}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export { syncSecrets };
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -76,7 +77,8 @@ export interface IIntegration {
|
||||
| "cloud-66"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
| "gcp-secret-manager"
|
||||
| "hasura-cloud";
|
||||
integrationAuth: Types.ObjectId;
|
||||
metadata: Metadata;
|
||||
}
|
||||
@@ -86,67 +88,67 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
app: {
|
||||
// name of app in provider
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
appId: {
|
||||
// id of app in provider
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetEnvironment: {
|
||||
// target environment
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetEnvironmentId: {
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetService: {
|
||||
// railway-specific service
|
||||
// qovery-specific project
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetServiceId: {
|
||||
// railway-specific service
|
||||
// qovery specific project
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
owner: {
|
||||
// github-specific repo owner-login
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
path: {
|
||||
// aws-parameter-store-specific path
|
||||
// (also) vercel preview-branch
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
region: {
|
||||
// aws-parameter-store-specific path
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
scope: {
|
||||
// qovery-specific scope
|
||||
@@ -183,19 +185,20 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
],
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
integrationAuth: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "IntegrationAuth",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/",
|
||||
default: "/"
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed,
|
||||
@@ -203,8 +206,8 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const Integration = model<IIntegration>("Integration", integrationSchema);
|
||||
export const Integration = model<IIntegration>("Integration", integrationSchema);
|
||||
|
||||
@@ -1,205 +1,203 @@
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IntegrationAuthMetadata } from "./types";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
metadata?: IntegrationAuthMetadata;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String,
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String,
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String,
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false,
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true,
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IntegrationAuthMetadata } from "./types";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager"
|
||||
| "hasura-cloud";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
metadata?: IntegrationAuthMetadata;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
],
|
||||
required: true
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false
|
||||
},
|
||||
algorithm: {
|
||||
// the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
);
|
||||
|
||||
export const IntegrationAuth = model<IIntegrationAuth>(
|
||||
"IntegrationAuth",
|
||||
integrationAuthSchema
|
||||
);
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const IntegrationAuth = model<IIntegrationAuth>("IntegrationAuth", integrationAuthSchema);
|
||||
|
||||
21
backend/src/validation/hasuraCloudIntegration.ts
Normal file
21
backend/src/validation/hasuraCloudIntegration.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as z from "zod";
|
||||
|
||||
export const ZGetTenantEnv = z.object({
|
||||
data: z.object({
|
||||
getTenantEnv: z.object({
|
||||
hash: z.string(),
|
||||
envVars: z.object({
|
||||
environment: z.record(z.any()).optional()
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
export const ZUpdateTenantEnv = z.object({
|
||||
data: z.object({
|
||||
updateTenantEnv: z.object({
|
||||
hash: z.string(),
|
||||
envVars: z.record(z.any())
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdHeroku,
|
||||
getClientIdNetlify,
|
||||
getClientSlugVercel
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdHeroku,
|
||||
getClientIdNetlify,
|
||||
getClientSlugVercel
|
||||
} from "../config";
|
||||
|
||||
// integrations
|
||||
@@ -22,7 +22,7 @@ export const INTEGRATION_GITLAB = "gitlab";
|
||||
export const INTEGRATION_RENDER = "render";
|
||||
export const INTEGRATION_RAILWAY = "railway";
|
||||
export const INTEGRATION_FLYIO = "flyio";
|
||||
export const INTEGRATION_LARAVELFORGE = "laravel-forge"
|
||||
export const INTEGRATION_LARAVELFORGE = "laravel-forge";
|
||||
export const INTEGRATION_CIRCLECI = "circleci";
|
||||
export const INTEGRATION_TRAVISCI = "travisci";
|
||||
export const INTEGRATION_TEAMCITY = "teamcity";
|
||||
@@ -38,32 +38,34 @@ export const INTEGRATION_WINDMILL = "windmill";
|
||||
export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform";
|
||||
export const INTEGRATION_CLOUD_66 = "cloud-66";
|
||||
export const INTEGRATION_NORTHFLANK = "northflank";
|
||||
export const INTEGRATION_HASURA_CLOUD = "hasura-cloud";
|
||||
export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
]);
|
||||
|
||||
// integration types
|
||||
@@ -71,15 +73,14 @@ export const INTEGRATION_OAUTH2 = "oauth2";
|
||||
|
||||
// integration oauth endpoints
|
||||
export const INTEGRATION_GCP_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||
export const INTEGRATION_AZURE_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
||||
export const INTEGRATION_AZURE_TOKEN_URL =
|
||||
"https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
||||
export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
|
||||
export const INTEGRATION_VERCEL_TOKEN_URL =
|
||||
"https://api.vercel.com/v2/oauth/access_token";
|
||||
export const INTEGRATION_VERCEL_TOKEN_URL = "https://api.vercel.com/v2/oauth/access_token";
|
||||
export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||
"https://github.com/login/oauth/access_token";
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
||||
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
||||
export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"
|
||||
export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token";
|
||||
|
||||
// integration apps endpoints
|
||||
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
||||
@@ -106,268 +107,279 @@ export const INTEGRATION_WINDMILL_API_URL = "https://app.windmill.dev/api";
|
||||
export const INTEGRATION_DIGITAL_OCEAN_API_URL = "https://api.digitalocean.com";
|
||||
export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api";
|
||||
export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com";
|
||||
export const INTEGRATION_HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql";
|
||||
|
||||
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com"
|
||||
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com";
|
||||
export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
|
||||
export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com";
|
||||
export const INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
||||
export const INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE =
|
||||
"https://www.googleapis.com/auth/cloud-platform";
|
||||
|
||||
export const getIntegrationOptions = async () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: "Heroku",
|
||||
slug: "heroku",
|
||||
image: "Heroku.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdHeroku(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Vercel",
|
||||
slug: "vercel",
|
||||
image: "Vercel.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: "",
|
||||
clientSlug: await getClientSlugVercel(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Netlify",
|
||||
slug: "netlify",
|
||||
image: "Netlify.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdNetlify(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
slug: "github",
|
||||
image: "GitHub.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGitHub(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Render",
|
||||
slug: "render",
|
||||
image: "Render.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Railway",
|
||||
slug: "railway",
|
||||
image: "Railway.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Fly.io",
|
||||
slug: "flyio",
|
||||
image: "Flyio.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "AWS Parameter Store",
|
||||
slug: "aws-parameter-store",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Laravel Forge",
|
||||
slug: "laravel-forge",
|
||||
image: "Laravel Forge.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "AWS Secrets Manager",
|
||||
slug: "aws-secret-manager",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Azure Key Vault",
|
||||
slug: "azure-key-vault",
|
||||
image: "Microsoft Azure.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdAzure(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Circle CI",
|
||||
slug: "circleci",
|
||||
image: "Circle CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GitLab",
|
||||
slug: "gitlab",
|
||||
image: "GitLab.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: await getClientIdGitLab(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Terraform Cloud",
|
||||
slug: "terraform-cloud",
|
||||
image: "Terraform Cloud.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
cliendId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Travis CI",
|
||||
slug: "travisci",
|
||||
image: "Travis CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "TeamCity",
|
||||
slug: "teamcity",
|
||||
image: "TeamCity.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Supabase",
|
||||
slug: "supabase",
|
||||
image: "Supabase.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Checkly",
|
||||
slug: "checkly",
|
||||
image: "Checkly.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
image: "Vault.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GCP Secret Manager",
|
||||
slug: "gcp-secret-manager",
|
||||
image: "Google Cloud Platform.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGCPSecretManager(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Pages",
|
||||
slug: "cloudflare-pages",
|
||||
image: "Cloudflare.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "BitBucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdBitBucket(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Codefresh",
|
||||
slug: "codefresh",
|
||||
image: "Codefresh.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Windmill",
|
||||
slug: "windmill",
|
||||
image: "Windmill.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Digital Ocean App Platform",
|
||||
slug: "digital-ocean-app-platform",
|
||||
image: "Digital Ocean.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Cloud 66",
|
||||
slug: "cloud-66",
|
||||
image: "Cloud 66.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Northflank",
|
||||
slug: "northflank",
|
||||
image: "Northflank.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
]
|
||||
|
||||
return INTEGRATION_OPTIONS;
|
||||
}
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: "Heroku",
|
||||
slug: "heroku",
|
||||
image: "Heroku.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdHeroku(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Vercel",
|
||||
slug: "vercel",
|
||||
image: "Vercel.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: "",
|
||||
clientSlug: await getClientSlugVercel(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Netlify",
|
||||
slug: "netlify",
|
||||
image: "Netlify.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdNetlify(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
slug: "github",
|
||||
image: "GitHub.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGitHub(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Render",
|
||||
slug: "render",
|
||||
image: "Render.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Railway",
|
||||
slug: "railway",
|
||||
image: "Railway.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Fly.io",
|
||||
slug: "flyio",
|
||||
image: "Flyio.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "AWS Parameter Store",
|
||||
slug: "aws-parameter-store",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Laravel Forge",
|
||||
slug: "laravel-forge",
|
||||
image: "Laravel Forge.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "AWS Secrets Manager",
|
||||
slug: "aws-secret-manager",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Azure Key Vault",
|
||||
slug: "azure-key-vault",
|
||||
image: "Microsoft Azure.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdAzure(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Circle CI",
|
||||
slug: "circleci",
|
||||
image: "Circle CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GitLab",
|
||||
slug: "gitlab",
|
||||
image: "GitLab.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: await getClientIdGitLab(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Terraform Cloud",
|
||||
slug: "terraform-cloud",
|
||||
image: "Terraform Cloud.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
cliendId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Travis CI",
|
||||
slug: "travisci",
|
||||
image: "Travis CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "TeamCity",
|
||||
slug: "teamcity",
|
||||
image: "TeamCity.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Supabase",
|
||||
slug: "supabase",
|
||||
image: "Supabase.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Checkly",
|
||||
slug: "checkly",
|
||||
image: "Checkly.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
image: "Vault.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GCP Secret Manager",
|
||||
slug: "gcp-secret-manager",
|
||||
image: "Google Cloud Platform.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGCPSecretManager(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Pages",
|
||||
slug: "cloudflare-pages",
|
||||
image: "Cloudflare.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "BitBucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdBitBucket(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Codefresh",
|
||||
slug: "codefresh",
|
||||
image: "Codefresh.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Windmill",
|
||||
slug: "windmill",
|
||||
image: "Windmill.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Digital Ocean App Platform",
|
||||
slug: "digital-ocean-app-platform",
|
||||
image: "Digital Ocean.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloud 66",
|
||||
slug: "cloud-66",
|
||||
image: "Cloud 66.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Northflank",
|
||||
slug: "northflank",
|
||||
image: "Northflank.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Hasura Cloud",
|
||||
slug: "hasura-cloud",
|
||||
image: "Hasura.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
}
|
||||
];
|
||||
|
||||
return INTEGRATION_OPTIONS;
|
||||
};
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 678 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
36
docs/integrations/cloud/hasura-cloud.mdx
Normal file
36
docs/integrations/cloud/hasura-cloud.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "Hasura Cloud"
|
||||
description: "How to sync secrets from Infisical to Hasura Cloud"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
## Navigate to your project's integrations tab
|
||||
|
||||

|
||||
|
||||
## Enter your Hasura Cloud Access Token
|
||||
|
||||
Obtain a Hasura Cloud Access Token in My Account > Access Tokens
|
||||
|
||||

|
||||
|
||||
Press on the Hasura Cloud tile and input your Hasura Cloud access token to grant Infisical access to your Hasura Cloud account.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to which Hasura Cloud project and press create integration to start syncing secrets to Hasura Cloud.
|
||||
|
||||

|
||||

|
||||
@@ -189,9 +189,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"integrations/overview"
|
||||
]
|
||||
"pages": ["integrations/overview"]
|
||||
},
|
||||
{
|
||||
"group": "Infrastructure Integrations",
|
||||
@@ -221,9 +219,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Digital Ocean",
|
||||
"pages": [
|
||||
"integrations/cloud/digital-ocean-app-platform"
|
||||
]
|
||||
"pages": ["integrations/cloud/digital-ocean-app-platform"]
|
||||
},
|
||||
"integrations/cloud/heroku",
|
||||
"integrations/cloud/vercel",
|
||||
@@ -234,6 +230,7 @@
|
||||
"integrations/cloud/laravel-forge",
|
||||
"integrations/cloud/supabase",
|
||||
"integrations/cloud/northflank",
|
||||
"integrations/cloud/hasura-cloud",
|
||||
"integrations/cloud/terraform-cloud",
|
||||
"integrations/cloud/teamcity",
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
@@ -277,9 +274,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Build Tool Integrations",
|
||||
"pages": [
|
||||
"integrations/build-tools/gradle"
|
||||
]
|
||||
"pages": ["integrations/build-tools/gradle"]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
|
||||
@@ -6,74 +6,75 @@ const integrationSlugNameMapping: Mapping = {
|
||||
"azure-key-vault": "Azure Key Vault",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
"aws-secret-manager": "AWS Secrets Manager",
|
||||
"heroku": "Heroku",
|
||||
"vercel": "Vercel",
|
||||
"netlify": "Netlify",
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab",
|
||||
"render": "Render",
|
||||
heroku: "Heroku",
|
||||
vercel: "Vercel",
|
||||
netlify: "Netlify",
|
||||
github: "GitHub",
|
||||
gitlab: "GitLab",
|
||||
render: "Render",
|
||||
"laravel-forge": "Laravel Forge",
|
||||
"railway": "Railway",
|
||||
"flyio": "Fly.io",
|
||||
"circleci": "CircleCI",
|
||||
"travisci": "TravisCI",
|
||||
"supabase": "Supabase",
|
||||
"checkly": "Checkly",
|
||||
"qovery": "Qovery",
|
||||
railway: "Railway",
|
||||
flyio: "Fly.io",
|
||||
circleci: "CircleCI",
|
||||
travisci: "TravisCI",
|
||||
supabase: "Supabase",
|
||||
checkly: "Checkly",
|
||||
qovery: "Qovery",
|
||||
"terraform-cloud": "Terraform Cloud",
|
||||
"teamcity": "TeamCity",
|
||||
teamcity: "TeamCity",
|
||||
"hashicorp-vault": "Vault",
|
||||
"cloudflare-pages": "Cloudflare Pages",
|
||||
"codefresh": "Codefresh",
|
||||
codefresh: "Codefresh",
|
||||
"digital-ocean-app-platform": "Digital Ocean App Platform",
|
||||
"bitbucket": "BitBucket",
|
||||
bitbucket: "BitBucket",
|
||||
"cloud-66": "Cloud 66",
|
||||
"northflank": "Northflank",
|
||||
"windmill": "Windmill",
|
||||
"gcp-secret-manager": "GCP Secret Manager"
|
||||
}
|
||||
northflank: "Northflank",
|
||||
windmill: "Windmill",
|
||||
"gcp-secret-manager": "GCP Secret Manager",
|
||||
"hasura-cloud": "Hasura Cloud"
|
||||
};
|
||||
|
||||
const envMapping: Mapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Testing: "test"
|
||||
};
|
||||
|
||||
const reverseEnvMapping: Mapping = {
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
test: "Testing"
|
||||
};
|
||||
|
||||
const contextNetlifyMapping: Mapping = {
|
||||
"dev": "Local development",
|
||||
dev: "Local development",
|
||||
"branch-deploy": "Branch deploys",
|
||||
"deploy-preview": "Deploy Previews",
|
||||
"production": "Production"
|
||||
}
|
||||
production: "Production"
|
||||
};
|
||||
|
||||
const reverseContextNetlifyMapping: Mapping = {
|
||||
"Local development": "dev",
|
||||
"Branch deploys": "branch-deploy",
|
||||
"Deploy Previews": "deploy-preview",
|
||||
"Production": "production"
|
||||
}
|
||||
Production: "production"
|
||||
};
|
||||
|
||||
const plansDev: Mapping = {
|
||||
"starter": "prod_Mb4ATFT5QAHoPM",
|
||||
"team": "prod_NEpD2WMXUS2eDn",
|
||||
"professional": "prod_Mb4CetZ2jE7jdl",
|
||||
"enterprise": "licence_key_required"
|
||||
}
|
||||
starter: "prod_Mb4ATFT5QAHoPM",
|
||||
team: "prod_NEpD2WMXUS2eDn",
|
||||
professional: "prod_Mb4CetZ2jE7jdl",
|
||||
enterprise: "licence_key_required"
|
||||
};
|
||||
|
||||
const plansProd: Mapping = {
|
||||
"starter": "prod_Mb8oR5XNwyFTul",
|
||||
"team": "prod_NEp7fAB3UJWK6A",
|
||||
"professional": "prod_Mb8pUIpA0OUi5N",
|
||||
"enterprise": "licence_key_required"
|
||||
}
|
||||
starter: "prod_Mb8oR5XNwyFTul",
|
||||
team: "prod_NEp7fAB3UJWK6A",
|
||||
professional: "prod_Mb8pUIpA0OUi5N",
|
||||
enterprise: "licence_key_required"
|
||||
};
|
||||
|
||||
const plans = plansProd || plansDev;
|
||||
|
||||
@@ -83,4 +84,5 @@ export {
|
||||
integrationSlugNameMapping,
|
||||
plans,
|
||||
reverseContextNetlifyMapping,
|
||||
reverseEnvMapping}
|
||||
reverseEnvMapping
|
||||
};
|
||||
|
||||
11
frontend/public/images/integrations/Hasura.svg
Normal file
11
frontend/public/images/integrations/Hasura.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="81" height="84" viewBox="-20 -20 121 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5273_21928)">
|
||||
<path d="M79.7186 28.6019C82.1218 21.073 80.6778 6.03601 76.0158 0.487861C75.4073 -0.238064 74.2624 -0.134361 73.757 0.664158L68.0121 9.72786C66.5887 11.5427 64.0308 11.9575 62.1124 10.6923C55.8827 6.59601 48.4359 4.21082 40.4322 4.21082C32.4285 4.21082 24.9817 6.59601 18.752 10.6923C16.8336 11.9575 14.2757 11.5323 12.8523 9.72786L7.10738 0.664158C6.60199 -0.134361 5.45712 -0.238064 4.84859 0.487861C0.186621 6.03601 -1.25735 21.073 1.14583 28.6019C1.94002 31.1012 2.16693 33.7456 1.69248 36.3279C1.22834 38.879 0.753897 41.9693 0.753897 44.1056C0.753897 66.1323 18.5251 84.0004 40.4322 84.0004C62.3497 84.0004 80.1105 66.1427 80.1105 44.1056C80.1105 41.959 79.6464 38.879 79.1719 36.3279C78.6975 33.7456 78.9244 31.1012 79.7186 28.6019ZM40.4322 75.0819C23.4965 75.0819 9.71684 61.2271 9.71684 44.199C9.71684 43.639 9.73747 43.0893 9.7581 42.5397C10.3769 30.9353 17.3802 21.0108 27.3024 16.2819C31.2836 14.3738 35.7393 13.316 40.4322 13.316C45.1251 13.316 49.5808 14.3842 53.5724 16.2923C63.4945 21.0212 70.4978 30.9456 71.1166 42.5397C71.1476 43.0893 71.1579 43.639 71.1579 44.199C71.1476 61.2271 57.3679 75.0819 40.4322 75.0819Z" fill="#1EB4D4"/>
|
||||
<path d="M53.7371 56.083L45.8881 42.4044L39.153 30.997C38.9983 30.7274 38.7095 30.5615 38.3898 30.5615H31.9538C31.634 30.5615 31.3452 30.7378 31.1905 31.0074C31.0358 31.2874 31.0358 31.6296 31.2008 31.8993L37.6368 42.7881L28.9936 56.0415C28.8183 56.3111 28.7977 56.6637 28.9524 56.9541C29.1071 57.2444 29.4062 57.4207 29.7259 57.4207H36.2032C36.5023 57.4207 36.7808 57.2652 36.9458 57.0163L41.6181 49.6741L45.8056 56.9748C45.9603 57.2548 46.2594 57.4207 46.5688 57.4207H52.9533C53.273 57.4207 53.5618 57.2548 53.7165 56.9748C53.9022 56.6948 53.9022 56.363 53.7371 56.083Z" fill="#1EB4D4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5273_21928">
|
||||
<rect width="81" height="84" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -41,7 +41,6 @@ export default function FlyioAuthorizeIntegrationPage() {
|
||||
const onFormSubmit = async ({
|
||||
accessToken
|
||||
}: FormData) => {
|
||||
console.log("onFormSubmit accessToken: ", accessToken);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
116
frontend/src/pages/integrations/hasura-cloud/authorize.tsx
Normal file
116
frontend/src/pages/integrations/hasura-cloud/authorize.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
||||
import { useSaveIntegrationAccessToken } from "@app/hooks/api";
|
||||
|
||||
const schema = yup.object({
|
||||
accessToken: yup.string().trim().required("Hasura Cloud Access Token is required")
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
const APP_NAME = "Hasura Cloud";
|
||||
export default function HasuraCloudAuthorizeIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const { control, handleSubmit } = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
accessToken: ""
|
||||
}
|
||||
});
|
||||
|
||||
const onFormSubmit = async ({ accessToken }: FormData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "hasura-cloud",
|
||||
accessToken
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/hasura-cloud/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Authorize {APP_NAME} Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="px-6 text-left text-xl"
|
||||
subTitle="After adding your access token, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Hasura.svg"
|
||||
height={30}
|
||||
width={30}
|
||||
alt={`${APP_NAME} logo`}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">{APP_NAME} Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/hasura-cloud" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
Docs
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)} className="px-6 pb-8 text-right">
|
||||
<Controller
|
||||
control={control}
|
||||
name="accessToken"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={`${APP_NAME} Access Token`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input {...field} placeholder="" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mt-2 w-min"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to {APP_NAME}
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
HasuraCloudAuthorizeIntegrationPage.requireAuth = true;
|
||||
228
frontend/src/pages/integrations/hasura-cloud/create.tsx
Normal file
228
frontend/src/pages/integrations/hasura-cloud/create.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "@app/hooks/api/workspace";
|
||||
|
||||
const schema = yup.object({
|
||||
secretPath: yup.string().trim().required("Secret path is required"),
|
||||
sourceEnvironment: yup.string().trim().required("Project environment is required"),
|
||||
appId: yup.string().trim().required("Hasura Cloud project is required")
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
const APP_NAME = "Hasura Cloud";
|
||||
export default function HasuraCloudCreateIntegrationPage() {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById(
|
||||
(integrationAuthId as string) ?? ""
|
||||
);
|
||||
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
|
||||
useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const onFormSubmit = async ({ secretPath, sourceEnvironment, appId }: FormData) => {
|
||||
try {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
const app = integrationAuthApps?.find((data) => data.appId === appId);
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
sourceEnvironment,
|
||||
secretPath,
|
||||
appId,
|
||||
app: app?.name
|
||||
});
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth && workspace && integrationAuthApps ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up {APP_NAME} Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600">
|
||||
<CardTitle
|
||||
className="px-6 text-left text-xl"
|
||||
subTitle={`Choose which environment or folder in Infisical you want to sync to ${APP_NAME} environment variables.`}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Hasura.svg"
|
||||
height={30}
|
||||
width={30}
|
||||
alt={`${APP_NAME} logo`}
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">{APP_NAME} Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/flyio" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
Docs
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
|
||||
<form onSubmit={handleSubmit(onFormSubmit)} className="flex w-full flex-col px-6">
|
||||
<Controller
|
||||
control={control}
|
||||
name="sourceEnvironment"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
className="w-full border border-mineshaft-500"
|
||||
value={field.value}
|
||||
onValueChange={(val) => {
|
||||
field.onChange(val);
|
||||
}}
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="appId"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Hasura Cloud Project"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
className="w-full border border-mineshaft-500"
|
||||
value={field.value}
|
||||
isDisabled={integrationAuthApps?.length === 0}
|
||||
onValueChange={(val) => {
|
||||
field.onChange(val);
|
||||
}}
|
||||
>
|
||||
{integrationAuthApps?.map((project) => (
|
||||
<SelectItem value={project.appId ?? ""} key={`project-id-${project.appId}`}>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up {APP_NAME} Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
{isIntegrationAuthLoading || isIntegrationAuthAppsLoading ? (
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-max max-w-md flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-center text-mineshaft-200">
|
||||
<FontAwesomeIcon icon={faBugs} className="inlineli my-2 text-6xl" />
|
||||
<p>
|
||||
Something went wrong. Please contact{" "}
|
||||
<a
|
||||
className="inline cursor-pointer text-mineshaft-100 underline decoration-primary-500 underline-offset-4 opacity-80 duration-200 hover:opacity-100"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="mailto:support@infisical.com"
|
||||
>
|
||||
support@infisical.com
|
||||
</a>{" "}
|
||||
if the issue persists.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
HasuraCloudCreateIntegrationPage.requireAuth = true;
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { TCloudIntegration,UserWsKeyPair } from "@app/hooks/api/types";
|
||||
import { TCloudIntegration, UserWsKeyPair } from "@app/hooks/api/types";
|
||||
|
||||
import {
|
||||
decryptAssymmetric,
|
||||
@@ -32,11 +32,10 @@ export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) =
|
||||
|
||||
export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => {
|
||||
try {
|
||||
|
||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
|
||||
|
||||
let link = "";
|
||||
switch (integrationOption.slug) {
|
||||
case "gcp-secret-manager":
|
||||
@@ -123,6 +122,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
case "teamcity":
|
||||
link = `${window.location.origin}/integrations/teamcity/authorize`;
|
||||
break;
|
||||
case "hasura-cloud":
|
||||
link = `${window.location.origin}/integrations/hasura-cloud/authorize`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -130,7 +132,6 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
if (link !== "") {
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user