mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 20:27:43 +00:00
Merge pull request #1112 from Infisical/auth-jwt-standardization
API Key V2
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
|
import * as usersController from "./usersController";
|
||||||
import * as secretsController from "./secretsController";
|
import * as secretsController from "./secretsController";
|
||||||
import * as workspacesController from "./workspacesController";
|
import * as workspacesController from "./workspacesController";
|
||||||
import * as authController from "./authController";
|
import * as authController from "./authController";
|
||||||
import * as signupController from "./signupController";
|
import * as signupController from "./signupController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
usersController,
|
||||||
authController,
|
authController,
|
||||||
secretsController,
|
secretsController,
|
||||||
signupController,
|
signupController,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { APIKeyDataV2 } from "../../models";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return API keys belonging to current user.
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getMyAPIKeys = async (req: Request, res: Response) => {
|
||||||
|
const apiKeyData = await APIKeyDataV2.find({
|
||||||
|
user: req.user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
apiKeyData
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { APIKeyDataV2 } from "../../../models/apiKeyDataV2";
|
||||||
|
import { validateRequest } from "../../../helpers/validation";
|
||||||
|
import { BadRequestError } from "../../../utils/errors";
|
||||||
|
import * as reqValidator from "../../../validation";
|
||||||
|
import { createToken } from "../../../helpers";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create API key data v2
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const createAPIKeyData = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.CreateAPIKeyV3, req);
|
||||||
|
|
||||||
|
const apiKeyData = await new APIKeyDataV2({
|
||||||
|
name,
|
||||||
|
user: req.user._id,
|
||||||
|
usageCount: 0,
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
const apiKey = createToken({
|
||||||
|
payload: {
|
||||||
|
authTokenType: AuthTokenType.API_KEY,
|
||||||
|
apiKeyDataId: apiKeyData._id.toString(),
|
||||||
|
userId: req.user._id.toString()
|
||||||
|
},
|
||||||
|
secret: await getAuthSecret()
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
apiKeyData,
|
||||||
|
apiKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update API key data v2 with id [apiKeyDataId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const updateAPIKeyData = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: { apiKeyDataId },
|
||||||
|
body: {
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.UpdateAPIKeyV3, req);
|
||||||
|
|
||||||
|
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: new Types.ObjectId(apiKeyDataId),
|
||||||
|
user: req.user._id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!apiKeyData) throw BadRequestError({
|
||||||
|
message: "Failed to update API key"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
apiKeyData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete API key data v2 with id [apiKeyDataId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const deleteAPIKeyData = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: { apiKeyDataId }
|
||||||
|
} = await validateRequest(reqValidator.DeleteAPIKeyV3, req);
|
||||||
|
|
||||||
|
const apiKeyData = await APIKeyDataV2.findOneAndDelete({
|
||||||
|
_id: new Types.ObjectId(apiKeyDataId),
|
||||||
|
user: req.user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKeyData) throw BadRequestError({
|
||||||
|
message: "Failed to delete API key"
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
apiKeyData
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||||
|
import * as apiKeyDataController from "./apiKeyDataController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
serviceTokenDataController
|
serviceTokenDataController,
|
||||||
|
apiKeyDataController
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ import { EEAuditLogService, EELicenseService } from "../../services";
|
|||||||
import { getJwtServiceTokenSecret } from "../../../config";
|
import { getJwtServiceTokenSecret } from "../../../config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return project key for service token
|
* Return project key for service token V3
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +57,7 @@ export const getServiceTokenDataKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create service token data
|
* Create service token data V3
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
@@ -165,7 +165,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update service token data with id [serviceTokenDataId]
|
* Update service token V3 data with id [serviceTokenDataId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../../middleware";
|
||||||
|
import { AuthMode } from "../../../variables";
|
||||||
|
import { apiKeyDataController } from "../../controllers/v3";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
apiKeyDataController.createAPIKeyData
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:apiKeyDataId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
apiKeyDataController.updateAPIKeyData
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:apiKeyDataId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
apiKeyDataController.deleteAPIKeyData
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import serviceTokenData from "./serviceTokenData";
|
import serviceTokenData from "./serviceTokenData";
|
||||||
|
import apiKeyData from "./apiKeyData";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
serviceTokenData
|
serviceTokenData,
|
||||||
|
apiKeyData
|
||||||
}
|
}
|
||||||
+37
-16
@@ -4,6 +4,7 @@ import jwt from "jsonwebtoken";
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import {
|
import {
|
||||||
APIKeyData,
|
APIKeyData,
|
||||||
|
APIKeyDataV2,
|
||||||
ITokenVersion,
|
ITokenVersion,
|
||||||
IUser,
|
IUser,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
@@ -105,6 +106,7 @@ export const validateAuthMode = ({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return user payload corresponding to JWT token [authTokenValue]
|
* Return user payload corresponding to JWT token [authTokenValue]
|
||||||
|
* that is either for browser / CLI or API Key
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.authTokenValue - JWT token value
|
* @param {String} obj.authTokenValue - JWT token value
|
||||||
* @returns {User} user - user corresponding to JWT token
|
* @returns {User} user - user corresponding to JWT token
|
||||||
@@ -120,7 +122,41 @@ export const getAuthUserPayload = async ({
|
|||||||
jwt.verify(authTokenValue, await getAuthSecret())
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
);
|
);
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN) throw UnauthorizedRequestError();
|
if (
|
||||||
|
decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN &&
|
||||||
|
decodedToken.authTokenType !== AuthTokenType.API_KEY
|
||||||
|
) {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType === AuthTokenType.ACCESS_TOKEN) {
|
||||||
|
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
||||||
|
user: decodedToken.userId
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenVersion) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError();
|
||||||
|
} else if (decodedToken.authTokenType === AuthTokenType.API_KEY) {
|
||||||
|
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: new Types.ObjectId(decodedToken.apiKeyDataId),
|
||||||
|
user: new Types.ObjectId(decodedToken.userId)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lastUsed: new Date(),
|
||||||
|
$inc: { usageCount: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!apiKeyData) throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: new Types.ObjectId(decodedToken.userId),
|
_id: new Types.ObjectId(decodedToken.userId),
|
||||||
@@ -130,21 +166,6 @@ export const getAuthUserPayload = async ({
|
|||||||
|
|
||||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
|
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
|
||||||
|
|
||||||
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
|
||||||
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
|
||||||
user: user._id,
|
|
||||||
}, {
|
|
||||||
lastUsed: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to validate access token",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to validate access token",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actor: {
|
actor: {
|
||||||
type: ActorType.USER,
|
type: ActorType.USER,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
secretApprovalRequest as v1SecretApprovalRequest,
|
secretApprovalRequest as v1SecretApprovalRequest,
|
||||||
secretScanning as v1SecretScanningRouter
|
secretScanning as v1SecretScanningRouter
|
||||||
} from "./ee/routes/v1";
|
} from "./ee/routes/v1";
|
||||||
|
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
|
||||||
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
|
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
|
||||||
import {
|
import {
|
||||||
auth as v1AuthRouter,
|
auth as v1AuthRouter,
|
||||||
@@ -68,6 +69,7 @@ import {
|
|||||||
auth as v3AuthRouter,
|
auth as v3AuthRouter,
|
||||||
secrets as v3SecretsRouter,
|
secrets as v3SecretsRouter,
|
||||||
signup as v3SignupRouter,
|
signup as v3SignupRouter,
|
||||||
|
users as v3UsersRouter,
|
||||||
workspaces as v3WorkspacesRouter
|
workspaces as v3WorkspacesRouter
|
||||||
} from "./routes/v3";
|
} from "./routes/v3";
|
||||||
import { healthCheck } from "./routes/status";
|
import { healthCheck } from "./routes/status";
|
||||||
@@ -180,7 +182,8 @@ const main = async () => {
|
|||||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||||
app.use("/api/v1/sso", eeSSORouter);
|
app.use("/api/v1/sso", eeSSORouter);
|
||||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||||
app.use("/api/v3/service-token", v3ServiceTokenDataRouter);
|
app.use("/api/v3/api-key", v3apiKeyDataRouter); // new
|
||||||
|
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new
|
||||||
|
|
||||||
// v1 routes
|
// v1 routes
|
||||||
app.use("/api/v1/signup", v1SignupRouter);
|
app.use("/api/v1/signup", v1SignupRouter);
|
||||||
@@ -226,6 +229,7 @@ const main = async () => {
|
|||||||
app.use("/api/v3/secrets", v3SecretsRouter);
|
app.use("/api/v3/secrets", v3SecretsRouter);
|
||||||
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
||||||
app.use("/api/v3/signup", v3SignupRouter);
|
app.use("/api/v3/signup", v3SignupRouter);
|
||||||
|
app.use("/api/v3/users", v3UsersRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface IAPIKeyDataV2 extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
lastUsed?: Date
|
||||||
|
usageCount: number;
|
||||||
|
expiresAt?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKeyDataV2Schema = new Schema(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
lastUsed: {
|
||||||
|
type: Date,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
usageCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const APIKeyDataV2 = model<IAPIKeyDataV2>("APIKeyDataV2", apiKeyDataV2Schema);
|
||||||
@@ -24,9 +24,10 @@ export * from "./user";
|
|||||||
export * from "./userAction";
|
export * from "./userAction";
|
||||||
export * from "./workspace";
|
export * from "./workspace";
|
||||||
export * from "./serviceTokenData"; // TODO: deprecate
|
export * from "./serviceTokenData"; // TODO: deprecate
|
||||||
export * from "./apiKeyData";
|
export * from "./serviceTokenDataV3";
|
||||||
|
export * from "./serviceTokenDataV3Key";
|
||||||
|
export * from "./apiKeyData"; // TODO: deprecate
|
||||||
|
export * from "./apiKeyDataV2";
|
||||||
export * from "./loginSRPDetail";
|
export * from "./loginSRPDetail";
|
||||||
export * from "./tokenVersion";
|
export * from "./tokenVersion";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
export * from "./serviceTokenDataV3";
|
|
||||||
export * from "./serviceTokenDataV3Key";
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const serviceTokenDataV3Schema = new Schema(
|
|||||||
},
|
},
|
||||||
isActive: {
|
isActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
lastUsed: {
|
lastUsed: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
import { serviceTokenDataController } from "../../controllers/v2";
|
import { serviceTokenDataController } from "../../controllers/v2";
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO: deprecate (moving to ST V3)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
||||||
@@ -14,7 +14,7 @@ router.get(
|
|||||||
serviceTokenDataController.getServiceTokenData
|
serviceTokenDataController.getServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post( // TODO: deprecate (moving to ST V3)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
@@ -22,7 +22,7 @@ router.post(
|
|||||||
serviceTokenDataController.createServiceTokenData
|
serviceTokenDataController.createServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete( // TODO: deprecate (moving to ST V3)
|
||||||
"/:serviceTokenDataId",
|
"/:serviceTokenDataId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
@@ -30,4 +30,4 @@ router.delete(
|
|||||||
serviceTokenDataController.deleteServiceTokenData
|
serviceTokenDataController.deleteServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -36,7 +36,7 @@ router.get(
|
|||||||
usersController.getMyOrganizations
|
usersController.getMyOrganizations
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get( // TODO: deprecate (moving to API Key V2)
|
||||||
"/me/api-keys",
|
"/me/api-keys",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import auth from "./auth";
|
import auth from "./auth";
|
||||||
|
import users from "./users";
|
||||||
import secrets from "./secrets";
|
import secrets from "./secrets";
|
||||||
import workspaces from "./workspaces";
|
import workspaces from "./workspaces";
|
||||||
import signup from "./signup";
|
import signup from "./signup";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
auth,
|
auth,
|
||||||
|
users,
|
||||||
secrets,
|
secrets,
|
||||||
signup,
|
signup,
|
||||||
workspaces
|
workspaces
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../middleware";
|
||||||
|
import { AuthMode } from "../../variables";
|
||||||
|
import { usersController } from "../../controllers/v3";
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/me/api-keys",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
usersController.getMyAPIKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const CreateAPIKeyV3 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAPIKeyV3 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
apiKeyDataId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteAPIKeyV3 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
apiKeyDataId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -10,3 +10,4 @@ export * from "./secrets";
|
|||||||
export * from "./serviceAccount";
|
export * from "./serviceAccount";
|
||||||
export * from "./serviceTokenData";
|
export * from "./serviceTokenData";
|
||||||
export * from "./serviceTokenDataV3";
|
export * from "./serviceTokenDataV3";
|
||||||
|
export * from "./apiKeyDataV3";
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export enum AuthTokenType {
|
|||||||
REFRESH_TOKEN = "refreshToken",
|
REFRESH_TOKEN = "refreshToken",
|
||||||
SIGNUP_TOKEN = "signupToken",
|
SIGNUP_TOKEN = "signupToken",
|
||||||
MFA_TOKEN = "mfaToken",
|
MFA_TOKEN = "mfaToken",
|
||||||
PROVIDER_TOKEN = "providerToken"
|
PROVIDER_TOKEN = "providerToken",
|
||||||
|
API_KEY = "apiKey"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthMode {
|
export enum AuthMode {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
useCreateAPIKeyV2,
|
||||||
|
useDeleteAPIKeyV2,
|
||||||
|
useUpdateAPIKeyV2} from "./queries";
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { userKeys } from "../users/queries";
|
||||||
|
import {
|
||||||
|
APIKeyDataV2,
|
||||||
|
CreateAPIKeyDataV2DTO,
|
||||||
|
CreateServiceTokenDataV3Res,
|
||||||
|
DeleteAPIKeyDataV2DTO,
|
||||||
|
UpdateAPIKeyDataV2DTO} from "./types";
|
||||||
|
|
||||||
|
export const useCreateAPIKeyV2 = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<CreateServiceTokenDataV3Res, {}, CreateAPIKeyDataV2DTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
name
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v3/api-key", {
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateAPIKeyV2 = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<APIKeyDataV2, {}, UpdateAPIKeyDataV2DTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
apiKeyDataId,
|
||||||
|
name
|
||||||
|
}) => {
|
||||||
|
const { data: { apiKeyData } } = await apiRequest.patch(`/api/v3/api-key/${apiKeyDataId}`, {
|
||||||
|
name
|
||||||
|
});
|
||||||
|
return apiKeyData;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteAPIKeyV2 = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation<APIKeyDataV2, {}, DeleteAPIKeyDataV2DTO>({
|
||||||
|
mutationFn: async ({
|
||||||
|
apiKeyDataId
|
||||||
|
}) => {
|
||||||
|
const { data: { apiKeyData } } = await apiRequest.delete(`/api/v3/api-key/${apiKeyDataId}`);
|
||||||
|
return apiKeyData;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export type APIKeyDataV2 = {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
user: string;
|
||||||
|
lastUsed?: string;
|
||||||
|
usageCount: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateAPIKeyDataV2DTO = {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateServiceTokenDataV3Res = {
|
||||||
|
apiKeyData: APIKeyDataV2;
|
||||||
|
apiKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateAPIKeyDataV2DTO = {
|
||||||
|
apiKeyDataId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeleteAPIKeyDataV2DTO = {
|
||||||
|
apiKeyDataId: string;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from "./apiKeys";
|
||||||
export * from "./auditLogs";
|
export * from "./auditLogs";
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./bots";
|
export * from "./bots";
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export {
|
|||||||
useDeleteServiceToken,
|
useDeleteServiceToken,
|
||||||
useDeleteServiceTokenV3,
|
useDeleteServiceTokenV3,
|
||||||
useGetUserWsServiceTokens,
|
useGetUserWsServiceTokens,
|
||||||
useUpdateServiceTokenV3} from "./queries";
|
useUpdateServiceTokenV3
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export {
|
|||||||
useDeleteOrgMembership,
|
useDeleteOrgMembership,
|
||||||
useDeleteUser,
|
useDeleteUser,
|
||||||
useGetMyAPIKeys,
|
useGetMyAPIKeys,
|
||||||
|
useGetMyAPIKeysV2,
|
||||||
useGetMyIp,
|
useGetMyIp,
|
||||||
useGetMyOrganizationProjects,
|
useGetMyOrganizationProjects,
|
||||||
useGetMySessions,
|
useGetMySessions,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { setAuthToken } from "@app/reactQuery";
|
import { setAuthToken } from "@app/reactQuery";
|
||||||
|
|
||||||
|
import { APIKeyDataV2 } from "../apiKeys/types";
|
||||||
import { useUploadWsKey } from "../keys/queries";
|
import { useUploadWsKey } from "../keys/queries";
|
||||||
import { workspaceKeys } from "../workspace/queries";
|
import { workspaceKeys } from "../workspace/queries";
|
||||||
import {
|
import {
|
||||||
@@ -24,12 +25,13 @@ import {
|
|||||||
User
|
User
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
const userKeys = {
|
export const userKeys = {
|
||||||
getUser: ["user"] as const,
|
getUser: ["user"] as const,
|
||||||
userAction: ["user-action"] as const,
|
userAction: ["user-action"] as const,
|
||||||
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
|
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
|
||||||
myIp: ["ip"] as const,
|
myIp: ["ip"] as const,
|
||||||
myAPIKeys: ["api-keys"] as const,
|
myAPIKeys: ["api-keys"] as const,
|
||||||
|
myAPIKeysV2: ["api-keys-v2"] as const,
|
||||||
mySessions: ["sessions"] as const,
|
mySessions: ["sessions"] as const,
|
||||||
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
||||||
};
|
};
|
||||||
@@ -270,7 +272,7 @@ export const useGetMyIp = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetMyAPIKeys = () => {
|
export const useGetMyAPIKeys = () => { // TODO: deprecate (moving to API Key V2)
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: userKeys.myAPIKeys,
|
queryKey: userKeys.myAPIKeys,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -281,7 +283,18 @@ export const useGetMyAPIKeys = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateAPIKey = () => {
|
export const useGetMyAPIKeysV2 = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: userKeys.myAPIKeysV2,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data: { apiKeyData } } = await apiRequest.get<{ apiKeyData: APIKeyDataV2[] }>("/api/v3/users/me/api-keys");
|
||||||
|
return apiKeyData;
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateAPIKey = () => { // TODO: deprecate (moving to API Key V2)
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => {
|
mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => {
|
||||||
@@ -298,7 +311,7 @@ export const useCreateAPIKey = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDeleteAPIKey = () => {
|
export const useDeleteAPIKey = () => { // TODO: deprecate (moving to API Key V2)
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (apiKeyDataId: string) => {
|
mutationFn: async (apiKeyDataId: string) => {
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent} from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import {
|
||||||
|
useCreateAPIKeyV2,
|
||||||
|
useUpdateAPIKeyV2
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
const schema = yup.object({
|
||||||
|
name: yup.string().required("API Key V2 name is required")
|
||||||
|
}).required();
|
||||||
|
|
||||||
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
popUp: UsePopUpState<["apiKeyV2"]>;
|
||||||
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["apiKeyV2"]>, state?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APIKeyV2Modal = ({
|
||||||
|
popUp,
|
||||||
|
handlePopUpToggle
|
||||||
|
}: Props) => {
|
||||||
|
const [newAPIKey, setNewAPIKey] = useState("");
|
||||||
|
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
|
||||||
|
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
|
const { mutateAsync: createMutateAsync } = useCreateAPIKeyV2();
|
||||||
|
const { mutateAsync: updateMutateAsync } = useUpdateAPIKeyV2();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (isAPIKeyCopied) {
|
||||||
|
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [setIsAPIKeyCopied]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const apiKeyData = popUp?.apiKeyV2?.data as {
|
||||||
|
apiKeyDataId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiKeyData) {
|
||||||
|
reset({
|
||||||
|
name: apiKeyData.name
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reset({
|
||||||
|
name: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [popUp?.apiKeyV2?.data]);
|
||||||
|
|
||||||
|
const copyTokenToClipboard = () => {
|
||||||
|
navigator.clipboard.writeText(newAPIKey);
|
||||||
|
setIsAPIKeyCopied.on();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFormSubmit = async ({
|
||||||
|
name
|
||||||
|
}: FormData) => {
|
||||||
|
try {
|
||||||
|
const apiKeyData = popUp?.apiKeyV2?.data as {
|
||||||
|
apiKeyDataId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apiKeyData) {
|
||||||
|
// update
|
||||||
|
|
||||||
|
await updateMutateAsync({
|
||||||
|
apiKeyDataId: apiKeyData.apiKeyDataId,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpToggle("apiKeyV2", false);
|
||||||
|
} else {
|
||||||
|
// create
|
||||||
|
|
||||||
|
const { apiKey } = await createMutateAsync({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
setNewAPIKey(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: `Successfully ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
reset();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: `Failed to ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAPIKey = Boolean(newAPIKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp?.apiKeyV2?.isOpen}
|
||||||
|
onOpenChange={(isOpen) => {
|
||||||
|
handlePopUpToggle("apiKeyV2", isOpen);
|
||||||
|
reset();
|
||||||
|
setNewAPIKey("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title={`${popUp?.apiKeyV2?.data ? "Update" : "Create"} API Key V2`}>
|
||||||
|
{!hasAPIKey ? (
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Name"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="My API Key"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{popUp?.apiKeyV2?.data ? "Update" : "Create"}
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||||
|
<p className="mr-4 break-all">{newAPIKey}</p>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={copyTokenToClipboard}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
|
||||||
|
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||||
|
Click to copy
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useDeleteAPIKeyV2 } from "@app/hooks/api";
|
||||||
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { APIKeyV2Modal } from "./APIKeyV2Modal";
|
||||||
|
import { APIKeyV2Table } from "./APIKeyV2Table";
|
||||||
|
|
||||||
|
export const APIKeyV2Section = () => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { mutateAsync: deleteMutateAsync } = useDeleteAPIKeyV2();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"apiKeyV2",
|
||||||
|
"deleteAPIKeyV2"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const onDeleteAPIKeyDataSubmit = async (apiKeyDataId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteMutateAsync({
|
||||||
|
apiKeyDataId
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted API Key V2",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("deleteAPIKeyV2");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to delete API Key V2",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
|
||||||
|
<div className="flex justify-between mb-8">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">
|
||||||
|
API Keys V2 (Beta)
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
type="submit"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("apiKeyV2")}
|
||||||
|
>
|
||||||
|
Add API Key
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<APIKeyV2Table
|
||||||
|
handlePopUpOpen={handlePopUpOpen}
|
||||||
|
/>
|
||||||
|
<APIKeyV2Modal
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.deleteAPIKeyV2.isOpen}
|
||||||
|
title={`Are you sure want to delete ${
|
||||||
|
(popUp?.deleteAPIKeyV2?.data as { name: string })?.name || ""
|
||||||
|
}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("deleteAPIKeyV2", isOpen)}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={() =>
|
||||||
|
onDeleteAPIKeyDataSubmit(
|
||||||
|
(popUp?.deleteAPIKeyV2?.data as { apiKeyDataId: string })?.apiKeyDataId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { faKey, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
useGetMyAPIKeysV2
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["deleteAPIKeyV2", "apiKeyV2"]>,
|
||||||
|
data?: {
|
||||||
|
apiKeyDataId?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APIKeyV2Table = ({
|
||||||
|
handlePopUpOpen
|
||||||
|
}: Props) => {
|
||||||
|
const { data, isLoading } = useGetMyAPIKeysV2();
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="">Name</Th>
|
||||||
|
<Th className="">Last Used</Th>
|
||||||
|
<Th className="">Created At</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isLoading && <TableSkeleton columns={4} innerKey="api-keys-v2" />}
|
||||||
|
{!isLoading &&
|
||||||
|
data &&
|
||||||
|
data.length > 0 &&
|
||||||
|
data.map(({
|
||||||
|
_id,
|
||||||
|
name,
|
||||||
|
lastUsed,
|
||||||
|
createdAt
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Tr className="h-10" key={`api-key-v2-${_id}`}>
|
||||||
|
<Td>{name}</Td>
|
||||||
|
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
|
||||||
|
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||||
|
<Td className="flex justify-end">
|
||||||
|
<IconButton
|
||||||
|
onClick={async () => {
|
||||||
|
handlePopUpOpen("apiKeyV2", {
|
||||||
|
apiKeyDataId: _id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faPencil} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("deleteAPIKeyV2", {
|
||||||
|
apiKeyDataId: _id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{!isLoading && data && data?.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={4}>
|
||||||
|
<EmptyState title="No API key v2 on file" icon={faKey} />
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { APIKeyV2Section } from "./APIKeyV2Section";
|
||||||
+5
-1
@@ -1,7 +1,11 @@
|
|||||||
|
// import { APIKeyV2Section } from "../APIKeyV2Section";
|
||||||
import { APIKeySection } from "../APIKeySection";
|
import { APIKeySection } from "../APIKeySection";
|
||||||
|
|
||||||
export const PersonalAPIKeyTab = () => {
|
export const PersonalAPIKeyTab = () => {
|
||||||
return (
|
return (
|
||||||
<APIKeySection />
|
<>
|
||||||
|
{/* <APIKeyV2Section /> */}
|
||||||
|
<APIKeySection />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user