diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index ff3268ab5..9630429b8 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -99,6 +99,7 @@ import { TIdentityGcpAuthsInsert, TIdentityGcpAuthsUpdate, TIdentityJwtAuths, + TIdentityJwtAuthsInsert, TIdentityJwtAuthsUpdate, TIdentityKubernetesAuths, TIdentityKubernetesAuthsInsert, diff --git a/backend/src/server/routes/v1/identity-jwt-auth-router.ts b/backend/src/server/routes/v1/identity-jwt-auth-router.ts index 3a7c0a69c..d60bb969d 100644 --- a/backend/src/server/routes/v1/identity-jwt-auth-router.ts +++ b/backend/src/server/routes/v1/identity-jwt-auth-router.ts @@ -94,17 +94,17 @@ const UpdateBaseSchema = z .partial(); const JwksConfigurationSchema = z.object({ - configurationType: z.literal(JwtConfigurationType.JWKS), - jwksUrl: z.string().trim().url(), - jwksCaCert: z.string().trim().default(""), - publicKeys: z.string().array().optional().default([]) + configurationType: z.literal(JwtConfigurationType.JWKS).describe(JWT_AUTH.ATTACH.configurationType), + jwksUrl: z.string().trim().url().describe(JWT_AUTH.ATTACH.jwksUrl), + jwksCaCert: z.string().trim().default("").describe(JWT_AUTH.ATTACH.jwksCaCert), + publicKeys: z.string().array().optional().default([]).describe(JWT_AUTH.ATTACH.publicKeys) }); const StaticConfigurationSchema = z.object({ - configurationType: z.literal(JwtConfigurationType.STATIC), - jwksUrl: z.string().trim().optional().default(""), - jwksCaCert: z.string().trim().optional().default(""), - publicKeys: z.string().min(1).array().min(1) + configurationType: z.literal(JwtConfigurationType.STATIC).describe(JWT_AUTH.ATTACH.configurationType), + jwksUrl: z.string().trim().optional().default("").describe(JWT_AUTH.ATTACH.jwksUrl), + jwksCaCert: z.string().trim().optional().default("").describe(JWT_AUTH.ATTACH.jwksCaCert), + publicKeys: z.string().min(1).array().min(1).describe(JWT_AUTH.ATTACH.publicKeys) }); export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-fns.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-fns.ts index bcbff5f0e..40fc87787 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-fns.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-fns.ts @@ -1,4 +1,9 @@ import picomatch from "picomatch"; -export const doesFieldValueMatchJwtPolicy = (fieldValue: string, policyValue: string) => - policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue); +export const doesFieldValueMatchJwtPolicy = (fieldValue: string | boolean, policyValue: string) => { + if (typeof fieldValue === "boolean") { + return fieldValue === (policyValue === "true"); + } + + return policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue); +}; diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts index aca96c145..35c40643c 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts @@ -75,7 +75,7 @@ export const identityJwtAuthServiceFactory = ({ }); } - let tokenData: Record = {}; + let tokenData: Record = {}; if (identityJwtAuth.configurationType === JwtConfigurationType.JWKS) { const decryptedJwksCaCert = orgDataKeyDecryptor({ @@ -127,13 +127,7 @@ export const identityJwtAuthServiceFactory = ({ } if (identityJwtAuth.boundIssuer) { - if (!tokenData.iss) { - throw new UnauthorizedError({ - message: "Access denied: token has no issuer field" - }); - } - - if (!doesFieldValueMatchJwtPolicy(tokenData.iss, identityJwtAuth.boundIssuer)) { + if (tokenData.iss !== identityJwtAuth.boundIssuer) { throw new ForbiddenRequestError({ message: "Access denied: issuer mismatch" }); diff --git a/docs/api-reference/endpoints/jwt-auth/attach.mdx b/docs/api-reference/endpoints/jwt-auth/attach.mdx new file mode 100644 index 000000000..f2905f1a0 --- /dev/null +++ b/docs/api-reference/endpoints/jwt-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/jwt-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/jwt-auth/login.mdx b/docs/api-reference/endpoints/jwt-auth/login.mdx new file mode 100644 index 000000000..c037fbf7f --- /dev/null +++ b/docs/api-reference/endpoints/jwt-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/jwt-auth/login" +--- diff --git a/docs/api-reference/endpoints/jwt-auth/retrieve.mdx b/docs/api-reference/endpoints/jwt-auth/retrieve.mdx new file mode 100644 index 000000000..8100ef843 --- /dev/null +++ b/docs/api-reference/endpoints/jwt-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/jwt-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/jwt-auth/revoke.mdx b/docs/api-reference/endpoints/jwt-auth/revoke.mdx new file mode 100644 index 000000000..13a61475a --- /dev/null +++ b/docs/api-reference/endpoints/jwt-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/jwt-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/jwt-auth/update.mdx b/docs/api-reference/endpoints/jwt-auth/update.mdx new file mode 100644 index 000000000..8a53907ab --- /dev/null +++ b/docs/api-reference/endpoints/jwt-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/jwt-auth/identities/{identityId}" +--- diff --git a/docs/mint.json b/docs/mint.json index c5f07b12e..030a0b4dc 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -583,6 +583,16 @@ "api-reference/endpoints/oidc-auth/revoke" ] }, + { + "group": "JWT Auth", + "pages": [ + "api-reference/endpoints/jwt-auth/login", + "api-reference/endpoints/jwt-auth/attach", + "api-reference/endpoints/jwt-auth/retrieve", + "api-reference/endpoints/jwt-auth/update", + "api-reference/endpoints/jwt-auth/revoke" + ] + }, { "group": "Groups", "pages": [