From 899757ab7c8f70f5ae3bb868815f919e642fd2ce Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 10 Jul 2024 13:30:06 +0800 Subject: [PATCH 1/5] doc: added native auth to api reference --- .../endpoints/aws-auth/attach.mdx | 4 ++ .../endpoints/aws-auth/login.mdx | 4 ++ .../endpoints/aws-auth/retrieve.mdx | 4 ++ .../endpoints/aws-auth/revoke.mdx | 4 ++ .../endpoints/aws-auth/update.mdx | 4 ++ .../endpoints/azure-auth/attach.mdx | 4 ++ .../endpoints/azure-auth/login.mdx | 4 ++ .../endpoints/azure-auth/retrieve.mdx | 4 ++ .../endpoints/azure-auth/revoke.mdx | 4 ++ .../endpoints/azure-auth/update.mdx | 4 ++ .../endpoints/gcp-auth/attach.mdx | 4 ++ .../endpoints/gcp-auth/login.mdx | 4 ++ .../endpoints/gcp-auth/retrieve.mdx | 4 ++ .../endpoints/gcp-auth/revoke.mdx | 4 ++ .../endpoints/gcp-auth/update.mdx | 4 ++ .../endpoints/kubernetes-auth/attach.mdx | 4 ++ .../endpoints/kubernetes-auth/login.mdx | 4 ++ .../endpoints/kubernetes-auth/retrieve.mdx | 4 ++ .../endpoints/kubernetes-auth/revoke.mdx | 4 ++ .../endpoints/kubernetes-auth/update.mdx | 4 ++ docs/mint.json | 40 +++++++++++++++++++ 21 files changed, 120 insertions(+) create mode 100644 docs/api-reference/endpoints/aws-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/aws-auth/login.mdx create mode 100644 docs/api-reference/endpoints/aws-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/aws-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/aws-auth/update.mdx create mode 100644 docs/api-reference/endpoints/azure-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/azure-auth/login.mdx create mode 100644 docs/api-reference/endpoints/azure-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/azure-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/azure-auth/update.mdx create mode 100644 docs/api-reference/endpoints/gcp-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/gcp-auth/login.mdx create mode 100644 docs/api-reference/endpoints/gcp-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/gcp-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/gcp-auth/update.mdx create mode 100644 docs/api-reference/endpoints/kubernetes-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/kubernetes-auth/login.mdx create mode 100644 docs/api-reference/endpoints/kubernetes-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/kubernetes-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/kubernetes-auth/update.mdx diff --git a/docs/api-reference/endpoints/aws-auth/attach.mdx b/docs/api-reference/endpoints/aws-auth/attach.mdx new file mode 100644 index 000000000..85b2d6819 --- /dev/null +++ b/docs/api-reference/endpoints/aws-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/aws-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/aws-auth/login.mdx b/docs/api-reference/endpoints/aws-auth/login.mdx new file mode 100644 index 000000000..751903be6 --- /dev/null +++ b/docs/api-reference/endpoints/aws-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/aws-auth/login" +--- diff --git a/docs/api-reference/endpoints/aws-auth/retrieve.mdx b/docs/api-reference/endpoints/aws-auth/retrieve.mdx new file mode 100644 index 000000000..4b1980dd2 --- /dev/null +++ b/docs/api-reference/endpoints/aws-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/aws-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/aws-auth/revoke.mdx b/docs/api-reference/endpoints/aws-auth/revoke.mdx new file mode 100644 index 000000000..4d19fa95f --- /dev/null +++ b/docs/api-reference/endpoints/aws-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/aws-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/aws-auth/update.mdx b/docs/api-reference/endpoints/aws-auth/update.mdx new file mode 100644 index 000000000..949d2a901 --- /dev/null +++ b/docs/api-reference/endpoints/aws-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/aws-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/azure-auth/attach.mdx b/docs/api-reference/endpoints/azure-auth/attach.mdx new file mode 100644 index 000000000..cafaf6df1 --- /dev/null +++ b/docs/api-reference/endpoints/azure-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/azure-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/azure-auth/login.mdx b/docs/api-reference/endpoints/azure-auth/login.mdx new file mode 100644 index 000000000..c9e51d51b --- /dev/null +++ b/docs/api-reference/endpoints/azure-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/azure-auth/login" +--- diff --git a/docs/api-reference/endpoints/azure-auth/retrieve.mdx b/docs/api-reference/endpoints/azure-auth/retrieve.mdx new file mode 100644 index 000000000..b07ee2ad4 --- /dev/null +++ b/docs/api-reference/endpoints/azure-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/azure-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/azure-auth/revoke.mdx b/docs/api-reference/endpoints/azure-auth/revoke.mdx new file mode 100644 index 000000000..7254bdd8b --- /dev/null +++ b/docs/api-reference/endpoints/azure-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/azure-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/azure-auth/update.mdx b/docs/api-reference/endpoints/azure-auth/update.mdx new file mode 100644 index 000000000..ee830a1f3 --- /dev/null +++ b/docs/api-reference/endpoints/azure-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/azure-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/gcp-auth/attach.mdx b/docs/api-reference/endpoints/gcp-auth/attach.mdx new file mode 100644 index 000000000..04bc754f5 --- /dev/null +++ b/docs/api-reference/endpoints/gcp-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/gcp-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/gcp-auth/login.mdx b/docs/api-reference/endpoints/gcp-auth/login.mdx new file mode 100644 index 000000000..ff5caf2ac --- /dev/null +++ b/docs/api-reference/endpoints/gcp-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/gcp-auth/login" +--- diff --git a/docs/api-reference/endpoints/gcp-auth/retrieve.mdx b/docs/api-reference/endpoints/gcp-auth/retrieve.mdx new file mode 100644 index 000000000..48124f2b9 --- /dev/null +++ b/docs/api-reference/endpoints/gcp-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/gcp-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/gcp-auth/revoke.mdx b/docs/api-reference/endpoints/gcp-auth/revoke.mdx new file mode 100644 index 000000000..1e1b3f1a4 --- /dev/null +++ b/docs/api-reference/endpoints/gcp-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/gcp-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/gcp-auth/update.mdx b/docs/api-reference/endpoints/gcp-auth/update.mdx new file mode 100644 index 000000000..ee1e269d3 --- /dev/null +++ b/docs/api-reference/endpoints/gcp-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/gcp-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/kubernetes-auth/attach.mdx b/docs/api-reference/endpoints/kubernetes-auth/attach.mdx new file mode 100644 index 000000000..969bbd8a1 --- /dev/null +++ b/docs/api-reference/endpoints/kubernetes-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/kubernetes-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/kubernetes-auth/login.mdx b/docs/api-reference/endpoints/kubernetes-auth/login.mdx new file mode 100644 index 000000000..0b6539be5 --- /dev/null +++ b/docs/api-reference/endpoints/kubernetes-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/kubernetes-auth/login" +--- diff --git a/docs/api-reference/endpoints/kubernetes-auth/retrieve.mdx b/docs/api-reference/endpoints/kubernetes-auth/retrieve.mdx new file mode 100644 index 000000000..ca7b30d10 --- /dev/null +++ b/docs/api-reference/endpoints/kubernetes-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/kubernetes-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/kubernetes-auth/revoke.mdx b/docs/api-reference/endpoints/kubernetes-auth/revoke.mdx new file mode 100644 index 000000000..94c11cf73 --- /dev/null +++ b/docs/api-reference/endpoints/kubernetes-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/kubernetes-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/kubernetes-auth/update.mdx b/docs/api-reference/endpoints/kubernetes-auth/update.mdx new file mode 100644 index 000000000..8eb173ac8 --- /dev/null +++ b/docs/api-reference/endpoints/kubernetes-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/kubernetes-auth/identities/{identityId}" +--- diff --git a/docs/mint.json b/docs/mint.json index aac76b99b..bca6e594a 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -443,6 +443,46 @@ "api-reference/endpoints/universal-auth/revoke-access-token" ] }, + { + "group": "GCP Auth", + "pages": [ + "api-reference/endpoints/gcp-auth/login", + "api-reference/endpoints/gcp-auth/attach", + "api-reference/endpoints/gcp-auth/retrieve", + "api-reference/endpoints/gcp-auth/update", + "api-reference/endpoints/gcp-auth/revoke" + ] + }, + { + "group": "AWS Auth", + "pages": [ + "api-reference/endpoints/aws-auth/login", + "api-reference/endpoints/aws-auth/attach", + "api-reference/endpoints/aws-auth/retrieve", + "api-reference/endpoints/aws-auth/update", + "api-reference/endpoints/aws-auth/revoke" + ] + }, + { + "group": "Azure Auth", + "pages": [ + "api-reference/endpoints/azure-auth/login", + "api-reference/endpoints/azure-auth/attach", + "api-reference/endpoints/azure-auth/retrieve", + "api-reference/endpoints/azure-auth/update", + "api-reference/endpoints/azure-auth/revoke" + ] + }, + { + "group": "Kubernetes Auth", + "pages": [ + "api-reference/endpoints/kubernetes-auth/login", + "api-reference/endpoints/kubernetes-auth/attach", + "api-reference/endpoints/kubernetes-auth/retrieve", + "api-reference/endpoints/kubernetes-auth/update", + "api-reference/endpoints/kubernetes-auth/revoke" + ] + }, { "group": "Organizations", "pages": [ From 4fab746b9578238720f87489d943aeb60e8f61ff Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 10 Jul 2024 15:17:23 +0800 Subject: [PATCH 2/5] misc: added description to native auth properties --- backend/src/lib/api-docs/constants.ts | 125 ++++++++++++++++++ .../routes/v1/identity-aws-iam-auth-router.ts | 42 +++--- .../routes/v1/identity-azure-auth-router.ts | 41 +++--- .../routes/v1/identity-gcp-auth-router.ts | 39 +++--- .../v1/identity-kubernetes-auth-router.ts | 61 +++++---- 5 files changed, 235 insertions(+), 73 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index de0a1d4c2..85fa3f25d 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -119,24 +119,149 @@ export const AWS_AUTH = { "The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.", iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request." }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + allowedPrincipalArns: + "The comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.", + allowedAccountIds: + "The comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + stsEndpoint: "The endpoint URL for the AWS STS API.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from." + }, + UPDATE: { + identityId: "The ID of the identity to be updated.", + allowedPrincipalArns: + "The new comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.", + allowedAccountIds: + "The new comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + stsEndpoint: "The new endpoint URL for the AWS STS API.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve." + }, REVOKE: { identityId: "The ID of the identity to revoke." } } as const; export const AZURE_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + tenantId: "The tenant ID for the Azure AD organization.", + resource: "The resource URL for the application registered in Azure AD.", + allowedServicePrincipalIds: + "The comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." + }, + UPDATE: { + identityId: "The new ID of the identity to be updated.", + tenantId: "The new tenant ID for the Azure AD organization.", + resource: "The new resource URL for the application registered in Azure AD.", + allowedServicePrincipalIds: + "The new comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve." + }, REVOKE: { identityId: "The ID of the identity to revoke." } } as const; export const GCP_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + allowedServiceAccounts: + "The comma-separated list of trusted service account emails corresponding to the GCE resource(s) allowed to authenticate with Infisical.", + allowedProjects: + "The comma-separated list of trusted GCP projects that the GCE instance must belong to authenticate with Infisical.", + allowedZones: + "The comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." + }, + UPDATE: { + identityId: "The ID of the identity to be updated.", + allowedServiceAccounts: + "The new comma-separated list of trusted service account emails corresponding to the GCE resource(s) allowed to authenticate with Infisical.", + allowedProjects: + "The new comma-separated list of trusted GCP projects that the GCE instance must belong to authenticate with Infisical.", + allowedZones: + "The new comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve." + }, REVOKE: { identityId: "The ID of the identity to revoke." } } as const; export const KUBERNETES_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.", + caCert: "The PEM-encoded CA cert for the Kubernetes API server.", + tokenReviewerJwt: + "The long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.", + allowedNamespaces: + "The comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.", + allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.", + allowedAudience: + "The optional audience claim that the service account JWT token must have to authenticate with Infisical.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." + }, + UPDATE: { + identityId: "The ID of the identity to be updated.", + kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.", + caCert: "The new PEM-encoded CA cert for the Kubernetes API server.", + tokenReviewerJwt: + "The new long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.", + allowedNamespaces: + "The new comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.", + allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.", + allowedAudience: + "The new optional audience claim that the service account JWT token must have to authenticate with Infisical.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve." + }, REVOKE: { identityId: "The ID of the identity to revoke." } diff --git a/backend/src/server/routes/v1/identity-aws-iam-auth-router.ts b/backend/src/server/routes/v1/identity-aws-iam-auth-router.ts index 8a85323a6..575a6aafd 100644 --- a/backend/src/server/routes/v1/identity-aws-iam-auth-router.ts +++ b/backend/src/server/routes/v1/identity-aws-iam-auth-router.ts @@ -77,19 +77,25 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(AWS_AUTH.ATTACH.identityId) }), body: z.object({ - stsEndpoint: z.string().trim().min(1).default("https://sts.amazonaws.com/"), - allowedPrincipalArns: validatePrincipalArns, - allowedAccountIds: validateAccountIds, + stsEndpoint: z + .string() + .trim() + .min(1) + .default("https://sts.amazonaws.com/") + .describe(AWS_AUTH.ATTACH.stsEndpoint), + allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.ATTACH.allowedPrincipalArns), + allowedAccountIds: validateAccountIds.describe(AWS_AUTH.ATTACH.allowedAccountIds), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(AWS_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -97,15 +103,17 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(AWS_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default(2592000) + .describe(AWS_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(AWS_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -160,21 +168,22 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(AWS_AUTH.UPDATE.identityId) }), body: z.object({ - stsEndpoint: z.string().trim().min(1).optional(), - allowedPrincipalArns: validatePrincipalArns, - allowedAccountIds: validateAccountIds, + stsEndpoint: z.string().trim().min(1).optional().describe(AWS_AUTH.UPDATE.stsEndpoint), + allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.UPDATE.allowedPrincipalArns), + allowedAccountIds: validateAccountIds.describe(AWS_AUTH.UPDATE.allowedAccountIds), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), + .optional() + .describe(AWS_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit), accessTokenMaxTTL: z .number() .int() @@ -182,6 +191,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) message: "accessTokenMaxTTL must have a non zero number" }) .optional() + .describe(AWS_AUTH.UPDATE.accessTokenMaxTTL) }), response: { 200: z.object({ @@ -236,7 +246,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(AWS_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/identity-azure-auth-router.ts b/backend/src/server/routes/v1/identity-azure-auth-router.ts index 6b4a7fb37..dbde2d9a7 100644 --- a/backend/src/server/routes/v1/identity-azure-auth-router.ts +++ b/backend/src/server/routes/v1/identity-azure-auth-router.ts @@ -19,7 +19,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider schema: { description: "Login with Azure Auth", body: z.object({ - identityId: z.string(), + identityId: z.string().describe(AZURE_AUTH.LOGIN.identityId), jwt: z.string() }), response: { @@ -72,19 +72,20 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId) }), body: z.object({ - tenantId: z.string().trim(), - resource: z.string().trim(), - allowedServicePrincipalIds: validateAzureAuthField, + tenantId: z.string().trim().describe(AZURE_AUTH.ATTACH.tenantId), + resource: z.string().trim().describe(AZURE_AUTH.ATTACH.resource), + allowedServicePrincipalIds: validateAzureAuthField.describe(AZURE_AUTH.ATTACH.allowedServicePrincipalIds), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(AZURE_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -92,15 +93,17 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(AZURE_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default(2592000) + .describe(AZURE_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(AZURE_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -154,21 +157,24 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(AZURE_AUTH.UPDATE.identityId) }), body: z.object({ - tenantId: z.string().trim().optional(), - resource: z.string().trim().optional(), - allowedServicePrincipalIds: validateAzureAuthField.optional(), + tenantId: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.tenantId), + resource: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.resource), + allowedServicePrincipalIds: validateAzureAuthField + .optional() + .describe(AZURE_AUTH.UPDATE.allowedServicePrincipalIds), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), + .optional() + .describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit), accessTokenMaxTTL: z .number() .int() @@ -176,6 +182,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider message: "accessTokenMaxTTL must have a non zero number" }) .optional() + .describe(AZURE_AUTH.UPDATE.accessTokenMaxTTL) }), response: { 200: z.object({ @@ -229,7 +236,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(AZURE_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/identity-gcp-auth-router.ts b/backend/src/server/routes/v1/identity-gcp-auth-router.ts index 0deeb95d3..6f10ba9f5 100644 --- a/backend/src/server/routes/v1/identity-gcp-auth-router.ts +++ b/backend/src/server/routes/v1/identity-gcp-auth-router.ts @@ -19,7 +19,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) schema: { description: "Login with GCP Auth", body: z.object({ - identityId: z.string(), + identityId: z.string().describe(GCP_AUTH.LOGIN.identityId), jwt: z.string() }), response: { @@ -72,20 +72,21 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(GCP_AUTH.ATTACH.identityId) }), body: z.object({ type: z.enum(["iam", "gce"]), - allowedServiceAccounts: validateGcpAuthField, - allowedProjects: validateGcpAuthField, - allowedZones: validateGcpAuthField, + allowedServiceAccounts: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedServiceAccounts), + allowedProjects: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedProjects), + allowedZones: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedZones), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(GCP_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -93,15 +94,17 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(GCP_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default(2592000) + .describe(GCP_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(GCP_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -157,22 +160,23 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(GCP_AUTH.UPDATE.identityId) }), body: z.object({ type: z.enum(["iam", "gce"]).optional(), - allowedServiceAccounts: validateGcpAuthField.optional(), - allowedProjects: validateGcpAuthField.optional(), - allowedZones: validateGcpAuthField.optional(), + allowedServiceAccounts: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedServiceAccounts), + allowedProjects: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedProjects), + allowedZones: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedZones), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), + .optional() + .describe(GCP_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit), accessTokenMaxTTL: z .number() .int() @@ -180,6 +184,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) message: "accessTokenMaxTTL must have a non zero number" }) .optional() + .describe(GCP_AUTH.UPDATE.accessTokenMaxTTL) }), response: { 200: z.object({ @@ -235,7 +240,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(GCP_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 4c54f1e7c..d69f028a1 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -30,7 +30,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide schema: { description: "Login with Kubernetes Auth", body: z.object({ - identityId: z.string().trim(), + identityId: z.string().trim().describe(KUBERNETES_AUTH.LOGIN.identityId), jwt: z.string().trim() }), response: { @@ -85,22 +85,23 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(KUBERNETES_AUTH.ATTACH.identityId) }), body: z.object({ - kubernetesHost: z.string().trim().min(1), - caCert: z.string().trim().default(""), - tokenReviewerJwt: z.string().trim().min(1), - allowedNamespaces: z.string(), // TODO: validation - allowedNames: z.string(), - allowedAudience: z.string(), + kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost), + caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert), + tokenReviewerJwt: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt), + allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation + allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames), + allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(KUBERNETES_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -108,15 +109,22 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(KUBERNETES_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default(2592000) + .describe(KUBERNETES_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z + .number() + .int() + .min(0) + .default(0) + .describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -171,24 +179,30 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(KUBERNETES_AUTH.UPDATE.identityId) }), body: z.object({ - kubernetesHost: z.string().trim().min(1).optional(), - caCert: z.string().trim().optional(), - tokenReviewerJwt: z.string().trim().min(1).optional(), - allowedNamespaces: z.string().optional(), // TODO: validation - allowedNames: z.string().optional(), - allowedAudience: z.string().optional(), + kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost), + caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert), + tokenReviewerJwt: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt), + allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation + allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames), + allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), + .optional() + .describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).optional().describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z + .number() + .int() + .min(0) + .optional() + .describe(KUBERNETES_AUTH.UPDATE.accessTokenNumUsesLimit), accessTokenMaxTTL: z .number() .int() @@ -196,6 +210,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide message: "accessTokenMaxTTL must have a non zero number" }) .optional() + .describe(KUBERNETES_AUTH.UPDATE.accessTokenMaxTTL) }), response: { 200: z.object({ @@ -250,7 +265,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(KUBERNETES_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ From 2d9435457d9a4f7f8c45971772033c8980ce9e4b Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 10 Jul 2024 18:58:42 +0800 Subject: [PATCH 3/5] misc: addressed typo --- backend/src/lib/api-docs/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 85fa3f25d..1d11d1e59 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -167,7 +167,7 @@ export const AZURE_AUTH = { accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." }, UPDATE: { - identityId: "The new ID of the identity to be updated.", + identityId: "The ID of the identity to be updated.", tenantId: "The new tenant ID for the Azure AD organization.", resource: "The new resource URL for the application registered in Azure AD.", allowedServicePrincipalIds: From 8fdd82a335bc2d7c4a7614a81b5785b9f095d02e Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 10 Jul 2024 23:37:36 +0700 Subject: [PATCH 4/5] Add token auth to api reference --- backend/src/lib/api-docs/constants.ts | 69 +++++++++++++++---- .../routes/v1/identity-token-auth-router.ts | 61 ++++++++-------- .../endpoints/token-auth/attach.mdx | 4 ++ .../endpoints/token-auth/create-token.mdx | 4 ++ .../endpoints/token-auth/get-tokens.mdx | 4 ++ .../endpoints/token-auth/retrieve.mdx | 4 ++ .../endpoints/token-auth/revoke-token.mdx | 4 ++ .../endpoints/token-auth/revoke.mdx | 4 ++ .../endpoints/token-auth/update-token.mdx | 4 ++ .../endpoints/token-auth/update.mdx | 4 ++ docs/mint.json | 13 ++++ 11 files changed, 128 insertions(+), 47 deletions(-) create mode 100644 docs/api-reference/endpoints/token-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/token-auth/create-token.mdx create mode 100644 docs/api-reference/endpoints/token-auth/get-tokens.mdx create mode 100644 docs/api-reference/endpoints/token-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/token-auth/revoke-token.mdx create mode 100644 docs/api-reference/endpoints/token-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/token-auth/update-token.mdx create mode 100644 docs/api-reference/endpoints/token-auth/update.mdx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 1d11d1e59..a7c1b4c3b 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -70,13 +70,13 @@ export const UNIVERSAL_AUTH = { "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses." }, RETRIEVE: { - identityId: "The ID of the identity to retrieve." + identityId: "The ID of the identity to retrieve the auth method for." }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." }, UPDATE: { - identityId: "The ID of the identity to update.", + identityId: "The ID of the identity to update the auth method for.", clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.", accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.", accessTokenTTL: "The new lifetime for an access token in seconds.", @@ -132,7 +132,7 @@ export const AWS_AUTH = { accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from." }, UPDATE: { - identityId: "The ID of the identity to be updated.", + identityId: "The ID of the identity to update the auth method for.", allowedPrincipalArns: "The new comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.", allowedAccountIds: @@ -144,10 +144,10 @@ export const AWS_AUTH = { accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from." }, RETRIEVE: { - identityId: "The ID of the identity to retrieve." + identityId: "The ID of the identity to retrieve the auth method for." }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." } } as const; @@ -167,7 +167,7 @@ export const AZURE_AUTH = { accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." }, UPDATE: { - identityId: "The ID of the identity to be updated.", + identityId: "The ID of the identity to update the auth method for.", tenantId: "The new tenant ID for the Azure AD organization.", resource: "The new resource URL for the application registered in Azure AD.", allowedServicePrincipalIds: @@ -178,10 +178,10 @@ export const AZURE_AUTH = { accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." }, RETRIEVE: { - identityId: "The ID of the identity to retrieve." + identityId: "The ID of the identity to retrieve the auth method for." }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." } } as const; @@ -203,7 +203,7 @@ export const GCP_AUTH = { accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." }, UPDATE: { - identityId: "The ID of the identity to be updated.", + identityId: "The ID of the identity to update the auth method for.", allowedServiceAccounts: "The new comma-separated list of trusted service account emails corresponding to the GCE resource(s) allowed to authenticate with Infisical.", allowedProjects: @@ -216,10 +216,10 @@ export const GCP_AUTH = { accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." }, RETRIEVE: { - identityId: "The ID of the identity to retrieve." + identityId: "The ID of the identity to retrieve the auth method for." }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." } } as const; @@ -244,7 +244,7 @@ export const KUBERNETES_AUTH = { accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." }, UPDATE: { - identityId: "The ID of the identity to be updated.", + identityId: "The ID of the identity to update the auth method for.", kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.", caCert: "The new PEM-encoded CA cert for the Kubernetes API server.", tokenReviewerJwt: @@ -260,10 +260,49 @@ export const KUBERNETES_AUTH = { accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." }, RETRIEVE: { - identityId: "The ID of the identity to retrieve." + identityId: "The ID of the identity to retrieve the auth method for." }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." + } +} as const; + +export const TOKEN_AUTH = { + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." + }, + UPDATE: { + identityId: "The ID of the identity to update the auth method for.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve the auth method for." + }, + REVOKE: { + identityId: "The ID of the identity to revoke the auth method for." + }, + GET_TOKENS: { + identityId: "The ID of the identity to list token metadata for.", + offset: "The offset to start from. If you enter 10, it will start from the 10th token.", + limit: "The number of tokens to return" + }, + CREATE_TOKEN: { + identityId: "The ID of the identity to create the token for.", + name: "The name of the token to create" + }, + UPDATE_TOKEN: { + tokenId: "The ID of the token to update metadata for", + name: "The name of the token to update to" + }, + REVOKE_TOKEN: { + tokenId: "The ID of the token to revoke" } } as const; diff --git a/backend/src/server/routes/v1/identity-token-auth-router.ts b/backend/src/server/routes/v1/identity-token-auth-router.ts index ac36c3c58..752447538 100644 --- a/backend/src/server/routes/v1/identity-token-auth-router.ts +++ b/backend/src/server/routes/v1/identity-token-auth-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { IdentityAccessTokensSchema, IdentityTokenAuthsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { TOKEN_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"; @@ -23,7 +24,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(TOKEN_AUTH.ATTACH.identityId) }), body: z.object({ accessTokenTrustedIps: z @@ -32,7 +33,8 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }) .array() .min(1) - .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(TOKEN_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -40,15 +42,17 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(TOKEN_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0) + .default(2592000) + .describe(TOKEN_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(TOKEN_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -102,7 +106,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string().trim() + identityId: z.string().trim().describe(TOKEN_AUTH.UPDATE.identityId) }), body: z.object({ accessTokenTrustedIps: z @@ -111,9 +115,10 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }) .array() .min(1) - .optional(), - accessTokenTTL: z.number().int().min(0).optional(), - accessTokenNumUsesLimit: z.number().int().min(0).optional(), + .optional() + .describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps), + accessTokenTTL: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL), + accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit), accessTokenMaxTTL: z .number() .int() @@ -121,6 +126,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider message: "accessTokenMaxTTL must have a non zero number" }) .optional() + .describe(TOKEN_AUTH.UPDATE.accessTokenMaxTTL) }), response: { 200: z.object({ @@ -174,7 +180,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(TOKEN_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ @@ -221,7 +227,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(TOKEN_AUTH.REVOKE.identityId) }), response: { 200: z.object({ @@ -253,15 +259,6 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider } }); - // proposed - // update token by id: PATCH /token-auth/tokens/:tokenId - // revoke token by id: POST /token-auth/tokens/:tokenId/revoke - - // current - // revoke token by id: POST /token/revoke-by-id - - // token-auth/identities/:identityId/tokens - server.route({ method: "POST", url: "/token-auth/identities/:identityId/tokens", @@ -270,17 +267,17 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Create token for identity with Token Auth configured", + description: "Create token for identity with Token Auth", security: [ { bearerAuth: [] } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(TOKEN_AUTH.CREATE_TOKEN.identityId) }), body: z.object({ - name: z.string().optional() + name: z.string().optional().describe(TOKEN_AUTH.CREATE_TOKEN.name) }), response: { 200: z.object({ @@ -331,18 +328,18 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Get tokens for identity with Token Auth configured", + description: "Get tokens for identity with Token Auth", security: [ { bearerAuth: [] } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(TOKEN_AUTH.GET_TOKENS.identityId) }), querystring: z.object({ - offset: z.coerce.number().min(0).max(100).default(0), - limit: z.coerce.number().min(1).max(100).default(20) + offset: z.coerce.number().min(0).max(100).default(0).describe(TOKEN_AUTH.GET_TOKENS.offset), + limit: z.coerce.number().min(1).max(100).default(20).describe(TOKEN_AUTH.GET_TOKENS.limit) }), response: { 200: z.object({ @@ -383,17 +380,17 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Update token for identity with Token Auth configured", + description: "Update token for identity with Token Auth", security: [ { bearerAuth: [] } ], params: z.object({ - tokenId: z.string() + tokenId: z.string().describe(TOKEN_AUTH.UPDATE_TOKEN.tokenId) }), body: z.object({ - name: z.string().optional() + name: z.string().optional().describe(TOKEN_AUTH.UPDATE_TOKEN.name) }), response: { 200: z.object({ @@ -436,14 +433,14 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Revoke token for identity with Token Auth configured", + description: "Revoke token for identity with Token Auth", security: [ { bearerAuth: [] } ], params: z.object({ - tokenId: z.string() + tokenId: z.string().describe(TOKEN_AUTH.REVOKE_TOKEN.tokenId) }), response: { 200: z.object({ diff --git a/docs/api-reference/endpoints/token-auth/attach.mdx b/docs/api-reference/endpoints/token-auth/attach.mdx new file mode 100644 index 000000000..a53855db3 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/token-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/token-auth/create-token.mdx b/docs/api-reference/endpoints/token-auth/create-token.mdx new file mode 100644 index 000000000..e147a4e12 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/create-token.mdx @@ -0,0 +1,4 @@ +--- +title: "Create Token" +openapi: "POST /api/v1/auth/token-auth/identities/{identityId}/tokens" +--- diff --git a/docs/api-reference/endpoints/token-auth/get-tokens.mdx b/docs/api-reference/endpoints/token-auth/get-tokens.mdx new file mode 100644 index 000000000..5f690a298 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/get-tokens.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Tokens" +openapi: "GET /api/v1/auth/token-auth/identities/{identityId}/tokens" +--- diff --git a/docs/api-reference/endpoints/token-auth/retrieve.mdx b/docs/api-reference/endpoints/token-auth/retrieve.mdx new file mode 100644 index 000000000..ed6bb762c --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/token-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/token-auth/revoke-token.mdx b/docs/api-reference/endpoints/token-auth/revoke-token.mdx new file mode 100644 index 000000000..ca6a0f056 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/revoke-token.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke Token" +openapi: "POST /api/v1/auth/token-auth/tokens/{tokenId}/revoke" +--- diff --git a/docs/api-reference/endpoints/token-auth/revoke.mdx b/docs/api-reference/endpoints/token-auth/revoke.mdx new file mode 100644 index 000000000..52e295fd0 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/token-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/token-auth/update-token.mdx b/docs/api-reference/endpoints/token-auth/update-token.mdx new file mode 100644 index 000000000..cf5e0f568 --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/update-token.mdx @@ -0,0 +1,4 @@ +--- +title: "Update Token" +openapi: "PATCH /api/v1/auth/token-auth/tokens/{tokenId}" +--- diff --git a/docs/api-reference/endpoints/token-auth/update.mdx b/docs/api-reference/endpoints/token-auth/update.mdx new file mode 100644 index 000000000..c9e332e8f --- /dev/null +++ b/docs/api-reference/endpoints/token-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/token-auth/identities/{identityId}" +--- diff --git a/docs/mint.json b/docs/mint.json index bca6e594a..5fc63dd12 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -427,6 +427,19 @@ "api-reference/endpoints/identities/list" ] }, + { + "group": "Token Auth", + "pages": [ + "api-reference/endpoints/token-auth/attach", + "api-reference/endpoints/token-auth/retrieve", + "api-reference/endpoints/token-auth/update", + "api-reference/endpoints/token-auth/revoke", + "api-reference/endpoints/token-auth/get-tokens", + "api-reference/endpoints/token-auth/create-token", + "api-reference/endpoints/token-auth/update-token", + "api-reference/endpoints/token-auth/revoke-token" + ] + }, { "group": "Universal Auth", "pages": [ From 17c7207f9d1e80d227805cbbc657a9df02fce2bc Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 11 Jul 2024 02:04:44 +0800 Subject: [PATCH 5/5] doc: added oidc auth api reference --- backend/src/lib/api-docs/constants.ts | 34 +++++++++++- .../routes/v1/identity-oidc-auth-router.ts | 55 +++++++++++-------- .../endpoints/oidc-auth/attach.mdx | 4 ++ .../endpoints/oidc-auth/login.mdx | 4 ++ .../endpoints/oidc-auth/retrieve.mdx | 4 ++ .../endpoints/oidc-auth/revoke.mdx | 4 ++ .../endpoints/oidc-auth/update.mdx | 4 ++ docs/mint.json | 10 ++++ 8 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 docs/api-reference/endpoints/oidc-auth/attach.mdx create mode 100644 docs/api-reference/endpoints/oidc-auth/login.mdx create mode 100644 docs/api-reference/endpoints/oidc-auth/retrieve.mdx create mode 100644 docs/api-reference/endpoints/oidc-auth/revoke.mdx create mode 100644 docs/api-reference/endpoints/oidc-auth/update.mdx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index a4fef17cd..188af00b7 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -307,8 +307,40 @@ export const TOKEN_AUTH = { } as const; export const OIDC_AUTH = { + LOGIN: { + identityId: "The ID of the identity to login." + }, + ATTACH: { + identityId: "The ID of the identity to attach the configuration onto.", + oidcDiscoveryUrl: "The URL used to retrieve the OpenID Connect configuration from the identity provider.", + caCert: "The PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.", + boundIssuer: "The unique identifier of the identity provider issuing the JWT.", + boundAudiences: "The list of intended recipients.", + boundClaims: "The attributes that should be present in the JWT for it to be valid.", + boundSubject: "The expected principal that is the subject of the JWT.", + accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The maximum number of times that an access token can be used." + }, + UPDATE: { + identityId: "The ID of the identity to update the auth method for.", + oidcDiscoveryUrl: "The new URL used to retrieve the OpenID Connect configuration from the identity provider.", + caCert: "The new PEM-encoded CA cert for establishing secure communication with the Identity Provider endpoints.", + boundIssuer: "The new unique identifier of the identity provider issuing the JWT.", + boundAudiences: "The new list of intended recipients.", + boundClaims: "The new attributes that should be present in the JWT for it to be valid.", + boundSubject: "The new expected principal that is the subject of the JWT.", + accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", + accessTokenTTL: "The new lifetime for an acccess token in seconds.", + accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + }, + RETRIEVE: { + identityId: "The ID of the identity to retrieve the auth method for." + }, REVOKE: { - identityId: "The ID of the identity to revoke." + identityId: "The ID of the identity to revoke the auth method for." } } as const; diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 7ebcd339f..417947ad7 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -30,7 +30,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) schema: { description: "Login with OIDC Auth", body: z.object({ - identityId: z.string().trim(), + identityId: z.string().trim().describe(OIDC_AUTH.LOGIN.identityId), jwt: z.string().trim() }), response: { @@ -85,16 +85,23 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + 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), + 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" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(OIDC_AUTH.ATTACH.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -102,21 +109,17 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(OIDC_AUTH.ATTACH.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0), - oidcDiscoveryUrl: z.string().url().min(1), - caCert: z.string().trim().default(""), - boundIssuer: z.string().min(1), - boundAudiences: validateOidcAuthAudiencesField, - boundClaims: validateOidcBoundClaimsField, - boundSubject: z.string().optional().default("") + .default(2592000) + .describe(OIDC_AUTH.ATTACH.accessTokenMaxTTL), + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.ATTACH.accessTokenNumUsesLimit) }), response: { 200: z.object({ @@ -176,17 +179,24 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string().trim() + 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), + 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" }]), + .default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]) + .describe(OIDC_AUTH.UPDATE.accessTokenTrustedIps), accessTokenTTL: z .number() .int() @@ -194,21 +204,18 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) .refine((value) => value !== 0, { message: "accessTokenTTL must have a non zero number" }) - .default(2592000), + .default(2592000) + .describe(OIDC_AUTH.UPDATE.accessTokenTTL), accessTokenMaxTTL: z .number() .int() .refine((value) => value !== 0, { message: "accessTokenMaxTTL must have a non zero number" }) - .default(2592000), - accessTokenNumUsesLimit: z.number().int().min(0).default(0), - oidcDiscoveryUrl: z.string().url().min(1), - caCert: z.string().trim().default(""), - boundIssuer: z.string().min(1), - boundAudiences: validateOidcAuthAudiencesField, - boundClaims: validateOidcBoundClaimsField, - boundSubject: z.string().optional().default("") + .default(2592000) + .describe(OIDC_AUTH.UPDATE.accessTokenMaxTTL), + + accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.UPDATE.accessTokenNumUsesLimit) }) .partial(), response: { @@ -267,7 +274,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider) } ], params: z.object({ - identityId: z.string() + identityId: z.string().describe(OIDC_AUTH.RETRIEVE.identityId) }), response: { 200: z.object({ diff --git a/docs/api-reference/endpoints/oidc-auth/attach.mdx b/docs/api-reference/endpoints/oidc-auth/attach.mdx new file mode 100644 index 000000000..c75bdc69f --- /dev/null +++ b/docs/api-reference/endpoints/oidc-auth/attach.mdx @@ -0,0 +1,4 @@ +--- +title: "Attach" +openapi: "POST /api/v1/auth/oidc-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/oidc-auth/login.mdx b/docs/api-reference/endpoints/oidc-auth/login.mdx new file mode 100644 index 000000000..baac1bef9 --- /dev/null +++ b/docs/api-reference/endpoints/oidc-auth/login.mdx @@ -0,0 +1,4 @@ +--- +title: "Login" +openapi: "POST /api/v1/auth/oidc-auth/login" +--- diff --git a/docs/api-reference/endpoints/oidc-auth/retrieve.mdx b/docs/api-reference/endpoints/oidc-auth/retrieve.mdx new file mode 100644 index 000000000..a870ffc03 --- /dev/null +++ b/docs/api-reference/endpoints/oidc-auth/retrieve.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/auth/oidc-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/oidc-auth/revoke.mdx b/docs/api-reference/endpoints/oidc-auth/revoke.mdx new file mode 100644 index 000000000..df46d6c45 --- /dev/null +++ b/docs/api-reference/endpoints/oidc-auth/revoke.mdx @@ -0,0 +1,4 @@ +--- +title: "Revoke" +openapi: "DELETE /api/v1/auth/oidc-auth/identities/{identityId}" +--- diff --git a/docs/api-reference/endpoints/oidc-auth/update.mdx b/docs/api-reference/endpoints/oidc-auth/update.mdx new file mode 100644 index 000000000..0d29c3db7 --- /dev/null +++ b/docs/api-reference/endpoints/oidc-auth/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/auth/oidc-auth/identities/{identityId}" +--- diff --git a/docs/mint.json b/docs/mint.json index a0082782d..1f66220f4 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -497,6 +497,16 @@ "api-reference/endpoints/kubernetes-auth/revoke" ] }, + { + "group": "OIDC Auth", + "pages": [ + "api-reference/endpoints/oidc-auth/login", + "api-reference/endpoints/oidc-auth/attach", + "api-reference/endpoints/oidc-auth/retrieve", + "api-reference/endpoints/oidc-auth/update", + "api-reference/endpoints/oidc-auth/revoke" + ] + }, { "group": "Organizations", "pages": [