mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 13:27:22 +00:00
Merge pull request #3566 from Infisical/daniel/identity-ldap-auth
feat(identities): ldap auth
This commit is contained in:
Vendored
+13
-1
@@ -66,6 +66,8 @@ import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-a
|
|||||||
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
||||||
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
||||||
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
||||||
|
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
||||||
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
|
||||||
@@ -146,6 +148,13 @@ declare module "fastify" {
|
|||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
externalProviderAccessToken?: string;
|
externalProviderAccessToken?: string;
|
||||||
};
|
};
|
||||||
|
passportMachineIdentity: {
|
||||||
|
identityId: string;
|
||||||
|
user: {
|
||||||
|
uid: string;
|
||||||
|
mail?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
kmipUser: {
|
kmipUser: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
@@ -153,7 +162,9 @@ declare module "fastify" {
|
|||||||
};
|
};
|
||||||
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
||||||
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
||||||
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
|
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>> & {
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@@ -199,6 +210,7 @@ declare module "fastify" {
|
|||||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||||
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
identityOidcAuth: TIdentityOidcAuthServiceFactory;
|
||||||
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
identityJwtAuth: TIdentityJwtAuthServiceFactory;
|
||||||
|
identityLdapAuth: TIdentityLdapAuthServiceFactory;
|
||||||
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
|
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
|
||||||
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
|
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
|
||||||
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
||||||
|
|||||||
Vendored
+10
@@ -432,6 +432,11 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TIdentityLdapAuths,
|
||||||
|
TIdentityLdapAuthsInsert,
|
||||||
|
TIdentityLdapAuthsUpdate
|
||||||
|
} from "@app/db/schemas/identity-ldap-auths";
|
||||||
import {
|
import {
|
||||||
TMicrosoftTeamsIntegrations,
|
TMicrosoftTeamsIntegrations,
|
||||||
TMicrosoftTeamsIntegrationsInsert,
|
TMicrosoftTeamsIntegrationsInsert,
|
||||||
@@ -735,6 +740,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityJwtAuthsInsert,
|
TIdentityJwtAuthsInsert,
|
||||||
TIdentityJwtAuthsUpdate
|
TIdentityJwtAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityLdapAuth]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityLdapAuths,
|
||||||
|
TIdentityLdapAuthsInsert,
|
||||||
|
TIdentityLdapAuthsUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityUaClientSecrets,
|
TIdentityUaClientSecrets,
|
||||||
TIdentityUaClientSecretsInsert,
|
TIdentityUaClientSecretsInsert,
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityLdapAuth))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||||
|
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||||
|
|
||||||
|
t.uuid("identityId").notNullable().unique();
|
||||||
|
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.binary("encryptedBindDN").notNullable();
|
||||||
|
t.binary("encryptedBindPass").notNullable();
|
||||||
|
t.binary("encryptedLdapCaCertificate").nullable();
|
||||||
|
|
||||||
|
t.string("url").notNullable();
|
||||||
|
t.string("searchBase").notNullable();
|
||||||
|
t.string("searchFilter").notNullable();
|
||||||
|
|
||||||
|
t.jsonb("allowedFields").nullable();
|
||||||
|
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityLdapAuth);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityLdapAuthsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
accessTokenTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||||
|
accessTokenTrustedIps: z.unknown(),
|
||||||
|
identityId: z.string().uuid(),
|
||||||
|
encryptedBindDN: zodBuffer,
|
||||||
|
encryptedBindPass: zodBuffer,
|
||||||
|
encryptedLdapCaCertificate: zodBuffer.nullable().optional(),
|
||||||
|
url: z.string(),
|
||||||
|
searchBase: z.string(),
|
||||||
|
searchFilter: z.string(),
|
||||||
|
allowedFields: z.unknown().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
||||||
|
export type TIdentityLdapAuthsInsert = Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityLdapAuthsUpdate = Partial<Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -80,6 +80,7 @@ export enum TableName {
|
|||||||
IdentityAwsAuth = "identity_aws_auths",
|
IdentityAwsAuth = "identity_aws_auths",
|
||||||
IdentityOidcAuth = "identity_oidc_auths",
|
IdentityOidcAuth = "identity_oidc_auths",
|
||||||
IdentityJwtAuth = "identity_jwt_auths",
|
IdentityJwtAuth = "identity_jwt_auths",
|
||||||
|
IdentityLdapAuth = "identity_ldap_auths",
|
||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
@@ -232,7 +233,8 @@ export enum IdentityAuthMethod {
|
|||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
OIDC_AUTH = "oidc-auth",
|
OIDC_AUTH = "oidc-auth",
|
||||||
JWT_AUTH = "jwt-auth"
|
JWT_AUTH = "jwt-auth",
|
||||||
|
LDAP_AUTH = "ldap-auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectType {
|
export enum ProjectType {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { WorkflowIntegration } from "@app/services/workflow-integration/workflow
|
|||||||
|
|
||||||
import { KmipPermission } from "../kmip/kmip-enum";
|
import { KmipPermission } from "../kmip/kmip-enum";
|
||||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
|
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
filter: {
|
filter: {
|
||||||
@@ -119,44 +120,60 @@ export enum EventType {
|
|||||||
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
|
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
|
||||||
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
|
||||||
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
|
||||||
|
|
||||||
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
|
||||||
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
|
||||||
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
|
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
|
||||||
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
|
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
|
||||||
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
|
||||||
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
|
||||||
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
|
||||||
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
|
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
|
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
|
||||||
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
|
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
|
||||||
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
|
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
|
||||||
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
|
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
|
||||||
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
|
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
|
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
|
||||||
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
|
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
|
||||||
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
|
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
|
||||||
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
|
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
|
||||||
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
|
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
|
||||||
|
|
||||||
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||||
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||||
|
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
|
||||||
|
|
||||||
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
|
||||||
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
|
||||||
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
|
||||||
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||||
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
|
||||||
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
|
||||||
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
|
||||||
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
|
||||||
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
|
||||||
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
|
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
|
||||||
|
|
||||||
|
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
|
||||||
|
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
|
||||||
|
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
|
||||||
|
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
|
||||||
|
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
|
||||||
|
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
@@ -1034,6 +1051,55 @@ interface GetIdentityAzureAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginIdentityLdapAuthEvent {
|
||||||
|
type: EventType.LOGIN_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
ldapUsername: string;
|
||||||
|
ldapEmail?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddIdentityLdapAuthEvent {
|
||||||
|
type: EventType.ADD_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateIdentityLdapAuthEvent {
|
||||||
|
type: EventType.UPDATE_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityLdapAuthEvent {
|
||||||
|
type: EventType.GET_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RevokeIdentityLdapAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_LDAP_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginIdentityOidcAuthEvent {
|
interface LoginIdentityOidcAuthEvent {
|
||||||
type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
|
type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2785,6 +2851,11 @@ export type Event =
|
|||||||
| UpdateIdentityJwtAuthEvent
|
| UpdateIdentityJwtAuthEvent
|
||||||
| GetIdentityJwtAuthEvent
|
| GetIdentityJwtAuthEvent
|
||||||
| DeleteIdentityJwtAuthEvent
|
| DeleteIdentityJwtAuthEvent
|
||||||
|
| LoginIdentityLdapAuthEvent
|
||||||
|
| AddIdentityLdapAuthEvent
|
||||||
|
| UpdateIdentityLdapAuthEvent
|
||||||
|
| GetIdentityLdapAuthEvent
|
||||||
|
| RevokeIdentityLdapAuthEvent
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
| GetEnvironmentEvent
|
| GetEnvironmentEvent
|
||||||
| UpdateEnvironmentEvent
|
| UpdateEnvironmentEvent
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export type TLDAPConfig = {
|
|||||||
caCert: string;
|
caCert: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TTestLDAPConfigDTO = Omit<
|
||||||
|
TLDAPConfig,
|
||||||
|
"organization" | "id" | "groupSearchBase" | "groupSearchFilter" | "isActive" | "uniqueUserAttribute" | "searchBase"
|
||||||
|
>;
|
||||||
|
|
||||||
export type TCreateLdapCfgDTO = {
|
export type TCreateLdapCfgDTO = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import ldapjs from "ldapjs";
|
|||||||
|
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
import { TLDAPConfig } from "./ldap-config-types";
|
import { TLDAPConfig, TTestLDAPConfigDTO } from "./ldap-config-types";
|
||||||
|
|
||||||
export const isValidLdapFilter = (filter: string) => {
|
export const isValidLdapFilter = (filter: string) => {
|
||||||
try {
|
try {
|
||||||
ldapjs.parseFilter(filter);
|
ldapjs.parseFilter(filter);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Invalid LDAP filter");
|
logger.error(error, "Invalid LDAP filter");
|
||||||
logger.error(error);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -20,7 +19,7 @@ export const isValidLdapFilter = (filter: string) => {
|
|||||||
* @param ldapConfig - The LDAP configuration to test
|
* @param ldapConfig - The LDAP configuration to test
|
||||||
* @returns {Boolean} isConnected - Whether or not the connection was successful
|
* @returns {Boolean} isConnected - Whether or not the connection was successful
|
||||||
*/
|
*/
|
||||||
export const testLDAPConfig = async (ldapConfig: TLDAPConfig): Promise<boolean> => {
|
export const testLDAPConfig = async (ldapConfig: TTestLDAPConfigDTO): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const ldapClient = ldapjs.createClient({
|
const ldapClient = ldapjs.createClient({
|
||||||
url: ldapConfig.url,
|
url: ldapConfig.url,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export enum ApiDocsTags {
|
|||||||
KubernetesAuth = "Kubernetes Auth",
|
KubernetesAuth = "Kubernetes Auth",
|
||||||
JwtAuth = "JWT Auth",
|
JwtAuth = "JWT Auth",
|
||||||
OidcAuth = "OIDC Auth",
|
OidcAuth = "OIDC Auth",
|
||||||
|
LdapAuth = "LDAP Auth",
|
||||||
Groups = "Groups",
|
Groups = "Groups",
|
||||||
Organizations = "Organizations",
|
Organizations = "Organizations",
|
||||||
Projects = "Projects",
|
Projects = "Projects",
|
||||||
@@ -184,6 +185,49 @@ export const UNIVERSAL_AUTH = {
|
|||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const LDAP_AUTH = {
|
||||||
|
LOGIN: {
|
||||||
|
identityId: "The ID of the identity to login.",
|
||||||
|
username: "The username of the LDAP user to login.",
|
||||||
|
password: "The password of the LDAP user to login."
|
||||||
|
},
|
||||||
|
ATTACH: {
|
||||||
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
|
url: "The URL of the LDAP server.",
|
||||||
|
allowedFields:
|
||||||
|
"The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.",
|
||||||
|
searchBase: "The base DN to search for the LDAP user.",
|
||||||
|
searchFilter: "The filter to use to search for the LDAP user.",
|
||||||
|
bindDN: "The DN of the user to bind to the LDAP server.",
|
||||||
|
bindPass: "The password of the user to bind to the LDAP server.",
|
||||||
|
ldapCaCertificate: "The PEM-encoded CA certificate for the LDAP server.",
|
||||||
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update the configuration for.",
|
||||||
|
url: "The new URL of the LDAP server.",
|
||||||
|
allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.",
|
||||||
|
searchBase: "The new base DN to search for the LDAP user.",
|
||||||
|
searchFilter: "The new filter to use to search for the LDAP user.",
|
||||||
|
bindDN: "The new DN of the user to bind to the LDAP server.",
|
||||||
|
bindPass: "The new password of the user to bind to the LDAP server.",
|
||||||
|
ldapCaCertificate: "The new PEM-encoded CA certificate for the LDAP server.",
|
||||||
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
RETRIEVE: {
|
||||||
|
identityId: "The ID of the identity to retrieve the configuration for."
|
||||||
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke the configuration for."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const AWS_AUTH = {
|
export const AWS_AUTH = {
|
||||||
LOGIN: {
|
LOGIN: {
|
||||||
identityId: "The ID of the identity to login.",
|
identityId: "The ID of the identity to login.",
|
||||||
|
|||||||
@@ -84,7 +84,9 @@ const redactedKeys = [
|
|||||||
"secrets",
|
"secrets",
|
||||||
"key",
|
"key",
|
||||||
"password",
|
"password",
|
||||||
"config"
|
"config",
|
||||||
|
"bindPass",
|
||||||
|
"bindDN"
|
||||||
];
|
];
|
||||||
|
|
||||||
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";
|
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ import { identityJwtAuthDALFactory } from "@app/services/identity-jwt-auth/ident
|
|||||||
import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
|
||||||
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
|
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
|
||||||
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
|
||||||
|
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
|
||||||
|
import { identityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
|
||||||
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
|
||||||
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
|
||||||
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
@@ -354,6 +356,7 @@ export const registerRoutes = async (
|
|||||||
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
||||||
const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
|
const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
|
||||||
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
||||||
|
const identityLdapAuthDAL = identityLdapAuthDALFactory(db);
|
||||||
|
|
||||||
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
|
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
|
||||||
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
||||||
@@ -1445,6 +1448,16 @@ export const registerRoutes = async (
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityLdapAuthService = identityLdapAuthServiceFactory({
|
||||||
|
identityLdapAuthDAL,
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
identityDAL
|
||||||
|
});
|
||||||
|
|
||||||
const gatewayService = gatewayServiceFactory({
|
const gatewayService = gatewayServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
gatewayDAL,
|
gatewayDAL,
|
||||||
@@ -1705,6 +1718,7 @@ export const registerRoutes = async (
|
|||||||
identityAzureAuth: identityAzureAuthService,
|
identityAzureAuth: identityAzureAuthService,
|
||||||
identityOidcAuth: identityOidcAuthService,
|
identityOidcAuth: identityOidcAuthService,
|
||||||
identityJwtAuth: identityJwtAuthService,
|
identityJwtAuth: identityJwtAuthService,
|
||||||
|
identityLdapAuth: identityLdapAuthService,
|
||||||
accessApprovalPolicy: accessApprovalPolicyService,
|
accessApprovalPolicy: accessApprovalPolicyService,
|
||||||
accessApprovalRequest: accessApprovalRequestService,
|
accessApprovalRequest: accessApprovalRequestService,
|
||||||
secretApprovalPolicy: secretApprovalPolicyService,
|
secretApprovalPolicy: secretApprovalPolicyService,
|
||||||
|
|||||||
@@ -0,0 +1,497 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
// All the any rules are disabled because passport typesense with fastify is really poor
|
||||||
|
|
||||||
|
import { Authenticator } from "@fastify/passport";
|
||||||
|
import fastifySession from "@fastify/session";
|
||||||
|
import { FastifyRequest } from "fastify";
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
import LdapStrategy from "passport-ldapauth";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityLdapAuthsSchema } from "@app/db/schemas/identity-ldap-auths";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { isValidLdapFilter } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
|
import { ApiDocsTags, LDAP_AUTH } from "@app/lib/api-docs";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { AllowedFieldsSchema } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
|
export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const passport = new Authenticator({ key: "ldap-identity-auth", userProperty: "passportMachineIdentity" });
|
||||||
|
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
|
||||||
|
await server.register(passport.initialize());
|
||||||
|
await server.register(passport.secureSession());
|
||||||
|
|
||||||
|
const getLdapPassportOpts = (req: FastifyRequest, done: any) => {
|
||||||
|
const { identityId } = req.body as {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.nextTick(async () => {
|
||||||
|
try {
|
||||||
|
const { ldapConfig, opts } = await server.services.identityLdapAuth.getLdapConfig(identityId);
|
||||||
|
req.ldapConfig = {
|
||||||
|
...ldapConfig,
|
||||||
|
isActive: true,
|
||||||
|
groupSearchBase: "",
|
||||||
|
uniqueUserAttribute: "",
|
||||||
|
groupSearchFilter: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
done(null, opts);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err, "Error in LDAP verification callback");
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
passport.use(
|
||||||
|
new LdapStrategy(
|
||||||
|
getLdapPassportOpts as any,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
async (req: IncomingMessage, user, cb) => {
|
||||||
|
try {
|
||||||
|
const requestBody = (req as unknown as FastifyRequest).body as {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!requestBody.username || !requestBody.password) {
|
||||||
|
return cb(new UnauthorizedError({ message: "Invalid request. Missing username or password." }), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestBody.identityId) {
|
||||||
|
return cb(new UnauthorizedError({ message: "Invalid request. Missing identity ID." }), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ldapConfig } = req as unknown as FastifyRequest;
|
||||||
|
|
||||||
|
if (ldapConfig.allowedFields) {
|
||||||
|
for (const field of ldapConfig.allowedFields) {
|
||||||
|
if (!user[field.key]) {
|
||||||
|
return cb(
|
||||||
|
new UnauthorizedError({ message: `Invalid request. Missing field ${field.key} on user.` }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = field.value.split(",");
|
||||||
|
|
||||||
|
if (!value.includes(user[field.key])) {
|
||||||
|
return cb(
|
||||||
|
new UnauthorizedError({
|
||||||
|
message: `Invalid request. User field '${field.key}' does not match required fields.`
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(null, { identityId: requestBody.identityId, user });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error in LDAP verification callback");
|
||||||
|
return cb(error, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/ldap-auth/login",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapAuth],
|
||||||
|
description: "Login with LDAP Auth",
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().trim().describe(LDAP_AUTH.LOGIN.identityId),
|
||||||
|
username: z.string().describe(LDAP_AUTH.LOGIN.username),
|
||||||
|
password: z.string().describe(LDAP_AUTH.LOGIN.password)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
accessToken: z.string(),
|
||||||
|
expiresIn: z.coerce.number(),
|
||||||
|
accessTokenMaxTTL: z.coerce.number(),
|
||||||
|
tokenType: z.literal("Bearer")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preValidation: passport.authenticate("ldapauth", {
|
||||||
|
failWithError: true,
|
||||||
|
session: false
|
||||||
|
}) as any,
|
||||||
|
|
||||||
|
errorHandler: (error) => {
|
||||||
|
if (error.name === "AuthenticationError") {
|
||||||
|
throw new UnauthorizedError({ message: "Invalid credentials" });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
},
|
||||||
|
|
||||||
|
handler: async (req) => {
|
||||||
|
if (!req.passportMachineIdentity?.identityId) {
|
||||||
|
throw new UnauthorizedError({ message: "Invalid request. Missing identity ID or LDAP entry details." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { identityId, user } = req.passportMachineIdentity;
|
||||||
|
|
||||||
|
const { accessToken, identityLdapAuth, identityMembershipOrg } = await server.services.identityLdapAuth.login({
|
||||||
|
identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityMembershipOrg?.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId,
|
||||||
|
ldapEmail: user.mail,
|
||||||
|
ldapUsername: user.uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
tokenType: "Bearer" as const,
|
||||||
|
expiresIn: identityLdapAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/ldap-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapAuth],
|
||||||
|
description: "Attach LDAP Auth configuration onto identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
url: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.url),
|
||||||
|
bindDN: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindDN),
|
||||||
|
bindPass: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindPass),
|
||||||
|
searchBase: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.searchBase),
|
||||||
|
searchFilter: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.default("(uid={{username}})")
|
||||||
|
.refine(isValidLdapFilter, "Invalid LDAP search filter")
|
||||||
|
.describe(LDAP_AUTH.ATTACH.searchFilter),
|
||||||
|
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
|
||||||
|
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
||||||
|
encryptedBindDN: true,
|
||||||
|
encryptedBindPass: true,
|
||||||
|
encryptedLdapCaCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityLdapAuth = await server.services.identityLdapAuth.attachLdapAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ADD_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
url: identityLdapAuth.url,
|
||||||
|
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
||||||
|
allowedFields: req.body.allowedFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityLdapAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/ldap-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapAuth],
|
||||||
|
description: "Update LDAP Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(LDAP_AUTH.UPDATE.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
url: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.url),
|
||||||
|
bindDN: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindDN),
|
||||||
|
bindPass: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindPass),
|
||||||
|
searchBase: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.searchBase),
|
||||||
|
searchFilter: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.optional()
|
||||||
|
.refine((v) => v === undefined || isValidLdapFilter(v), "Invalid LDAP search filter")
|
||||||
|
.describe(LDAP_AUTH.UPDATE.searchFilter),
|
||||||
|
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.UPDATE.allowedFields),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional()
|
||||||
|
.describe(LDAP_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(LDAP_AUTH.UPDATE.accessTokenTTL),
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(LDAP_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.max(315360000)
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
||||||
|
encryptedBindDN: true,
|
||||||
|
encryptedBindPass: true,
|
||||||
|
encryptedLdapCaCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityLdapAuth = await server.services.identityLdapAuth.updateLdapAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
url: identityLdapAuth.url,
|
||||||
|
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
|
allowedFields: req.body.allowedFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityLdapAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/ldap-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapAuth],
|
||||||
|
description: "Retrieve LDAP Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(LDAP_AUTH.RETRIEVE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
||||||
|
encryptedBindDN: true,
|
||||||
|
encryptedBindPass: true,
|
||||||
|
encryptedLdapCaCertificate: true
|
||||||
|
}).extend({
|
||||||
|
bindDN: z.string(),
|
||||||
|
bindPass: z.string(),
|
||||||
|
ldapCaCertificate: z.string().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityLdapAuth = await server.services.identityLdapAuth.getLdapAuth({
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityLdapAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityLdapAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/ldap-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapAuth],
|
||||||
|
description: "Delete LDAP Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(LDAP_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
||||||
|
encryptedBindDN: true,
|
||||||
|
encryptedBindPass: true,
|
||||||
|
encryptedLdapCaCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityLdapAuth = await server.services.identityLdapAuth.revokeIdentityLdapAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityLdapAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityLdapAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -19,6 +19,7 @@ import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
|||||||
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
||||||
import { registerIdentityJwtAuthRouter } from "./identity-jwt-auth-router";
|
import { registerIdentityJwtAuthRouter } from "./identity-jwt-auth-router";
|
||||||
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
|
||||||
|
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
|
||||||
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
|
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
|
||||||
import { registerIdentityRouter } from "./identity-router";
|
import { registerIdentityRouter } from "./identity-router";
|
||||||
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
|
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
|
||||||
@@ -63,6 +64,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await authRouter.register(registerIdentityAzureAuthRouter);
|
await authRouter.register(registerIdentityAzureAuthRouter);
|
||||||
await authRouter.register(registerIdentityOidcAuthRouter);
|
await authRouter.register(registerIdentityOidcAuthRouter);
|
||||||
await authRouter.register(registerIdentityJwtAuthRouter);
|
await authRouter.register(registerIdentityJwtAuthRouter);
|
||||||
|
await authRouter.register(registerIdentityLdapAuthRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/auth" }
|
{ prefix: "/auth" }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||||
|
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityKubernetesAuth,
|
TableName.IdentityKubernetesAuth,
|
||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
@@ -48,6 +49,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
|
||||||
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityLdapAuth).as("accessTokenTrustedIpsLdap"),
|
||||||
db.ref("name").withSchema(TableName.Identity)
|
db.ref("name").withSchema(TableName.Identity)
|
||||||
)
|
)
|
||||||
.first();
|
.first();
|
||||||
@@ -63,7 +65,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
||||||
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
|
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
|
||||||
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
|
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
|
||||||
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt
|
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt,
|
||||||
|
trustedIpsAccessLdapAuth: doc.accessTokenTrustedIpsLdap
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
|
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
|
||||||
|
|||||||
@@ -186,7 +186,8 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
|
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
|
||||||
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
|
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
|
||||||
[IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth,
|
[IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth,
|
||||||
[IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth
|
[IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth,
|
||||||
|
[IdentityAuthMethod.LDAP_AUTH]: identityAccessToken.trustedIpsAccessLdapAuth
|
||||||
};
|
};
|
||||||
|
|
||||||
const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod];
|
const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod];
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityLdapAuthDALFactory = ReturnType<typeof identityLdapAuthDALFactory>;
|
||||||
|
|
||||||
|
export const identityLdapAuthDALFactory = (db: TDbClient) => {
|
||||||
|
const ldapAuthOrm = ormify(db, TableName.IdentityLdapAuth);
|
||||||
|
|
||||||
|
return ldapAuthOrm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,543 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||||
|
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import {
|
||||||
|
constructPermissionErrorMessage,
|
||||||
|
validatePrivilegeChangeOperation
|
||||||
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
|
||||||
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
|
import { TIdentityLdapAuthDALFactory } from "./identity-ldap-auth-dal";
|
||||||
|
import {
|
||||||
|
AllowedFieldsSchema,
|
||||||
|
TAttachLdapAuthDTO,
|
||||||
|
TGetLdapAuthDTO,
|
||||||
|
TLoginLdapAuthDTO,
|
||||||
|
TRevokeLdapAuthDTO,
|
||||||
|
TUpdateLdapAuthDTO
|
||||||
|
} from "./identity-ldap-auth-types";
|
||||||
|
|
||||||
|
type TIdentityLdapAuthServiceFactoryDep = {
|
||||||
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||||
|
identityLdapAuthDAL: Pick<
|
||||||
|
TIdentityLdapAuthDALFactory,
|
||||||
|
"findOne" | "transaction" | "create" | "updateById" | "delete"
|
||||||
|
>;
|
||||||
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
kmsService: TKmsServiceFactory;
|
||||||
|
identityDAL: TIdentityDALFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
|
||||||
|
|
||||||
|
export const identityLdapAuthServiceFactory = ({
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityDAL,
|
||||||
|
identityLdapAuthDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService,
|
||||||
|
kmsService
|
||||||
|
}: TIdentityLdapAuthServiceFactoryDep) => {
|
||||||
|
const getLdapConfig = async (identityId: string) => {
|
||||||
|
const identity = await identityDAL.findOne({ id: identityId });
|
||||||
|
if (!identity) throw new NotFoundError({ message: `Identity with ID '${identityId}' not found` });
|
||||||
|
|
||||||
|
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: identity.id });
|
||||||
|
if (!identityOrgMembership) throw new NotFoundError({ message: `Identity with ID '${identityId}' not found` });
|
||||||
|
|
||||||
|
const ldapAuth = await identityLdapAuthDAL.findOne({ identityId: identity.id });
|
||||||
|
if (!ldapAuth) throw new NotFoundError({ message: `LDAP auth with ID '${identityId}' not found` });
|
||||||
|
|
||||||
|
const parsedAllowedFields = ldapAuth.allowedFields
|
||||||
|
? AllowedFieldsSchema.array().parse(ldapAuth.allowedFields)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: identityOrgMembership.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const bindDN = decryptor({ cipherTextBlob: ldapAuth.encryptedBindDN }).toString();
|
||||||
|
const bindPass = decryptor({ cipherTextBlob: ldapAuth.encryptedBindPass }).toString();
|
||||||
|
const ldapCaCertificate = ldapAuth.encryptedLdapCaCertificate
|
||||||
|
? decryptor({ cipherTextBlob: ldapAuth.encryptedLdapCaCertificate }).toString()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const ldapConfig = {
|
||||||
|
id: ldapAuth.id,
|
||||||
|
organization: identityOrgMembership.orgId,
|
||||||
|
url: ldapAuth.url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase: ldapAuth.searchBase,
|
||||||
|
searchFilter: ldapAuth.searchFilter,
|
||||||
|
caCert: ldapCaCertificate || "",
|
||||||
|
allowedFields: parsedAllowedFields
|
||||||
|
};
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
server: {
|
||||||
|
url: ldapAuth.url,
|
||||||
|
bindDN,
|
||||||
|
bindCredentials: bindPass,
|
||||||
|
searchBase: ldapAuth.searchBase,
|
||||||
|
searchFilter: ldapAuth.searchFilter,
|
||||||
|
...(ldapCaCertificate
|
||||||
|
? {
|
||||||
|
tlsOptions: {
|
||||||
|
ca: [ldapCaCertificate]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
passReqToCallback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return { opts, ldapConfig };
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async ({ identityId }: TLoginLdapAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg) {
|
||||||
|
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
if (!identityLdapAuth) {
|
||||||
|
throw new NotFoundError({ message: `Failed to find LDAP auth for identity with ID ${identityId}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
if (!plan.ldap) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to login to identity due to plan restriction. Upgrade plan to login to use LDAP authentication."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||||
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityLdapAuth.identityId,
|
||||||
|
isAccessTokenRevoked: false,
|
||||||
|
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenNumUses: 0,
|
||||||
|
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
||||||
|
authMethod: IdentityAuthMethod.LDAP_AUTH
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return newToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const accessToken = jwt.sign(
|
||||||
|
{
|
||||||
|
identityId: identityLdapAuth.identityId,
|
||||||
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
|
||||||
|
Number(identityAccessToken.accessTokenTTL) === 0
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { accessToken, identityLdapAuth, identityAccessToken, identityMembershipOrg };
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachLdapAuth = async ({
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
ldapCaCertificate,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
isActorSuperAdmin,
|
||||||
|
allowedFields
|
||||||
|
}: TAttachLdapAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to add LDAP Auth to already configured identity"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
|
||||||
|
if (!plan.ldap) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to add LDAP Auth to identity due to plan restriction. Upgrade plan to add LDAP Auth."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
||||||
|
|
||||||
|
const identityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: identityMembershipOrg.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedBindPass } = encryptor({
|
||||||
|
plainText: Buffer.from(bindPass)
|
||||||
|
});
|
||||||
|
|
||||||
|
let encryptedLdapCaCertificate: Buffer | undefined;
|
||||||
|
if (ldapCaCertificate) {
|
||||||
|
const { cipherTextBlob: encryptedCertificate } = encryptor({
|
||||||
|
plainText: Buffer.from(ldapCaCertificate)
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptedLdapCaCertificate = encryptedCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedBindDN } = encryptor({
|
||||||
|
plainText: Buffer.from(bindDN)
|
||||||
|
});
|
||||||
|
|
||||||
|
const isConnected = await testLDAPConfig({
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
caCert: ldapCaCertificate || "",
|
||||||
|
url
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to connect to LDAP server. Please ensure that the LDAP server is running and your credentials are correct."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await identityLdapAuthDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityMembershipOrg.identityId,
|
||||||
|
encryptedBindDN,
|
||||||
|
encryptedBindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
url,
|
||||||
|
encryptedLdapCaCertificate,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||||
|
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
return { ...identityLdapAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLdapAuth = async ({
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateLdapAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "The identity does not have LDAP Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
if (
|
||||||
|
(accessTokenMaxTTL || identityLdapAuth.accessTokenMaxTTL) > 0 &&
|
||||||
|
(accessTokenTTL || identityLdapAuth.accessTokenTTL) > (accessTokenMaxTTL || identityLdapAuth.accessTokenMaxTTL)
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
|
||||||
|
if (!plan.ldap) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update LDAP Auth due to plan restriction. Upgrade plan to update LDAP Auth."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
||||||
|
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: identityMembershipOrg.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
let encryptedBindPass: Buffer | undefined;
|
||||||
|
if (bindPass) {
|
||||||
|
const { cipherTextBlob: bindPassCiphertext } = encryptor({
|
||||||
|
plainText: Buffer.from(bindPass)
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptedBindPass = bindPassCiphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedLdapCaCertificate: Buffer | undefined;
|
||||||
|
if (ldapCaCertificate) {
|
||||||
|
const { cipherTextBlob: ldapCaCertificateCiphertext } = encryptor({
|
||||||
|
plainText: Buffer.from(ldapCaCertificate)
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptedLdapCaCertificate = ldapCaCertificateCiphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedBindDN: Buffer | undefined;
|
||||||
|
if (bindDN) {
|
||||||
|
const { cipherTextBlob: bindDNCiphertext } = encryptor({
|
||||||
|
plainText: Buffer.from(bindDN)
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptedBindDN = bindDNCiphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ldapConfig } = await getLdapConfig(identityId);
|
||||||
|
|
||||||
|
const isConnected = await testLDAPConfig({
|
||||||
|
bindDN: bindDN || ldapConfig.bindDN,
|
||||||
|
bindPass: bindPass || ldapConfig.bindPass,
|
||||||
|
caCert: ldapCaCertificate || ldapConfig.caCert,
|
||||||
|
url: url || ldapConfig.url
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to connect to LDAP server. Please ensure that the LDAP server is running and your credentials are correct."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedLdapAuth = await identityLdapAuthDAL.updateById(identityLdapAuth.id, {
|
||||||
|
url,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
encryptedBindDN,
|
||||||
|
encryptedBindPass,
|
||||||
|
encryptedLdapCaCertificate,
|
||||||
|
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||||
|
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
|
: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...updatedLdapAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLdapAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetLdapAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have LDAP Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: identityMembershipOrg.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const bindDN = decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedBindDN }).toString();
|
||||||
|
const bindPass = decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedBindPass }).toString();
|
||||||
|
const ldapCaCertificate = ldapIdentityAuth.encryptedLdapCaCertificate
|
||||||
|
? decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedLdapCaCertificate }).toString()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
return { ...ldapIdentityAuth, orgId: identityMembershipOrg.orgId, bindDN, bindPass, ldapCaCertificate };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeIdentityLdapAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeLdapAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have LDAP Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission, membership } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity,
|
||||||
|
permission,
|
||||||
|
rolePermission
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to revoke LDAP auth of identity with more privileged role",
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||||
|
const [deletedLdapAuth] = await identityLdapAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.LDAP_AUTH }, tx);
|
||||||
|
|
||||||
|
return { ...deletedLdapAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityLdapAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
attachLdapAuth,
|
||||||
|
getLdapConfig,
|
||||||
|
updateLdapAuth,
|
||||||
|
login,
|
||||||
|
revokeIdentityLdapAuth,
|
||||||
|
getLdapAuth
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export const AllowedFieldsSchema = z.object({
|
||||||
|
key: z.string().trim(),
|
||||||
|
value: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.transform((val) => val.replace(/\s/g, ""))
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TAllowedFields = z.infer<typeof AllowedFieldsSchema>;
|
||||||
|
|
||||||
|
export type TAttachLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
url: string;
|
||||||
|
searchBase: string;
|
||||||
|
searchFilter: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
url?: string;
|
||||||
|
searchBase?: string;
|
||||||
|
searchFilter?: string;
|
||||||
|
bindDN?: string;
|
||||||
|
bindPass?: string;
|
||||||
|
allowedFields?: TAllowedFields[];
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TLoginLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TRevokeLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -8,7 +8,8 @@ export const buildAuthMethods = ({
|
|||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
jwtId
|
jwtId,
|
||||||
|
ldapId
|
||||||
}: {
|
}: {
|
||||||
uaId?: string;
|
uaId?: string;
|
||||||
gcpId?: string;
|
gcpId?: string;
|
||||||
@@ -18,6 +19,7 @@ export const buildAuthMethods = ({
|
|||||||
azureId?: string;
|
azureId?: string;
|
||||||
tokenId?: string;
|
tokenId?: string;
|
||||||
jwtId?: string;
|
jwtId?: string;
|
||||||
|
ldapId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return [
|
return [
|
||||||
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
|
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
|
||||||
@@ -27,6 +29,7 @@ export const buildAuthMethods = ({
|
|||||||
...[oidcId ? IdentityAuthMethod.OIDC_AUTH : null],
|
...[oidcId ? IdentityAuthMethod.OIDC_AUTH : null],
|
||||||
...[azureId ? IdentityAuthMethod.AZURE_AUTH : null],
|
...[azureId ? IdentityAuthMethod.AZURE_AUTH : null],
|
||||||
...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null],
|
...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null],
|
||||||
...[jwtId ? IdentityAuthMethod.JWT_AUTH : null]
|
...[jwtId ? IdentityAuthMethod.JWT_AUTH : null],
|
||||||
|
...[ldapId ? IdentityAuthMethod.LDAP_AUTH : null]
|
||||||
].filter((authMethod) => authMethod) as IdentityAuthMethod[];
|
].filter((authMethod) => authMethod) as IdentityAuthMethod[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TIdentityUniversalAuths,
|
TIdentityUniversalAuths,
|
||||||
TOrgRoles
|
TOrgRoles
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { TIdentityLdapAuths } from "@app/db/schemas/identity-ldap-auths";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
import { buildKnexFilterForSearchResource } from "@app/lib/search-resource/db";
|
import { buildKnexFilterForSearchResource } from "@app/lib/search-resource/db";
|
||||||
@@ -81,6 +82,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
`${TableName.IdentityJwtAuth}.identityId`
|
`${TableName.IdentityJwtAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityLdapAuths>(
|
||||||
|
TableName.IdentityLdapAuth,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.IdentityLdapAuth}.identityId`
|
||||||
|
)
|
||||||
|
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.IdentityOrgMembership),
|
selectAllTableCols(TableName.IdentityOrgMembership),
|
||||||
@@ -93,7 +99,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||||
|
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
|
||||||
db.ref("name").withSchema(TableName.Identity)
|
db.ref("name").withSchema(TableName.Identity)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -200,6 +206,12 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.identityId",
|
||||||
`${TableName.IdentityJwtAuth}.identityId`
|
`${TableName.IdentityJwtAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityLdapAuths>(
|
||||||
|
TableName.IdentityLdapAuth,
|
||||||
|
"paginatedIdentity.identityId",
|
||||||
|
`${TableName.IdentityLdapAuth}.identityId`
|
||||||
|
)
|
||||||
|
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema("paginatedIdentity"),
|
db.ref("id").withSchema("paginatedIdentity"),
|
||||||
db.ref("role").withSchema("paginatedIdentity"),
|
db.ref("role").withSchema("paginatedIdentity"),
|
||||||
@@ -217,7 +229,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth)
|
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||||
|
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth)
|
||||||
)
|
)
|
||||||
// cr stands for custom role
|
// cr stands for custom role
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
||||||
@@ -259,6 +272,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
|
ldapId,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt
|
updatedAt
|
||||||
}) => ({
|
}) => ({
|
||||||
@@ -290,7 +304,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
jwtId
|
jwtId,
|
||||||
|
ldapId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -406,6 +421,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
`${TableName.IdentityJwtAuth}.identityId`
|
`${TableName.IdentityJwtAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityLdapAuth,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.IdentityLdapAuth}.identityId`
|
||||||
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.IdentityOrgMembership),
|
db.ref("id").withSchema(TableName.IdentityOrgMembership),
|
||||||
db.ref("total_count").withSchema("searchedIdentities"),
|
db.ref("total_count").withSchema("searchedIdentities"),
|
||||||
@@ -424,7 +444,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth)
|
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||||
|
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth)
|
||||||
)
|
)
|
||||||
// cr stands for custom role
|
// cr stands for custom role
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
||||||
@@ -467,6 +488,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
|
ldapId,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt
|
updatedAt
|
||||||
}) => ({
|
}) => ({
|
||||||
@@ -498,7 +520,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
oidcId,
|
oidcId,
|
||||||
azureId,
|
azureId,
|
||||||
tokenId,
|
tokenId,
|
||||||
jwtId
|
jwtId,
|
||||||
|
ldapId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -182,10 +182,17 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
|||||||
"Microsoft Teams Workflow Integration Check Installation Status",
|
"Microsoft Teams Workflow Integration Check Installation Status",
|
||||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS]: "Get Microsoft Teams tenant teams",
|
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS]: "Get Microsoft Teams tenant teams",
|
||||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET]: "Get Microsoft Teams Workflow Integration",
|
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET]: "Get Microsoft Teams Workflow Integration",
|
||||||
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]: "List Microsoft Teams Workflow Integration"
|
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]:
|
||||||
|
"List Microsoft Teams Workflow Integration",
|
||||||
|
|
||||||
|
[EventType.LOGIN_IDENTITY_LDAP_AUTH]: "Identity login via LDAP Auth",
|
||||||
|
[EventType.ADD_IDENTITY_LDAP_AUTH]: "Attached LDAP Auth to identity",
|
||||||
|
[EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity",
|
||||||
|
[EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved LDAP Auth for identity",
|
||||||
|
[EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
|
export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = {
|
||||||
[UserAgentType.WEB]: "Web",
|
[UserAgentType.WEB]: "Web",
|
||||||
[UserAgentType.CLI]: "CLI",
|
[UserAgentType.CLI]: "CLI",
|
||||||
[UserAgentType.K8_OPERATOR]: "K8s operator",
|
[UserAgentType.K8_OPERATOR]: "K8s operator",
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ export enum EventType {
|
|||||||
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||||
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||||
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||||
|
|
||||||
|
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
|
||||||
|
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
|
||||||
|
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
|
||||||
|
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
|
||||||
|
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
|
||||||
|
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
|
|||||||
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
||||||
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
||||||
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
|
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
|
||||||
|
[IdentityAuthMethod.LDAP_AUTH]: "LDAP Auth",
|
||||||
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
|
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export enum IdentityAuthMethod {
|
|||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
OIDC_AUTH = "oidc-auth",
|
OIDC_AUTH = "oidc-auth",
|
||||||
|
LDAP_AUTH = "ldap-auth",
|
||||||
JWT_AUTH = "jwt-auth"
|
JWT_AUTH = "jwt-auth"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,4 @@
|
|||||||
export { identityAuthToNameMap } from "./constants";
|
export { identityAuthToNameMap } from "./constants";
|
||||||
export { IdentityAuthMethod } from "./enums";
|
export { IdentityAuthMethod } from "./enums";
|
||||||
export {
|
export * from "./mutations";
|
||||||
useAddIdentityAwsAuth,
|
export * from "./queries";
|
||||||
useAddIdentityAzureAuth,
|
|
||||||
useAddIdentityGcpAuth,
|
|
||||||
useAddIdentityJwtAuth,
|
|
||||||
useAddIdentityKubernetesAuth,
|
|
||||||
useAddIdentityOidcAuth,
|
|
||||||
useAddIdentityTokenAuth,
|
|
||||||
useAddIdentityUniversalAuth,
|
|
||||||
useCreateIdentity,
|
|
||||||
useCreateIdentityUniversalAuthClientSecret,
|
|
||||||
useCreateTokenIdentityTokenAuth,
|
|
||||||
useDeleteIdentity,
|
|
||||||
useDeleteIdentityAwsAuth,
|
|
||||||
useDeleteIdentityAzureAuth,
|
|
||||||
useDeleteIdentityGcpAuth,
|
|
||||||
useDeleteIdentityJwtAuth,
|
|
||||||
useDeleteIdentityKubernetesAuth,
|
|
||||||
useDeleteIdentityOidcAuth,
|
|
||||||
useDeleteIdentityTokenAuth,
|
|
||||||
useDeleteIdentityUniversalAuth,
|
|
||||||
useRevokeIdentityTokenAuthToken,
|
|
||||||
useRevokeIdentityUniversalAuthClientSecret,
|
|
||||||
useUpdateIdentity,
|
|
||||||
useUpdateIdentityAwsAuth,
|
|
||||||
useUpdateIdentityAzureAuth,
|
|
||||||
useUpdateIdentityGcpAuth,
|
|
||||||
useUpdateIdentityJwtAuth,
|
|
||||||
useUpdateIdentityKubernetesAuth,
|
|
||||||
useUpdateIdentityOidcAuth,
|
|
||||||
useUpdateIdentityTokenAuth,
|
|
||||||
useUpdateIdentityTokenAuthToken,
|
|
||||||
useUpdateIdentityUniversalAuth
|
|
||||||
} from "./mutations";
|
|
||||||
export {
|
|
||||||
useGetIdentityAwsAuth,
|
|
||||||
useGetIdentityAzureAuth,
|
|
||||||
useGetIdentityById,
|
|
||||||
useGetIdentityGcpAuth,
|
|
||||||
useGetIdentityJwtAuth,
|
|
||||||
useGetIdentityKubernetesAuth,
|
|
||||||
useGetIdentityOidcAuth,
|
|
||||||
useGetIdentityProjectMemberships,
|
|
||||||
useGetIdentityTokenAuth,
|
|
||||||
useGetIdentityTokensTokenAuth,
|
|
||||||
useGetIdentityUniversalAuth,
|
|
||||||
useGetIdentityUniversalAuthClientSecrets,
|
|
||||||
useSearchIdentities
|
|
||||||
} from "./queries";
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
AddIdentityGcpAuthDTO,
|
AddIdentityGcpAuthDTO,
|
||||||
AddIdentityJwtAuthDTO,
|
AddIdentityJwtAuthDTO,
|
||||||
AddIdentityKubernetesAuthDTO,
|
AddIdentityKubernetesAuthDTO,
|
||||||
|
AddIdentityLdapAuthDTO,
|
||||||
AddIdentityOidcAuthDTO,
|
AddIdentityOidcAuthDTO,
|
||||||
AddIdentityTokenAuthDTO,
|
AddIdentityTokenAuthDTO,
|
||||||
AddIdentityUniversalAuthDTO,
|
AddIdentityUniversalAuthDTO,
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
DeleteIdentityGcpAuthDTO,
|
DeleteIdentityGcpAuthDTO,
|
||||||
DeleteIdentityJwtAuthDTO,
|
DeleteIdentityJwtAuthDTO,
|
||||||
DeleteIdentityKubernetesAuthDTO,
|
DeleteIdentityKubernetesAuthDTO,
|
||||||
|
DeleteIdentityLdapAuthDTO,
|
||||||
DeleteIdentityOidcAuthDTO,
|
DeleteIdentityOidcAuthDTO,
|
||||||
DeleteIdentityTokenAuthDTO,
|
DeleteIdentityTokenAuthDTO,
|
||||||
DeleteIdentityUniversalAuthClientSecretDTO,
|
DeleteIdentityUniversalAuthClientSecretDTO,
|
||||||
@@ -36,6 +38,7 @@ import {
|
|||||||
IdentityGcpAuth,
|
IdentityGcpAuth,
|
||||||
IdentityJwtAuth,
|
IdentityJwtAuth,
|
||||||
IdentityKubernetesAuth,
|
IdentityKubernetesAuth,
|
||||||
|
IdentityLdapAuth,
|
||||||
IdentityOidcAuth,
|
IdentityOidcAuth,
|
||||||
IdentityTokenAuth,
|
IdentityTokenAuth,
|
||||||
IdentityUniversalAuth,
|
IdentityUniversalAuth,
|
||||||
@@ -47,6 +50,7 @@ import {
|
|||||||
UpdateIdentityGcpAuthDTO,
|
UpdateIdentityGcpAuthDTO,
|
||||||
UpdateIdentityJwtAuthDTO,
|
UpdateIdentityJwtAuthDTO,
|
||||||
UpdateIdentityKubernetesAuthDTO,
|
UpdateIdentityKubernetesAuthDTO,
|
||||||
|
UpdateIdentityLdapAuthDTO,
|
||||||
UpdateIdentityOidcAuthDTO,
|
UpdateIdentityOidcAuthDTO,
|
||||||
UpdateIdentityTokenAuthDTO,
|
UpdateIdentityTokenAuthDTO,
|
||||||
UpdateIdentityUniversalAuthDTO,
|
UpdateIdentityUniversalAuthDTO,
|
||||||
@@ -1049,3 +1053,116 @@ export const useRevokeIdentityTokenAuthToken = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAddIdentityLdapAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityLdapAuth, object, AddIdentityLdapAuthDTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||||
|
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.identityLdapAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { identityId, organizationId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateIdentityLdapAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityLdapAuth, object, UpdateIdentityLdapAuthDTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||||
|
`/api/v1/auth/ldap-auth/identities/${identityId}`,
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data.identityLdapAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { identityId, organizationId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteIdentityLdapAuth = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<IdentityLdapAuth, object, DeleteIdentityLdapAuthDTO>({
|
||||||
|
mutationFn: async ({ identityId }) => {
|
||||||
|
const { data } = await apiRequest.delete(`/api/v1/auth/ldap-auth/identities/${identityId}`);
|
||||||
|
return data.identityLdapAuth;
|
||||||
|
},
|
||||||
|
onSuccess: (_, { organizationId, identityId }) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
IdentityGcpAuth,
|
IdentityGcpAuth,
|
||||||
IdentityJwtAuth,
|
IdentityJwtAuth,
|
||||||
IdentityKubernetesAuth,
|
IdentityKubernetesAuth,
|
||||||
|
IdentityLdapAuth,
|
||||||
IdentityMembership,
|
IdentityMembership,
|
||||||
IdentityMembershipOrg,
|
IdentityMembershipOrg,
|
||||||
IdentityOidcAuth,
|
IdentityOidcAuth,
|
||||||
@@ -34,6 +35,7 @@ export const identitiesKeys = {
|
|||||||
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
||||||
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
||||||
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
|
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
|
||||||
|
getIdentityLdapAuth: (identityId: string) => [{ identityId }, "identity-ldap-auth"] as const,
|
||||||
getIdentityTokensTokenAuth: (identityId: string) =>
|
getIdentityTokensTokenAuth: (identityId: string) =>
|
||||||
[{ identityId }, "identity-tokens-token-auth"] as const,
|
[{ identityId }, "identity-tokens-token-auth"] as const,
|
||||||
getIdentityProjectMemberships: (identityId: string) =>
|
getIdentityProjectMemberships: (identityId: string) =>
|
||||||
@@ -231,6 +233,27 @@ export const useGetIdentityTokenAuth = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetIdentityLdapAuth = (
|
||||||
|
identityId: string,
|
||||||
|
options?: TReactQueryOptions["options"]
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: identitiesKeys.getIdentityLdapAuth(identityId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const {
|
||||||
|
data: { identityLdapAuth }
|
||||||
|
} = await apiRequest.get<{ identityLdapAuth: IdentityLdapAuth }>(
|
||||||
|
`/api/v1/auth/ldap-auth/identities/${identityId}`
|
||||||
|
);
|
||||||
|
return identityLdapAuth;
|
||||||
|
},
|
||||||
|
staleTime: 0,
|
||||||
|
gcTime: 0,
|
||||||
|
...options,
|
||||||
|
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetIdentityTokensTokenAuth = (identityId: string) => {
|
export const useGetIdentityTokensTokenAuth = (identityId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
enabled: Boolean(identityId),
|
enabled: Boolean(identityId),
|
||||||
|
|||||||
@@ -425,6 +425,72 @@ export type IdentityTokenAuth = {
|
|||||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AddIdentityLdapAuthDTO = {
|
||||||
|
organizationId: string;
|
||||||
|
identityId: string;
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
searchFilter: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
allowedFields?: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateIdentityLdapAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
organizationId: string;
|
||||||
|
url?: string;
|
||||||
|
bindDN?: string;
|
||||||
|
bindPass?: string;
|
||||||
|
searchBase?: string;
|
||||||
|
searchFilter?: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
allowedFields?: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteIdentityLdapAuthDTO = {
|
||||||
|
organizationId: string;
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IdentityLdapAuth = {
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
searchFilter: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
allowedFields?: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
identityId: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||||
|
};
|
||||||
|
|
||||||
export type AddIdentityTokenAuthDTO = {
|
export type AddIdentityTokenAuthDTO = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
identityId: string;
|
identityId: string;
|
||||||
|
|||||||
+12
@@ -13,6 +13,7 @@ import { IdentityAzureAuthForm } from "./IdentityAzureAuthForm";
|
|||||||
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
|
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
|
||||||
import { IdentityJwtAuthForm } from "./IdentityJwtAuthForm";
|
import { IdentityJwtAuthForm } from "./IdentityJwtAuthForm";
|
||||||
import { IdentityKubernetesAuthForm } from "./IdentityKubernetesAuthForm";
|
import { IdentityKubernetesAuthForm } from "./IdentityKubernetesAuthForm";
|
||||||
|
import { IdentityLdapAuthForm } from "./IdentityLdapAuthForm";
|
||||||
import { IdentityOidcAuthForm } from "./IdentityOidcAuthForm";
|
import { IdentityOidcAuthForm } from "./IdentityOidcAuthForm";
|
||||||
import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
|
import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
|
||||||
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
|
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
|
||||||
@@ -46,6 +47,7 @@ const identityAuthMethods = [
|
|||||||
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH },
|
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH },
|
||||||
{ label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH },
|
{ label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH },
|
||||||
{ label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH },
|
{ label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH },
|
||||||
|
{ label: "LDAP Auth", value: IdentityAuthMethod.LDAP_AUTH },
|
||||||
{
|
{
|
||||||
label: "JWT Auth",
|
label: "JWT Auth",
|
||||||
value: IdentityAuthMethod.JWT_AUTH
|
value: IdentityAuthMethod.JWT_AUTH
|
||||||
@@ -186,6 +188,16 @@ export const IdentityAuthMethodModalContent = ({
|
|||||||
handlePopUpToggle={handlePopUpToggle}
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
[IdentityAuthMethod.LDAP_AUTH]: {
|
||||||
|
render: () => (
|
||||||
|
<IdentityLdapAuthForm
|
||||||
|
identityId={identityAuthMethodData.identityId}
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+608
@@ -0,0 +1,608 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
|
import { faPlus, faQuestionCircle, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
Tabs,
|
||||||
|
TextArea,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useOrganization, useSubscription } from "@app/context";
|
||||||
|
import {
|
||||||
|
useAddIdentityLdapAuth,
|
||||||
|
useGetIdentityLdapAuth,
|
||||||
|
useUpdateIdentityLdapAuth
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { IdentityFormTab } from "./types";
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
url: z.string().min(1),
|
||||||
|
bindDN: z.string(),
|
||||||
|
bindPass: z.string(),
|
||||||
|
searchBase: z.string(),
|
||||||
|
searchFilter: z.string(), // defaults to (uid={{username}})
|
||||||
|
ldapCaCertificate: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val || undefined),
|
||||||
|
allowedFields: z
|
||||||
|
.object({
|
||||||
|
key: z.string().trim(),
|
||||||
|
value: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.transform((val) => val.replace(/\s/g, ""))
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||||
|
message: "Access Token TTL cannot be greater than 315360000"
|
||||||
|
}),
|
||||||
|
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
|
||||||
|
message: "Access Token Max TTL cannot be greater than 315360000"
|
||||||
|
}),
|
||||||
|
accessTokenNumUsesLimit: z.string(),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
ipAddress: z.string().max(50)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||||
|
handlePopUpToggle: (
|
||||||
|
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||||
|
state?: boolean
|
||||||
|
) => void;
|
||||||
|
identityId?: string;
|
||||||
|
isUpdate?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IdentityLdapAuthForm = ({
|
||||||
|
handlePopUpOpen,
|
||||||
|
handlePopUpToggle,
|
||||||
|
identityId,
|
||||||
|
isUpdate
|
||||||
|
}: Props) => {
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const orgId = currentOrg?.id || "";
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const { mutateAsync: addMutateAsync } = useAddIdentityLdapAuth();
|
||||||
|
const { mutateAsync: updateMutateAsync } = useUpdateIdentityLdapAuth();
|
||||||
|
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||||
|
|
||||||
|
const { data } = useGetIdentityLdapAuth(identityId ?? "", {
|
||||||
|
enabled: isUpdate
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
url: "",
|
||||||
|
bindDN: "",
|
||||||
|
bindPass: "",
|
||||||
|
searchBase: "",
|
||||||
|
searchFilter: "(uid={{username}})",
|
||||||
|
accessTokenTTL: "2592000",
|
||||||
|
accessTokenMaxTTL: "2592000",
|
||||||
|
accessTokenNumUsesLimit: "0",
|
||||||
|
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
fields: accessTokenTrustedIpsFields,
|
||||||
|
append: appendAccessTokenTrustedIp,
|
||||||
|
remove: removeAccessTokenTrustedIp
|
||||||
|
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
|
||||||
|
|
||||||
|
const {
|
||||||
|
fields: allowedFieldsFields,
|
||||||
|
append: appendAllowedField,
|
||||||
|
remove: removeAllowedField
|
||||||
|
} = useFieldArray({ control, name: "allowedFields" });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
reset({
|
||||||
|
url: data.url,
|
||||||
|
bindDN: data.bindDN,
|
||||||
|
bindPass: data.bindPass,
|
||||||
|
searchBase: data.searchBase,
|
||||||
|
searchFilter: data.searchFilter,
|
||||||
|
ldapCaCertificate: data.ldapCaCertificate || undefined,
|
||||||
|
allowedFields: data.allowedFields,
|
||||||
|
accessTokenTTL: String(data.accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
|
||||||
|
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
|
||||||
|
({ ipAddress, prefix }: IdentityTrustedIp) => {
|
||||||
|
return {
|
||||||
|
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
url: "",
|
||||||
|
bindDN: "",
|
||||||
|
bindPass: "",
|
||||||
|
searchBase: "",
|
||||||
|
searchFilter: "(uid={{username}})",
|
||||||
|
ldapCaCertificate: undefined,
|
||||||
|
allowedFields: [],
|
||||||
|
accessTokenTTL: "2592000",
|
||||||
|
accessTokenMaxTTL: "2592000",
|
||||||
|
accessTokenNumUsesLimit: "0",
|
||||||
|
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!subscription?.ldap) {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
handlePopUpToggle("identityAuthMethod", false);
|
||||||
|
}
|
||||||
|
}, [subscription]);
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
if (!identityId) return;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
await updateMutateAsync({
|
||||||
|
organizationId: orgId,
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL: Number(accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||||
|
accessTokenTrustedIps
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await addMutateAsync({
|
||||||
|
organizationId: orgId,
|
||||||
|
identityId,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
searchFilter,
|
||||||
|
ldapCaCertificate,
|
||||||
|
allowedFields,
|
||||||
|
accessTokenTTL: Number(accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||||
|
accessTokenTrustedIps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpToggle("identityAuthMethod", false);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
reset();
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${isUpdate ? "update" : "configure"} identity`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onFormSubmit, (fields) => {
|
||||||
|
setTabValue(
|
||||||
|
[
|
||||||
|
"url",
|
||||||
|
"bindDN",
|
||||||
|
"bindPass",
|
||||||
|
"searchBase",
|
||||||
|
"searchFilter",
|
||||||
|
"accessTokenTTL",
|
||||||
|
"allowedFields",
|
||||||
|
"accessTokenMaxTTL",
|
||||||
|
"accessTokenNumUsesLimit"
|
||||||
|
].includes(Object.keys(fields)[0])
|
||||||
|
? IdentityFormTab.Configuration
|
||||||
|
: IdentityFormTab.Advanced
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
|
||||||
|
<TabList>
|
||||||
|
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
|
||||||
|
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value={IdentityFormTab.Configuration}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue="2592000"
|
||||||
|
name="url"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="LDAP URL"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ldaps://domain-or-ip:636" type="text" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="bindDN"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
label="Bind DN"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="bindPass"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
label="Bind Pass"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="********" type="password" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="searchBase"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
label="Search Base / DN"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ou=machines,dc=acme,dc=com" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="searchFilter"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
label="Search Filter"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="(uid={{username}})" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{allowedFieldsFields.map(({ id }, index) => (
|
||||||
|
<div className="mb-3 flex items-end space-x-2" key={id}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`allowedFields.${index}.key`}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
const isFirstField = index === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
label={isFirstField ? "Required Attributes" : undefined}
|
||||||
|
icon={
|
||||||
|
isFirstField ? (
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-[420px]"
|
||||||
|
content={
|
||||||
|
<div className="max-h-[300px] space-y-4 overflow-y-auto text-sm">
|
||||||
|
<p>
|
||||||
|
Specify the fields that the user must contain in their LDAP entry
|
||||||
|
in order to authenticate with this identity. If nothing is
|
||||||
|
specified, all users in the configured LDAP directory will be able
|
||||||
|
to authenticate.
|
||||||
|
<p className="mt-2">
|
||||||
|
You can specify multiple required attributes by separating them
|
||||||
|
with a comma.
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p>Example:</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
'uid' → 'user1,user2,user3'
|
||||||
|
<br />
|
||||||
|
'mail' → 'user@example.com'
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The above example would allow users with the UID user1, user2, or
|
||||||
|
user3 to authenticate but only if their emails also match
|
||||||
|
user@example.com
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
|
||||||
|
</Tooltip>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={field.value}
|
||||||
|
onChange={(e) => field.onChange(e)}
|
||||||
|
placeholder="uid"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`allowedFields.${index}.value`}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={field.value}
|
||||||
|
onChange={(e) => field.onChange(e)}
|
||||||
|
placeholder="userid1,userid2,userid3"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeAllowedField(index)}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="p-3"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="my-4 ml-1">
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() =>
|
||||||
|
appendAllowedField({
|
||||||
|
key: "",
|
||||||
|
value: ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Add Required Attribute
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue="2592000"
|
||||||
|
name="accessTokenTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Access Token TTL (seconds)"
|
||||||
|
tooltipText="The lifetime for an acccess token in seconds. This value will be referenced at renewal time."
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue="2592000"
|
||||||
|
name="accessTokenMaxTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Access Token Max TTL (seconds)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
tooltipText="The maximum lifetime for an access token in seconds. This value will be referenced at renewal time."
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue="0"
|
||||||
|
name="accessTokenNumUsesLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Access Token Max Number of Uses"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
tooltipText="The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="0" type="number" min="0" step="1" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={IdentityFormTab.Advanced}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ldapCaCertificate"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="CA Certificate"
|
||||||
|
isOptional
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
tooltipText="An optional PEM-encoded CA cert for the LDAP server. This is used by the TLS client for secure communication with the LDAP server."
|
||||||
|
>
|
||||||
|
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{accessTokenTrustedIpsFields.map(({ id }, index) => (
|
||||||
|
<div className="mb-3 flex items-end space-x-2" key={id}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`accessTokenTrustedIps.${index}.ipAddress`}
|
||||||
|
defaultValue="0.0.0.0/0"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
label={index === 0 ? "Access Token Trusted IPs" : undefined}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
tooltipText="The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address."
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={field.value}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (subscription?.ipAllowlisting) {
|
||||||
|
field.onChange(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}}
|
||||||
|
placeholder="123.456.789.0"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
if (subscription?.ipAllowlisting) {
|
||||||
|
removeAccessTokenTrustedIp(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="p-3"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="my-4 ml-1">
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() => {
|
||||||
|
if (subscription?.ipAllowlisting) {
|
||||||
|
appendAccessTokenTrustedIp({
|
||||||
|
ipAddress: "0.0.0.0/0"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
}}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Add IP Address
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isUpdate ? "Update" : "Add"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -30,7 +30,7 @@ export const Route = createFileRoute(
|
|||||||
link: linkOptions({ to: "/" })
|
link: linkOptions({ to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "access control"
|
label: "Access Control"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { useGetUserWorkspaces } from "@app/hooks/api";
|
|||||||
import {
|
import {
|
||||||
eventToNameMap,
|
eventToNameMap,
|
||||||
secretEvents,
|
secretEvents,
|
||||||
userAgentTTypeoNameMap
|
userAgentTypeToNameMap
|
||||||
} from "@app/hooks/api/auditLogs/constants";
|
} from "@app/hooks/api/auditLogs/constants";
|
||||||
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { UserAgentType } from "@app/hooks/api/auth/types";
|
import { UserAgentType } from "@app/hooks/api/auth/types";
|
||||||
@@ -47,7 +47,7 @@ import { LogFilterItem } from "./LogFilterItem";
|
|||||||
import { AuditLogFilterFormData, Presets } from "./types";
|
import { AuditLogFilterFormData, Presets } from "./types";
|
||||||
|
|
||||||
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
|
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
|
||||||
const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({
|
const userAgentTypes = Object.entries(userAgentTypeToNameMap).map(([value, label]) => ({
|
||||||
label,
|
label,
|
||||||
value
|
value
|
||||||
}));
|
}));
|
||||||
|
|||||||
+7
@@ -11,6 +11,7 @@ import {
|
|||||||
useDeleteIdentityGcpAuth,
|
useDeleteIdentityGcpAuth,
|
||||||
useDeleteIdentityJwtAuth,
|
useDeleteIdentityJwtAuth,
|
||||||
useDeleteIdentityKubernetesAuth,
|
useDeleteIdentityKubernetesAuth,
|
||||||
|
useDeleteIdentityLdapAuth,
|
||||||
useDeleteIdentityOidcAuth,
|
useDeleteIdentityOidcAuth,
|
||||||
useDeleteIdentityTokenAuth,
|
useDeleteIdentityTokenAuth,
|
||||||
useDeleteIdentityUniversalAuth
|
useDeleteIdentityUniversalAuth
|
||||||
@@ -22,6 +23,7 @@ import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
|
|||||||
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
||||||
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
|
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
|
||||||
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
|
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
|
||||||
|
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
|
||||||
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
|
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
|
||||||
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
|
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
|
||||||
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
|
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
|
||||||
@@ -61,6 +63,7 @@ export const Content = ({
|
|||||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||||
|
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
|
||||||
|
|
||||||
let Component: (props: ViewAuthMethodProps) => JSX.Element;
|
let Component: (props: ViewAuthMethodProps) => JSX.Element;
|
||||||
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
|
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
|
||||||
@@ -100,6 +103,10 @@ export const Content = ({
|
|||||||
revokeMethod = revokeJwtAuth;
|
revokeMethod = revokeJwtAuth;
|
||||||
Component = ViewIdentityJwtAuthContent;
|
Component = ViewIdentityJwtAuthContent;
|
||||||
break;
|
break;
|
||||||
|
case IdentityAuthMethod.LDAP_AUTH:
|
||||||
|
revokeMethod = revokeLdapAuth;
|
||||||
|
Component = ViewIdentityLdapAuthContent;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Auth Method: ${authMethod}`);
|
throw new Error(`Unhandled Auth Method: ${authMethod}`);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -47,10 +47,10 @@ export const ViewIdentityAwsAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -47,10 +47,10 @@ export const ViewIdentityAzureAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -47,10 +47,10 @@ export const ViewIdentityGcpAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -49,10 +49,10 @@ export const ViewIdentityJwtAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -51,10 +51,10 @@ export const ViewIdentityKubernetesAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
|
||||||
|
import { useGetIdentityLdapAuth } from "@app/hooks/api";
|
||||||
|
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
|
||||||
|
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
|
||||||
|
|
||||||
|
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||||
|
import { ViewAuthMethodProps } from "./types";
|
||||||
|
|
||||||
|
export const ViewIdentityLdapAuthContent = ({
|
||||||
|
identityId,
|
||||||
|
handlePopUpToggle,
|
||||||
|
handlePopUpOpen,
|
||||||
|
onDelete,
|
||||||
|
popUp
|
||||||
|
}: ViewAuthMethodProps) => {
|
||||||
|
const { data, isPending } = useGetIdentityLdapAuth(identityId);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full items-center justify-center">
|
||||||
|
<Spinner className="text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<EmptyState icon={faBan} title="Could not find LDAP Auth associated with this Identity." />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (popUp.identityAuthMethod.isOpen) {
|
||||||
|
return (
|
||||||
|
<IdentityLdapAuthForm
|
||||||
|
identityId={identityId}
|
||||||
|
isUpdate
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewIdentityContentWrapper
|
||||||
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
|
onDelete={onDelete}
|
||||||
|
>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
|
{data.accessTokenTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
|
{data.accessTokenMaxTTL}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
{data.accessTokenNumUsesLimit}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||||
|
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="LDAP URL">{data.url}</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Bind DN">{data.bindDN}</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Bind Pass">
|
||||||
|
<Tooltip
|
||||||
|
side="right"
|
||||||
|
className="max-w-xl p-2"
|
||||||
|
content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.bindPass}</p>}
|
||||||
|
>
|
||||||
|
<div className="w-min">
|
||||||
|
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||||
|
<FontAwesomeIcon icon={faEye} />
|
||||||
|
<span>Reveal</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Search Base / DN">
|
||||||
|
{data.searchBase}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="Search Filter">{data.searchFilter}</IdentityAuthFieldDisplay>
|
||||||
|
<IdentityAuthFieldDisplay label="CA Certificate">
|
||||||
|
{data.ldapCaCertificate && (
|
||||||
|
<Tooltip
|
||||||
|
side="right"
|
||||||
|
className="max-w-xl p-2"
|
||||||
|
content={
|
||||||
|
<p className="break-words rounded bg-mineshaft-600 p-2">{data.ldapCaCertificate}</p>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="w-min">
|
||||||
|
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||||
|
<FontAwesomeIcon icon={faEye} />
|
||||||
|
<span>Reveal</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</IdentityAuthFieldDisplay>
|
||||||
|
</ViewIdentityContentWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
+2
-2
@@ -48,10 +48,10 @@ export const ViewIdentityOidcAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -50,10 +50,10 @@ export const ViewIdentityTokenAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
+2
-2
@@ -62,10 +62,10 @@ export const ViewIdentityUniversalAuthContent = ({
|
|||||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
>
|
>
|
||||||
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||||
{data.accessTokenTTL}
|
{data.accessTokenTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
|
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||||
{data.accessTokenMaxTTL}
|
{data.accessTokenMaxTTL}
|
||||||
</IdentityAuthFieldDisplay>
|
</IdentityAuthFieldDisplay>
|
||||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const Route = createFileRoute(
|
|||||||
link: linkOptions({ to: "/organization/access-management" })
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "identities"
|
label: "Identities"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user