mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 11:27:47 +00:00
fix: select sub-org handler
This commit is contained in:
@@ -2693,7 +2693,7 @@ interface SelectSubOrganizationEvent {
|
||||
metadata: {
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
parentOrganizationId: string;
|
||||
rootOrganizationId: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,15 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
userAgent: z.enum(["cli"]).optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
organizationId: z.string().trim().optional(),
|
||||
subOrganizationId: z.string().trim().optional(),
|
||||
userAgent: z.enum(["cli"]).optional()
|
||||
})
|
||||
.refine((body) => Boolean(body.organizationId || body.subOrganizationId), {
|
||||
message: "organizationId or subOrganizationId is required"
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
token: z.string(),
|
||||
@@ -57,12 +62,25 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const cfg = getConfig();
|
||||
const tokens = await server.services.login.selectOrganization({
|
||||
userAgent: req.body.userAgent ?? req.headers["user-agent"],
|
||||
authJwtToken: req.headers.authorization,
|
||||
organizationId: req.body.organizationId,
|
||||
ipAddress: req.realIp
|
||||
});
|
||||
let tokens;
|
||||
|
||||
const targetOrgId = req.body.subOrganizationId ?? req.body.organizationId ?? "";
|
||||
|
||||
if (req.body.subOrganizationId) {
|
||||
tokens = await server.services.login.selectSubOrganization({
|
||||
userAgent: req.body.userAgent ?? req.headers["user-agent"],
|
||||
authJwtToken: req.headers.authorization,
|
||||
subOrganizationId: req.body.subOrganizationId,
|
||||
ipAddress: req.realIp
|
||||
});
|
||||
} else {
|
||||
tokens = await server.services.login.selectOrganization({
|
||||
userAgent: req.body.userAgent ?? req.headers["user-agent"],
|
||||
authJwtToken: req.headers.authorization,
|
||||
organizationId: req.body.organizationId as string,
|
||||
ipAddress: req.realIp
|
||||
});
|
||||
}
|
||||
|
||||
if (tokens.isMfaEnabled) {
|
||||
return {
|
||||
@@ -75,7 +93,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
const githubOauthAccessToken = req.cookies[INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN];
|
||||
if (githubOauthAccessToken) {
|
||||
await server.services.githubOrgSync
|
||||
.syncUserGroups(req.body.organizationId, tokens.user.userId, githubOauthAccessToken)
|
||||
.syncUserGroups(targetOrgId, tokens.user.userId, githubOauthAccessToken)
|
||||
.finally(() => {
|
||||
void res.setCookie(INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN, "", {
|
||||
httpOnly: true,
|
||||
@@ -108,74 +126,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/select-sub-organization",
|
||||
config: {
|
||||
rateLimit: authRateLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
subOrganizationId: z.string().trim(),
|
||||
userAgent: z.enum(["cli"]).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
token: z.string(),
|
||||
isMfaEnabled: z.boolean(),
|
||||
mfaMethod: z.string().optional(),
|
||||
subOrganization: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req, res) => {
|
||||
const cfg = getConfig();
|
||||
const result = await server.services.login.selectSubOrganization({
|
||||
userAgent: req.body.userAgent ?? req.headers["user-agent"],
|
||||
authJwtToken: req.headers.authorization,
|
||||
subOrganizationId: req.body.subOrganizationId,
|
||||
ipAddress: req.realIp
|
||||
});
|
||||
|
||||
if (result.isMfaEnabled) {
|
||||
return {
|
||||
token: result.mfa as string,
|
||||
isMfaEnabled: true,
|
||||
mfaMethod: result.mfaMethod
|
||||
};
|
||||
}
|
||||
|
||||
void res.setCookie("jid", result.refresh, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: cfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
addAuthOriginDomainCookie(res);
|
||||
|
||||
void res.cookie("infisical-project-assume-privileges", "", {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: cfg.HTTPS_ENABLED,
|
||||
maxAge: 0
|
||||
});
|
||||
|
||||
return {
|
||||
token: result.access,
|
||||
isMfaEnabled: false,
|
||||
subOrganization: result.subOrganization
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/login2",
|
||||
|
||||
@@ -725,39 +725,58 @@ export const authLoginServiceFactory = ({
|
||||
authJwtToken = authJwtToken.replace("Bearer ", "");
|
||||
|
||||
const decodedToken = crypto.jwt().verify(authJwtToken, cfg.AUTH_SECRET) as AuthModeJwtTokenPayload;
|
||||
|
||||
if (!decodedToken.authMethod) throw new UnauthorizedError({ name: "Auth method not found on existing token" });
|
||||
if (!decodedToken.organizationId)
|
||||
throw new BadRequestError({ message: "No organization selected in current token" });
|
||||
|
||||
const user = await userDAL.findUserEncKeyByUserId(decodedToken.userId);
|
||||
if (!user) throw new BadRequestError({ message: "User not found", name: "Find user from token" });
|
||||
|
||||
// Check user membership in the sub-organization
|
||||
const userSubOrgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
scopeOrgId: subOrganizationId,
|
||||
scope: AccessScope.Organization,
|
||||
status: OrgMembershipStatus.Accepted
|
||||
});
|
||||
|
||||
// Fetch the sub-organization
|
||||
const subOrg = await orgDAL.findById(subOrganizationId);
|
||||
if (!subOrg) {
|
||||
throw new BadRequestError({ message: `Sub-organization with ID ${subOrganizationId} not found` });
|
||||
}
|
||||
|
||||
// Verify this is actually a sub-organization of the current root org
|
||||
if (subOrg.rootOrgId !== decodedToken.organizationId && subOrg.id !== decodedToken.organizationId) {
|
||||
if (!userSubOrgMembership) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Sub-organization does not belong to the current organization"
|
||||
message: `User does not have access to the sub-organization named ${subOrg.name}`
|
||||
});
|
||||
}
|
||||
|
||||
// Check user membership in the sub-organization
|
||||
const orgMembership = await membershipUserDAL.findOne({
|
||||
actorUserId: user.id,
|
||||
scopeOrgId: subOrganizationId,
|
||||
scope: AccessScope.Organization
|
||||
});
|
||||
const subOrgmembershipRole = await membershipRoleDAL.findOne({ membershipId: userSubOrgMembership.id });
|
||||
|
||||
if (!orgMembership) {
|
||||
throw new ForbiddenRequestError({ message: "User is not a member of this sub-organization" });
|
||||
// Check if authEnforced is true and the current auth method is not an enforced method
|
||||
if (
|
||||
subOrg.authEnforced &&
|
||||
!isAuthMethodSaml(decodedToken.authMethod) &&
|
||||
decodedToken.authMethod !== AuthMethod.OIDC &&
|
||||
!(subOrg.bypassOrgAuthEnabled && subOrgmembershipRole.role === OrgMembershipRole.Admin)
|
||||
) {
|
||||
throw new BadRequestError({
|
||||
message: "Login with the auth method required by your organization."
|
||||
});
|
||||
}
|
||||
|
||||
if (!orgMembership.isActive) {
|
||||
throw new ForbiddenRequestError({ message: "User membership in sub-organization is inactive" });
|
||||
if (subOrg.googleSsoAuthEnforced && decodedToken.authMethod !== AuthMethod.GOOGLE) {
|
||||
const canBypass = subOrg.bypassOrgAuthEnabled && subOrgmembershipRole.role === OrgMembershipRole.Admin;
|
||||
|
||||
if (!canBypass) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Google SSO is enforced for this organization. Please use Google SSO to login.",
|
||||
error: "GoogleSsoEnforced"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedToken.authMethod === AuthMethod.GOOGLE) {
|
||||
await orgDAL.updateById(subOrg.id, {
|
||||
googleSsoAuthLastUsed: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
// Check MFA requirements for the sub-organization
|
||||
@@ -797,8 +816,8 @@ export const authLoginServiceFactory = ({
|
||||
user,
|
||||
userAgent,
|
||||
ip: ipAddress,
|
||||
organizationId: decodedToken.organizationId, // Keep root org ID
|
||||
subOrganizationId, // Add sub-org ID
|
||||
...(subOrg.rootOrgId && { organizationId: subOrg.rootOrgId }),
|
||||
subOrganizationId,
|
||||
isMfaVerified: decodedToken.isMfaVerified,
|
||||
mfaMethod: decodedToken.mfaMethod
|
||||
});
|
||||
@@ -823,7 +842,7 @@ export const authLoginServiceFactory = ({
|
||||
metadata: {
|
||||
organizationId: subOrganizationId,
|
||||
organizationName: subOrg.name,
|
||||
parentOrganizationId: decodedToken.organizationId
|
||||
rootOrganizationId: subOrg.rootOrgId ?? ""
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -831,12 +850,7 @@ export const authLoginServiceFactory = ({
|
||||
return {
|
||||
...tokens,
|
||||
user,
|
||||
isMfaEnabled: false,
|
||||
subOrganization: {
|
||||
id: subOrg.id,
|
||||
name: subOrg.name,
|
||||
slug: subOrg.slug
|
||||
}
|
||||
isMfaEnabled: false
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user