mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 23:52:09 +00:00
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { IdentityOidcAuthsSchema } from "@app/db/schemas";
|
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|
import { OIDC_AUTH } from "@app/lib/api-docs";
|
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|
import { AuthMode } from "@app/services/auth/auth-type";
|
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
|
import {
|
|
validateOidcAuthAudiencesField,
|
|
validateOidcBoundClaimsField
|
|
} from "@app/services/identity-oidc-auth/identity-oidc-auth-validators";
|
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
|
|
|
const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
|
|
id: true,
|
|
accessTokenTTL: true,
|
|
accessTokenMaxTTL: true,
|
|
accessTokenNumUsesLimit: true,
|
|
accessTokenTrustedIps: true,
|
|
identityId: true,
|
|
oidcDiscoveryUrl: true,
|
|
boundIssuer: true,
|
|
boundAudiences: true,
|
|
boundClaims: true,
|
|
claimMetadataMapping: true,
|
|
boundSubject: true,
|
|
createdAt: true,
|
|
updatedAt: true
|
|
}).extend({
|
|
caCert: z.string()
|
|
});
|
|
|
|
const MAX_OIDC_CLAIM_SIZE = 32_768;
|
|
|
|
export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) => {
|
|
server.route({
|
|
method: "POST",
|
|
url: "/oidc-auth/login",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
schema: {
|
|
description: "Login with OIDC Auth",
|
|
body: z.object({
|
|
identityId: z.string().trim().describe(OIDC_AUTH.LOGIN.identityId),
|
|
jwt: z.string().trim()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
accessToken: z.string(),
|
|
expiresIn: z.coerce.number(),
|
|
accessTokenMaxTTL: z.coerce.number(),
|
|
tokenType: z.literal("Bearer")
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const { identityOidcAuth, accessToken, identityAccessToken, identityMembershipOrg, oidcTokenData } =
|
|
await server.services.identityOidcAuth.login({
|
|
identityId: req.body.identityId,
|
|
jwt: req.body.jwt
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
orgId: identityMembershipOrg?.orgId,
|
|
event: {
|
|
type: EventType.LOGIN_IDENTITY_OIDC_AUTH,
|
|
metadata: {
|
|
identityId: identityOidcAuth.identityId,
|
|
identityAccessTokenId: identityAccessToken.id,
|
|
identityOidcAuthId: identityOidcAuth.id,
|
|
oidcClaimsReceived:
|
|
Buffer.from(JSON.stringify(oidcTokenData), "utf8").byteLength < MAX_OIDC_CLAIM_SIZE
|
|
? oidcTokenData
|
|
: { payload: "Error: Payload exceeds 32KB, provided oidc claim not recorded in audit log." }
|
|
}
|
|
}
|
|
});
|
|
return {
|
|
accessToken,
|
|
tokenType: "Bearer" as const,
|
|
expiresIn: identityOidcAuth.accessTokenTTL,
|
|
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "POST",
|
|
url: "/oidc-auth/identities/:identityId",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
description: "Attach OIDC Auth configuration onto identity",
|
|
security: [
|
|
{
|
|
bearerAuth: []
|
|
}
|
|
],
|
|
params: z.object({
|
|
identityId: z.string().trim().describe(OIDC_AUTH.ATTACH.identityId)
|
|
}),
|
|
body: z
|
|
.object({
|
|
oidcDiscoveryUrl: z.string().url().min(1).describe(OIDC_AUTH.ATTACH.oidcDiscoveryUrl),
|
|
caCert: z.string().trim().default("").describe(OIDC_AUTH.ATTACH.caCert),
|
|
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
|
|
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
|
|
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
|
|
claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping).optional(),
|
|
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
|
|
accessTokenTrustedIps: z
|
|
.object({
|
|
ipAddress: z.string().trim()
|
|
})
|
|
.array()
|
|
.min(1)
|
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
|
.describe(OIDC_AUTH.ATTACH.accessTokenTrustedIps),
|
|
accessTokenTTL: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(315360000)
|
|
.default(2592000)
|
|
.describe(OIDC_AUTH.ATTACH.accessTokenTTL),
|
|
accessTokenMaxTTL: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(315360000)
|
|
.default(2592000)
|
|
.describe(OIDC_AUTH.ATTACH.accessTokenMaxTTL),
|
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.ATTACH.accessTokenNumUsesLimit)
|
|
})
|
|
.refine(
|
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
|
),
|
|
response: {
|
|
200: z.object({
|
|
identityOidcAuth: IdentityOidcAuthResponseSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const identityOidcAuth = await server.services.identityOidcAuth.attachOidcAuth({
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
...req.body,
|
|
identityId: req.params.identityId,
|
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
orgId: identityOidcAuth.orgId,
|
|
event: {
|
|
type: EventType.ADD_IDENTITY_OIDC_AUTH,
|
|
metadata: {
|
|
identityId: identityOidcAuth.identityId,
|
|
oidcDiscoveryUrl: identityOidcAuth.oidcDiscoveryUrl,
|
|
caCert: identityOidcAuth.caCert,
|
|
boundIssuer: identityOidcAuth.boundIssuer,
|
|
boundAudiences: identityOidcAuth.boundAudiences,
|
|
boundClaims: identityOidcAuth.boundClaims as Record<string, string>,
|
|
claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record<string, string>,
|
|
boundSubject: identityOidcAuth.boundSubject as string,
|
|
accessTokenTTL: identityOidcAuth.accessTokenTTL,
|
|
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
|
|
accessTokenTrustedIps: identityOidcAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
|
accessTokenNumUsesLimit: identityOidcAuth.accessTokenNumUsesLimit
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
identityOidcAuth
|
|
};
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "PATCH",
|
|
url: "/oidc-auth/identities/:identityId",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
description: "Update OIDC Auth configuration on identity",
|
|
security: [
|
|
{
|
|
bearerAuth: []
|
|
}
|
|
],
|
|
params: z.object({
|
|
identityId: z.string().trim().describe(OIDC_AUTH.UPDATE.identityId)
|
|
}),
|
|
body: z
|
|
.object({
|
|
oidcDiscoveryUrl: z.string().url().min(1).describe(OIDC_AUTH.UPDATE.oidcDiscoveryUrl),
|
|
caCert: z.string().trim().default("").describe(OIDC_AUTH.UPDATE.caCert),
|
|
boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer),
|
|
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences),
|
|
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims),
|
|
claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping).optional(),
|
|
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject),
|
|
accessTokenTrustedIps: z
|
|
.object({
|
|
ipAddress: z.string().trim()
|
|
})
|
|
.array()
|
|
.min(1)
|
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
|
.describe(OIDC_AUTH.UPDATE.accessTokenTrustedIps),
|
|
accessTokenTTL: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(315360000)
|
|
.default(2592000)
|
|
.describe(OIDC_AUTH.UPDATE.accessTokenTTL),
|
|
accessTokenMaxTTL: z
|
|
.number()
|
|
.int()
|
|
.min(0)
|
|
.max(315360000)
|
|
.default(2592000)
|
|
.describe(OIDC_AUTH.UPDATE.accessTokenMaxTTL),
|
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.UPDATE.accessTokenNumUsesLimit)
|
|
})
|
|
.partial()
|
|
.refine(
|
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
|
),
|
|
response: {
|
|
200: z.object({
|
|
identityOidcAuth: IdentityOidcAuthResponseSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const identityOidcAuth = await server.services.identityOidcAuth.updateOidcAuth({
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorOrgId: req.permission.orgId,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
...req.body,
|
|
identityId: req.params.identityId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
orgId: identityOidcAuth.orgId,
|
|
event: {
|
|
type: EventType.UPDATE_IDENTITY_OIDC_AUTH,
|
|
metadata: {
|
|
identityId: identityOidcAuth.identityId,
|
|
oidcDiscoveryUrl: identityOidcAuth.oidcDiscoveryUrl,
|
|
caCert: identityOidcAuth.caCert,
|
|
boundIssuer: identityOidcAuth.boundIssuer,
|
|
boundAudiences: identityOidcAuth.boundAudiences,
|
|
boundClaims: identityOidcAuth.boundClaims as Record<string, string>,
|
|
claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record<string, string>,
|
|
boundSubject: identityOidcAuth.boundSubject as string,
|
|
accessTokenTTL: identityOidcAuth.accessTokenTTL,
|
|
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
|
|
accessTokenTrustedIps: identityOidcAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
|
accessTokenNumUsesLimit: identityOidcAuth.accessTokenNumUsesLimit
|
|
}
|
|
}
|
|
});
|
|
|
|
return { identityOidcAuth };
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "GET",
|
|
url: "/oidc-auth/identities/:identityId",
|
|
config: {
|
|
rateLimit: readLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
description: "Retrieve OIDC Auth configuration on identity",
|
|
security: [
|
|
{
|
|
bearerAuth: []
|
|
}
|
|
],
|
|
params: z.object({
|
|
identityId: z.string().describe(OIDC_AUTH.RETRIEVE.identityId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
identityOidcAuth: IdentityOidcAuthResponseSchema
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const identityOidcAuth = await server.services.identityOidcAuth.getOidcAuth({
|
|
identityId: req.params.identityId,
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorOrgId: req.permission.orgId,
|
|
actorAuthMethod: req.permission.authMethod
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
orgId: identityOidcAuth.orgId,
|
|
event: {
|
|
type: EventType.GET_IDENTITY_OIDC_AUTH,
|
|
metadata: {
|
|
identityId: identityOidcAuth.identityId
|
|
}
|
|
}
|
|
});
|
|
|
|
return { identityOidcAuth };
|
|
}
|
|
});
|
|
|
|
server.route({
|
|
method: "DELETE",
|
|
url: "/oidc-auth/identities/:identityId",
|
|
config: {
|
|
rateLimit: writeLimit
|
|
},
|
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
schema: {
|
|
description: "Delete OIDC Auth configuration on identity",
|
|
security: [
|
|
{
|
|
bearerAuth: []
|
|
}
|
|
],
|
|
params: z.object({
|
|
identityId: z.string().describe(OIDC_AUTH.REVOKE.identityId)
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
identityOidcAuth: IdentityOidcAuthResponseSchema.omit({
|
|
caCert: true
|
|
})
|
|
})
|
|
}
|
|
},
|
|
handler: async (req) => {
|
|
const identityOidcAuth = await server.services.identityOidcAuth.revokeOidcAuth({
|
|
actor: req.permission.type,
|
|
actorId: req.permission.id,
|
|
actorAuthMethod: req.permission.authMethod,
|
|
actorOrgId: req.permission.orgId,
|
|
identityId: req.params.identityId
|
|
});
|
|
|
|
await server.services.auditLog.createAuditLog({
|
|
...req.auditLogInfo,
|
|
orgId: identityOidcAuth.orgId,
|
|
event: {
|
|
type: EventType.REVOKE_IDENTITY_OIDC_AUTH,
|
|
metadata: {
|
|
identityId: identityOidcAuth.identityId
|
|
}
|
|
}
|
|
});
|
|
|
|
return { identityOidcAuth };
|
|
}
|
|
});
|
|
};
|