mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 06:27:40 +00:00
Update ST V3 impl to (rotating) refresh token impl
This commit is contained in:
@@ -24,7 +24,6 @@ export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_RE
|
|||||||
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
|
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
|
||||||
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
|
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
|
||||||
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
|
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
|
||||||
export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue;
|
|
||||||
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
|
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
|
||||||
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
|
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
|
||||||
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true;
|
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true;
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ declare module "jsonwebtoken" {
|
|||||||
userId: string;
|
userId: string;
|
||||||
refreshVersion?: number;
|
refreshVersion?: number;
|
||||||
}
|
}
|
||||||
|
export interface ServiceRefreshTokenJwtPayload extends jwt.JwtPayload {
|
||||||
|
serviceTokenDataId: string;
|
||||||
|
authTokenType: string;
|
||||||
|
tokenVersion: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
@@ -24,10 +25,11 @@ import {
|
|||||||
getUserProjectPermissions
|
getUserProjectPermissions
|
||||||
} from "../../services/ProjectRoleService";
|
} from "../../services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { BadRequestError, ResourceNotFoundError } from "../../../utils/errors";
|
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||||
import { getJwtServiceTokenSecret } from "../../../config";
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return project key for service token V3
|
* Return project key for service token V3
|
||||||
@@ -56,6 +58,99 @@ export const getServiceTokenDataKey = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return access and refresh token as per refresh operation
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const refreshToken = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: {
|
||||||
|
refresh_token
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.RefreshTokenV3, req);
|
||||||
|
|
||||||
|
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
||||||
|
jwt.verify(refresh_token, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_REFRESH_TOKEN) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
let serviceTokenData = await ServiceTokenDataV3.findOne({
|
||||||
|
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
||||||
|
// raise alarm
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: {
|
||||||
|
refresh_token?: string;
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
token_type: string;
|
||||||
|
} = {
|
||||||
|
refresh_token,
|
||||||
|
access_token: "",
|
||||||
|
expires_in: 0,
|
||||||
|
token_type: "Bearer"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serviceTokenData.isRefreshTokenRotationEnabled) {
|
||||||
|
serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
$inc: {
|
||||||
|
tokenVersion: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw BadRequestError();
|
||||||
|
|
||||||
|
response.refresh_token = createToken({
|
||||||
|
payload: {
|
||||||
|
serviceTokenDataId: serviceTokenData._id.toString(),
|
||||||
|
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
||||||
|
tokenVersion: serviceTokenData.tokenVersion
|
||||||
|
},
|
||||||
|
secret: await getAuthSecret()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
response.access_token = createToken({
|
||||||
|
payload: {
|
||||||
|
serviceTokenDataId: serviceTokenData._id.toString(),
|
||||||
|
authTokenType: AuthTokenType.SERVICE_ACCESS_TOKEN,
|
||||||
|
tokenVersion: serviceTokenData.tokenVersion
|
||||||
|
},
|
||||||
|
expiresIn: serviceTokenData.accessTokenTTL,
|
||||||
|
secret: await getAuthSecret()
|
||||||
|
});
|
||||||
|
|
||||||
|
response.expires_in = serviceTokenData.accessTokenTTL;
|
||||||
|
|
||||||
|
await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
refreshTokenLastUsed: new Date(),
|
||||||
|
$inc: { refreshTokenUsageCount: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send(response);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create service token data V3
|
* Create service token data V3
|
||||||
* @param req
|
* @param req
|
||||||
@@ -71,8 +166,10 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
scopes,
|
scopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled,
|
||||||
encryptedKey, // for ServiceTokenDataV3Key
|
encryptedKey, // for ServiceTokenDataV3Key
|
||||||
nonce // for ServiceTokenDataV3Key
|
nonce, // for ServiceTokenDataV3Key
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
@@ -112,17 +209,21 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
user = req.authData.authPayload._id;
|
user = req.authData.authPayload._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = true;
|
const isActive = false;
|
||||||
const serviceTokenData = await new ServiceTokenDataV3({
|
const serviceTokenData = await new ServiceTokenDataV3({
|
||||||
name,
|
name,
|
||||||
user,
|
user,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
publicKey,
|
publicKey,
|
||||||
usageCount: 0,
|
refreshTokenUsageCount: 0,
|
||||||
|
accessTokenUsageCount: 0,
|
||||||
|
tokenVersion: 1,
|
||||||
trustedIps: reformattedTrustedIps,
|
trustedIps: reformattedTrustedIps,
|
||||||
scopes,
|
scopes,
|
||||||
isActive,
|
isActive,
|
||||||
expiresAt
|
expiresAt,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
await new ServiceTokenDataV3Key({
|
await new ServiceTokenDataV3Key({
|
||||||
@@ -133,18 +234,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const token = createToken({
|
const refreshToken = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
_id: serviceTokenData._id.toString()
|
serviceTokenDataId: serviceTokenData._id.toString(),
|
||||||
|
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
||||||
|
tokenVersion: serviceTokenData.tokenVersion
|
||||||
},
|
},
|
||||||
expiresIn,
|
secret: await getAuthSecret()
|
||||||
secret: await getJwtServiceTokenSecret()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
{
|
{
|
||||||
type: EventType.CREATE_SERVICE_TOKEN_V3,
|
type: EventType.CREATE_SERVICE_TOKEN_V3, // TODO: update
|
||||||
metadata: {
|
metadata: {
|
||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
@@ -160,7 +262,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
serviceToken: `stv3.${token}`
|
refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +280,9 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn
|
expiresIn,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
||||||
|
|
||||||
@@ -233,7 +337,9 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps: reformattedTrustedIps,
|
trustedIps: reformattedTrustedIps,
|
||||||
expiresAt
|
expiresAt,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ import { serviceTokenDataController } from "../../controllers/v3";
|
|||||||
router.get(
|
router.get(
|
||||||
"/me/key",
|
"/me/key",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
serviceTokenDataController.getServiceTokenDataKey
|
serviceTokenDataController.getServiceTokenDataKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/me/token",
|
||||||
|
serviceTokenDataController.refreshToken
|
||||||
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
+39
-24
@@ -22,8 +22,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getAuthSecret,
|
getAuthSecret,
|
||||||
getJwtAuthLifetime,
|
getJwtAuthLifetime,
|
||||||
getJwtRefreshLifetime,
|
getJwtRefreshLifetime
|
||||||
getJwtServiceTokenSecret
|
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import {
|
import {
|
||||||
AuthMode,
|
AuthMode,
|
||||||
@@ -43,7 +42,7 @@ import { getUserAgentType } from "../utils/posthog";
|
|||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {Object} obj.headers - HTTP request headers object
|
* @param {Object} obj.headers - HTTP request headers object
|
||||||
*/
|
*/
|
||||||
export const validateAuthMode = ({
|
export const validateAuthMode = async ({
|
||||||
headers,
|
headers,
|
||||||
acceptedAuthModes,
|
acceptedAuthModes,
|
||||||
}: {
|
}: {
|
||||||
@@ -84,13 +83,19 @@ export const validateAuthMode = ({
|
|||||||
authMode = AuthMode.SERVICE_TOKEN;
|
authMode = AuthMode.SERVICE_TOKEN;
|
||||||
authTokenValue = tokenValue;
|
authTokenValue = tokenValue;
|
||||||
break;
|
break;
|
||||||
case "stv3":
|
default: {
|
||||||
authMode = AuthMode.SERVICE_TOKEN_V3;
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
authTokenValue = parts.slice(1).join(".");
|
jwt.verify(tokenValue, await getAuthSecret())
|
||||||
break;
|
);
|
||||||
default:
|
|
||||||
authMode = AuthMode.JWT;
|
if (decodedToken.authTokenType === AuthTokenType.SERVICE_ACCESS_TOKEN) {
|
||||||
|
authMode = AuthMode.SERVICE_ACCESS_TOKEN;
|
||||||
|
} else {
|
||||||
|
authMode = AuthMode.JWT;
|
||||||
|
}
|
||||||
|
|
||||||
authTokenValue = tokenValue;
|
authTokenValue = tokenValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,23 +259,17 @@ export const getAuthSTDPayload = async ({
|
|||||||
req: Request,
|
req: Request,
|
||||||
authTokenValue: string;
|
authTokenValue: string;
|
||||||
}): Promise<ServiceTokenV3AuthData> => {
|
}): Promise<ServiceTokenV3AuthData> => {
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
|
||||||
jwt.verify(authTokenValue, await getJwtServiceTokenSecret())
|
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
);
|
);
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findOneAndUpdate(
|
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
||||||
{
|
|
||||||
_id: new Types.ObjectId(decodedToken._id),
|
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
||||||
isActive: true
|
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
||||||
},
|
isActive: true
|
||||||
{
|
});
|
||||||
lastUsed: new Date(),
|
|
||||||
$inc: { usageCount: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
if (!serviceTokenData) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
@@ -288,11 +287,27 @@ export const getAuthSTDPayload = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate",
|
||||||
|
});
|
||||||
|
} else if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
||||||
|
// TODO: raise alarm
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to authenticate",
|
message: "Failed to authenticate",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
accessTokenLastUsed: new Date(),
|
||||||
|
$inc: { accessTokenUsageCount: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actor: {
|
actor: {
|
||||||
type: ActorType.SERVICE_V3,
|
type: ActorType.SERVICE_V3,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const requireAuth = ({
|
|||||||
|
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
const { authMode, authTokenValue } = validateAuthMode({
|
const { authMode, authTokenValue } = await validateAuthMode({
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
acceptedAuthModes,
|
acceptedAuthModes,
|
||||||
});
|
});
|
||||||
@@ -50,7 +50,7 @@ const requireAuth = ({
|
|||||||
});
|
});
|
||||||
req.serviceTokenData = authData.authPayload;
|
req.serviceTokenData = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
case AuthMode.SERVICE_TOKEN_V3:
|
case AuthMode.SERVICE_ACCESS_TOKEN:
|
||||||
authData = await getAuthSTDV3Payload({
|
authData = await getAuthSTDV3Payload({
|
||||||
req,
|
req,
|
||||||
authTokenValue
|
authTokenValue
|
||||||
|
|||||||
@@ -25,9 +25,14 @@ export interface IServiceTokenDataV3 extends Document {
|
|||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
lastUsed?: Date;
|
refreshTokenLastUsed?: Date;
|
||||||
usageCount: number;
|
accessTokenLastUsed?: Date;
|
||||||
|
refreshTokenUsageCount: number;
|
||||||
|
accessTokenUsageCount: number;
|
||||||
|
tokenVersion: number;
|
||||||
|
isRefreshTokenRotationEnabled: boolean;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
|
accessTokenTTL: number;
|
||||||
scopes: Array<IServiceTokenV3Scope>;
|
scopes: Array<IServiceTokenV3Scope>;
|
||||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||||
}
|
}
|
||||||
@@ -57,19 +62,43 @@ const serviceTokenDataV3Schema = new Schema(
|
|||||||
default: true,
|
default: true,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
lastUsed: {
|
refreshTokenLastUsed: {
|
||||||
type: Date,
|
type: Date,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
usageCount: {
|
accessTokenLastUsed: {
|
||||||
|
type: Date,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
refreshTokenUsageCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
expiresAt: {
|
accessTokenUsageCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
tokenVersion: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isRefreshTokenRotationEnabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
expiresAt: { // consider revising field name
|
||||||
type: Date,
|
type: Date,
|
||||||
required: false,
|
required: false,
|
||||||
expires: 0
|
// expires: 0
|
||||||
|
},
|
||||||
|
accessTokenTTL: { // seconds
|
||||||
|
type: Number,
|
||||||
|
default: 7200,
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
scopes: {
|
scopes: {
|
||||||
type: [
|
type: [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretsRaw
|
secretsController.getSecretsRaw
|
||||||
);
|
);
|
||||||
@@ -15,7 +15,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -29,7 +29,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -43,7 +43,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -57,7 +57,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -71,7 +71,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -116,7 +116,7 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -127,7 +127,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -138,7 +138,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -149,7 +149,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_TOKEN_V3]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ import { checkIPAgainstBlocklist } from "../utils/ip";
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const RefreshTokenV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
refresh_token: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const CreateServiceTokenV3 = z.object({
|
export const CreateServiceTokenV3 = z.object({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
@@ -80,8 +86,10 @@ export const CreateServiceTokenV3 = z.object({
|
|||||||
.array()
|
.array()
|
||||||
.min(1),
|
.min(1),
|
||||||
expiresIn: z.number().optional(),
|
expiresIn: z.number().optional(),
|
||||||
|
accessTokenTTL: z.number().int().min(1),
|
||||||
encryptedKey: z.string().trim(),
|
encryptedKey: z.string().trim(),
|
||||||
nonce: z.string().trim()
|
nonce: z.string().trim(),
|
||||||
|
isRefreshTokenRotationEnabled: z.boolean().default(false)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,7 +116,9 @@ export const UpdateServiceTokenV3 = z.object({
|
|||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional(),
|
.optional(),
|
||||||
expiresIn: z.number().optional()
|
expiresIn: z.number().optional(),
|
||||||
|
accessTokenTTL: z.number().int().min(1).optional(),
|
||||||
|
isRefreshTokenRotationEnabled: z.boolean().optional()
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
|
// TODO: merge [AuthTokenType] and [AuthMode]
|
||||||
|
|
||||||
export enum AuthTokenType {
|
export enum AuthTokenType {
|
||||||
ACCESS_TOKEN = "accessToken",
|
ACCESS_TOKEN = "accessToken",
|
||||||
REFRESH_TOKEN = "refreshToken",
|
REFRESH_TOKEN = "refreshToken",
|
||||||
SIGNUP_TOKEN = "signupToken",
|
SIGNUP_TOKEN = "signupToken", // TODO: remove in favor of claim
|
||||||
MFA_TOKEN = "mfaToken",
|
MFA_TOKEN = "mfaToken", // TODO: remove in favor of claim
|
||||||
PROVIDER_TOKEN = "providerToken",
|
PROVIDER_TOKEN = "providerToken", // TODO: remove in favor of claim
|
||||||
API_KEY = "apiKey"
|
API_KEY = "apiKey",
|
||||||
|
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
||||||
|
SERVICE_REFRESH_TOKEN = "serviceRefreshToken"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthMode {
|
export enum AuthMode {
|
||||||
JWT = "jwt",
|
JWT = "jwt",
|
||||||
SERVICE_TOKEN = "serviceToken",
|
SERVICE_TOKEN = "serviceToken",
|
||||||
SERVICE_TOKEN_V3 = "serviceTokenV3",
|
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
||||||
API_KEY = "apiKey"
|
API_KEY = "apiKey"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,14 +88,18 @@ export const useUpdateServiceTokenV3 = () => {
|
|||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn
|
expiresIn,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
}) => {
|
}) => {
|
||||||
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
|
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
|
||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn
|
expiresIn,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
return serviceTokenData;
|
return serviceTokenData;
|
||||||
|
|||||||
@@ -56,11 +56,15 @@ export type ServiceTokenDataV3 = {
|
|||||||
name: string;
|
name: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
lastUsed?: string;
|
refreshTokenLastUsed?: string;
|
||||||
usageCount: number;
|
accessTokenLastUsed?: string;
|
||||||
|
refreshTokenUsageCount: number;
|
||||||
|
accessTokenUsageCount: number;
|
||||||
scopes: ServiceTokenV3Scope[];
|
scopes: ServiceTokenV3Scope[];
|
||||||
trustedIps: ServiceTokenV3TrustedIp[];
|
trustedIps: ServiceTokenV3TrustedIp[];
|
||||||
expiresAt?: string;
|
expiresAt?: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
isRefreshTokenRotationEnabled: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
@@ -74,12 +78,14 @@ export type CreateServiceTokenDataV3DTO = {
|
|||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
}[];
|
}[];
|
||||||
expiresIn?: number;
|
expiresIn?: number;
|
||||||
|
accessTokenTTL: number;
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
nonce: string;
|
nonce: string;
|
||||||
|
isRefreshTokenRotationEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateServiceTokenDataV3Res = {
|
export type CreateServiceTokenDataV3Res = {
|
||||||
serviceToken: string;
|
refreshToken: string;
|
||||||
serviceTokenData: ServiceTokenDataV3;
|
serviceTokenData: ServiceTokenDataV3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +98,8 @@ export type UpdateServiceTokenDataV3DTO = {
|
|||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
}[];
|
}[];
|
||||||
expiresIn?: number;
|
expiresIn?: number;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
isRefreshTokenRotationEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeleteServiceTokenDataV3DTO = {
|
export type DeleteServiceTokenDataV3DTO = {
|
||||||
|
|||||||
+70
-17
@@ -21,6 +21,7 @@ import {
|
|||||||
ModalContent,
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
|
Switch,
|
||||||
UpgradePlanModal
|
UpgradePlanModal
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
@@ -42,7 +43,7 @@ import {
|
|||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const expirations = [
|
const expirations = [
|
||||||
{ label: "Never", value: undefined },
|
{ label: "Never", value: "" },
|
||||||
{ label: "1 day", value: "86400" },
|
{ label: "1 day", value: "86400" },
|
||||||
{ label: "7 days", value: "604800" },
|
{ label: "7 days", value: "604800" },
|
||||||
{ label: "1 month", value: "2592000" },
|
{ label: "1 month", value: "2592000" },
|
||||||
@@ -60,6 +61,13 @@ const permissionsMap: {
|
|||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
name: yup.string().required("ST V3 name is required"),
|
name: yup.string().required("ST V3 name is required"),
|
||||||
expiresIn: yup.string(),
|
expiresIn: yup.string(),
|
||||||
|
accessTokenTTL: yup
|
||||||
|
.string()
|
||||||
|
.test("is-positive-integer", "Access Token TTL must be a positive integer", (value) => {
|
||||||
|
const num = parseInt(value, 10);
|
||||||
|
return !Number.isNaN(num) && num > 0 && String(num) === value;
|
||||||
|
})
|
||||||
|
.required("Access Token TTL is required"),
|
||||||
scopes: yup
|
scopes: yup
|
||||||
.array(
|
.array(
|
||||||
yup.object({
|
yup.object({
|
||||||
@@ -79,14 +87,15 @@ const schema = yup.object({
|
|||||||
.required()
|
.required()
|
||||||
.label("Scope"),
|
.label("Scope"),
|
||||||
trustedIps: yup
|
trustedIps: yup
|
||||||
.array(
|
.array(
|
||||||
yup.object({
|
yup.object({
|
||||||
ipAddress: yup.string().max(50).required().label("IP Address")
|
ipAddress: yup.string().max(50).required().label("IP Address")
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1)
|
.min(1)
|
||||||
.required()
|
.required()
|
||||||
.label("Trusted IP")
|
.label("Trusted IP"),
|
||||||
|
isRefreshTokenRotationEnabled: yup.boolean().default(false)
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
@@ -118,6 +127,7 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
accessTokenTTL: "7200",
|
||||||
scopes: [{
|
scopes: [{
|
||||||
permission: "read",
|
permission: "read",
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
@@ -135,6 +145,8 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
name: string;
|
name: string;
|
||||||
scopes: ServiceTokenV3Scope[];
|
scopes: ServiceTokenV3Scope[];
|
||||||
trustedIps: ServiceTokenV3TrustedIp[];
|
trustedIps: ServiceTokenV3TrustedIp[];
|
||||||
|
accessTokenTTL: number;
|
||||||
|
isRefreshTokenRotationEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (serviceTokenData) {
|
if (serviceTokenData) {
|
||||||
@@ -163,11 +175,14 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
return ({
|
return ({
|
||||||
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
||||||
});
|
});
|
||||||
})
|
}),
|
||||||
|
accessTokenTTL: String(serviceTokenData.accessTokenTTL),
|
||||||
|
isRefreshTokenRotationEnabled: serviceTokenData.isRefreshTokenRotationEnabled
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reset({
|
reset({
|
||||||
name: "",
|
name: "",
|
||||||
|
accessTokenTTL: "7200",
|
||||||
scopes: [{
|
scopes: [{
|
||||||
permission: "read",
|
permission: "read",
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
@@ -186,8 +201,10 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
const onFormSubmit = async ({
|
const onFormSubmit = async ({
|
||||||
name,
|
name,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
|
accessTokenTTL,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps
|
trustedIps,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||||
@@ -213,7 +230,9 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
name,
|
name,
|
||||||
scopes: reformattedScopes,
|
scopes: reformattedScopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||||
|
accessTokenTTL: Number(accessTokenTTL),
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// create
|
// create
|
||||||
@@ -239,21 +258,23 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
privateKey: localStorage.getItem("PRIVATE_KEY") as string
|
||||||
});
|
});
|
||||||
|
|
||||||
const { serviceToken } = await createMutateAsync({
|
const { refreshToken } = await createMutateAsync({
|
||||||
name,
|
name,
|
||||||
workspaceId: currentWorkspace._id,
|
workspaceId: currentWorkspace._id,
|
||||||
publicKey,
|
publicKey,
|
||||||
scopes: reformattedScopes,
|
scopes: reformattedScopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||||
|
accessTokenTTL: Number(accessTokenTTL),
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce
|
nonce,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloadData = {
|
const downloadData = {
|
||||||
publicKey,
|
publicKey,
|
||||||
privateKey,
|
privateKey,
|
||||||
serviceToken
|
refreshToken
|
||||||
};
|
};
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: "application/json" });
|
const blob = new Blob([JSON.stringify(downloadData, null, 2)], { type: "application/json" });
|
||||||
@@ -476,10 +497,10 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="expiresIn"
|
name="expiresIn"
|
||||||
defaultValue="15552000"
|
defaultValue=""
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Expire In`}
|
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Refresh Token Expires In`}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
@@ -499,6 +520,38 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue="7200"
|
||||||
|
name="accessTokenTTL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Access Token TTL (seconds)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="7200"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 mb-[2.36rem]">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isRefreshTokenRotationEnabled"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Switch
|
||||||
|
id="label-refresh-token-rotation"
|
||||||
|
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||||
|
isChecked={value}
|
||||||
|
>
|
||||||
|
Refresh Token Rotation
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
+11
-9
@@ -34,6 +34,8 @@ type Props = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
scopes?: ServiceTokenV3Scope[];
|
scopes?: ServiceTokenV3Scope[];
|
||||||
trustedIps?: ServiceTokenV3TrustedIp[];
|
trustedIps?: ServiceTokenV3TrustedIp[];
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
isRefreshTokenRotationEnabled?: boolean;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
@@ -81,10 +83,9 @@ export const ServiceTokenV3Table = ({
|
|||||||
<Th>Status</Th>
|
<Th>Status</Th>
|
||||||
<Th>Scopes</Th>
|
<Th>Scopes</Th>
|
||||||
<Th>Trusted IPs</Th>
|
<Th>Trusted IPs</Th>
|
||||||
{/* <Th># Times Used</Th> */}
|
<Th>Access Token TTL</Th>
|
||||||
<Th>Last Used</Th>
|
|
||||||
<Th>Created At</Th>
|
<Th>Created At</Th>
|
||||||
<Th>Expires At</Th>
|
<Th>Valid Until</Th>
|
||||||
<Th className="w-5" />
|
<Th className="w-5" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
@@ -97,12 +98,12 @@ export const ServiceTokenV3Table = ({
|
|||||||
_id,
|
_id,
|
||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed,
|
|
||||||
// usageCount,
|
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps,
|
trustedIps,
|
||||||
createdAt,
|
createdAt,
|
||||||
expiresAt
|
expiresAt,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`st-v3-${_id}`}>
|
<Tr className="h-10" key={`st-v3-${_id}`}>
|
||||||
@@ -160,8 +161,7 @@ export const ServiceTokenV3Table = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Td>
|
</Td>
|
||||||
{/* <Td>{usageCount}</Td> */}
|
<Td>{accessTokenTTL}</Td>
|
||||||
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
|
|
||||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||||
<Td>{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"}</Td>
|
<Td>{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"}</Td>
|
||||||
<Td className="flex justify-end">
|
<Td className="flex justify-end">
|
||||||
@@ -176,7 +176,9 @@ export const ServiceTokenV3Table = ({
|
|||||||
serviceTokenDataId: _id,
|
serviceTokenDataId: _id,
|
||||||
name,
|
name,
|
||||||
scopes,
|
scopes,
|
||||||
trustedIps
|
trustedIps,
|
||||||
|
accessTokenTTL,
|
||||||
|
isRefreshTokenRotationEnabled
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|||||||
Reference in New Issue
Block a user