mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 07:26:45 +00:00
fix: improve auth step to avoid takeovers
This commit is contained in:
@@ -47,7 +47,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
table.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE"); // the ID itself is the workflow integration ID
|
||||
|
||||
table.string("internalTeamsAppId").nullable();
|
||||
table.string("tenantId").unique().notNullable();
|
||||
table.string("tenantId").notNullable();
|
||||
table.binary("encryptedAccessToken").nullable();
|
||||
table.binary("encryptedBotAccessToken").nullable();
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
|
||||
redirectUri: z.string(),
|
||||
tenantId: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
description: z.string().optional()
|
||||
description: z.string().optional(),
|
||||
code: z.string().trim()
|
||||
})
|
||||
},
|
||||
|
||||
@@ -72,6 +73,7 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
|
||||
slug: req.body.slug,
|
||||
description: req.body.description,
|
||||
redirectUri: req.body.redirectUri,
|
||||
code: req.body.code,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
|
||||
@@ -16,48 +16,77 @@ import { TWorkflowIntegrationDALFactory } from "../workflow-integration/workflow
|
||||
import { WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types";
|
||||
import { TMicrosoftTeamsIntegrationDALFactory } from "./microsoft-teams-integration-dal";
|
||||
|
||||
const ConsentError = "AADSTS65001";
|
||||
|
||||
export const verifyTenantFromCode = async (
|
||||
tenantId: string,
|
||||
code: string,
|
||||
redirectUri: string,
|
||||
clientId: string,
|
||||
clientSecret: string
|
||||
) => {
|
||||
const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
||||
const getAccessToken = async (params: URLSearchParams) => {
|
||||
const response = await axios
|
||||
.post<{ access_token: string }>(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, params, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (axios.isAxiosError(err)) {
|
||||
if ((err.response?.data as { error_description?: string })?.error_description?.includes(ConsentError)) {
|
||||
throw new BadRequestError({
|
||||
message: "Unable to verify tenant, please ensure that you have granted admin consent."
|
||||
});
|
||||
}
|
||||
logger.error(err.response?.data, "Error fetching Microsoft Teams access token");
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope: "https://graph.microsoft.com/.default",
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: "client_credentials"
|
||||
});
|
||||
return response.data.access_token;
|
||||
};
|
||||
|
||||
const response = await axios
|
||||
.post<{ access_token: string }>(tokenEndpoint, params, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
// Azure App-based auth
|
||||
const applicationAccessToken = await getAccessToken(
|
||||
new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope: "https://graph.microsoft.com/.default",
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: "client_credentials"
|
||||
})
|
||||
.catch((err) => {
|
||||
if (axios.isAxiosError(err)) {
|
||||
logger.error(err.response?.data, "Error fetching Microsoft Teams access token");
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
);
|
||||
|
||||
const accessToken = response.data.access_token;
|
||||
const decodedToken = jwt.decode(accessToken) as { tid: string };
|
||||
// User-based auth
|
||||
const authorizationAccessToken = await getAccessToken(
|
||||
new URLSearchParams({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope: "https://graph.microsoft.com/.default",
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: "authorization_code",
|
||||
code
|
||||
})
|
||||
);
|
||||
|
||||
// the 'tid' claim in the token contains the tenant ID
|
||||
const tenantIdFromToken = decodedToken.tid;
|
||||
// Verify application token
|
||||
const { tid: tenantIdFromApplicationAccessToken } = jwt.decode(applicationAccessToken) as { tid: string };
|
||||
|
||||
if (tenantIdFromToken !== tenantId) {
|
||||
if (tenantIdFromApplicationAccessToken !== tenantId) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid tenant state ID. Expected ${tenantId}, got ${tenantIdFromToken}`
|
||||
message: `Invalid application token tenant ID. Expected ${tenantId}, got ${tenantIdFromApplicationAccessToken}`
|
||||
});
|
||||
}
|
||||
|
||||
return tenantIdFromToken;
|
||||
// Verify user authorization token
|
||||
const { tid: tenantIdFromAuthorizationAccessToken } = jwt.decode(authorizationAccessToken) as { tid: string };
|
||||
|
||||
if (tenantIdFromAuthorizationAccessToken !== tenantId) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid authorization token tenant ID. Expected ${tenantId}, got ${tenantIdFromAuthorizationAccessToken}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getMicrosoftTeamsAccessToken = async (
|
||||
|
||||
@@ -211,6 +211,7 @@ export const microsoftTeamsServiceFactory = ({
|
||||
};
|
||||
|
||||
const completeMicrosoftTeamsIntegration = async ({
|
||||
code,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@@ -251,7 +252,7 @@ export const microsoftTeamsServiceFactory = ({
|
||||
const botAppPassword = decryptWithRoot(encryptedMicrosoftTeamsClientSecret);
|
||||
const botId = decryptWithRoot(encryptedMicrosoftTeamsBotId);
|
||||
|
||||
await verifyTenantFromCode(tenantId, redirectUri, botAppId.toString(), botAppPassword.toString());
|
||||
await verifyTenantFromCode(tenantId, code, redirectUri, botAppId.toString(), botAppPassword.toString());
|
||||
|
||||
await workflowIntegrationDAL.transaction(async (tx) => {
|
||||
const workflowIntegration = await workflowIntegrationDAL.create(
|
||||
|
||||
@@ -10,6 +10,7 @@ export type TCreateMicrosoftTeamsIntegrationDTO = Omit<TOrgPermission, "orgId">
|
||||
slug: string;
|
||||
redirectUri: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type TCheckInstallationStatusDTO = { workflowIntegrationId: string } & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
Reference in New Issue
Block a user