mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 03:26:46 +00:00
Add trusted IP rules to ST V3
This commit is contained in:
@@ -106,6 +106,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -209,6 +210,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -281,6 +283,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -380,6 +383,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -460,6 +464,7 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -555,6 +560,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -640,6 +646,7 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -713,6 +720,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -812,6 +820,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -888,6 +897,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -946,6 +956,7 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -995,6 +1006,7 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
@@ -1044,6 +1056,7 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.SERVICE_V3: {
|
||||||
await validateServiceTokenDataV3ClientForWorkspace({
|
await validateServiceTokenDataV3ClientForWorkspace({
|
||||||
|
authData: req.authData,
|
||||||
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
serviceTokenData: req.authData.authPayload as IServiceTokenDataV3,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
ServiceTokenDataV3Key
|
ServiceTokenDataV3Key
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import {
|
import {
|
||||||
Scope
|
IServiceTokenV3Scope,
|
||||||
|
IServiceTokenV3TrustedIp
|
||||||
} from "../../models/serviceTokenDataV3";
|
} from "../../models/serviceTokenDataV3";
|
||||||
import {
|
import {
|
||||||
ActorType,
|
ActorType,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||||
|
import { extractIPDetails, isValidIpOrCidr } from "../../utils/ip";
|
||||||
import { EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService } from "../../ee/services";
|
||||||
import { getJwtServiceTokenSecret } from "../../config";
|
import { getJwtServiceTokenSecret } from "../../config";
|
||||||
|
|
||||||
@@ -66,6 +68,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
publicKey,
|
publicKey,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
encryptedKey, // for ServiceTokenDataV3Key
|
encryptedKey, // for ServiceTokenDataV3Key
|
||||||
nonce // for ServiceTokenDataV3Key
|
nonce // for ServiceTokenDataV3Key
|
||||||
@@ -77,6 +80,17 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionSub.ServiceTokens
|
ProjectPermissionSub.ServiceTokens
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// validate trusted ips
|
||||||
|
const reformattedTrustedIps = trustedIps.map((trustedIp) => {
|
||||||
|
const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
|
||||||
|
|
||||||
|
if (!isValidIPOrCidr) return res.status(400).send({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
|
||||||
|
return extractIPDetails(trustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
let expiresAt;
|
let expiresAt;
|
||||||
if (expiresIn) {
|
if (expiresIn) {
|
||||||
expiresAt = new Date();
|
expiresAt = new Date();
|
||||||
@@ -95,6 +109,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
publicKey,
|
publicKey,
|
||||||
usageCount: 0,
|
usageCount: 0,
|
||||||
|
trustedIps: reformattedTrustedIps,
|
||||||
scopes,
|
scopes,
|
||||||
isActive,
|
isActive,
|
||||||
expiresAt
|
expiresAt
|
||||||
@@ -123,7 +138,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes: scopes as Array<Scope>,
|
scopes: scopes as Array<IServiceTokenV3Scope>,
|
||||||
|
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
|
||||||
expiresAt
|
expiresAt
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -151,6 +167,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps,
|
||||||
expiresIn
|
expiresIn
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
||||||
@@ -171,6 +188,20 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionSub.ServiceTokens
|
ProjectPermissionSub.ServiceTokens
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// validate trusted ips
|
||||||
|
let reformattedTrustedIps;
|
||||||
|
if (trustedIps) {
|
||||||
|
reformattedTrustedIps = trustedIps.map((trustedIp) => {
|
||||||
|
const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
|
||||||
|
|
||||||
|
if (!isValidIPOrCidr) return res.status(400).send({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
|
||||||
|
return extractIPDetails(trustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let expiresAt;
|
let expiresAt;
|
||||||
if (expiresIn) {
|
if (expiresIn) {
|
||||||
expiresAt = new Date();
|
expiresAt = new Date();
|
||||||
@@ -183,6 +214,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps: reformattedTrustedIps,
|
||||||
expiresAt
|
expiresAt
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -201,7 +233,8 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: serviceTokenData.name,
|
name: serviceTokenData.name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes: scopes as Array<Scope>,
|
scopes: scopes as Array<IServiceTokenV3Scope>,
|
||||||
|
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
|
||||||
expiresAt
|
expiresAt
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -254,7 +287,8 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: serviceTokenData.name,
|
name: serviceTokenData.name,
|
||||||
isActive: serviceTokenData.isActive,
|
isActive: serviceTokenData.isActive,
|
||||||
scopes: serviceTokenData.scopes as Array<Scope>,
|
scopes: serviceTokenData.scopes as Array<IServiceTokenV3Scope>,
|
||||||
|
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
|
||||||
expiresAt: serviceTokenData.expiresAt
|
expiresAt: serviceTokenData.expiresAt
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import {
|
|||||||
EventType
|
EventType
|
||||||
} from "./enums";
|
} from "./enums";
|
||||||
import {
|
import {
|
||||||
Scope
|
IServiceTokenV3Scope,
|
||||||
|
IServiceTokenV3TrustedIp
|
||||||
} from "../../../models/serviceTokenDataV3";
|
} from "../../../models/serviceTokenDataV3";
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -229,7 +230,8 @@ interface CreateServiceTokenV3Event {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
scopes: Array<Scope>;
|
scopes: Array<IServiceTokenV3Scope>;
|
||||||
|
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +241,8 @@ interface UpdateServiceTokenV3Event {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name?: string;
|
name?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
scopes?: Array<Scope>;
|
scopes?: Array<IServiceTokenV3Scope>;
|
||||||
|
trustedIps?: Array<IServiceTokenV3TrustedIp>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,8 +252,9 @@ interface DeleteServiceTokenV3Event {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
scopes: Array<Scope>;
|
scopes: Array<IServiceTokenV3Scope>;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
|
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ import {
|
|||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
TFolderRootSchema
|
TFolderRootSchema
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import { Permission } from "../models/serviceTokenDataV3";
|
||||||
Scope,
|
|
||||||
Permission
|
|
||||||
} from "../models/serviceTokenDataV3";
|
|
||||||
import { EventType, SecretVersion } from "../ee/models";
|
import { EventType, SecretVersion } from "../ee/models";
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { IPType } from "../ee/models";
|
||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
READ = "read",
|
READ = "read",
|
||||||
WRITE = "write"
|
WRITE = "write"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Scope {
|
export interface IServiceTokenV3Scope {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
permissions: Permission[];
|
permissions: Permission[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IServiceTokenV3TrustedIp {
|
||||||
|
ipAddress: string;
|
||||||
|
type: IPType;
|
||||||
|
prefix: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IServiceTokenDataV3 extends Document {
|
export interface IServiceTokenDataV3 extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -21,7 +28,8 @@ export interface IServiceTokenDataV3 extends Document {
|
|||||||
lastUsed?: Date;
|
lastUsed?: Date;
|
||||||
usageCount: number;
|
usageCount: number;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
scopes: Array<Scope>;
|
scopes: Array<IServiceTokenV3Scope>;
|
||||||
|
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceTokenDataV3Schema = new Schema(
|
const serviceTokenDataV3Schema = new Schema(
|
||||||
@@ -83,6 +91,34 @@ const serviceTokenDataV3Schema = new Schema(
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
trustedIps: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
ipAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
IPType.IPV4,
|
||||||
|
IPType.IPV6
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prefix: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: IPType.IPV4.toString(),
|
||||||
|
prefix: 0
|
||||||
|
}],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import net from "net";
|
import net from "net";
|
||||||
import { IPType } from "../../ee/models";
|
import { IPType } from "../../ee/models";
|
||||||
import { InternalServerError } from "../errors";
|
import { InternalServerError, UnauthorizedRequestError } from "../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return details of IP [ip]:
|
* Return details of IP [ip]:
|
||||||
@@ -98,4 +98,39 @@ export const isValidIpOrCidr = (ip: string): boolean => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the IP address [ipAddress] against the trusted IPs [trustedIps].
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.ipAddress - IP address to check
|
||||||
|
* @param {Object[]} obj.trustedIps - IPs to trust in blocklist
|
||||||
|
*/
|
||||||
|
export const checkIPAgainstBlocklist = ({
|
||||||
|
ipAddress,
|
||||||
|
trustedIps
|
||||||
|
}: {
|
||||||
|
ipAddress: string;
|
||||||
|
trustedIps: {
|
||||||
|
ipAddress: string;
|
||||||
|
type: IPType;
|
||||||
|
prefix: number;
|
||||||
|
}[]
|
||||||
|
}) => {
|
||||||
|
const blockList = new net.BlockList();
|
||||||
|
|
||||||
|
for (const trustedIp of trustedIps) {
|
||||||
|
if (trustedIp.prefix !== undefined) {
|
||||||
|
blockList.addSubnet(trustedIp.ipAddress, trustedIp.prefix, trustedIp.type);
|
||||||
|
} else {
|
||||||
|
blockList.addAddress(trustedIp.ipAddress, trustedIp.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type } = extractIPDetails(ipAddress);
|
||||||
|
const check = blockList.check(ipAddress, type);
|
||||||
|
|
||||||
|
if (!check) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Permission } from "../models/serviceTokenDataV3";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { UnauthorizedRequestError } from "../utils/errors";
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
import { isValidScopeV3 } from "../helpers";
|
import { isValidScopeV3 } from "../helpers";
|
||||||
|
import { AuthData } from "../interfaces/middleware";
|
||||||
|
import { checkIPAgainstBlocklist } from "../utils/ip";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that service token (client) can access workspace
|
* Validate that service token (client) can access workspace
|
||||||
@@ -16,19 +18,27 @@ import { isValidScopeV3 } from "../helpers";
|
|||||||
* @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint
|
* @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
export const validateServiceTokenDataV3ClientForWorkspace = async ({
|
export const validateServiceTokenDataV3ClientForWorkspace = async ({
|
||||||
|
authData,
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath = "/",
|
secretPath = "/",
|
||||||
requiredPermissions
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
serviceTokenData: IServiceTokenDataV3;
|
serviceTokenData: IServiceTokenDataV3;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
requiredPermissions: Permission[];
|
requiredPermissions: Permission[];
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
// validate ST V3 IP address
|
||||||
|
checkIPAgainstBlocklist({
|
||||||
|
ipAddress: authData.ipAddress,
|
||||||
|
trustedIps: serviceTokenData.trustedIps
|
||||||
|
});
|
||||||
|
|
||||||
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||||
// case: invalid workspaceId passed
|
// case: invalid workspaceId passed
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
@@ -63,6 +73,12 @@ export const CreateServiceTokenV3 = z.object({
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.min(1),
|
.min(1),
|
||||||
|
trustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1),
|
||||||
expiresIn: z.number().optional(),
|
expiresIn: z.number().optional(),
|
||||||
encryptedKey: z.string().trim(),
|
encryptedKey: z.string().trim(),
|
||||||
nonce: z.string().trim()
|
nonce: z.string().trim()
|
||||||
@@ -85,6 +101,13 @@ export const UpdateServiceTokenV3 = z.object({
|
|||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional(),
|
.optional(),
|
||||||
|
trustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional(),
|
||||||
expiresIn: z.number().optional()
|
expiresIn: z.number().optional()
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -87,12 +87,14 @@ export const useUpdateServiceTokenV3 = () => {
|
|||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps,
|
||||||
expiresIn
|
expiresIn
|
||||||
}) => {
|
}) => {
|
||||||
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,
|
||||||
expiresIn
|
expiresIn
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ export type ServiceTokenV3Scope = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ServiceTokenV3TrustedIp = {
|
||||||
|
_id: string;
|
||||||
|
ipAddress: string;
|
||||||
|
type: "ipv4" | "ipv6";
|
||||||
|
prefix?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ServiceTokenDataV3 = {
|
export type ServiceTokenDataV3 = {
|
||||||
_id: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -52,6 +59,7 @@ export type ServiceTokenDataV3 = {
|
|||||||
lastUsed?: string;
|
lastUsed?: string;
|
||||||
usageCount: number;
|
usageCount: number;
|
||||||
scopes: ServiceTokenV3Scope[];
|
scopes: ServiceTokenV3Scope[];
|
||||||
|
trustedIps: ServiceTokenV3TrustedIp[];
|
||||||
expiresAt?: string;
|
expiresAt?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -62,6 +70,9 @@ export type CreateServiceTokenDataV3DTO = {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
scopes: ServiceTokenV3Scope[];
|
scopes: ServiceTokenV3Scope[];
|
||||||
|
trustedIps: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
expiresIn?: number;
|
expiresIn?: number;
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
nonce: string;
|
nonce: string;
|
||||||
@@ -77,6 +88,9 @@ export type UpdateServiceTokenDataV3DTO = {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
scopes?: ServiceTokenV3Scope[];
|
scopes?: ServiceTokenV3Scope[];
|
||||||
|
trustedIps?: {
|
||||||
|
ipAddress: string;
|
||||||
|
}[];
|
||||||
expiresIn?: number;
|
expiresIn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+208
-184
@@ -20,11 +20,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem
|
||||||
// Accordion,
|
|
||||||
// AccordionItem,
|
|
||||||
// AccordionTrigger,
|
|
||||||
// AccordionContent
|
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +31,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
Permission
|
Permission
|
||||||
} from "@app/hooks/api/serviceTokens/enums";
|
} from "@app/hooks/api/serviceTokens/enums";
|
||||||
import { ServiceTokenV3Scope } from "@app/hooks/api/serviceTokens/types";
|
import {
|
||||||
|
ServiceTokenV3Scope,
|
||||||
|
ServiceTokenV3TrustedIp
|
||||||
|
} from "@app/hooks/api/serviceTokens/types";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const expirations = [
|
const expirations = [
|
||||||
@@ -58,23 +57,32 @@ 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(),
|
||||||
scopes: yup
|
scopes: yup
|
||||||
|
.array(
|
||||||
|
yup.object({
|
||||||
|
permission: yup.string().oneOf(Object.keys(permissionsMap), "Invalid permission").required().label("Permission"),
|
||||||
|
environment: yup.string().max(50).required().label("Environment"),
|
||||||
|
secretPath: yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.default("/")
|
||||||
|
.label("Secret Path")
|
||||||
|
.transform((val) =>
|
||||||
|
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.required()
|
||||||
|
.label("Scope"),
|
||||||
|
trustedIps: yup
|
||||||
.array(
|
.array(
|
||||||
yup.object({
|
yup.object({
|
||||||
permission: yup.string().oneOf(Object.keys(permissionsMap), "Invalid permission").required().label("Permission"),
|
ipAddress: yup.string().max(50).required().label("IP Address")
|
||||||
environment: yup.string().max(50).required().label("Environment"),
|
|
||||||
secretPath: yup
|
|
||||||
.string()
|
|
||||||
.required()
|
|
||||||
.default("/")
|
|
||||||
.label("Secret Path")
|
|
||||||
.transform((val) =>
|
|
||||||
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1)
|
.min(1)
|
||||||
.required()
|
.required()
|
||||||
.label("Scope")
|
.label("Trusted IP")
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
@@ -107,6 +115,9 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
permission: "read",
|
permission: "read",
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
|
}],
|
||||||
|
trustedIps: [{
|
||||||
|
ipAddress: "0.0.0.0/0"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -116,6 +127,7 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
serviceTokenDataId: string;
|
serviceTokenDataId: string;
|
||||||
name: string;
|
name: string;
|
||||||
scopes: ServiceTokenV3Scope[];
|
scopes: ServiceTokenV3Scope[];
|
||||||
|
trustedIps: ServiceTokenV3TrustedIp[];
|
||||||
};
|
};
|
||||||
|
|
||||||
if (serviceTokenData) {
|
if (serviceTokenData) {
|
||||||
@@ -132,6 +144,14 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
permission
|
permission
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
trustedIps: serviceTokenData.trustedIps.map(({
|
||||||
|
ipAddress,
|
||||||
|
prefix
|
||||||
|
}: ServiceTokenV3TrustedIp) => {
|
||||||
|
return ({
|
||||||
|
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
||||||
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -141,17 +161,22 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
permission: "read",
|
permission: "read",
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug,
|
environment: currentWorkspace?.environments?.[0]?.slug,
|
||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
|
}],
|
||||||
|
trustedIps: [{
|
||||||
|
ipAddress: "0.0.0.0/0"
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [popUp?.serviceTokenV3?.data]);
|
}, [popUp?.serviceTokenV3?.data]);
|
||||||
|
|
||||||
const { fields: tokenScopes, append, remove } = useFieldArray({ control, name: "scopes" });
|
const { fields: tokenScopes, append, remove } = useFieldArray({ control, name: "scopes" });
|
||||||
|
const { fields: tokenTrustedIps, append: appendTrustedIp, remove: removeTrustedIp } = useFieldArray({ control, name: "trustedIps" });
|
||||||
|
|
||||||
const onFormSubmit = async ({
|
const onFormSubmit = async ({
|
||||||
name,
|
name,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
scopes
|
scopes,
|
||||||
|
trustedIps
|
||||||
}: FormData) => {
|
}: FormData) => {
|
||||||
try {
|
try {
|
||||||
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
const serviceTokenData = popUp?.serviceTokenV3?.data as {
|
||||||
@@ -167,14 +192,16 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
secretPath: scope.secretPath,
|
secretPath: scope.secretPath,
|
||||||
permissions: permissionsMap[scope.permission]
|
permissions: permissionsMap[scope.permission]
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
if (serviceTokenData) {
|
if (serviceTokenData) {
|
||||||
// update
|
// update
|
||||||
|
|
||||||
await updateMutateAsync({
|
await updateMutateAsync({
|
||||||
serviceTokenDataId: serviceTokenData.serviceTokenDataId,
|
serviceTokenDataId: serviceTokenData.serviceTokenDataId,
|
||||||
name,
|
name,
|
||||||
scopes: reformattedScopes,
|
scopes: reformattedScopes,
|
||||||
|
trustedIps,
|
||||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -206,6 +233,7 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
workspaceId: currentWorkspace._id,
|
workspaceId: currentWorkspace._id,
|
||||||
publicKey,
|
publicKey,
|
||||||
scopes: reformattedScopes,
|
scopes: reformattedScopes,
|
||||||
|
trustedIps,
|
||||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
nonce
|
nonce
|
||||||
@@ -270,175 +298,171 @@ export const AddServiceTokenV3Modal = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{tokenScopes.map(({ id }, index) => (
|
{tokenScopes.map(({ id }, index) => (
|
||||||
<div className="flex items-end space-x-2 mb-3" key={id}>
|
<div className="flex items-end space-x-2 mb-3" key={id}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`scopes.${index}.permission`}
|
name={`scopes.${index}.permission`}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
className="mb-0"
|
className="mb-0"
|
||||||
label={index === 0 ? "Permission" : undefined}
|
label={index === 0 ? "Permission" : undefined}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-36"
|
|
||||||
>
|
|
||||||
<SelectItem value="read" key="st-v3-read">
|
|
||||||
Read
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="readWrite" key="st-v3-write">
|
|
||||||
Read & Write
|
|
||||||
</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name={`scopes.${index}.environment`}
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
className="mb-0"
|
|
||||||
label={index === 0 ? "Environment" : undefined}
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-36"
|
|
||||||
>
|
|
||||||
{currentWorkspace?.environments.map(({ name, slug }) => (
|
|
||||||
<SelectItem value={slug} key={slug}>
|
|
||||||
{name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name={`scopes.${index}.secretPath`}
|
|
||||||
defaultValue="/"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
className="mb-0 flex-grow"
|
|
||||||
label={index === 0 ? "Secrets Path" : undefined}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder="can be /, /nested/**, /**/deep" />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
size="lg"
|
|
||||||
colorSchema="danger"
|
|
||||||
variant="plain"
|
|
||||||
ariaLabel="update"
|
|
||||||
className="p-3"
|
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
<Select
|
||||||
</IconButton>
|
defaultValue={field.value}
|
||||||
</div>
|
{...field}
|
||||||
))}
|
onValueChange={(e) => onChange(e)}
|
||||||
<div className="my-4 ml-1">
|
className="w-36"
|
||||||
<Button
|
>
|
||||||
variant="outline_bg"
|
<SelectItem value="read" key="st-v3-read">
|
||||||
onClick={() =>
|
Read
|
||||||
append({
|
</SelectItem>
|
||||||
permission: "read",
|
<SelectItem value="readWrite" key="st-v3-write">
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug || "",
|
Read & Write
|
||||||
secretPath: "/"
|
</SelectItem>
|
||||||
})
|
</Select>
|
||||||
}
|
</FormControl>
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
)}
|
||||||
size="xs"
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`scopes.${index}.environment`}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0"
|
||||||
|
label={index === 0 ? "Environment" : undefined}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-36"
|
||||||
|
>
|
||||||
|
{currentWorkspace?.environments.map(({ name, slug }) => (
|
||||||
|
<SelectItem value={slug} key={slug}>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`scopes.${index}.secretPath`}
|
||||||
|
defaultValue="/"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
label={index === 0 ? "Secrets Path" : undefined}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="can be /, /nested/**, /**/deep" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="p-3"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="my-4 ml-1">
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
onClick={() =>
|
||||||
|
append({
|
||||||
|
permission: "read",
|
||||||
|
environment: currentWorkspace?.environments?.[0]?.slug || "",
|
||||||
|
secretPath: "/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Add Scope
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{tokenTrustedIps.map(({ id }, index) => (
|
||||||
|
<div className="flex items-end space-x-2 mb-3" key={id}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`trustedIps.${index}.ipAddress`}
|
||||||
|
defaultValue="0.0.0.0/0"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="mb-0 flex-grow"
|
||||||
|
label={index === 0 ? "Trusted IP" : undefined}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="123.456.789.0" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => removeTrustedIp(index)}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="p-3"
|
||||||
>
|
>
|
||||||
Add Scope
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
</Button>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<Controller
|
))}
|
||||||
control={control}
|
<div className="my-4 ml-1">
|
||||||
name="expiresIn"
|
<Button
|
||||||
defaultValue="15552000"
|
variant="outline_bg"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
onClick={() =>
|
||||||
<FormControl
|
appendTrustedIp({
|
||||||
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Expire In`}
|
ipAddress: "0.0.0.0/0"
|
||||||
errorText={error?.message}
|
})
|
||||||
isError={Boolean(error)}
|
}
|
||||||
className="mt-4"
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Add IP Address
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="expiresIn"
|
||||||
|
defaultValue="15552000"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={`${popUp?.serviceTokenV3?.data ? "Update" : ""} Expire In`}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Select
|
{expirations.map(({ label, value }) => (
|
||||||
defaultValue={field.value}
|
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||||
{...field}
|
{label}
|
||||||
onValueChange={(e) => onChange(e)}
|
</SelectItem>
|
||||||
className="w-full"
|
))}
|
||||||
>
|
</Select>
|
||||||
{expirations.map(({ label, value }) => (
|
</FormControl>
|
||||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
)}
|
||||||
{label}
|
/>
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/* <Accordion
|
|
||||||
type="multiple"
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
<AccordionItem value="section-1">
|
|
||||||
<AccordionTrigger>Scopes</AccordionTrigger>
|
|
||||||
<AccordionContent>Description of Section 1</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion> */}
|
|
||||||
{/* <h3 className="text-mineshaft-400 text-sm mb-2">Temporariness</h3>
|
|
||||||
<Switch
|
|
||||||
id={`enable-ephemerality`}
|
|
||||||
onCheckedChange={(value) => setIsTemporary(value)}
|
|
||||||
isChecked={isTemporary}
|
|
||||||
>
|
|
||||||
<div className="w-96 mr-4">
|
|
||||||
<p className="text-gray-400 text-md">This token will be deactivated after your specified duration.</p>
|
|
||||||
</div>
|
|
||||||
</Switch>
|
|
||||||
{isTemporary && (
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="expiresIn"
|
|
||||||
defaultValue="15552000"
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Duration"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
className="mt-4"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{expirations.map(({ label, value }) => (
|
|
||||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
|
||||||
{label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)} */}
|
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex items-center">
|
||||||
<Button
|
<Button
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
|
|||||||
+21
-4
@@ -22,7 +22,7 @@ import {
|
|||||||
useUpdateServiceTokenV3
|
useUpdateServiceTokenV3
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { Permission } from "@app/hooks/api/serviceTokens/enums"
|
import { Permission } from "@app/hooks/api/serviceTokens/enums"
|
||||||
import { ServiceTokenV3Scope } from "@app/hooks/api/serviceTokens/types"
|
import { ServiceTokenV3Scope, ServiceTokenV3TrustedIp } from "@app/hooks/api/serviceTokens/types"
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -32,6 +32,7 @@ type Props = {
|
|||||||
serviceTokenDataId?: string;
|
serviceTokenDataId?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
scopes?: ServiceTokenV3Scope[];
|
scopes?: ServiceTokenV3Scope[];
|
||||||
|
trustedIps?: ServiceTokenV3TrustedIp[];
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
@@ -89,7 +90,8 @@ export const ServiceTokenV3Table = ({
|
|||||||
<Th>Name</Th>
|
<Th>Name</Th>
|
||||||
<Th>Status</Th>
|
<Th>Status</Th>
|
||||||
<Th>Scopes</Th>
|
<Th>Scopes</Th>
|
||||||
<Th># Times Used</Th>
|
<Th>Trusted IPs</Th>
|
||||||
|
{/* <Th># Times Used</Th> */}
|
||||||
<Th>Last Used</Th>
|
<Th>Last Used</Th>
|
||||||
<Th>Created At</Th>
|
<Th>Created At</Th>
|
||||||
<Th>Expires At</Th>
|
<Th>Expires At</Th>
|
||||||
@@ -106,8 +108,9 @@ export const ServiceTokenV3Table = ({
|
|||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed,
|
lastUsed,
|
||||||
usageCount,
|
// usageCount,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps,
|
||||||
createdAt,
|
createdAt,
|
||||||
expiresAt
|
expiresAt
|
||||||
}) => {
|
}) => {
|
||||||
@@ -154,7 +157,20 @@ export const ServiceTokenV3Table = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{usageCount}</Td>
|
<Td>
|
||||||
|
{trustedIps.map(({
|
||||||
|
_id: trustedIpId,
|
||||||
|
ipAddress,
|
||||||
|
prefix
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<p key={`service-token-${_id}-}-trusted-ip-${trustedIpId}`}>
|
||||||
|
{`${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Td>
|
||||||
|
{/* <Td>{usageCount}</Td> */}
|
||||||
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
||||||
<Td>{formatDate(createdAt)}</Td>
|
<Td>{formatDate(createdAt)}</Td>
|
||||||
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
||||||
@@ -170,6 +186,7 @@ export const ServiceTokenV3Table = ({
|
|||||||
serviceTokenDataId: _id,
|
serviceTokenDataId: _id,
|
||||||
name,
|
name,
|
||||||
scopes,
|
scopes,
|
||||||
|
trustedIps
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|||||||
Reference in New Issue
Block a user