mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-02 16:25:52 +00:00
Merge branch 'main' into identities-docs
This commit is contained in:
@@ -3,28 +3,28 @@ import { Types } from "mongoose";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import {
|
import {
|
||||||
IIdentity,
|
IIdentity,
|
||||||
IIdentityTrustedIp,
|
IIdentityTrustedIp,
|
||||||
IIdentityUniversalAuthClientSecret,
|
IIdentityUniversalAuthClientSecret,
|
||||||
Identity,
|
Identity,
|
||||||
IdentityAccessToken,
|
IdentityAccessToken,
|
||||||
IdentityAuthMethod,
|
IdentityAuthMethod,
|
||||||
IdentityMembershipOrg,
|
IdentityMembershipOrg,
|
||||||
IdentityUniversalAuth,
|
IdentityUniversalAuth,
|
||||||
IdentityUniversalAuthClientSecret,
|
IdentityUniversalAuthClientSecret,
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { createToken } from "../../helpers/auth";
|
import { createToken } from "../../helpers/auth";
|
||||||
import { AuthTokenType } from "../../variables";
|
import { AuthTokenType } from "../../variables";
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
ForbiddenRequestError,
|
ForbiddenRequestError,
|
||||||
ResourceNotFoundError,
|
ResourceNotFoundError,
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from "../../utils/errors";
|
} from "../../utils/errors";
|
||||||
import {
|
import {
|
||||||
getAuthSecret,
|
getAuthSecret,
|
||||||
getSaltRounds
|
getSaltRounds
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { ActorType, EventType, IRole } from "../../ee/models";
|
import { ActorType, EventType, IRole } from "../../ee/models";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
@@ -32,12 +32,12 @@ import * as reqValidator from "../../validation/auth";
|
|||||||
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr } from "../../utils/ip";
|
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr } from "../../utils/ip";
|
||||||
import { getUserAgentType } from "../../utils/posthog";
|
import { getUserAgentType } from "../../utils/posthog";
|
||||||
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getAuthDataOrgPermissions,
|
getAuthDataOrgPermissions,
|
||||||
getOrgRolePermissions,
|
getOrgRolePermissions,
|
||||||
isAtLeastAsPrivilegedOrg
|
isAtLeastAsPrivilegedOrg
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ const packageUniversalAuthClientSecretData = (identityUniversalAuthClientSecret:
|
|||||||
createdAt: identityUniversalAuthClientSecret.createdAt,
|
createdAt: identityUniversalAuthClientSecret.createdAt,
|
||||||
updatedAt: identityUniversalAuthClientSecret.updatedAt
|
updatedAt: identityUniversalAuthClientSecret.updatedAt
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renews an access token by its TTL
|
* Renews an access token by its TTL
|
||||||
* @param req
|
* @param req
|
||||||
@@ -132,9 +132,6 @@ export const renewAccessToken = async (req: Request, res: Response) => {
|
|||||||
createdAt: accessTokenCreatedAt
|
createdAt: accessTokenCreatedAt
|
||||||
} = identityAccessToken;
|
} = identityAccessToken;
|
||||||
|
|
||||||
if (accessTokenTTL === accessTokenMaxTTL) throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to renew non-renewable access token"
|
|
||||||
});
|
|
||||||
|
|
||||||
// ttl check
|
// ttl check
|
||||||
if (accessTokenTTL > 0) {
|
if (accessTokenTTL > 0) {
|
||||||
@@ -187,6 +184,7 @@ export const renewAccessToken = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
accessToken,
|
accessToken,
|
||||||
expiresIn: identityAccessToken.accessTokenTTL,
|
expiresIn: identityAccessToken.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityAccessToken.accessTokenMaxTTL,
|
||||||
tokenType: "Bearer"
|
tokenType: "Bearer"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -259,7 +257,7 @@ export const loginIdentityUniversalAuth = async (req: Request, res: Response) =>
|
|||||||
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
||||||
clientId
|
clientId
|
||||||
}).populate<{ identity: IIdentity }>("identity");
|
}).populate<{ identity: IIdentity }>("identity");
|
||||||
|
|
||||||
if (!identityUniversalAuth) throw UnauthorizedRequestError();
|
if (!identityUniversalAuth) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
checkIPAgainstBlocklist({
|
checkIPAgainstBlocklist({
|
||||||
@@ -334,16 +332,16 @@ export const loginIdentityUniversalAuth = async (req: Request, res: Response) =>
|
|||||||
|
|
||||||
// increment usage count by 1
|
// increment usage count by 1
|
||||||
await IdentityUniversalAuthClientSecret
|
await IdentityUniversalAuthClientSecret
|
||||||
.findByIdAndUpdate(
|
.findByIdAndUpdate(
|
||||||
validatedClientSecretDatum._id,
|
validatedClientSecretDatum._id,
|
||||||
{
|
{
|
||||||
clientSecretLastUsedAt: new Date(),
|
clientSecretLastUsedAt: new Date(),
|
||||||
$inc: { clientSecretNumUses: 1 }
|
$inc: { clientSecretNumUses: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const identityAccessToken = await new IdentityAccessToken({
|
const identityAccessToken = await new IdentityAccessToken({
|
||||||
identity: identityUniversalAuth.identity,
|
identity: identityUniversalAuth.identity,
|
||||||
@@ -397,7 +395,8 @@ export const loginIdentityUniversalAuth = async (req: Request, res: Response) =>
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
accessToken,
|
accessToken,
|
||||||
expiresIn: identityUniversalAuth.accessTokenTTL,
|
expiresIn: identityUniversalAuth.accessTokenTTL,
|
||||||
tokenType: "Bearer"
|
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
|
||||||
|
tokenType: "Bearer",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +522,7 @@ export const attachIdentityUniversalAuth = async (req: Request, res: Response) =
|
|||||||
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
message: `Failed to find identity with id ${identityId}`
|
message: `Failed to find identity with id ${identityId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
if (identityMembershipOrg.identity?.authMethod) throw BadRequestError({
|
if (identityMembershipOrg.identity?.authMethod) throw BadRequestError({
|
||||||
message: "Failed to add universal auth to already-configured identity"
|
message: "Failed to add universal auth to already-configured identity"
|
||||||
});
|
});
|
||||||
@@ -572,7 +571,7 @@ export const attachIdentityUniversalAuth = async (req: Request, res: Response) =
|
|||||||
|
|
||||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityUniversalAuth = await new IdentityUniversalAuth({
|
const identityUniversalAuth = await new IdentityUniversalAuth({
|
||||||
identity: identityMembershipOrg.identity._id,
|
identity: identityMembershipOrg.identity._id,
|
||||||
clientId: crypto.randomUUID(),
|
clientId: crypto.randomUUID(),
|
||||||
@@ -582,7 +581,7 @@ export const attachIdentityUniversalAuth = async (req: Request, res: Response) =
|
|||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: reformattedAccessTokenTrustedIps,
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps,
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
await Identity.findByIdAndUpdate(
|
await Identity.findByIdAndUpdate(
|
||||||
identityMembershipOrg.identity._id,
|
identityMembershipOrg.identity._id,
|
||||||
{
|
{
|
||||||
@@ -725,7 +724,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
|
|||||||
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
message: `Failed to find identity with id ${identityId}`
|
message: `Failed to find identity with id ${identityId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) throw BadRequestError({
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) throw BadRequestError({
|
||||||
message: "Failed to add universal auth to already-configured identity"
|
message: "Failed to add universal auth to already-configured identity"
|
||||||
});
|
});
|
||||||
@@ -776,7 +775,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
|
|||||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityUniversalAuth = await IdentityUniversalAuth.findOneAndUpdate(
|
const identityUniversalAuth = await IdentityUniversalAuth.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
identity: identityMembershipOrg.identity._id,
|
identity: identityMembershipOrg.identity._id,
|
||||||
@@ -853,7 +852,7 @@ export const getIdentityUniversalAuth = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
params: { identityId }
|
params: { identityId }
|
||||||
} = await validateRequest(reqValidator.GetUniversalAuthForIdentityV1, req);
|
} = await validateRequest(reqValidator.GetUniversalAuthForIdentityV1, req);
|
||||||
|
|
||||||
const identityMembershipOrg = await IdentityMembershipOrg
|
const identityMembershipOrg = await IdentityMembershipOrg
|
||||||
.findOne({
|
.findOne({
|
||||||
identity: new Types.ObjectId(identityId)
|
identity: new Types.ObjectId(identityId)
|
||||||
@@ -880,7 +879,7 @@ export const getIdentityUniversalAuth = async (req: Request, res: Response) => {
|
|||||||
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) throw BadRequestError({
|
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.UNIVERSAL_AUTH) throw BadRequestError({
|
||||||
message: "The identity does not have universal auth configured"
|
message: "The identity does not have universal auth configured"
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
||||||
identity: identityMembershipOrg.identity._id,
|
identity: identityMembershipOrg.identity._id,
|
||||||
});
|
});
|
||||||
@@ -1017,11 +1016,11 @@ export const createUniversalAuthClientSecret = async (req: Request, res: Respons
|
|||||||
|
|
||||||
const clientSecret = crypto.randomBytes(32).toString("hex");
|
const clientSecret = crypto.randomBytes(32).toString("hex");
|
||||||
const clientSecretHash = await bcrypt.hash(clientSecret, await getSaltRounds());
|
const clientSecretHash = await bcrypt.hash(clientSecret, await getSaltRounds());
|
||||||
|
|
||||||
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
const identityUniversalAuth = await IdentityUniversalAuth.findOne({
|
||||||
identity: identityMembershipOrg.identity._id
|
identity: identityMembershipOrg.identity._id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!identityUniversalAuth) throw ResourceNotFoundError();
|
if (!identityUniversalAuth) throw ResourceNotFoundError();
|
||||||
|
|
||||||
const identityUniversalAuthClientSecret = await new IdentityUniversalAuthClientSecret({
|
const identityUniversalAuthClientSecret = await new IdentityUniversalAuthClientSecret({
|
||||||
@@ -1096,7 +1095,7 @@ export const getUniversalAuthClientSecretsDetails = async (req: Request, res: Re
|
|||||||
const {
|
const {
|
||||||
params: { identityId }
|
params: { identityId }
|
||||||
} = await validateRequest(reqValidator.GetUniversalAuthClientSecretsV1, req);
|
} = await validateRequest(reqValidator.GetUniversalAuthClientSecretsV1, req);
|
||||||
|
|
||||||
const identityMembershipOrg = await IdentityMembershipOrg.findOne({
|
const identityMembershipOrg = await IdentityMembershipOrg.findOne({
|
||||||
identity: new Types.ObjectId(identityId)
|
identity: new Types.ObjectId(identityId)
|
||||||
}).populate<{
|
}).populate<{
|
||||||
@@ -1199,7 +1198,7 @@ export const revokeUniversalAuthClientSecret = async (req: Request, res: Respons
|
|||||||
const {
|
const {
|
||||||
params: { identityId, clientSecretId }
|
params: { identityId, clientSecretId }
|
||||||
} = await validateRequest(reqValidator.RevokeUniversalAuthClientSecretV1, req);
|
} = await validateRequest(reqValidator.RevokeUniversalAuthClientSecretV1, req);
|
||||||
|
|
||||||
const identityMembershipOrg = await IdentityMembershipOrg
|
const identityMembershipOrg = await IdentityMembershipOrg
|
||||||
.findOne({
|
.findOne({
|
||||||
identity: new Types.ObjectId(identityId)
|
identity: new Types.ObjectId(identityId)
|
||||||
@@ -1247,7 +1246,7 @@ export const revokeUniversalAuthClientSecret = async (req: Request, res: Respons
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!clientSecretData) throw ResourceNotFoundError();
|
if (!clientSecretData) throw ResourceNotFoundError();
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
IIdentity,
|
IIdentity,
|
||||||
IdentityMembership,
|
IdentityMembership,
|
||||||
IdentityMembershipOrg,
|
IdentityMembershipOrg,
|
||||||
Key,
|
Key,
|
||||||
Membership,
|
Membership,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
Workspace
|
Workspace
|
||||||
@@ -182,11 +182,11 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
@@ -211,7 +211,7 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
}).populate("sender", "+publicKey");
|
}).populate("sender", "+publicKey");
|
||||||
|
|
||||||
if (!key) throw new Error("Failed to find workspace key");
|
if (!key) throw new Error(`getWorkspaceKey: Failed to find workspace key [workspaceId=${workspaceId}] [receiver=${req.user._id}]`);
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
@@ -257,26 +257,26 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"memberships": {
|
"memberships": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
$ref: "#/components/schemas/Membership"
|
$ref: "#/components/schemas/Membership"
|
||||||
},
|
},
|
||||||
"description": "Memberships of project"
|
"description": "Memberships of project"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,19 +321,19 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.parameters['membershipId'] = {
|
#swagger.parameters['membershipId'] = {
|
||||||
"description": "ID of project membership to update",
|
"description": "ID of project membership to update",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.requestBody = {
|
#swagger.requestBody = {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
@@ -354,13 +354,13 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"membership": {
|
"membership": {
|
||||||
$ref: "#/components/schemas/Membership",
|
$ref: "#/components/schemas/Membership",
|
||||||
"description": "Updated membership"
|
"description": "Updated membership"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,29 +412,29 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.parameters['membershipId'] = {
|
#swagger.parameters['membershipId'] = {
|
||||||
"description": "ID of project membership to delete",
|
"description": "ID of project membership to delete",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"membership": {
|
"membership": {
|
||||||
$ref: "#/components/schemas/Membership",
|
$ref: "#/components/schemas/Membership",
|
||||||
"description": "Deleted membership"
|
"description": "Deleted membership"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,14 +514,14 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const addIdentityToWorkspace = async (req: Request, res: Response) => {
|
export const addIdentityToWorkspace = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { workspaceId, identityId },
|
params: { workspaceId, identityId },
|
||||||
body: {
|
body: {
|
||||||
role
|
role
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.AddIdentityToWorkspaceV2, req);
|
} = await validateRequest(reqValidator.AddIdentityToWorkspaceV2, req);
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@@ -541,7 +541,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
message: `Identity with id ${identityId} already exists in project with id ${workspaceId}`
|
message: `Identity with id ${identityId} already exists in project with id ${workspaceId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const workspace = await Workspace.findById(workspaceId);
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
if (!workspace) throw ResourceNotFoundError();
|
if (!workspace) throw ResourceNotFoundError();
|
||||||
|
|
||||||
@@ -553,16 +553,16 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
message: `Failed to find identity with id ${identityId}`
|
message: `Failed to find identity with id ${identityId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!identityMembershipOrg.organization.equals(workspace.organization)) throw BadRequestError({
|
if (!identityMembershipOrg.organization.equals(workspace.organization)) throw BadRequestError({
|
||||||
message: "Failed to add identity to project in another organization"
|
message: "Failed to add identity to project in another organization"
|
||||||
});
|
});
|
||||||
|
|
||||||
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
||||||
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||||
|
|
||||||
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||||
message: "Failed to add identity to project with more privileged role"
|
message: "Failed to add identity to project with more privileged role"
|
||||||
});
|
});
|
||||||
|
|
||||||
let customRole;
|
let customRole;
|
||||||
@@ -574,18 +574,18 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
isOrgRole: false,
|
isOrgRole: false,
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identityMembership = await new IdentityMembership({
|
identityMembership = await new IdentityMembership({
|
||||||
identity: identityMembershipOrg.identity,
|
identity: identityMembershipOrg.identity,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
role: customRole ? CUSTOM : role,
|
role: customRole ? CUSTOM : role,
|
||||||
customRole
|
customRole
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
identityMembership
|
identityMembership
|
||||||
});
|
});
|
||||||
@@ -657,7 +657,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
role
|
role
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.UpdateIdentityWorkspaceRoleV2, req);
|
} = await validateRequest(reqValidator.UpdateIdentityWorkspaceRoleV2, req);
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@@ -667,7 +667,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.Identity
|
ProjectPermissionSub.Identity
|
||||||
);
|
);
|
||||||
|
|
||||||
let identityMembership = await IdentityMembership
|
let identityMembership = await IdentityMembership
|
||||||
.findOne({
|
.findOne({
|
||||||
identity: new Types.ObjectId(identityId),
|
identity: new Types.ObjectId(identityId),
|
||||||
@@ -681,21 +681,21 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
if (!identityMembership) throw BadRequestError({
|
if (!identityMembership) throw BadRequestError({
|
||||||
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityRolePermission = await getWorkspaceRolePermissions(
|
const identityRolePermission = await getWorkspaceRolePermissions(
|
||||||
identityMembership?.customRole?.slug ?? identityMembership.role,
|
identityMembership?.customRole?.slug ?? identityMembership.role,
|
||||||
identityMembership.workspace.toString()
|
identityMembership.workspace.toString()
|
||||||
);
|
);
|
||||||
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
||||||
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
||||||
message: "Failed to update role of more privileged identity"
|
message: "Failed to update role of more privileged identity"
|
||||||
});
|
});
|
||||||
|
|
||||||
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
||||||
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||||
|
|
||||||
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||||
message: "Failed to update identity to a more privileged role"
|
message: "Failed to update identity to a more privileged role"
|
||||||
});
|
});
|
||||||
|
|
||||||
let customRole;
|
let customRole;
|
||||||
@@ -707,11 +707,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
isOrgRole: false,
|
isOrgRole: false,
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identityMembership = await IdentityMembership.findOneAndUpdate(
|
identityMembership = await IdentityMembership.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
identity: identityMembership.identity._id,
|
identity: identityMembership.identity._id,
|
||||||
@@ -777,7 +777,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
params: { workspaceId, identityId }
|
params: { workspaceId, identityId }
|
||||||
} = await validateRequest(reqValidator.DeleteIdentityFromWorkspaceV2, req);
|
} = await validateRequest(reqValidator.DeleteIdentityFromWorkspaceV2, req);
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@@ -787,7 +787,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.Identity
|
ProjectPermissionSub.Identity
|
||||||
);
|
);
|
||||||
|
|
||||||
const identityMembership = await IdentityMembership
|
const identityMembership = await IdentityMembership
|
||||||
.findOne({
|
.findOne({
|
||||||
identity: new Types.ObjectId(identityId),
|
identity: new Types.ObjectId(identityId),
|
||||||
@@ -797,20 +797,20 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
identity: IIdentity,
|
identity: IIdentity,
|
||||||
customRole: IRole
|
customRole: IRole
|
||||||
}>("identity customRole");
|
}>("identity customRole");
|
||||||
|
|
||||||
if (!identityMembership) throw ResourceNotFoundError({
|
if (!identityMembership) throw ResourceNotFoundError({
|
||||||
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityRolePermission = await getWorkspaceRolePermissions(
|
const identityRolePermission = await getWorkspaceRolePermissions(
|
||||||
identityMembership?.customRole?.slug ?? identityMembership.role,
|
identityMembership?.customRole?.slug ?? identityMembership.role,
|
||||||
identityMembership.workspace.toString()
|
identityMembership.workspace.toString()
|
||||||
);
|
);
|
||||||
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
||||||
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
||||||
message: "Failed to remove more privileged identity from project"
|
message: "Failed to remove more privileged identity from project"
|
||||||
});
|
});
|
||||||
|
|
||||||
await IdentityMembership.findByIdAndDelete(identityMembership._id);
|
await IdentityMembership.findByIdAndDelete(identityMembership._id);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -862,7 +862,7 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
params: { workspaceId }
|
params: { workspaceId }
|
||||||
} = await validateRequest(reqValidator.GetWorkspaceIdentityMembersV2, req);
|
} = await validateRequest(reqValidator.GetWorkspaceIdentityMembersV2, req);
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ export const AddUniversalAuthToIdentityV1 = z.object({
|
|||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }]),
|
.default([{ ipAddress: "0.0.0.0/0" }]),
|
||||||
accessTokenTTL: z.number().int().min(0).default(7200),
|
accessTokenTTL: z.number().int().min(1).refine(value => value !== 0, {
|
||||||
|
message: "accessTokenTTL must have a non zero number",
|
||||||
|
}).default(2592000),
|
||||||
accessTokenMaxTTL: z.number().int().refine(value => value !== 0, {
|
accessTokenMaxTTL: z.number().int().refine(value => value !== 0, {
|
||||||
message: "accessTokenMaxTTL must have a non zero number",
|
message: "accessTokenMaxTTL must have a non zero number",
|
||||||
}).default(2592000), // 30 days
|
}).default(2592000), // 30 days
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
infisical:
|
||||||
|
address: "http://localhost:8080"
|
||||||
|
auth:
|
||||||
|
type: "universal-auth"
|
||||||
|
config:
|
||||||
|
client-id: "./client-id"
|
||||||
|
client-secret: "./client-secret"
|
||||||
|
remove_client_secret_on_read: false
|
||||||
|
sinks:
|
||||||
|
- type: "file"
|
||||||
|
config:
|
||||||
|
path: "access-token"
|
||||||
|
templates:
|
||||||
|
- source-path: my-dot-ev-secret-template
|
||||||
|
destination-path: my-dot-env.env
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
infisical:
|
|
||||||
address: "http://localhost:8080"
|
|
||||||
auth:
|
|
||||||
type: "token"
|
|
||||||
config:
|
|
||||||
token-path: "./role-id"
|
|
||||||
sinks:
|
|
||||||
- type: "file"
|
|
||||||
config:
|
|
||||||
path: "/Users/maidulislam/Desktop/test/infisical-token"
|
|
||||||
- type: "file"
|
|
||||||
config:
|
|
||||||
path: "access-token"
|
|
||||||
- type: "file"
|
|
||||||
config:
|
|
||||||
path: "maiduls-access-token"
|
|
||||||
templates:
|
|
||||||
+28
-8
@@ -425,24 +425,44 @@ func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceToken
|
|||||||
return createServiceTokenResponse, nil
|
return createServiceTokenResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CallServiceTokenV3Refresh(httpClient *resty.Client, request ServiceTokenV3RefreshTokenRequest) (ServiceTokenV3RefreshTokenResponse, error) {
|
func CallUniversalAuthLogin(httpClient *resty.Client, request UniversalAuthLoginRequest) (UniversalAuthLoginResponse, error) {
|
||||||
var serviceTokenV3RefreshTokenResponse ServiceTokenV3RefreshTokenResponse
|
var universalAuthLoginResponse UniversalAuthLoginResponse
|
||||||
response, err := httpClient.
|
response, err := httpClient.
|
||||||
R().
|
R().
|
||||||
SetResult(&serviceTokenV3RefreshTokenResponse).
|
SetResult(&universalAuthLoginResponse).
|
||||||
SetHeader("User-Agent", USER_AGENT).
|
SetHeader("User-Agent", USER_AGENT).
|
||||||
SetBody(request).
|
SetBody(request).
|
||||||
Post(fmt.Sprintf("%v/v3/service-token/me/token", config.INFISICAL_URL))
|
Post(fmt.Sprintf("%v/v1/auth/universal-auth/login/", config.INFISICAL_URL))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ServiceTokenV3RefreshTokenResponse{}, fmt.Errorf("CallServiceTokenV3Refresh: Unable to complete api request [err=%s]", err)
|
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return ServiceTokenV3RefreshTokenResponse{}, fmt.Errorf("CallServiceTokenV3Refresh: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return serviceTokenV3RefreshTokenResponse, nil
|
return universalAuthLoginResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallUniversalAuthRefreshAccessToken(httpClient *resty.Client, request UniversalAuthRefreshRequest) (UniversalAuthRefreshResponse, error) {
|
||||||
|
var universalAuthRefreshResponse UniversalAuthRefreshResponse
|
||||||
|
response, err := httpClient.
|
||||||
|
R().
|
||||||
|
SetResult(&universalAuthRefreshResponse).
|
||||||
|
SetHeader("User-Agent", USER_AGENT).
|
||||||
|
SetBody(request).
|
||||||
|
Post(fmt.Sprintf("%v/v1/auth/token/renew", config.INFISICAL_URL))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unable to complete api request [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.IsError() {
|
||||||
|
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return universalAuthRefreshResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Request) (GetRawSecretsV3Response, error) {
|
func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Request) (GetRawSecretsV3Response, error) {
|
||||||
@@ -466,7 +486,7 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
return GetRawSecretsV3Response{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return getRawSecretsV3Response, nil
|
return getRawSecretsV3Response, nil
|
||||||
|
|||||||
@@ -463,14 +463,27 @@ type CreateServiceTokenResponse struct {
|
|||||||
ServiceTokenData ServiceTokenData `json:"serviceTokenData"`
|
ServiceTokenData ServiceTokenData `json:"serviceTokenData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceTokenV3RefreshTokenRequest struct {
|
type UniversalAuthLoginRequest struct {
|
||||||
RefreshToken string `json:"refresh_token"`
|
ClientSecret string `json:"clientSecret"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
}
|
}
|
||||||
type ServiceTokenV3RefreshTokenResponse struct {
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
type UniversalAuthLoginResponse struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"accessToken"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
AccessTokenTTL int `json:"expiresIn"`
|
||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"tokenType"`
|
||||||
|
AccessTokenMaxTTL int `json:"accessTokenMaxTTL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UniversalAuthRefreshRequest struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UniversalAuthRefreshResponse struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
AccessTokenTTL int `json:"expiresIn"`
|
||||||
|
TokenType string `json:"tokenType"`
|
||||||
|
AccessTokenMaxTTL int `json:"accessTokenMaxTTL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetRawSecretsV3Request struct {
|
type GetRawSecretsV3Request struct {
|
||||||
|
|||||||
+249
-79
@@ -5,12 +5,12 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
@@ -44,8 +44,10 @@ type AuthConfig struct {
|
|||||||
Config interface{} `yaml:"config"`
|
Config interface{} `yaml:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenAuthConfig struct {
|
type UniversalAuth struct {
|
||||||
TokenPath string `yaml:"token-path"`
|
ClientIDPath string `yaml:"client-id"`
|
||||||
|
ClientSecretPath string `yaml:"client-secret"`
|
||||||
|
RemoveClientSecretOnRead bool `yaml:"remove_client_secret_on_read"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthConfig struct {
|
type OAuthConfig struct {
|
||||||
@@ -149,11 +151,12 @@ func ParseAgentConfig(filePath string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch rawConfig.Auth.Type {
|
switch rawConfig.Auth.Type {
|
||||||
case "token":
|
case "universal-auth":
|
||||||
var tokenConfig TokenAuthConfig
|
var tokenConfig UniversalAuth
|
||||||
if err := yaml.Unmarshal(configBytes, &tokenConfig); err != nil {
|
if err := yaml.Unmarshal(configBytes, &tokenConfig); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Auth.Config = tokenConfig
|
config.Auth.Config = tokenConfig
|
||||||
case "oauth": // aws, gcp, k8s service account, etc
|
case "oauth": // aws, gcp, k8s service account, etc
|
||||||
var oauthConfig OAuthConfig
|
var oauthConfig OAuthConfig
|
||||||
@@ -199,59 +202,231 @@ func ProcessTemplate(templatePath string, data interface{}, accessToken string)
|
|||||||
return &buf, nil
|
return &buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshTokenAndProcessTemplate(refreshToken string, config *Config, errChan chan error) {
|
type TokenManager struct {
|
||||||
for {
|
accessToken string
|
||||||
httpClient := resty.New()
|
accessTokenTTL time.Duration
|
||||||
httpClient.SetRetryCount(10000).
|
accessTokenMaxTTL time.Duration
|
||||||
SetRetryMaxWaitTime(20 * time.Second).
|
accessTokenFetchedTime time.Time
|
||||||
SetRetryWaitTime(5 * time.Second)
|
accessTokenRefreshedTime time.Time
|
||||||
|
mutex sync.Mutex
|
||||||
|
filePaths []Sink // Store file paths if needed
|
||||||
|
templates []Template
|
||||||
|
clientIdPath string
|
||||||
|
clientSecretPath string
|
||||||
|
newAccessTokenNotificationChan chan bool
|
||||||
|
removeClientSecretOnRead bool
|
||||||
|
cachedClientSecret string
|
||||||
|
}
|
||||||
|
|
||||||
tokenResponse, err := api.CallServiceTokenV3Refresh(httpClient, api.ServiceTokenV3RefreshTokenRequest{RefreshToken: refreshToken})
|
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool) *TokenManager {
|
||||||
if err != nil {
|
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead}
|
||||||
errChan <- fmt.Errorf("unable to complete renewal because [%s]", err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for _, sinkFile := range config.Sinks {
|
func (tm *TokenManager) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
|
||||||
if sinkFile.Type == "file" {
|
tm.mutex.Lock()
|
||||||
err = ioutil.WriteFile(sinkFile.Config.Path, []byte(tokenResponse.AccessToken), 0644)
|
defer tm.mutex.Unlock()
|
||||||
if err != nil {
|
|
||||||
errChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errChan <- errors.New("unsupported sink type. Only 'file' type is supported")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = tokenResponse.RefreshToken
|
tm.accessToken = token
|
||||||
nextRefreshCycle := time.Duration(tokenResponse.ExpiresIn-5) * time.Second // when the next access refresh will happen
|
tm.accessTokenTTL = accessTokenTTL
|
||||||
|
tm.accessTokenMaxTTL = accessTokenMaxTTL
|
||||||
|
|
||||||
d, err := time.ParseDuration(nextRefreshCycle.String())
|
tm.newAccessTokenNotificationChan <- true
|
||||||
if err != nil {
|
}
|
||||||
errChan <- fmt.Errorf("unable to parse refresh time because %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Msgf("token refreshed and saved to selected path; next cycle will occur in %s", d.String())
|
func (tm *TokenManager) GetToken() string {
|
||||||
|
tm.mutex.Lock()
|
||||||
|
defer tm.mutex.Unlock()
|
||||||
|
|
||||||
for _, secretTemplate := range config.Templates {
|
return tm.accessToken
|
||||||
processedTemplate, err := ProcessTemplate(secretTemplate.SourcePath, nil, tokenResponse.AccessToken)
|
}
|
||||||
if err != nil {
|
|
||||||
errChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := WriteBytesToFile(processedTemplate, secretTemplate.DestinationPath); err != nil {
|
// Fetches a new access token using client credentials
|
||||||
errChan <- err
|
func (tm *TokenManager) FetchNewAccessToken() error {
|
||||||
return
|
clientIDAsByte, err := ReadFile(tm.clientIdPath)
|
||||||
}
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
|
||||||
log.Info().Msgf("secret template at path %s has been rendered and saved to path %s", secretTemplate.SourcePath, secretTemplate.DestinationPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(nextRefreshCycle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
|
||||||
|
if err != nil {
|
||||||
|
if len(tm.cachedClientSecret) == 0 {
|
||||||
|
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
|
||||||
|
} else {
|
||||||
|
clientSecretAsByte = []byte(tm.cachedClientSecret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove client secret after first read
|
||||||
|
if tm.removeClientSecretOnRead {
|
||||||
|
os.Remove(tm.clientSecretPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := string(clientIDAsByte)
|
||||||
|
clientSecret := string(clientSecretAsByte)
|
||||||
|
|
||||||
|
// save as cache in memory
|
||||||
|
tm.cachedClientSecret = clientSecret
|
||||||
|
|
||||||
|
err, loginResponse := universalAuthLogin(clientId, clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessTokenTTL := time.Duration(loginResponse.AccessTokenTTL * int(time.Second))
|
||||||
|
accessTokenMaxTTL := time.Duration(loginResponse.AccessTokenMaxTTL * int(time.Second))
|
||||||
|
|
||||||
|
if accessTokenTTL <= time.Duration(5)*time.Second {
|
||||||
|
util.PrintErrorMessageAndExit("At this this, agent does not support refresh of tokens with 5 seconds or less ttl. Please increase access token ttl and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
tm.accessTokenFetchedTime = time.Now()
|
||||||
|
tm.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refreshes the existing access token
|
||||||
|
func (tm *TokenManager) RefreshAccessToken() error {
|
||||||
|
httpClient := resty.New()
|
||||||
|
httpClient.SetRetryCount(10000).
|
||||||
|
SetRetryMaxWaitTime(20 * time.Second).
|
||||||
|
SetRetryWaitTime(5 * time.Second)
|
||||||
|
|
||||||
|
accessToken := tm.GetToken()
|
||||||
|
response, err := api.CallUniversalAuthRefreshAccessToken(httpClient, api.UniversalAuthRefreshRequest{AccessToken: accessToken})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessTokenTTL := time.Duration(response.AccessTokenTTL * int(time.Second))
|
||||||
|
accessTokenMaxTTL := time.Duration(response.AccessTokenMaxTTL * int(time.Second))
|
||||||
|
tm.accessTokenRefreshedTime = time.Now()
|
||||||
|
|
||||||
|
tm.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TokenManager) ManageTokenLifecycle() {
|
||||||
|
for {
|
||||||
|
accessTokenMaxTTLExpiresInTime := tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second))
|
||||||
|
accessTokenRefreshedTime := tm.accessTokenRefreshedTime
|
||||||
|
|
||||||
|
if accessTokenRefreshedTime.IsZero() {
|
||||||
|
accessTokenRefreshedTime = tm.accessTokenFetchedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second))
|
||||||
|
|
||||||
|
if tm.accessTokenFetchedTime.IsZero() && tm.accessTokenRefreshedTime.IsZero() {
|
||||||
|
// case: init login to get access token
|
||||||
|
log.Info().Msg("attempting to authenticate...")
|
||||||
|
err := tm.FetchNewAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("unable to authenticate because %v. Will retry in 30 seconds", err)
|
||||||
|
|
||||||
|
// wait a bit before trying again
|
||||||
|
time.Sleep((30 * time.Second))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if time.Now().After(accessTokenMaxTTLExpiresInTime) {
|
||||||
|
log.Info().Msgf("token has reached max ttl, attempting to re authenticate...")
|
||||||
|
err := tm.FetchNewAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("unable to authenticate because %v. Will retry in 30 seconds", err)
|
||||||
|
|
||||||
|
// wait a bit before trying again
|
||||||
|
time.Sleep((30 * time.Second))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info().Msgf("attempting to refresh existing token...")
|
||||||
|
err := tm.RefreshAccessToken()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("unable to refresh token because %v. Will retry in 30 seconds", err)
|
||||||
|
|
||||||
|
// wait a bit before trying again
|
||||||
|
time.Sleep((30 * time.Second))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessTokenRefreshedTime.IsZero() {
|
||||||
|
accessTokenRefreshedTime = tm.accessTokenFetchedTime
|
||||||
|
} else {
|
||||||
|
accessTokenRefreshedTime = tm.accessTokenRefreshedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
nextAccessTokenExpiresInTime = accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second))
|
||||||
|
accessTokenMaxTTLExpiresInTime = tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second))
|
||||||
|
|
||||||
|
if nextAccessTokenExpiresInTime.After(accessTokenMaxTTLExpiresInTime) {
|
||||||
|
// case: Refreshed so close that the next refresh would occur beyond max ttl (this is because currently, token renew tries to add +access-token-ttl amount of time)
|
||||||
|
// example: access token ttl is 11 sec and max ttl is 30 sec. So it will start with 11 seconds, then 22 seconds but the next time you call refresh it would try to extend it to 33 but max ttl only allows 30, so the token will be valid until 30 before we need to reauth
|
||||||
|
time.Sleep(tm.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime))
|
||||||
|
} else {
|
||||||
|
time.Sleep(tm.accessTokenTTL - (5 * time.Second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TokenManager) WriteTokenToFiles() {
|
||||||
|
token := tm.GetToken()
|
||||||
|
for _, sinkFile := range tm.filePaths {
|
||||||
|
if sinkFile.Type == "file" {
|
||||||
|
err := ioutil.WriteFile(sinkFile.Config.Path, []byte(token), 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("unable to write file sink to path '%s' because %v", sinkFile.Config.Path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("new access token saved to file at path '%s'", sinkFile.Config.Path)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Error().Msg("unsupported sink type. Only 'file' type is supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TokenManager) FetchSecrets() {
|
||||||
|
log.Info().Msgf("template engine started...")
|
||||||
|
for {
|
||||||
|
token := tm.GetToken()
|
||||||
|
if token != "" {
|
||||||
|
for _, secretTemplate := range tm.templates {
|
||||||
|
processedTemplate, err := ProcessTemplate(secretTemplate.SourcePath, nil, token)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf("template engine: unable to render secrets because %s. Will try again on next cycle", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := WriteBytesToFile(processedTemplate, secretTemplate.DestinationPath); err != nil {
|
||||||
|
log.Error().Msgf("template engine: unable to write secrets to path because %s. Will try again on next cycle", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("template engine: secret template at path %s has been rendered and saved to path %s", secretTemplate.SourcePath, secretTemplate.DestinationPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch new secrets every 5 minutes (TODO: add PubSub in the future )
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func universalAuthLogin(clientId string, clientSecret string) (error, api.UniversalAuthLoginResponse) {
|
||||||
|
httpClient := resty.New()
|
||||||
|
httpClient.SetRetryCount(10000).
|
||||||
|
SetRetryMaxWaitTime(20 * time.Second).
|
||||||
|
SetRetryWaitTime(5 * time.Second)
|
||||||
|
|
||||||
|
tokenResponse, err := api.CallUniversalAuthLogin(httpClient, api.UniversalAuthLoginRequest{ClientId: clientId, ClientSecret: clientSecret})
|
||||||
|
if err != nil {
|
||||||
|
return err, api.UniversalAuthLoginResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, tokenResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
@@ -260,7 +435,7 @@ var agentCmd = &cobra.Command{
|
|||||||
infisical agent
|
infisical agent
|
||||||
`,
|
`,
|
||||||
Use: "agent",
|
Use: "agent",
|
||||||
Short: "Used to launch a client daemon that streamlines authentication and secret retrieval processes in some environments",
|
Short: "Used to launch a client daemon that streamlines authentication and secret retrieval processes in various environments",
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
@@ -282,36 +457,31 @@ var agentCmd = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
errChan := make(chan error)
|
if agentConfig.Auth.Type != "universal-auth" {
|
||||||
sigChan := make(chan os.Signal, 1)
|
util.PrintErrorMessageAndExit("Only auth type of 'universal-auth' is supported at this time")
|
||||||
|
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
switch configAuthType := agentConfig.Auth.Config.(type) {
|
|
||||||
case TokenAuthConfig:
|
|
||||||
content, err := ReadFile(configAuthType.TokenPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Msgf("unable to read initial token from file path %s because %v", configAuthType.TokenPath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken := string(content)
|
|
||||||
go refreshTokenAndProcessTemplate(refreshToken, agentConfig, errChan)
|
|
||||||
|
|
||||||
case OAuthConfig:
|
|
||||||
// future auth types
|
|
||||||
default:
|
|
||||||
log.Error().Msgf("unknown auth config type. Only 'file' type is supported")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
configUniversalAuthType := agentConfig.Auth.Config.(UniversalAuth)
|
||||||
case err := <-errChan:
|
|
||||||
log.Fatal().Msgf("agent stopped due to error: %v", err)
|
tokenRefreshNotifier := make(chan bool)
|
||||||
os.Exit(1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
case <-sigChan:
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
log.Info().Msg("agent is gracefully shutting...")
|
|
||||||
os.Exit(1)
|
filePaths := agentConfig.Sinks
|
||||||
|
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead)
|
||||||
|
|
||||||
|
go tm.ManageTokenLifecycle()
|
||||||
|
go tm.FetchSecrets()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tokenRefreshNotifier:
|
||||||
|
go tm.WriteTokenToFiles()
|
||||||
|
case <-sigChan:
|
||||||
|
log.Info().Msg("agent is gracefully shutting...")
|
||||||
|
// TODO: check if we are in the middle of writing files to disk
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ It eliminates the need to modify application logic by enabling clients to decide
|
|||||||
- Templating: Renders secrets via user provided templates to desired formats for applications to consume
|
- Templating: Renders secrets via user provided templates to desired formats for applications to consume
|
||||||
|
|
||||||
### Token renewal
|
### Token renewal
|
||||||
The Infisical agent can help manage the life cycle of access tokens. The token renewal process is split into two main components: a Method, which is the authentication process suitable for your current setup, and Sinks, which are the places where the agent deposits the new access token whenever it receives updates.
|
The Infisical agent can help manage the life cycle of access tokens. The token renewal process is split into two main components: a `Method`, which is the authentication process suitable for your current setup, and `Sinks`, which are the places where the agent deposits the new access token whenever it receives updates.
|
||||||
|
|
||||||
When the Infisical Agent is started, it will attempt to obtain a valid access token using the authentication method you have configured. If the agent is unable to fetch a valid token, the agent will keep trying, increasing the time between each attempt.
|
When the Infisical Agent is started, it will attempt to obtain a valid access token using the authentication method you have configured. If the agent is unable to fetch a valid token, the agent will keep trying, increasing the time between each attempt.
|
||||||
|
|
||||||
@@ -43,8 +43,10 @@ While specifying an authentication method is mandatory to start the agent, confi
|
|||||||
| Field | Description |
|
| Field | Description |
|
||||||
| ---------------------------- | ----------- |
|
| ---------------------------- | ----------- |
|
||||||
| `infisical.address` | The URL of the Infisical service. Default: `"https://app.infisical.com"`. |
|
| `infisical.address` | The URL of the Infisical service. Default: `"https://app.infisical.com"`. |
|
||||||
| `auth.type` | The type of authentication method used. Only `"token"` type is currently available |
|
| `auth.type` | The type of authentication method used. Only `"universal-auth"` type is currently available |
|
||||||
| `auth.config.token-path` | The file path where the initial token for authentication is stored. |
|
| `auth.config.client-id` | The file path where the universal-auth client id is stored. |
|
||||||
|
| `auth.config.client-secret` | The file path where the universal-auth client secret is stored. |
|
||||||
|
| `auth.config.remove_client_secret_on_read` | This will instruct the agent to remove the client secret from disk. |
|
||||||
| `sinks[].type` | The type of sink in a list of sinks. Each item specifies a sink type. Currently, only `"file"` type is available. |
|
| `sinks[].type` | The type of sink in a list of sinks. Each item specifies a sink type. Currently, only `"file"` type is available. |
|
||||||
| `sinks[].config.path` | The file path where the access token should be stored for each sink in the list. |
|
| `sinks[].config.path` | The file path where the access token should be stored for each sink in the list. |
|
||||||
| `templates[].source-path` | The path to the template file that should be used to render secrets. |
|
| `templates[].source-path` | The path to the template file that should be used to render secrets. |
|
||||||
@@ -60,9 +62,11 @@ Once you have the CLI installed, you will need to create a agent configuration f
|
|||||||
infisical:
|
infisical:
|
||||||
address: "https://app.infisical.com"
|
address: "https://app.infisical.com"
|
||||||
auth:
|
auth:
|
||||||
type: "token"
|
type: "universal-auth"
|
||||||
config:
|
config:
|
||||||
token-path: "/path/to/initial/token"
|
client-id: "./client-id"
|
||||||
|
client-secret: "./client-secret"
|
||||||
|
remove_client_secret_on_read: false
|
||||||
sinks:
|
sinks:
|
||||||
- type: "file"
|
- type: "file"
|
||||||
config:
|
config:
|
||||||
|
|||||||
Generated
+9067
-342
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,9 @@
|
|||||||
"storybook": "storybook dev -p 6006 -s ./public",
|
"storybook": "storybook dev -p 6006 -s ./public",
|
||||||
"build-storybook": "storybook build"
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@radix-ui/react-focus-scope": "1.0.4"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@casl/react": "^3.1.0",
|
"@casl/react": "^3.1.0",
|
||||||
@@ -30,16 +33,16 @@
|
|||||||
"@hookform/resolvers": "^2.9.10",
|
"@hookform/resolvers": "^2.9.10",
|
||||||
"@octokit/rest": "^19.0.7",
|
"@octokit/rest": "^19.0.7",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-checkbox": "^1.0.1",
|
"@radix-ui/react-checkbox": "^1.0.1",
|
||||||
"@radix-ui/react-dialog": "^1.0.2",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-hover-card": "^1.0.3",
|
"@radix-ui/react-hover-card": "^1.0.3",
|
||||||
"@radix-ui/react-label": "^2.0.0",
|
"@radix-ui/react-label": "^2.0.0",
|
||||||
"@radix-ui/react-popover": "^1.0.3",
|
"@radix-ui/react-popover": "^1.0.3",
|
||||||
"@radix-ui/react-popper": "^1.1.1",
|
"@radix-ui/react-popper": "^1.1.1",
|
||||||
"@radix-ui/react-progress": "^1.0.1",
|
"@radix-ui/react-progress": "^1.0.1",
|
||||||
"@radix-ui/react-select": "^1.2.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-switch": "^1.0.1",
|
"@radix-ui/react-switch": "^1.0.1",
|
||||||
"@radix-ui/react-tabs": "^1.0.2",
|
"@radix-ui/react-tabs": "^1.0.2",
|
||||||
"@radix-ui/react-toast": "^1.1.2",
|
"@radix-ui/react-toast": "^1.1.2",
|
||||||
|
|||||||
+1
-1
@@ -223,7 +223,7 @@ export const IdentityUniversalAuthForm = ({
|
|||||||
{...field}
|
{...field}
|
||||||
placeholder="2592000"
|
placeholder="2592000"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="1"
|
||||||
step="1"
|
step="1"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
+8
-6
@@ -88,10 +88,12 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
scopes: [{
|
scopes: [
|
||||||
secretPath: "/",
|
{
|
||||||
environment: currentWorkspace?.environments?.[0]?.slug
|
secretPath: "/",
|
||||||
}]
|
environment: currentWorkspace?.environments?.[0]?.slug
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,7 +138,7 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
plaintext: key,
|
plaintext: key,
|
||||||
key: randomBytes
|
key: randomBytes
|
||||||
});
|
});
|
||||||
|
|
||||||
const { serviceToken } = await createServiceToken.mutateAsync({
|
const { serviceToken } = await createServiceToken.mutateAsync({
|
||||||
encryptedKey: ciphertext,
|
encryptedKey: ciphertext,
|
||||||
iv,
|
iv,
|
||||||
@@ -279,7 +281,7 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{apiTokenExpiry.map(({ label, value }) => (
|
{apiTokenExpiry.map(({ label, value }) => (
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
<SelectItem value={String(value)} key={label}>
|
||||||
{label}
|
{label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
||||||
|
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
|
||||||
// import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
|
// import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
|
||||||
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
||||||
import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow";
|
import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow";
|
||||||
@@ -260,7 +261,7 @@ export const SecretOverviewPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
{/* <ProjectIndexSecretsSection decryptFileKey={latestFileKey!} /> */}
|
<ProjectIndexSecretsSection decryptFileKey={latestFileKey!} />
|
||||||
<div className="relative right-5 ml-4">
|
<div className="relative right-5 ml-4">
|
||||||
<NavHeader pageName={t("dashboard.title")} isProjectRelated />
|
<NavHeader pageName={t("dashboard.title")} isProjectRelated />
|
||||||
</div>
|
</div>
|
||||||
@@ -328,7 +329,7 @@ export const SecretOverviewPage = () => {
|
|||||||
<div className="flex items-center justify-center border-b border-mineshaft-600 px-5 pt-3.5 pb-3">
|
<div className="flex items-center justify-center border-b border-mineshaft-600 px-5 pt-3.5 pb-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="font-medium hover:text-mineshaft-100 duration-100 text-sm"
|
className="text-sm font-medium duration-100 hover:text-mineshaft-100"
|
||||||
onClick={() => handleExploreEnvClick(slug)}
|
onClick={() => handleExploreEnvClick(slug)}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
+10
-8
@@ -61,22 +61,24 @@ export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return !isBlindIndexedLoading && !isBlindIndexed ? (
|
// for non admin this would throw an error
|
||||||
<div className="p-4 mt-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
|
// so no need to render
|
||||||
|
return !isBlindIndexedLoading && typeof isBlindIndexed === "boolean" && !isBlindIndexed ? (
|
||||||
|
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
{isIndexing && (
|
{isIndexing && (
|
||||||
<div className="w-screen absolute top-0 left-0 h-screen z-50 bg-bunker-500 bg-opacity-80 flex items-center justify-center">
|
<div className="absolute top-0 left-0 z-50 flex h-screen w-screen items-center justify-center bg-bunker-500 bg-opacity-80">
|
||||||
<Spinner size="lg" className="text-primary" />
|
<Spinner size="lg" className="text-primary" />
|
||||||
<div className="flex flex-col space-y-1 ml-4">
|
<div className="ml-4 flex flex-col space-y-1">
|
||||||
<div className="text-3xl font-medium">Please wait</div>
|
<div className="text-3xl font-medium">Please wait</div>
|
||||||
<span className="inline-block">Re-indexing your secrets...</span>
|
<span className="inline-block">Re-indexing your secrets...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="mb-2 text-lg font-semibold">Enable Blind Indices</p>
|
<p className="mb-2 text-lg font-semibold">Enable Blind Indices</p>
|
||||||
<p className="text-gray-400 mb-4 leading-7">
|
<p className="mb-4 leading-7 text-gray-400">
|
||||||
Your project was created before the introduction of blind indexing.
|
Your project was created before the introduction of blind indexing. To continue accessing
|
||||||
To continue accessing secrets by name through the SDK, public API and web dashboard, please enable blind
|
secrets by name through the SDK, public API and web dashboard, please enable blind indexing.{" "}
|
||||||
indexing. <b>This is a one time process.</b>
|
<b>This is a one time process.</b>
|
||||||
</p>
|
</p>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user