fix: sub-org support in login endpoint for machine identities

This commit is contained in:
Piyush Gupta
2025-11-28 18:00:59 +05:30
parent b94b1d480f
commit e9ba9c0867
39 changed files with 457 additions and 71 deletions

View File

@@ -170,10 +170,13 @@ export const IDENTITIES = {
}
} as const;
const IDENTITY_AUTH_SUB_ORGANIZATION_NAME = "sub-organization slug to scope the token to";
export const UNIVERSAL_AUTH = {
LOGIN: {
clientId: "Your Machine Identity Client ID.",
clientSecret: "Your Machine Identity Client Secret."
clientSecret: "Your Machine Identity Client Secret.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -247,7 +250,8 @@ export const LDAP_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login.",
username: "The username of the LDAP user to login.",
password: "The password of the LDAP user to login."
password: "The password of the LDAP user to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
templateId: "The ID of the identity auth template to attach the configuration onto.",
@@ -312,7 +316,8 @@ export const ALICLOUD_AUTH = {
Timestamp: "The timestamp of the request in UTC, formatted as 'YYYY-MM-DDTHH:mm:ssZ'.",
SignatureVersion: "The signature version. For STS GetCallerIdentity, this should be '1.0'.",
SignatureNonce: "A unique random string to prevent replay attacks.",
Signature: "The signature string calculated based on the request parameters and AccessKey Secret."
Signature: "The signature string calculated based on the request parameters and AccessKey Secret.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -340,7 +345,8 @@ export const ALICLOUD_AUTH = {
export const TLS_CERT_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -378,7 +384,8 @@ export const AWS_AUTH = {
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/.",
iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -416,7 +423,8 @@ export const OCI_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login.",
userOcid: "The OCID of the user attempting login.",
headers: "The headers of the signed request."
headers: "The headers of the signed request.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -448,7 +456,8 @@ export const OCI_AUTH = {
export const AZURE_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -482,7 +491,8 @@ export const AZURE_AUTH = {
export const GCP_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -520,7 +530,8 @@ export const GCP_AUTH = {
export const KUBERNETES_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -600,7 +611,8 @@ export const TOKEN_AUTH = {
},
CREATE_TOKEN: {
identityId: "The ID of the machine identity to create the token for.",
name: "The name of the token to create."
name: "The name of the token to create.",
subOrganizationName: "The sub organization slug to scope the token to."
},
UPDATE_TOKEN: {
tokenId: "The ID of the token to update metadata for.",
@@ -613,7 +625,8 @@ export const TOKEN_AUTH = {
export const OIDC_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",
@@ -653,7 +666,8 @@ export const OIDC_AUTH = {
export const JWT_AUTH = {
LOGIN: {
identityId: "The ID of the machine identity to login."
identityId: "The ID of the machine identity to login.",
subOrganizationName: IDENTITY_AUTH_SUB_ORGANIZATION_NAME
},
ATTACH: {
identityId: "The ID of the machine identity to attach the configuration onto.",

View File

@@ -38,6 +38,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
message: "AccessKeyId must be alphanumeric"
})
.describe(ALICLOUD_AUTH.LOGIN.AccessKeyId),
subOrganizationName: z.string().trim().optional().describe(ALICLOUD_AUTH.LOGIN.subOrganizationName),
SignatureMethod: z.enum(["HMAC-SHA1"]).describe(ALICLOUD_AUTH.LOGIN.SignatureMethod),
Timestamp: z
.string()
@@ -74,7 +75,10 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
},
handler: async (req) => {
const { identityAliCloudAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAliCloudAuth.login(req.body);
await server.services.identityAliCloudAuth.login({
...req.body,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -28,7 +28,8 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders),
subOrganizationName: z.string().trim().optional().describe(AWS_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -41,7 +42,10 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
},
handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAwsAuth.login(req.body);
await server.services.identityAwsAuth.login({
...req.body,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -23,7 +23,8 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
description: "Login with Azure Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId),
jwt: z.string()
jwt: z.string(),
subOrganizationName: z.string().trim().optional().describe(AZURE_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({

View File

@@ -23,7 +23,8 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
description: "Login with GCP Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(GCP_AUTH.LOGIN.identityId),
jwt: z.string()
jwt: z.string(),
subOrganizationName: z.string().trim().optional().describe(GCP_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -36,7 +37,10 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
},
handler: async (req) => {
const { identityGcpAuth, accessToken, identityAccessToken, identity } =
await server.services.identityGcpAuth.login(req.body);
await server.services.identityGcpAuth.login({
...req.body,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -99,7 +99,8 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
description: "Login with JWT Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(JWT_AUTH.LOGIN.identityId),
jwt: z.string().trim()
jwt: z.string().trim(),
subOrganizationName: z.string().trim().optional().describe(JWT_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -114,7 +115,8 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
const { identityJwtAuth, accessToken, identityAccessToken, identity } =
await server.services.identityJwtAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
jwt: req.body.jwt,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -44,7 +44,8 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
description: "Login with Kubernetes Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(KUBERNETES_AUTH.LOGIN.identityId),
jwt: z.string().trim()
jwt: z.string().trim(),
subOrganizationName: z.string().trim().optional().describe(KUBERNETES_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -59,7 +60,8 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
const { identityKubernetesAuth, accessToken, identityAccessToken, identity } =
await server.services.identityKubernetesAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
jwt: req.body.jwt,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -124,7 +124,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
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)
password: z.string().describe(LDAP_AUTH.LOGIN.password),
subOrganizationName: z.string().trim().optional().describe(LDAP_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -163,7 +164,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
const { identityId, user } = req.passportMachineIdentity;
const { accessToken, identityLdapAuth, identity } = await server.services.identityLdapAuth.login({
identityId
identityId,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -40,7 +40,8 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
});
}
})
.describe(OCI_AUTH.LOGIN.headers)
.describe(OCI_AUTH.LOGIN.headers),
subOrganizationName: z.string().trim().optional().describe(OCI_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -53,7 +54,10 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
},
handler: async (req) => {
const { identityOciAuth, accessToken, identityAccessToken, identity } =
await server.services.identityOciAuth.login(req.body);
await server.services.identityOciAuth.login({
...req.body,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -47,7 +47,8 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
description: "Login with OIDC Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(OIDC_AUTH.LOGIN.identityId),
jwt: z.string().trim()
jwt: z.string().trim(),
subOrganizationName: z.string().trim().optional().describe(OIDC_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -62,7 +63,8 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
const { identityOidcAuth, accessToken, identityAccessToken, identity, oidcTokenData } =
await server.services.identityOidcAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
jwt: req.body.jwt,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -46,7 +46,8 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
tags: [ApiDocsTags.TlsCertAuth],
description: "Login with TLS Certificate Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(TLS_CERT_AUTH.LOGIN.identityId)
identityId: z.string().trim().describe(TLS_CERT_AUTH.LOGIN.identityId),
subOrganizationName: z.string().trim().optional().describe(TLS_CERT_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -67,7 +68,8 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
const { identityTlsCertAuth, accessToken, identityAccessToken, identity } =
await server.services.identityTlsCertAuth.login({
identityId: req.body.identityId,
clientCertificate: clientCertificate as string
clientCertificate: clientCertificate as string,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -307,7 +307,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
identityId: z.string().describe(TOKEN_AUTH.CREATE_TOKEN.identityId)
}),
body: z.object({
name: z.string().optional().describe(TOKEN_AUTH.CREATE_TOKEN.name)
name: z.string().optional().describe(TOKEN_AUTH.CREATE_TOKEN.name),
subOrganizationName: z.string().trim().optional().describe(TOKEN_AUTH.CREATE_TOKEN.subOrganizationName)
}),
response: {
200: z.object({
@@ -328,7 +329,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
isActorSuperAdmin: isSuperAdmin(req.auth),
...req.body
...req.body,
subOrganizationName: req.body.subOrganizationName
});
await server.services.auditLog.createAuditLog({

View File

@@ -35,7 +35,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
description: "Login with Universal Auth for machine identity",
body: z.object({
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret),
subOrganizationName: z.string().trim().optional().describe(UNIVERSAL_AUTH.LOGIN.subOrganizationName)
}),
response: {
200: z.object({
@@ -55,7 +56,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
identity,
accessTokenTTL,
accessTokenMaxTTL
} = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
} = await server.services.identityUa.login(
req.body.clientId,
req.body.clientSecret,
req.realIp,
req.body.subOrganizationName
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,

View File

@@ -7,7 +7,6 @@ export type TIdentityAccessTokenJwtPayload = {
clientSecretId: string;
identityAccessTokenId: string;
authTokenType: string;
subOrganizationId?: string;
identityAuth: {
oidc?: {
claims: Record<string, string>;

View File

@@ -53,7 +53,7 @@ type TIdentityAliCloudAuthServiceFactoryDep = {
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>;
@@ -67,7 +67,7 @@ export const identityAliCloudAuthServiceFactory = ({
permissionService,
orgDAL
}: TIdentityAliCloudAuthServiceFactoryDep) => {
const login = async ({ identityId, ...params }: TLoginAliCloudAuthDTO) => {
const login = async ({ identityId, subOrganizationName, ...params }: TLoginAliCloudAuthDTO) => {
const appCfg = getConfig();
const identityAliCloudAuth = await identityAliCloudAuthDAL.findOne({ identityId });
if (!identityAliCloudAuth) {
@@ -80,6 +80,30 @@ export const identityAliCloudAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const requestUrl = new URL("https://sts.aliyuncs.com");
@@ -132,7 +156,8 @@ export const identityAliCloudAuthServiceFactory = ({
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.ALICLOUD_AUTH
authMethod: IdentityAuthMethod.ALICLOUD_AUTH,
scopeOrgId
},
tx
);

View File

@@ -11,6 +11,7 @@ export type TLoginAliCloudAuthDTO = {
SignatureVersion: string;
SignatureNonce: string;
Signature: string;
subOrganizationName?: string;
};
export type TAttachAliCloudAuthDTO = {

View File

@@ -53,7 +53,7 @@ type TIdentityAwsAuthServiceFactoryDep = {
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>;
@@ -101,7 +101,13 @@ export const identityAwsAuthServiceFactory = ({
permissionService,
orgDAL
}: TIdentityAwsAuthServiceFactoryDep) => {
const login = async ({ identityId, iamHttpRequestMethod, iamRequestBody, iamRequestHeaders }: TLoginAwsAuthDTO) => {
const login = async ({
identityId,
iamHttpRequestMethod,
iamRequestBody,
iamRequestHeaders,
subOrganizationName
}: TLoginAwsAuthDTO) => {
const appCfg = getConfig();
const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId });
if (!identityAwsAuth) {
@@ -112,6 +118,32 @@ export const identityAwsAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString();
@@ -207,7 +239,8 @@ export const identityAwsAuthServiceFactory = ({
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.AWS_AUTH
authMethod: IdentityAuthMethod.AWS_AUTH,
scopeOrgId
},
tx
);

View File

@@ -5,6 +5,7 @@ export type TLoginAwsAuthDTO = {
iamHttpRequestMethod: string;
iamRequestBody: string;
iamRequestHeaders: string;
subOrganizationName?: string;
};
export type TAttachAwsAuthDTO = {

View File

@@ -49,7 +49,7 @@ type TIdentityAzureAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityAzureAuthServiceFactory = ReturnType<typeof identityAzureAuthServiceFactory>;
@@ -63,7 +63,7 @@ export const identityAzureAuthServiceFactory = ({
licenseService,
orgDAL
}: TIdentityAzureAuthServiceFactoryDep) => {
const login = async ({ identityId, jwt: azureJwt }: TLoginAzureAuthDTO) => {
const login = async ({ identityId, jwt: azureJwt, subOrganizationName }: TLoginAzureAuthDTO) => {
const appCfg = getConfig();
const identityAzureAuth = await identityAzureAuthDAL.findOne({ identityId });
if (!identityAzureAuth) {
@@ -75,6 +75,32 @@ export const identityAzureAuthServiceFactory = ({
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const azureIdentity = await validateAzureIdentity({
tenantId: identityAzureAuth.tenantId,
@@ -126,7 +152,8 @@ export const identityAzureAuthServiceFactory = ({
accessTokenMaxTTL: identityAzureAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAzureAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.AZURE_AUTH
authMethod: IdentityAuthMethod.AZURE_AUTH,
scopeOrgId
},
tx
);

View File

@@ -3,6 +3,7 @@ import { TProjectPermission } from "@app/lib/types";
export type TLoginAzureAuthDTO = {
identityId: string;
jwt: string;
subOrganizationName?: string;
};
export type TAttachAzureAuthDTO = {

View File

@@ -47,7 +47,7 @@ type TIdentityGcpAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityGcpAuthServiceFactory = ReturnType<typeof identityGcpAuthServiceFactory>;
@@ -61,7 +61,7 @@ export const identityGcpAuthServiceFactory = ({
licenseService,
orgDAL
}: TIdentityGcpAuthServiceFactoryDep) => {
const login = async ({ identityId, jwt: gcpJwt }: TLoginGcpAuthDTO) => {
const login = async ({ identityId, jwt: gcpJwt, subOrganizationName }: TLoginGcpAuthDTO) => {
const appCfg = getConfig();
const identityGcpAuth = await identityGcpAuthDAL.findOne({ identityId });
if (!identityGcpAuth) {
@@ -72,6 +72,31 @@ export const identityGcpAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
let gcpIdentityDetails: TGcpIdentityDetails;
switch (identityGcpAuth.type) {
@@ -166,7 +191,8 @@ export const identityGcpAuthServiceFactory = ({
accessTokenMaxTTL: identityGcpAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityGcpAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.GCP_AUTH
authMethod: IdentityAuthMethod.GCP_AUTH,
scopeOrgId
},
tx
);

View File

@@ -3,6 +3,7 @@ import { TProjectPermission } from "@app/lib/types";
export type TLoginGcpAuthDTO = {
identityId: string;
jwt: string;
subOrganizationName?: string;
};
export type TAttachGcpAuthDTO = {

View File

@@ -60,7 +60,7 @@ type TIdentityJwtAuthServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityJwtAuthServiceFactory = ReturnType<typeof identityJwtAuthServiceFactory>;
@@ -75,7 +75,7 @@ export const identityJwtAuthServiceFactory = ({
kmsService,
orgDAL
}: TIdentityJwtAuthServiceFactoryDep) => {
const login = async ({ identityId, jwt: jwtValue }: TLoginJwtAuthDTO) => {
const login = async ({ identityId, jwt: jwtValue, subOrganizationName }: TLoginJwtAuthDTO) => {
const appCfg = getConfig();
const identityJwtAuth = await identityJwtAuthDAL.findOne({ identityId });
if (!identityJwtAuth) {
@@ -86,6 +86,30 @@ export const identityJwtAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@@ -246,7 +270,8 @@ export const identityJwtAuthServiceFactory = ({
accessTokenMaxTTL: identityJwtAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityJwtAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.JWT_AUTH
authMethod: IdentityAuthMethod.JWT_AUTH,
scopeOrgId
},
tx
);

View File

@@ -49,4 +49,5 @@ export type TRevokeJwtAuthDTO = {
export type TLoginJwtAuthDTO = {
identityId: string;
jwt: string;
subOrganizationName?: string;
};

View File

@@ -78,7 +78,7 @@ type TIdentityKubernetesAuthServiceFactoryDep = {
gatewayV2Service: TGatewayV2ServiceFactory;
gatewayDAL: Pick<TGatewayDALFactory, "find">;
gatewayV2DAL: Pick<TGatewayV2DALFactory, "find">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityKubernetesAuthServiceFactory = ReturnType<typeof identityKubernetesAuthServiceFactory>;
@@ -185,7 +185,7 @@ export const identityKubernetesAuthServiceFactory = ({
return callbackResult;
};
const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => {
const login = async ({ identityId, jwt: serviceAccountJwt, subOrganizationName }: TLoginKubernetesAuthDTO) => {
const appCfg = getConfig();
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
if (!identityKubernetesAuth) {
@@ -198,6 +198,30 @@ export const identityKubernetesAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -487,7 +511,8 @@ export const identityKubernetesAuthServiceFactory = ({
accessTokenMaxTTL: identityKubernetesAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityKubernetesAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.KUBERNETES_AUTH
authMethod: IdentityAuthMethod.KUBERNETES_AUTH,
scopeOrgId
},
tx
);

View File

@@ -3,6 +3,7 @@ import { TProjectPermission } from "@app/lib/types";
export type TLoginKubernetesAuthDTO = {
identityId: string;
jwt: string;
subOrganizationName?: string;
};
export enum IdentityKubernetesAuthTokenReviewMode {

View File

@@ -70,7 +70,7 @@ type TIdentityLdapAuthServiceFactoryDep = {
TKeyStoreFactory,
"setItemWithExpiry" | "getItem" | "deleteItem" | "getKeysByPattern" | "deleteItems" | "acquireLock"
>;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
@@ -153,7 +153,7 @@ export const identityLdapAuthServiceFactory = ({
return { opts, ldapConfig };
};
const login = async ({ identityId }: TLoginLdapAuthDTO) => {
const login = async ({ identityId, subOrganizationName }: TLoginLdapAuthDTO) => {
const appCfg = getConfig();
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
@@ -167,6 +167,30 @@ export const identityLdapAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
const plan = await licenseService.getPlan(identity.orgId);
if (!plan.ldap) {
throw new BadRequestError({
@@ -204,7 +228,8 @@ export const identityLdapAuthServiceFactory = ({
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.LDAP_AUTH
authMethod: IdentityAuthMethod.LDAP_AUTH,
scopeOrgId
},
tx
);

View File

@@ -59,6 +59,7 @@ export type TGetLdapAuthDTO = {
export type TLoginLdapAuthDTO = {
identityId: string;
subOrganizationName?: string;
};
export type TRevokeLdapAuthDTO = {

View File

@@ -51,7 +51,7 @@ type TIdentityOciAuthServiceFactoryDep = {
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne" | "update" | "getIdentityById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityOciAuthServiceFactory = ReturnType<typeof identityOciAuthServiceFactory>;
@@ -65,7 +65,7 @@ export const identityOciAuthServiceFactory = ({
permissionService,
orgDAL
}: TIdentityOciAuthServiceFactoryDep) => {
const login = async ({ identityId, headers, userOcid }: TLoginOciAuthDTO) => {
const login = async ({ identityId, headers, userOcid, subOrganizationName }: TLoginOciAuthDTO) => {
const appCfg = getConfig();
const identityOciAuth = await identityOciAuthDAL.findOne({ identityId });
if (!identityOciAuth) {
@@ -76,6 +76,30 @@ export const identityOciAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
// Validate OCI host format. Ensures that the host is in "identity.<region>.oraclecloud.com" format.
if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) {
@@ -137,7 +161,8 @@ export const identityOciAuthServiceFactory = ({
accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityOciAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.OCI_AUTH
authMethod: IdentityAuthMethod.OCI_AUTH,
scopeOrgId
},
tx
);

View File

@@ -9,6 +9,7 @@ export type TLoginOciAuthDTO = {
"x-date"?: string;
date?: string;
};
subOrganizationName?: string;
};
export type TAttachOciAuthDTO = {

View File

@@ -61,7 +61,7 @@ type TIdentityOidcAuthServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityOidcAuthServiceFactory = ReturnType<typeof identityOidcAuthServiceFactory>;
@@ -76,7 +76,7 @@ export const identityOidcAuthServiceFactory = ({
kmsService,
orgDAL
}: TIdentityOidcAuthServiceFactoryDep) => {
const login = async ({ identityId, jwt: oidcJwt }: TLoginOidcAuthDTO) => {
const login = async ({ identityId, jwt: oidcJwt, subOrganizationName }: TLoginOidcAuthDTO) => {
const appCfg = getConfig();
const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId });
if (!identityOidcAuth) {
@@ -87,6 +87,30 @@ export const identityOidcAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@@ -299,7 +323,8 @@ export const identityOidcAuthServiceFactory = ({
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityOidcAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.OIDC_AUTH
authMethod: IdentityAuthMethod.OIDC_AUTH,
scopeOrgId
},
tx
);

View File

@@ -38,6 +38,7 @@ export type TGetOidcAuthDTO = {
export type TLoginOidcAuthDTO = {
identityId: string;
jwt: string;
subOrganizationName?: string;
};
export type TRevokeOidcAuthDTO = {

View File

@@ -46,7 +46,7 @@ type TIdentityTlsCertAuthServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
const parseSubjectDetails = (data: string) => {
@@ -68,7 +68,11 @@ export const identityTlsCertAuthServiceFactory = ({
kmsService,
orgDAL
}: TIdentityTlsCertAuthServiceFactoryDep): TIdentityTlsCertAuthServiceFactory => {
const login: TIdentityTlsCertAuthServiceFactory["login"] = async ({ identityId, clientCertificate }) => {
const login: TIdentityTlsCertAuthServiceFactory["login"] = async ({
identityId,
clientCertificate,
subOrganizationName
}) => {
const appCfg = getConfig();
const identityTlsCertAuth = await identityTlsCertAuthDAL.findOne({ identityId });
if (!identityTlsCertAuth) {
@@ -81,6 +85,30 @@ export const identityTlsCertAuthServiceFactory = ({
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -157,7 +185,8 @@ export const identityTlsCertAuthServiceFactory = ({
accessTokenMaxTTL: identityTlsCertAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityTlsCertAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.TLS_CERT_AUTH
authMethod: IdentityAuthMethod.TLS_CERT_AUTH,
scopeOrgId
},
tx
);

View File

@@ -4,6 +4,7 @@ import { TProjectPermission } from "@app/lib/types";
export type TLoginTlsCertAuthDTO = {
identityId: string;
clientCertificate: string;
subOrganizationName?: string;
};
export type TAttachTlsCertAuthDTO = {

View File

@@ -59,7 +59,7 @@ type TIdentityTokenAuthServiceFactoryDep = {
>;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
};
export type TIdentityTokenAuthServiceFactory = ReturnType<typeof identityTokenAuthServiceFactory>;
@@ -424,7 +424,8 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId,
name,
isActorSuperAdmin
isActorSuperAdmin,
subOrganizationName
}: TCreateTokenAuthTokenDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
@@ -503,6 +504,32 @@ export const identityTokenAuthServiceFactory = ({
const identity = await identityDAL.findById(identityTokenAuth.identityId);
if (!identity) throw new UnauthorizedError({ message: "Identity not found" });
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
const identityAccessToken = await identityTokenAuthDAL.transaction(async (tx) => {
await membershipIdentityDAL.update(
identity.projectId
@@ -529,7 +556,8 @@ export const identityTokenAuthServiceFactory = ({
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityTokenAuth.accessTokenNumUsesLimit,
name,
authMethod: IdentityAuthMethod.TOKEN_AUTH
authMethod: IdentityAuthMethod.TOKEN_AUTH,
scopeOrgId
},
tx
);

View File

@@ -30,6 +30,7 @@ export type TRevokeTokenAuthDTO = {
export type TCreateTokenAuthTokenDTO = {
identityId: string;
name?: string;
subOrganizationName?: string;
isActorSuperAdmin?: boolean;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -41,6 +41,7 @@ import {
TGetUaClientSecretsDTO,
TGetUaDTO,
TGetUniversalAuthClientSecretByIdDTO,
TLoginUaDTO,
TRevokeUaClientSecretDTO,
TRevokeUaDTO,
TUpdateUaDTO
@@ -54,7 +55,7 @@ type TIdentityUaServiceFactoryDep = {
membershipIdentityDAL: TMembershipIdentityDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findById">;
orgDAL: Pick<TOrgDALFactory, "findById" | "findOne">;
keyStore: Pick<
TKeyStoreFactory,
"setItemWithExpiry" | "getItem" | "deleteItem" | "getKeysByPattern" | "deleteItems" | "acquireLock"
@@ -79,7 +80,7 @@ export const identityUaServiceFactory = ({
keyStore,
identityDAL
}: TIdentityUaServiceFactoryDep) => {
const login = async (clientId: string, clientSecret: string, ip: string) => {
const login = async ({ clientId, clientSecret, ip, subOrganizationName }: TLoginUaDTO) => {
const appCfg = getConfig();
const identityUa = await identityUaDAL.findOne({ clientId });
if (!identityUa) {
@@ -90,6 +91,30 @@ export const identityUaServiceFactory = ({
const identity = await identityDAL.findById(identityUa.identityId);
const org = await orgDAL.findById(identity.orgId);
const isSubOrg = !!(org.rootOrgId || org.parentOrgId);
const rootOrgId = isSubOrg ? org.rootOrgId || org.id : org.id;
// Resolve sub-organization if specified
let scopeOrgId = rootOrgId;
if (subOrganizationName) {
const subOrg = await orgDAL.findOne({ slug: subOrganizationName });
if (subOrg) {
if (!isSubOrg || (isSubOrg && subOrg.rootOrgId === rootOrgId)) {
// Verify identity has membership in the sub-organization
const subOrgMembership = await membershipIdentityDAL.findOne({
scope: AccessScope.Organization,
actorIdentityId: identity.id,
scopeOrgId: subOrg.id
});
if (subOrgMembership) {
scopeOrgId = subOrg.id;
}
}
}
}
try {
checkIPAgainstBlocklist({
@@ -259,6 +284,7 @@ export const identityUaServiceFactory = ({
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
accessTokenPeriod: identityUa.accessTokenPeriod,
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
scopeOrgId,
...accessTokenTTLParams
},
tx

View File

@@ -1,5 +1,12 @@
import { TProjectPermission } from "@app/lib/types";
export type TLoginUaDTO = {
clientId: string;
clientSecret: string;
ip: string;
subOrganizationName?: string;
};
export type TAttachUaDTO = {
identityId: string;
accessTokenTTL: number;

View File

@@ -640,7 +640,8 @@ export const superAdminServiceFactory = ({
accessTokenNumUses: 0,
accessTokenNumUsesLimit: tokenAuth.accessTokenNumUsesLimit,
name: "Instance Admin Token",
authMethod: IdentityAuthMethod.TOKEN_AUTH
authMethod: IdentityAuthMethod.TOKEN_AUTH,
scopeOrgId: organization.id
},
tx
);