diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index a95fca451..afdf1a932 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -30,6 +30,7 @@ export type TAuthMode = serviceToken: TServiceTokens & { createdByEmail: string }; actor: ActorType.SERVICE; serviceTokenId: string; + orgId: string; } | { authMode: AuthMode.IDENTITY_ACCESS_TOKEN; @@ -99,8 +100,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { if (!authMode) return; switch (authMode) { + // May or may not have an orgId. If it doesn't have an org ID, it's likely because the token is from an org that doesn't enforce org-level auth. case AuthMode.JWT: { - const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity( + token, + req.headers?.["x-infisical-organization-id"] + ); req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; break; } @@ -116,21 +121,25 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { }; break; } + // Will always contain an orgId. case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); req.auth = { authMode: AuthMode.SERVICE_TOKEN as const, serviceToken, + orgId: serviceToken.orgId, serviceTokenId: serviceToken.id, actor }; break; } + // Will never contain an orgId. API keys are not tied to an organization. case AuthMode.API_KEY: { const user = await server.services.apiKey.fnValidateApiKey(token as string); req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user }; break; } + // OK case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId }; diff --git a/backend/src/server/plugins/auth/inject-permission.ts b/backend/src/server/plugins/auth/inject-permission.ts index 8510d88c2..bd4ac66db 100644 --- a/backend/src/server/plugins/auth/inject-permission.ts +++ b/backend/src/server/plugins/auth/inject-permission.ts @@ -13,7 +13,7 @@ export const injectPermission = fp(async (server) => { } else if (req.auth.actor === ActorType.IDENTITY) { req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId, orgId: req.auth.orgId }; } else if (req.auth.actor === ActorType.SERVICE) { - req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId }; + req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId, orgId: req.auth.orgId }; } else if (req.auth.actor === ActorType.SCIM_CLIENT) { req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId }; } diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 464ea7d0d..ae126ac34 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -264,7 +264,7 @@ export const registerRoutes = async ( queueService }); - const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); + const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgDAL }); const userService = userServiceFactory({ userDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService }); const passwordService = authPaswordServiceFactory({ @@ -517,6 +517,7 @@ export const registerRoutes = async ( const serviceTokenService = serviceTokenServiceFactory({ projectEnvDAL, serviceTokenDAL, + orgDAL, userDAL, permissionService }); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index 59f336e5a..4c44a101b 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env"; import { UnauthorizedError } from "@app/lib/errors"; import { AuthModeJwtTokenPayload } from "../auth/auth-type"; +import { TOrgDALFactory } from "../org/org-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TTokenDALFactory } from "./auth-token-dal"; import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenForUserDTO } from "./auth-token-types"; @@ -14,6 +15,7 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo type TAuthTokenServiceFactoryDep = { tokenDAL: TTokenDALFactory; userDAL: Pick; + orgDAL: Pick; }; export type TAuthTokenServiceFactory = ReturnType; @@ -54,7 +56,7 @@ export const getTokenConfig = (tokenType: TokenType) => { } }; -export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => { +export const tokenServiceFactory = ({ tokenDAL, userDAL, orgDAL }: TAuthTokenServiceFactoryDep) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const { token, ...tkCfg } = getTokenConfig(type); const appCfg = getConfig(); @@ -130,7 +132,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const revokeAllMySessions = async (userId: string) => tokenDAL.deleteTokenSession({ userId }); // to parse jwt identity in inject identity plugin - const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => { + const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, organizationIdHeader?: string | string[]) => { const session = await tokenDAL.findOneTokenSession({ id: token.tokenVersionId, userId: token.userId @@ -141,7 +143,22 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFact const user = await userDAL.findById(session.userId); if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" }); - return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId }; + let orgId = token.organizationId; + if (!token.organizationId && organizationIdHeader) { + // If the token doesn't have an organization ID, but an organization ID is provided in the header, we need to check if the user is a member of the organization before concluding the organization ID is valid. + const userMembership = ( + await orgDAL.findMembership({ + userId: user.id, + orgId: organizationIdHeader as string + }) + )[0]; + + if (!userMembership) throw new UnauthorizedError({ name: "User not a member of the organization" }); + + orgId = userMembership.orgId; + } + + return { user, tokenVersionId: token.tokenVersionId, orgId }; }; return { diff --git a/backend/src/services/service-token/service-token-service.ts b/backend/src/services/service-token/service-token-service.ts index 3410aacad..40e414acd 100644 --- a/backend/src/services/service-token/service-token-service.ts +++ b/backend/src/services/service-token/service-token-service.ts @@ -9,6 +9,7 @@ import { getConfig } from "@app/lib/config/env"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; +import { TOrgDALFactory } from "../org/org-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TServiceTokenDALFactory } from "./service-token-dal"; @@ -23,6 +24,7 @@ type TServiceTokenServiceFactoryDep = { serviceTokenDAL: TServiceTokenDALFactory; userDAL: TUserDALFactory; permissionService: Pick; + orgDAL: Pick; projectEnvDAL: Pick; }; @@ -31,6 +33,7 @@ export type TServiceTokenServiceFactory = ReturnType {