feat: add token period support for ua

This commit is contained in:
Sheen Capadngan
2025-05-28 15:35:10 +08:00
parent e739b29b3c
commit 8ed8f1200d
21 changed files with 333 additions and 75 deletions
@@ -0,0 +1,139 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
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");
});
}
}
@@ -21,7 +21,8 @@ export const IdentityAccessTokensSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
name: z.string().nullable().optional(), name: z.string().nullable().optional(),
authMethod: z.string() authMethod: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>; export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
+2 -1
View File
@@ -19,7 +19,8 @@ export const IdentityAwsAuthsSchema = z.object({
type: z.string(), type: z.string(),
stsEndpoint: z.string(), stsEndpoint: z.string(),
allowedPrincipalArns: z.string(), allowedPrincipalArns: z.string(),
allowedAccountIds: z.string() allowedAccountIds: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>; export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
@@ -18,7 +18,8 @@ export const IdentityAzureAuthsSchema = z.object({
identityId: z.string().uuid(), identityId: z.string().uuid(),
tenantId: z.string(), tenantId: z.string(),
resource: z.string(), resource: z.string(),
allowedServicePrincipalIds: z.string() allowedServicePrincipalIds: z.string(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>; export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>;
+2 -1
View File
@@ -19,7 +19,8 @@ export const IdentityGcpAuthsSchema = z.object({
type: z.string(), type: z.string(),
allowedServiceAccounts: z.string().nullable().optional(), allowedServiceAccounts: z.string().nullable().optional(),
allowedProjects: 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<typeof IdentityGcpAuthsSchema>; export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
+2 -1
View File
@@ -25,7 +25,8 @@ export const IdentityJwtAuthsSchema = z.object({
boundClaims: z.unknown(), boundClaims: z.unknown(),
boundSubject: z.string(), boundSubject: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>; export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>;
@@ -30,7 +30,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
allowedAudience: z.string(), allowedAudience: z.string(),
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
encryptedKubernetesCaCertificate: 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<typeof IdentityKubernetesAuthsSchema>; export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
@@ -24,7 +24,8 @@ export const IdentityLdapAuthsSchema = z.object({
searchFilter: z.string(), searchFilter: z.string(),
allowedFields: z.unknown().nullable().optional(), allowedFields: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>; export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
+2 -1
View File
@@ -18,7 +18,8 @@ export const IdentityOciAuthsSchema = z.object({
identityId: z.string().uuid(), identityId: z.string().uuid(),
type: z.string(), type: z.string(),
tenancyOcid: 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<typeof IdentityOciAuthsSchema>; export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
@@ -27,7 +27,8 @@ export const IdentityOidcAuthsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
encryptedCaCertificate: zodBuffer.nullable().optional(), 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<typeof IdentityOidcAuthsSchema>; export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>;
@@ -15,7 +15,8 @@ export const IdentityTokenAuthsSchema = z.object({
accessTokenTrustedIps: z.unknown(), accessTokenTrustedIps: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid() identityId: z.string().uuid(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>; export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>;
@@ -17,7 +17,8 @@ export const IdentityUniversalAuthsSchema = z.object({
accessTokenTrustedIps: z.unknown(), accessTokenTrustedIps: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid() identityId: z.string().uuid(),
accessTokenPeriod: z.coerce.number().default(0)
}); });
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>; export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;
+4 -2
View File
@@ -147,7 +147,8 @@ export const UNIVERSAL_AUTH = {
accessTokenMaxTTL: accessTokenMaxTTL:
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.", "The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenNumUsesLimit: 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: { RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for." 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.", 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.", accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum 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: { CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.", identityId: "The ID of the identity to create a client secret for.",
@@ -47,8 +47,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo, identityMembershipOrg } = const {
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp); 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({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@@ -63,11 +70,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
} }
}); });
return { return {
accessToken, accessToken,
tokenType: "Bearer" as const, tokenType: "Bearer" as const,
expiresIn: identityUa.accessTokenTTL, expiresIn: accessTokenTTL,
accessTokenMaxTTL: identityUa.accessTokenMaxTTL accessTokenMaxTTL
}; };
} }
}); });
@@ -128,7 +136,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.int() .int()
.min(0) .min(0)
.default(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( .refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL, (val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
@@ -227,7 +236,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.min(0) .min(0)
.max(315360000) .max(315360000)
.optional() .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( .refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true), (val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
@@ -96,10 +96,15 @@ export const identityAccessTokenServiceFactory = ({
} }
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses }); 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 // Only enforce Max TTL for non-periodic tokens
if (Number(accessTokenMaxTTL) > 0) { if (Number(accessTokenMaxTTL) > 0 && Number(accessTokenPeriod) === 0) {
const accessTokenCreated = new Date(accessTokenCreatedAt); const accessTokenCreated = new Date(accessTokenCreatedAt);
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000; const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
const currentDate = new Date(); const currentDate = new Date();
@@ -125,6 +130,18 @@ export const identityAccessTokenServiceFactory = ({
accessTokenLastRenewedAt: new Date() 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( const renewedToken = jwt.sign(
{ {
identityId: decodedToken.identityId, identityId: decodedToken.identityId,
@@ -133,12 +150,7 @@ export const identityAccessTokenServiceFactory = ({
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload, } as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET, appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error expiresIn !== undefined ? { expiresIn } : undefined
Number(identityAccessToken.accessTokenTTL) === 0
? undefined
: {
expiresIn: Number(identityAccessToken.accessTokenTTL)
}
); );
return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken }; return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken };
@@ -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 identityAccessToken = await identityUaDAL.transaction(async (tx) => {
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx); const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
const newToken = await identityAccessTokenDAL.create( const newToken = await identityAccessTokenDAL.create(
{ {
identityId: identityUa.identityId, identityId: identityUa.identityId,
isAccessTokenRevoked: false, isAccessTokenRevoked: false,
identityUAClientSecretId: uaClientSecretDoc.id, identityUAClientSecretId: uaClientSecretDoc.id,
accessTokenTTL: identityUa.accessTokenTTL,
accessTokenMaxTTL: identityUa.accessTokenMaxTTL,
accessTokenNumUses: 0, accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit, accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH accessTokenPeriod: identityUa.accessTokenPeriod,
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
...accessTokenTTLParams
}, },
tx tx
); );
return newToken; 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 ({ const attachUniversalAuth = async ({
@@ -163,7 +183,8 @@ export const identityUaServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId, actorOrgId,
isActorSuperAdmin isActorSuperAdmin,
accessTokenPeriod
}: TAttachUaDTO) => { }: TAttachUaDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin); await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
@@ -232,7 +253,8 @@ export const identityUaServiceFactory = ({
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenTTL, accessTokenTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
accessTokenPeriod
}, },
tx tx
); );
@@ -248,6 +270,7 @@ export const identityUaServiceFactory = ({
accessTokenTTL, accessTokenTTL,
accessTokenTrustedIps, accessTokenTrustedIps,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenPeriod,
actorId, actorId,
actorAuthMethod, actorAuthMethod,
actor, actor,
@@ -324,6 +347,7 @@ export const identityUaServiceFactory = ({
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenTTL, accessTokenTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenPeriod,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps accessTokenTrustedIps: reformattedAccessTokenTrustedIps
? JSON.stringify(reformattedAccessTokenTrustedIps) ? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined : undefined
@@ -5,6 +5,7 @@ export type TAttachUaDTO = {
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
accessTokenPeriod: number;
clientSecretTrustedIps: { ipAddress: string }[]; clientSecretTrustedIps: { ipAddress: string }[];
accessTokenTrustedIps: { ipAddress: string }[]; accessTokenTrustedIps: { ipAddress: string }[];
isActorSuperAdmin?: boolean; isActorSuperAdmin?: boolean;
@@ -15,6 +16,7 @@ export type TUpdateUaDTO = {
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
accessTokenPeriod?: number;
clientSecretTrustedIps?: { ipAddress: string }[]; clientSecretTrustedIps?: { ipAddress: string }[];
accessTokenTrustedIps?: { ipAddress: string }[]; accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@@ -163,7 +163,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenTrustedIps accessTokenTrustedIps,
accessTokenPeriod
}) => { }) => {
const { const {
data: { identityUniversalAuth } data: { identityUniversalAuth }
@@ -172,7 +173,8 @@ export const useUpdateIdentityUniversalAuth = () => {
accessTokenTTL, accessTokenTTL,
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenTrustedIps accessTokenTrustedIps,
accessTokenPeriod
}); });
return identityUniversalAuth; return identityUniversalAuth;
}, },
@@ -107,6 +107,7 @@ export type IdentityUniversalAuth = {
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[]; accessTokenTrustedIps: IdentityTrustedIp[];
accessTokenPeriod: number;
}; };
export type AddIdentityUniversalAuthDTO = { export type AddIdentityUniversalAuthDTO = {
@@ -118,6 +119,7 @@ export type AddIdentityUniversalAuthDTO = {
accessTokenTTL: number; accessTokenTTL: number;
accessTokenMaxTTL: number; accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number; accessTokenNumUsesLimit: number;
accessTokenPeriod: number;
accessTokenTrustedIps: { accessTokenTrustedIps: {
ipAddress: string; ipAddress: string;
}[]; }[];
@@ -132,6 +134,7 @@ export type UpdateIdentityUniversalAuthDTO = {
accessTokenTTL?: number; accessTokenTTL?: number;
accessTokenMaxTTL?: number; accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number; accessTokenNumUsesLimit?: number;
accessTokenPeriod?: number;
accessTokenTrustedIps?: { accessTokenTrustedIps?: {
ipAddress: string; ipAddress: string;
}[]; }[];
@@ -41,6 +41,13 @@ const schema = z
(value) => Number(value) <= 315360000, (value) => Number(value) <= 315360000,
"Access Max Token TTL cannot be greater than 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(), accessTokenNumUsesLimit: z.string(),
clientSecretTrustedIps: z clientSecretTrustedIps: z
.object({ .object({
@@ -90,7 +97,8 @@ export const IdentityUniversalAuthForm = ({
control, control,
handleSubmit, handleSubmit,
reset, reset,
formState: { isSubmitting } formState: { isSubmitting },
watch
} = useForm<FormData>({ } = useForm<FormData>({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
@@ -98,10 +106,13 @@ export const IdentityUniversalAuthForm = ({
accessTokenMaxTTL: "2592000", accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0", accessTokenNumUsesLimit: "0",
clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/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 { const {
fields: clientSecretTrustedIpsFields, fields: clientSecretTrustedIpsFields,
append: appendClientSecretTrustedIp, append: appendClientSecretTrustedIp,
@@ -119,6 +130,7 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: String(data.accessTokenTTL), accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
accessTokenPeriod: String(data.accessTokenPeriod),
clientSecretTrustedIps: data.clientSecretTrustedIps.map( clientSecretTrustedIps: data.clientSecretTrustedIps.map(
({ ipAddress, prefix }: IdentityTrustedIp) => { ({ ipAddress, prefix }: IdentityTrustedIp) => {
return { return {
@@ -139,6 +151,7 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: "2592000", accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000", accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0", accessTokenNumUsesLimit: "0",
accessTokenPeriod: "0",
clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/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" }]
}); });
@@ -150,7 +163,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenMaxTTL, accessTokenMaxTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
clientSecretTrustedIps, clientSecretTrustedIps,
accessTokenTrustedIps accessTokenTrustedIps,
accessTokenPeriod
}: FormData) => { }: FormData) => {
try { try {
if (!identityId) return; if (!identityId) return;
@@ -164,7 +178,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps accessTokenTrustedIps,
accessTokenPeriod: Number(accessTokenPeriod)
}); });
} else { } else {
// create new universal auth configuration // create new universal auth configuration
@@ -176,7 +191,8 @@ export const IdentityUniversalAuthForm = ({
accessTokenTTL: Number(accessTokenTTL), accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps accessTokenTrustedIps,
accessTokenPeriod: Number(accessTokenPeriod)
}); });
} }
@@ -214,34 +230,42 @@ export const IdentityUniversalAuthForm = ({
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab> <Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList> </TabList>
<TabPanel value={IdentityFormTab.Configuration}> <TabPanel value={IdentityFormTab.Configuration}>
<Controller {accessTokenPeriodValue > 0 ? (
control={control} <div className="mb-4 text-xs text-bunker-400">
defaultValue="2592000" When Access Token Period is set, TTL and Max TTL are ignored.
name="accessTokenTTL" </div>
render={({ field, fieldState: { error } }) => ( ) : (
<FormControl <>
label="Access Token TTL (seconds)" <Controller
isError={Boolean(error)} control={control}
errorText={error?.message} defaultValue="2592000"
> name="accessTokenTTL"
<Input {...field} placeholder="2592000" type="number" min="0" step="1" /> render={({ field, fieldState: { error } }) => (
</FormControl> <FormControl
)} label="Access Token TTL (seconds)"
/> isError={Boolean(error)}
<Controller errorText={error?.message}
control={control} >
defaultValue="2592000" <Input {...field} placeholder="2592000" type="number" min="0" step="1" />
name="accessTokenMaxTTL" </FormControl>
render={({ field, fieldState: { error } }) => ( )}
<FormControl />
label="Access Token Max TTL (seconds)" <Controller
isError={Boolean(error)} control={control}
errorText={error?.message} defaultValue="2592000"
> name="accessTokenMaxTTL"
<Input {...field} placeholder="2592000" type="number" min="0" step="1" /> render={({ field, fieldState: { error } }) => (
</FormControl> <FormControl
)} label="Access Token Max TTL (seconds)"
/> isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
</FormControl>
)}
/>
</>
)}
<Controller <Controller
control={control} control={control}
defaultValue="0" defaultValue="0"
@@ -256,6 +280,21 @@ export const IdentityUniversalAuthForm = ({
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
defaultValue="0"
name="accessTokenPeriod"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Period (seconds)"
isError={Boolean(error)}
errorText={error?.message}
helperText="For periodic tokens: set a period (in seconds) to allow indefinite renewal. Set to 0 to disable periodic tokens and use TTL-based expiration."
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel> </TabPanel>
<TabPanel value={IdentityFormTab.Advanced}> <TabPanel value={IdentityFormTab.Advanced}>
{clientSecretTrustedIpsFields.map(({ id }, index) => ( {clientSecretTrustedIpsFields.map(({ id }, index) => (
@@ -62,12 +62,20 @@ export const ViewIdentityUniversalAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")} onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete} onDelete={onDelete}
> >
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)"> {Number(data.accessTokenPeriod) > 0 ? (
{data.accessTokenTTL} <IdentityAuthFieldDisplay label="Access Token Period (seconds)">
</IdentityAuthFieldDisplay> {data.accessTokenPeriod}
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)"> </IdentityAuthFieldDisplay>
{data.accessTokenMaxTTL} ) : (
</IdentityAuthFieldDisplay> <>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
</>
)}
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses"> <IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit} {data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay> </IdentityAuthFieldDisplay>