diff --git a/backend/src/db/migrations/20250527164523_add-mi-access-token-period.ts b/backend/src/db/migrations/20250527164523_add-mi-access-token-period.ts new file mode 100644 index 000000000..6c2442036 --- /dev/null +++ b/backend/src/db/migrations/20250527164523_add-mi-access-token-period.ts @@ -0,0 +1,139 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod"))) { + await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => { + t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } + + if (await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod")) { + await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => { + t.dropColumn("accessTokenPeriod"); + }); + } +} diff --git a/backend/src/db/schemas/identity-access-tokens.ts b/backend/src/db/schemas/identity-access-tokens.ts index bbff1b88c..8f2b8b73b 100644 --- a/backend/src/db/schemas/identity-access-tokens.ts +++ b/backend/src/db/schemas/identity-access-tokens.ts @@ -21,7 +21,8 @@ export const IdentityAccessTokensSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), name: z.string().nullable().optional(), - authMethod: z.string() + authMethod: z.string(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityAccessTokens = z.infer; diff --git a/backend/src/db/schemas/identity-aws-auths.ts b/backend/src/db/schemas/identity-aws-auths.ts index f4444b00f..83f5b43cf 100644 --- a/backend/src/db/schemas/identity-aws-auths.ts +++ b/backend/src/db/schemas/identity-aws-auths.ts @@ -19,7 +19,8 @@ export const IdentityAwsAuthsSchema = z.object({ type: z.string(), stsEndpoint: z.string(), allowedPrincipalArns: z.string(), - allowedAccountIds: z.string() + allowedAccountIds: z.string(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityAwsAuths = z.infer; diff --git a/backend/src/db/schemas/identity-azure-auths.ts b/backend/src/db/schemas/identity-azure-auths.ts index 856f7b8f1..e8e1905e4 100644 --- a/backend/src/db/schemas/identity-azure-auths.ts +++ b/backend/src/db/schemas/identity-azure-auths.ts @@ -18,7 +18,8 @@ export const IdentityAzureAuthsSchema = z.object({ identityId: z.string().uuid(), tenantId: z.string(), resource: z.string(), - allowedServicePrincipalIds: z.string() + allowedServicePrincipalIds: z.string(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityAzureAuths = z.infer; diff --git a/backend/src/db/schemas/identity-gcp-auths.ts b/backend/src/db/schemas/identity-gcp-auths.ts index 208058f60..536e7200a 100644 --- a/backend/src/db/schemas/identity-gcp-auths.ts +++ b/backend/src/db/schemas/identity-gcp-auths.ts @@ -19,7 +19,8 @@ export const IdentityGcpAuthsSchema = z.object({ type: z.string(), allowedServiceAccounts: z.string().nullable().optional(), allowedProjects: z.string().nullable().optional(), - allowedZones: z.string().nullable().optional() + allowedZones: z.string().nullable().optional(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityGcpAuths = z.infer; diff --git a/backend/src/db/schemas/identity-jwt-auths.ts b/backend/src/db/schemas/identity-jwt-auths.ts index 1d3ea9c03..c11ba8adc 100644 --- a/backend/src/db/schemas/identity-jwt-auths.ts +++ b/backend/src/db/schemas/identity-jwt-auths.ts @@ -25,7 +25,8 @@ export const IdentityJwtAuthsSchema = z.object({ boundClaims: z.unknown(), boundSubject: z.string(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityJwtAuths = z.infer; diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index 3c9dd400c..00d1fd771 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -30,7 +30,8 @@ export const IdentityKubernetesAuthsSchema = z.object({ allowedAudience: z.string(), encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(), - gatewayId: z.string().uuid().nullable().optional() + gatewayId: z.string().uuid().nullable().optional(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityKubernetesAuths = z.infer; diff --git a/backend/src/db/schemas/identity-ldap-auths.ts b/backend/src/db/schemas/identity-ldap-auths.ts index d5b15fc6a..e8d0658d5 100644 --- a/backend/src/db/schemas/identity-ldap-auths.ts +++ b/backend/src/db/schemas/identity-ldap-auths.ts @@ -24,7 +24,8 @@ export const IdentityLdapAuthsSchema = z.object({ searchFilter: z.string(), allowedFields: z.unknown().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityLdapAuths = z.infer; diff --git a/backend/src/db/schemas/identity-oci-auths.ts b/backend/src/db/schemas/identity-oci-auths.ts index e0be86b78..438837691 100644 --- a/backend/src/db/schemas/identity-oci-auths.ts +++ b/backend/src/db/schemas/identity-oci-auths.ts @@ -18,7 +18,8 @@ export const IdentityOciAuthsSchema = z.object({ identityId: z.string().uuid(), type: z.string(), tenancyOcid: z.string(), - allowedUsernames: z.string().nullable().optional() + allowedUsernames: z.string().nullable().optional(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityOciAuths = z.infer; diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts index 03bfcf40a..5c652b0f8 100644 --- a/backend/src/db/schemas/identity-oidc-auths.ts +++ b/backend/src/db/schemas/identity-oidc-auths.ts @@ -27,7 +27,8 @@ export const IdentityOidcAuthsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), encryptedCaCertificate: zodBuffer.nullable().optional(), - claimMetadataMapping: z.unknown().nullable().optional() + claimMetadataMapping: z.unknown().nullable().optional(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityOidcAuths = z.infer; diff --git a/backend/src/db/schemas/identity-token-auths.ts b/backend/src/db/schemas/identity-token-auths.ts index 0f3c8c9ff..e90e67533 100644 --- a/backend/src/db/schemas/identity-token-auths.ts +++ b/backend/src/db/schemas/identity-token-auths.ts @@ -15,7 +15,8 @@ export const IdentityTokenAuthsSchema = z.object({ accessTokenTrustedIps: z.unknown(), createdAt: z.date(), updatedAt: z.date(), - identityId: z.string().uuid() + identityId: z.string().uuid(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityTokenAuths = z.infer; diff --git a/backend/src/db/schemas/identity-universal-auths.ts b/backend/src/db/schemas/identity-universal-auths.ts index eeec2f666..da27b4a55 100644 --- a/backend/src/db/schemas/identity-universal-auths.ts +++ b/backend/src/db/schemas/identity-universal-auths.ts @@ -17,7 +17,8 @@ export const IdentityUniversalAuthsSchema = z.object({ accessTokenTrustedIps: z.unknown(), createdAt: z.date(), updatedAt: z.date(), - identityId: z.string().uuid() + identityId: z.string().uuid(), + accessTokenPeriod: z.coerce.number().default(0) }); export type TIdentityUniversalAuths = z.infer; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 2eeb86380..a1aeca95f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -147,7 +147,8 @@ export const UNIVERSAL_AUTH = { accessTokenMaxTTL: "The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.", accessTokenNumUsesLimit: - "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses." + "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.", + accessTokenPeriod: "The period for an access token in seconds. This value will be referenced at renewal time." }, RETRIEVE: { identityId: "The ID of the identity to retrieve the auth method for." @@ -161,7 +162,8 @@ export const UNIVERSAL_AUTH = { 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.", accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.", - accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used." + accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.", + accessTokenPeriod: "The new period for an access token in seconds." }, CREATE_CLIENT_SECRET: { identityId: "The ID of the identity to create a client secret for.", diff --git a/backend/src/server/routes/v1/identity-universal-auth-router.ts b/backend/src/server/routes/v1/identity-universal-auth-router.ts index 6fe4c7a85..09fffbff8 100644 --- a/backend/src/server/routes/v1/identity-universal-auth-router.ts +++ b/backend/src/server/routes/v1/identity-universal-auth-router.ts @@ -47,8 +47,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - const { identityUa, accessToken, identityAccessToken, validClientSecretInfo, identityMembershipOrg } = - await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp); + const { + identityUa, + accessToken, + identityAccessToken, + validClientSecretInfo, + identityMembershipOrg, + accessTokenTTL, + accessTokenMaxTTL + } = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, @@ -63,11 +70,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { } } }); + return { accessToken, tokenType: "Bearer" as const, - expiresIn: identityUa.accessTokenTTL, - accessTokenMaxTTL: identityUa.accessTokenMaxTTL + expiresIn: accessTokenTTL, + accessTokenMaxTTL }; } }); @@ -128,7 +136,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { .int() .min(0) .default(0) - .describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit) + .describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit), + accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod) }) .refine( (val) => val.accessTokenTTL <= val.accessTokenMaxTTL, @@ -227,7 +236,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => { .min(0) .max(315360000) .optional() - .describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL) + .describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL), + accessTokenPeriod: z + .number() + .int() + .min(0) + .max(315360000) + .optional() + .describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod) }) .refine( (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), diff --git a/backend/src/services/identity-access-token/identity-access-token-service.ts b/backend/src/services/identity-access-token/identity-access-token-service.ts index 6a082c432..c5b57373d 100644 --- a/backend/src/services/identity-access-token/identity-access-token-service.ts +++ b/backend/src/services/identity-access-token/identity-access-token-service.ts @@ -96,10 +96,15 @@ export const identityAccessTokenServiceFactory = ({ } await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses }); - const { accessTokenMaxTTL, createdAt: accessTokenCreatedAt, accessTokenTTL } = identityAccessToken; + const { + accessTokenMaxTTL, + createdAt: accessTokenCreatedAt, + accessTokenTTL, + accessTokenPeriod + } = identityAccessToken; - // max ttl checks - will it go above max ttl - if (Number(accessTokenMaxTTL) > 0) { + // Only enforce Max TTL for non-periodic tokens + if (Number(accessTokenMaxTTL) > 0 && Number(accessTokenPeriod) === 0) { const accessTokenCreated = new Date(accessTokenCreatedAt); const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000; const currentDate = new Date(); @@ -125,6 +130,18 @@ export const identityAccessTokenServiceFactory = ({ accessTokenLastRenewedAt: new Date() }); + const ttl = Number(accessTokenTTL); + const period = Number(accessTokenPeriod); + + let expiresIn: number | undefined; + if (period > 0) { + expiresIn = period; + } else if (ttl > 0) { + expiresIn = ttl; + } else { + expiresIn = undefined; + } + const renewedToken = jwt.sign( { identityId: decodedToken.identityId, @@ -133,12 +150,7 @@ export const identityAccessTokenServiceFactory = ({ authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN } as TIdentityAccessTokenJwtPayload, appCfg.AUTH_SECRET, - // akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error - Number(identityAccessToken.accessTokenTTL) === 0 - ? undefined - : { - expiresIn: Number(identityAccessToken.accessTokenTTL) - } + expiresIn !== undefined ? { expiresIn } : undefined ); return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken }; diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index 8ab499e65..69a54d2cc 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -114,21 +114,34 @@ export const identityUaServiceFactory = ({ }); } + const accessTokenTTLParams = + Number(identityUa.accessTokenPeriod) === 0 + ? { + accessTokenTTL: identityUa.accessTokenTTL, + accessTokenMaxTTL: identityUa.accessTokenMaxTTL + } + : { + accessTokenTTL: identityUa.accessTokenPeriod, + accessTokenMaxTTL: identityUa.accessTokenPeriod + }; + const identityAccessToken = await identityUaDAL.transaction(async (tx) => { const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx); + const newToken = await identityAccessTokenDAL.create( { identityId: identityUa.identityId, isAccessTokenRevoked: false, identityUAClientSecretId: uaClientSecretDoc.id, - accessTokenTTL: identityUa.accessTokenTTL, - accessTokenMaxTTL: identityUa.accessTokenMaxTTL, accessTokenNumUses: 0, accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit, - authMethod: IdentityAuthMethod.UNIVERSAL_AUTH + accessTokenPeriod: identityUa.accessTokenPeriod, + authMethod: IdentityAuthMethod.UNIVERSAL_AUTH, + ...accessTokenTTLParams }, tx ); + return newToken; }); @@ -149,7 +162,14 @@ export const identityUaServiceFactory = ({ } ); - return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg }; + return { + accessToken, + identityUa, + validClientSecretInfo, + identityAccessToken, + identityMembershipOrg, + ...accessTokenTTLParams + }; }; const attachUniversalAuth = async ({ @@ -163,7 +183,8 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actor, actorOrgId, - isActorSuperAdmin + isActorSuperAdmin, + accessTokenPeriod }: TAttachUaDTO) => { await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); @@ -232,7 +253,8 @@ export const identityUaServiceFactory = ({ accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) + accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), + accessTokenPeriod }, tx ); @@ -248,6 +270,7 @@ export const identityUaServiceFactory = ({ accessTokenTTL, accessTokenTrustedIps, clientSecretTrustedIps, + accessTokenPeriod, actorId, actorAuthMethod, actor, @@ -324,6 +347,7 @@ export const identityUaServiceFactory = ({ accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, + accessTokenPeriod, accessTokenTrustedIps: reformattedAccessTokenTrustedIps ? JSON.stringify(reformattedAccessTokenTrustedIps) : undefined diff --git a/backend/src/services/identity-ua/identity-ua-types.ts b/backend/src/services/identity-ua/identity-ua-types.ts index 07b6a4810..f7938e0f7 100644 --- a/backend/src/services/identity-ua/identity-ua-types.ts +++ b/backend/src/services/identity-ua/identity-ua-types.ts @@ -5,6 +5,7 @@ export type TAttachUaDTO = { accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; + accessTokenPeriod: number; clientSecretTrustedIps: { ipAddress: string }[]; accessTokenTrustedIps: { ipAddress: string }[]; isActorSuperAdmin?: boolean; @@ -15,6 +16,7 @@ export type TUpdateUaDTO = { accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; + accessTokenPeriod?: number; clientSecretTrustedIps?: { ipAddress: string }[]; accessTokenTrustedIps?: { ipAddress: string }[]; } & Omit; diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx index 748745986..c08081f11 100644 --- a/frontend/src/hooks/api/identities/mutations.tsx +++ b/frontend/src/hooks/api/identities/mutations.tsx @@ -163,7 +163,8 @@ export const useUpdateIdentityUniversalAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + accessTokenPeriod }) => { const { data: { identityUniversalAuth } @@ -172,7 +173,8 @@ export const useUpdateIdentityUniversalAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + accessTokenPeriod }); return identityUniversalAuth; }, diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index e31b39cbe..c26466213 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -107,6 +107,7 @@ export type IdentityUniversalAuth = { accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; accessTokenTrustedIps: IdentityTrustedIp[]; + accessTokenPeriod: number; }; export type AddIdentityUniversalAuthDTO = { @@ -118,6 +119,7 @@ export type AddIdentityUniversalAuthDTO = { accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; + accessTokenPeriod: number; accessTokenTrustedIps: { ipAddress: string; }[]; @@ -132,6 +134,7 @@ export type UpdateIdentityUniversalAuthDTO = { accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; + accessTokenPeriod?: number; accessTokenTrustedIps?: { ipAddress: string; }[]; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx index 2ebe631fe..e14acd869 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx @@ -41,6 +41,13 @@ const schema = z (value) => Number(value) <= 315360000, "Access Max Token TTL cannot be greater than 315360000" ), + accessTokenPeriod: z + .string() + .optional() + .refine( + (value) => !value || Number(value) <= 315360000, + "Access Token Period cannot be greater than 315360000" + ), accessTokenNumUsesLimit: z.string(), clientSecretTrustedIps: z .object({ @@ -90,7 +97,8 @@ export const IdentityUniversalAuthForm = ({ control, handleSubmit, reset, - formState: { isSubmitting } + formState: { isSubmitting }, + watch } = useForm({ resolver: zodResolver(schema), defaultValues: { @@ -98,10 +106,13 @@ export const IdentityUniversalAuthForm = ({ accessTokenMaxTTL: "2592000", accessTokenNumUsesLimit: "0", clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + accessTokenPeriod: "0" } }); + const accessTokenPeriodValue = Number(watch("accessTokenPeriod")); + const { fields: clientSecretTrustedIpsFields, append: appendClientSecretTrustedIp, @@ -119,6 +130,7 @@ export const IdentityUniversalAuthForm = ({ accessTokenTTL: String(data.accessTokenTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), + accessTokenPeriod: String(data.accessTokenPeriod), clientSecretTrustedIps: data.clientSecretTrustedIps.map( ({ ipAddress, prefix }: IdentityTrustedIp) => { return { @@ -139,6 +151,7 @@ export const IdentityUniversalAuthForm = ({ accessTokenTTL: "2592000", accessTokenMaxTTL: "2592000", accessTokenNumUsesLimit: "0", + accessTokenPeriod: "0", clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] }); @@ -150,7 +163,8 @@ export const IdentityUniversalAuthForm = ({ accessTokenMaxTTL, accessTokenNumUsesLimit, clientSecretTrustedIps, - accessTokenTrustedIps + accessTokenTrustedIps, + accessTokenPeriod }: FormData) => { try { if (!identityId) return; @@ -164,7 +178,8 @@ export const IdentityUniversalAuthForm = ({ accessTokenTTL: Number(accessTokenTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod) }); } else { // create new universal auth configuration @@ -176,7 +191,8 @@ export const IdentityUniversalAuthForm = ({ accessTokenTTL: Number(accessTokenTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod) }); } @@ -214,34 +230,42 @@ export const IdentityUniversalAuthForm = ({ Advanced - ( - - - - )} - /> - ( - - - - )} - /> + {accessTokenPeriodValue > 0 ? ( +
+ When Access Token Period is set, TTL and Max TTL are ignored. +
+ ) : ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} )} /> + ( + + + + )} + />
{clientSecretTrustedIpsFields.map(({ id }, index) => ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx index 3a794ce60..ce6415171 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityUniversalAuthContent.tsx @@ -62,12 +62,20 @@ export const ViewIdentityUniversalAuthContent = ({ onEdit={() => handlePopUpOpen("identityAuthMethod")} onDelete={onDelete} > - - {data.accessTokenTTL} - - - {data.accessTokenMaxTTL} - + {Number(data.accessTokenPeriod) > 0 ? ( + + {data.accessTokenPeriod} + + ) : ( + <> + + {data.accessTokenTTL} + + + {data.accessTokenMaxTTL} + + + )} {data.accessTokenNumUsesLimit}