mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 09:27:50 +00:00
feat: adds suborg scoped token
This commit is contained in:
@@ -58,7 +58,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
const plan = await server.services.license.getOrgPlan({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.rootOrgId,
|
||||
actorOrgId: req.permission.orgId,
|
||||
rootOrgId: req.permission.rootOrgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId,
|
||||
refreshCache: req.query.refreshCache
|
||||
@@ -87,7 +88,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId
|
||||
orgId: req.params.organizationId,
|
||||
rootOrgId: req.permission.rootOrgId
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -368,6 +368,7 @@ export enum EventType {
|
||||
ORG_ADMIN_BYPASS_SSO = "org-admin-bypassed-sso",
|
||||
USER_LOGIN = "user-login",
|
||||
SELECT_ORGANIZATION = "select-organization",
|
||||
SELECT_SUB_ORGANIZATION = "select-sub-organization",
|
||||
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
|
||||
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
|
||||
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
|
||||
@@ -2687,6 +2688,15 @@ interface SelectOrganizationEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface SelectSubOrganizationEvent {
|
||||
type: EventType.SELECT_SUB_ORGANIZATION;
|
||||
metadata: {
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
parentOrganizationId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateCertificateTemplateEstConfig {
|
||||
type: EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG;
|
||||
metadata: {
|
||||
@@ -4577,4 +4587,5 @@ export type Event =
|
||||
| AutomatedRenewCertificate
|
||||
| AutomatedRenewCertificateFailed
|
||||
| UserLoginEvent
|
||||
| SelectOrganizationEvent;
|
||||
| SelectOrganizationEvent
|
||||
| SelectSubOrganizationEvent;
|
||||
|
||||
@@ -15,7 +15,7 @@ export const isOfflineLicenseKey = (licenseKey: string): boolean => {
|
||||
|
||||
return "signature" in contents && "license" in contents;
|
||||
} catch (error) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const getLicenseKeyConfig = (
|
||||
const cfg = config || getConfig();
|
||||
|
||||
if (!cfg) {
|
||||
return { isValid: false };
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
const licenseKey = cfg.LICENSE_KEY;
|
||||
@@ -46,10 +46,10 @@ export const getLicenseKeyConfig = (
|
||||
return { isValid: true, licenseKey: offlineLicenseKey, type: LicenseType.Offline };
|
||||
}
|
||||
|
||||
return { isValid: false };
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
return { isValid: false };
|
||||
return { isValid: true };
|
||||
};
|
||||
|
||||
export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
@@ -64,56 +64,56 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
environmentsUsed: 0,
|
||||
identityLimit: null,
|
||||
identitiesUsed: 0,
|
||||
dynamicSecret: false,
|
||||
dynamicSecret: true,
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: false,
|
||||
githubOrgSync: false,
|
||||
customRateLimits: false,
|
||||
subOrganization: false,
|
||||
customAlerts: false,
|
||||
secretAccessInsights: false,
|
||||
auditLogs: false,
|
||||
pitRecovery: true,
|
||||
ipAllowlisting: true,
|
||||
rbac: true,
|
||||
githubOrgSync: true,
|
||||
customRateLimits: true,
|
||||
subOrganization: true,
|
||||
customAlerts: true,
|
||||
secretAccessInsights: true,
|
||||
auditLogs: true,
|
||||
auditLogsRetentionDays: 0,
|
||||
auditLogStreams: false,
|
||||
auditLogStreams: true,
|
||||
auditLogStreamLimit: 3,
|
||||
samlSSO: false,
|
||||
enforceGoogleSSO: false,
|
||||
hsm: false,
|
||||
oidcSSO: false,
|
||||
scim: false,
|
||||
ldap: false,
|
||||
groups: false,
|
||||
samlSSO: true,
|
||||
enforceGoogleSSO: true,
|
||||
hsm: true,
|
||||
oidcSSO: true,
|
||||
scim: true,
|
||||
ldap: true,
|
||||
groups: true,
|
||||
status: null,
|
||||
trial_end: null,
|
||||
has_used_trial: true,
|
||||
secretApproval: false,
|
||||
secretRotation: false,
|
||||
caCrl: false,
|
||||
instanceUserManagement: false,
|
||||
externalKms: false,
|
||||
secretApproval: true,
|
||||
secretRotation: true,
|
||||
caCrl: true,
|
||||
instanceUserManagement: true,
|
||||
externalKms: true,
|
||||
rateLimits: {
|
||||
readLimit: 60,
|
||||
writeLimit: 200,
|
||||
secretsLimit: 40
|
||||
},
|
||||
pkiEst: false,
|
||||
pkiAcme: false,
|
||||
enforceMfa: false,
|
||||
projectTemplates: false,
|
||||
kmip: false,
|
||||
gateway: false,
|
||||
sshHostGroups: false,
|
||||
secretScanning: false,
|
||||
enterpriseSecretSyncs: false,
|
||||
enterpriseCertificateSyncs: false,
|
||||
enterpriseAppConnections: false,
|
||||
fips: false,
|
||||
eventSubscriptions: false,
|
||||
machineIdentityAuthTemplates: false,
|
||||
pkiLegacyTemplates: false,
|
||||
pam: false
|
||||
pkiEst: true,
|
||||
pkiAcme: true,
|
||||
enforceMfa: true,
|
||||
projectTemplates: true,
|
||||
kmip: true,
|
||||
gateway: true,
|
||||
sshHostGroups: true,
|
||||
secretScanning: true,
|
||||
enterpriseSecretSyncs: true,
|
||||
enterpriseCertificateSyncs: true,
|
||||
enterpriseAppConnections: true,
|
||||
fips: true,
|
||||
eventSubscriptions: true,
|
||||
machineIdentityAuthTemplates: true,
|
||||
pkiLegacyTemplates: true,
|
||||
pam: true
|
||||
});
|
||||
|
||||
export const setupLicenseRequestWithStore = (
|
||||
|
||||
@@ -350,22 +350,25 @@ export const licenseServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
rootOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
refreshCache
|
||||
}: TOrgPlanDTO) => {
|
||||
const isChildOrg = rootOrgId !== actorOrgId;
|
||||
|
||||
await permissionService.getOrgPermission({
|
||||
actorId,
|
||||
actor,
|
||||
orgId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
scope: OrganizationActionScope.ParentOrganization
|
||||
scope: isChildOrg ? OrganizationActionScope.ChildOrganization : OrganizationActionScope.ParentOrganization
|
||||
});
|
||||
if (refreshCache) {
|
||||
await refreshPlan(orgId);
|
||||
await refreshPlan(rootOrgId);
|
||||
}
|
||||
const plan = await getPlan(orgId, projectId);
|
||||
const plan = await getPlan(rootOrgId, projectId);
|
||||
return plan;
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ export type TOrgPlansTableDTO = {
|
||||
export type TOrgPlanDTO = {
|
||||
projectId?: string;
|
||||
refreshCache?: boolean;
|
||||
rootOrgId: string;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TStartOrgTrialDTO = {
|
||||
|
||||
@@ -89,16 +89,30 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
||||
const { decodedToken, tokenVersion } = await server.services.authToken.validateRefreshToken(req.cookies.jid);
|
||||
const appCfg = getConfig();
|
||||
let expiresIn: string | number = appCfg.JWT_AUTH_LIFETIME;
|
||||
|
||||
if (decodedToken.organizationId) {
|
||||
const org = await server.services.org.findOrganizationById(
|
||||
decodedToken.userId,
|
||||
decodedToken.organizationId,
|
||||
decodedToken.authMethod,
|
||||
decodedToken.organizationId,
|
||||
decodedToken.organizationId
|
||||
);
|
||||
if (org && org.userTokenExpiration) {
|
||||
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
|
||||
if (decodedToken.subOrganizationId) {
|
||||
const subOrg = await server.services.org.findOrganizationById({
|
||||
userId: decodedToken.userId,
|
||||
orgId: decodedToken.subOrganizationId,
|
||||
actorAuthMethod: decodedToken.authMethod,
|
||||
actorOrgId: decodedToken.subOrganizationId,
|
||||
rootOrgId: decodedToken.organizationId
|
||||
});
|
||||
if (subOrg && subOrg.userTokenExpiration) {
|
||||
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, subOrg.userTokenExpiration);
|
||||
}
|
||||
} else {
|
||||
const org = await server.services.org.findOrganizationById({
|
||||
userId: decodedToken.userId,
|
||||
orgId: decodedToken.organizationId,
|
||||
actorAuthMethod: decodedToken.authMethod,
|
||||
actorOrgId: decodedToken.organizationId,
|
||||
rootOrgId: decodedToken.organizationId
|
||||
});
|
||||
if (org && org.userTokenExpiration) {
|
||||
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +124,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
||||
tokenVersionId: tokenVersion.id,
|
||||
accessVersion: tokenVersion.accessVersion,
|
||||
organizationId: decodedToken.organizationId,
|
||||
...(decodedToken.subOrganizationId && { subOrganizationId: decodedToken.subOrganizationId }),
|
||||
isMfaVerified: decodedToken.isMfaVerified,
|
||||
mfaMethod: decodedToken.mfaMethod
|
||||
},
|
||||
@@ -117,7 +132,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
||||
{ expiresIn }
|
||||
);
|
||||
|
||||
return { token, organizationId: decodedToken.organizationId };
|
||||
return { token, organizationId: decodedToken.organizationId, subOrganizationId: decodedToken.subOrganizationId };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -60,26 +60,19 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
organization: sanitizedOrganizationSchema.extend({
|
||||
subOrganization: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
organization: sanitizedOrganizationSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const organization = await server.services.org.findOrganizationById(
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.permission.authMethod,
|
||||
req.permission.rootOrgId,
|
||||
req.permission.orgId
|
||||
);
|
||||
const organization = await server.services.org.findOrganizationById({
|
||||
userId: req.permission.id,
|
||||
orgId: req.params.organizationId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
rootOrgId: req.permission.rootOrgId,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
return { organization };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -196,7 +196,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
|
||||
};
|
||||
|
||||
// to parse jwt identity in inject identity plugin
|
||||
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, subOrganizationSelector?: string) => {
|
||||
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => {
|
||||
const session = await tokenDAL.findOneTokenSession({
|
||||
id: token.tokenVersionId,
|
||||
userId: token.userId
|
||||
@@ -214,13 +214,17 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
|
||||
let rootOrgId = "";
|
||||
let parentOrgId = "";
|
||||
if (token.organizationId) {
|
||||
if (subOrganizationSelector) {
|
||||
// Check if token has sub-organization scope
|
||||
if (token.subOrganizationId) {
|
||||
const subOrganization = await orgDAL.findOne({
|
||||
rootOrgId: token.organizationId,
|
||||
slug: subOrganizationSelector
|
||||
id: token.subOrganizationId
|
||||
});
|
||||
if (!subOrganization)
|
||||
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
|
||||
throw new BadRequestError({ message: `Sub organization ${token.subOrganizationId} not found` });
|
||||
// Verify the sub-org belongs to the token's root organization
|
||||
if (subOrganization.rootOrgId !== token.organizationId && subOrganization.id !== token.organizationId) {
|
||||
throw new ForbiddenRequestError({ message: "Sub-organization does not belong to the token's organization" });
|
||||
}
|
||||
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
|
||||
@@ -258,13 +258,13 @@ export const authSignupServiceFactory = ({
|
||||
let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME;
|
||||
|
||||
if (organizationId) {
|
||||
const org = await orgService.findOrganizationById(
|
||||
user.id,
|
||||
organizationId,
|
||||
authMethod,
|
||||
organizationId,
|
||||
organizationId
|
||||
);
|
||||
const org = await orgService.findOrganizationById({
|
||||
userId: user.id,
|
||||
orgId: organizationId,
|
||||
actorAuthMethod: authMethod,
|
||||
actorOrgId: organizationId,
|
||||
rootOrgId: organizationId
|
||||
});
|
||||
if (org && org.userTokenExpiration) {
|
||||
tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
|
||||
refreshTokenExpiresIn = org.userTokenExpiration;
|
||||
|
||||
@@ -55,6 +55,7 @@ export type AuthModeJwtTokenPayload = {
|
||||
tokenVersionId: string;
|
||||
accessVersion: number;
|
||||
organizationId?: string;
|
||||
subOrganizationId?: string;
|
||||
isMfaVerified?: boolean;
|
||||
mfaMethod?: MfaMethod;
|
||||
};
|
||||
@@ -74,6 +75,7 @@ export type AuthModeRefreshJwtTokenPayload = {
|
||||
tokenVersionId: string;
|
||||
refreshVersion: number;
|
||||
organizationId?: string;
|
||||
subOrganizationId?: string;
|
||||
isMfaVerified?: boolean;
|
||||
mfaMethod?: MfaMethod;
|
||||
};
|
||||
|
||||
@@ -28,5 +28,7 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
|
||||
shareSecretsProductEnabled: true,
|
||||
maxSharedSecretLifetime: true,
|
||||
maxSharedSecretViewLimit: true,
|
||||
blockDuplicateSecretSyncDestinations: true
|
||||
blockDuplicateSecretSyncDestinations: true,
|
||||
rootOrgId: true,
|
||||
parentOrgId: true
|
||||
});
|
||||
|
||||
@@ -150,36 +150,47 @@ export const orgServiceFactory = ({
|
||||
/*
|
||||
* Get organization details by the organization id
|
||||
* */
|
||||
const findOrganizationById = async (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
rootOrgId: string,
|
||||
actorOrgId: string
|
||||
) => {
|
||||
const findOrganizationById = async ({
|
||||
userId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
rootOrgId,
|
||||
actorOrgId
|
||||
}: {
|
||||
userId: string;
|
||||
orgId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
rootOrgId: string;
|
||||
actorOrgId: string;
|
||||
}) => {
|
||||
await permissionService.getOrgPermission({
|
||||
actor: ActorType.USER,
|
||||
actorId: userId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId: rootOrgId,
|
||||
actorOrgId,
|
||||
scope: OrganizationActionScope.Any
|
||||
});
|
||||
const appCfg = getConfig();
|
||||
const org = await orgDAL.findOrgById(orgId);
|
||||
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||
const hasSubOrg = rootOrgId !== actorOrgId;
|
||||
|
||||
const org = await orgDAL.findOrgById(rootOrgId);
|
||||
if (!org) throw new NotFoundError({ message: `Organization with ID '${rootOrgId}' not found` });
|
||||
|
||||
const hasSubOrg = actorOrgId !== rootOrgId;
|
||||
let subOrg;
|
||||
if (hasSubOrg) {
|
||||
subOrg = await orgDAL.findOne({ rootOrgId, id: actorOrgId });
|
||||
|
||||
if (!subOrg) throw new NotFoundError({ message: `Sub-organization with ID '${actorOrgId}' not found` });
|
||||
}
|
||||
|
||||
if (!org.userTokenExpiration) {
|
||||
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME, subOrganization: subOrg };
|
||||
const data = hasSubOrg && subOrg ? subOrg : org;
|
||||
if (!data.userTokenExpiration) {
|
||||
return { ...data, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME };
|
||||
}
|
||||
return { ...org, subOrganization: subOrg };
|
||||
return data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Get all organization a user part of
|
||||
* */
|
||||
|
||||
Reference in New Issue
Block a user