@@ -5,6 +5,7 @@ interface UserAttributes {
|
||||
id: number;
|
||||
email: string;
|
||||
password: string;
|
||||
api_key_name?: string | null;
|
||||
api_key?: string | null;
|
||||
proxy_url?: string | null;
|
||||
proxy_username?: string | null;
|
||||
@@ -18,6 +19,7 @@ class User extends Model<UserAttributes, UserCreationAttributes> implements User
|
||||
public id!: number;
|
||||
public email!: string;
|
||||
public password!: string;
|
||||
public api_key_name!: string | null;
|
||||
public api_key!: string | null;
|
||||
public proxy_url!: string | null;
|
||||
public proxy_username!: string | null;
|
||||
@@ -43,6 +45,11 @@ User.init(
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
api_key_name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: 'Maxun API Key',
|
||||
},
|
||||
api_key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { genAPIKey } from '../utils/api';
|
||||
import User from '../models/User';
|
||||
|
||||
export const router = Router();
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: { id: string };
|
||||
}
|
||||
|
||||
router.get('/generate-api-key', async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ ok: false, error: 'Unauthorized' });
|
||||
}
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: { exclude: ['password'] },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
if (user.api_key) {
|
||||
return res.status(400).json({ message: 'API key already exists' });
|
||||
}
|
||||
|
||||
const apiKey = genAPIKey();
|
||||
|
||||
user.api_key = apiKey;
|
||||
await user.save();
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'API key generated successfully',
|
||||
api_key: apiKey,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: 'Error generating API key', error });
|
||||
}
|
||||
});
|
||||
@@ -17,7 +17,7 @@ router.post('/register', async (req, res) => {
|
||||
if (!email) return res.status(400).send('Email is required')
|
||||
if (!password || password.length < 6) return res.status(400).send('Password is required and must be at least 6 characters')
|
||||
|
||||
let userExist = await User.findOne({ where: { email } });
|
||||
let userExist = await User.findOne({ raw: true, where: { email } });
|
||||
if (userExist) return res.status(400).send('User already exists')
|
||||
|
||||
const hashedPassword = await hashPassword(password)
|
||||
@@ -107,11 +107,9 @@ router.post('/generate-api-key', requireSignIn, async (req: AuthenticatedRequest
|
||||
if (user.api_key) {
|
||||
return res.status(400).json({ message: 'API key already exists' });
|
||||
}
|
||||
|
||||
const apiKey = genAPIKey();
|
||||
|
||||
user.api_key = apiKey;
|
||||
await user.save();
|
||||
await user.update({ api_key: apiKey });
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'API key generated successfully',
|
||||
@@ -121,3 +119,47 @@ router.post('/generate-api-key', requireSignIn, async (req: AuthenticatedRequest
|
||||
return res.status(500).json({ message: 'Error generating API key', error });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/api-key', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ ok: false, error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
raw: true,
|
||||
attributes: ['api_key'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'API key fetched successfully',
|
||||
api_key: user.api_key || null,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: 'Error fetching API key', error });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/delete-api-key', requireSignIn, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.user.id, { raw: true });
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
if (!user.api_key) {
|
||||
return res.status(404).json({ message: 'API Key not found' });
|
||||
}
|
||||
|
||||
await User.update({ api_key: null }, { where: { id: req.user.id } });
|
||||
|
||||
return res.status(200).json({ message: 'API Key deleted successfully' });
|
||||
} catch (error: any) {
|
||||
return res.status(500).json({ message: 'Error deleting API key', error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"type":"service_account","project_id":"maxun12345","private_key_id":"45859ac1196ba6a371bf5427eb990e44b61d4237","private_key":"-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0Sx73gWatvgGX\nhhtt/isiaH+EL1Y9K3X0404ZZndm7iNvILk190Jv2RjDNjASvWZBMcH72MKwaMkP\neYspK33DiA7+IDzE+tVefrC8PFPBepO3VtVfNeKe6xBMa3j2TwlfVgpDqPQBun69\nLuhvG1QMh1GfCnTi0wSeksRSiTS2KlmAuY79I/Y8KRQefLwoPZDDjVD9/3B0Xgly\nC3kKAtmfOSi+U1BUdVf3J58Tj57Yge2QpSBHBPjxwL60iadJoBSgcvBUXg+VgdXg\nqNyENYau5ohyvOjtW4Fhv/d7g/lyi84NBEn6+2ljFzY2xE0tZYM1CLouCU9MeDDu\ngvcHzGOnAgMBAAECggEAUwJbSZ18hvX6iacnLedeOMNgIp4g2J0P/CZDqRIyW2O9\nUMcCP0SOhCyyZ/6k95vYXp/oLmpIiSxVlhhh6BysFMCqFnsFP7Q1VQKDoMct0OD8\n7ea+6s1Gf+C+alD5Kn6NVCWrKCe74Kfa/oOAZNdyRSpwfAc2ddCuScxNM6sUZ+Ik\nK89fM576j8rfBAF1JW4ynQE/oM1y9A+88o1g602tOC5rm4vZH/Jv9tZwnfHMV+S7\n2PMSGDD1N4Mq1z3Gg3HjRHC/zo6SW6Jvk4O6mx9scIaui+u3nn7qQUVCLSMiFV3i\nsPv9zLktadu0h9o+6pCHSDuOFvFQOFIVEMU0fmgkQQKBgQD1Vnitk6X3aRBI/1u9\nJCk1l0GarfvdUed5fb3qRwH85vEhwmWKyglwDtWxxYNSNOIlpxOGKnq9CMdpkF15\nQO1bgbhsrbx75YYT29ulbAhLjm/tIP3OdvokzsH/Lz9qVh0AnEi+lgueRjerALvZ\nfZuLeSFi3FlxEX229a89TN9c0QKBgQC8IP4ea+Zu9hK+8qsF6htQEMqYagAMI63R\nwTVzRHL4H/GGYcs39ZjA9F0y0g65MAtT7+PfR8QifNUAJNRAnu5/CPDyQX9GvBM2\n9uBnwI1pJSdse1m0ez7kigl2TQDYaOADoSvRGALmBAFXXB7CNXgy8l1aQBiRvZjy\nulBQCcn29wKBgQDYQxM5ns9L2lc6oa6Sec/Bp8Vyvi8olsw+sfK5E0LTVhf0tFGi\nGBpdpxYEEKGD044NtstlFwj+nUB684ESI4OXiC+zzSo65MZdtw5VMXfWcoaDNvPE\nDejOjVtAwLtb1vDV2u3c4pL3P9pOaOUuAKUeOvaNGMPXAZ4Zq1R/6sVyIQKBgH93\nCzapfPPpnkHqQZ48RE03U015ZJbVBcx80C5NTmh3nDmkwQAlU15JM2xfjsJCnyo7\n+3UpNub3FYqHaZhvFsDT2g0J+6Z9f7daBinF+QootlF2Mg1rA+3s6QRSoCQAyucq\nqHl/f1dBl3cNX3nOqKY8OKwRiZQVli+/tPLF7yV5AoGBAI8+aLt3R9rioBtPFDUK\nuwxoeide0LH8UWFa5H+LV/13sWk6io/E6gdQOi1A2CrOdR+YFTCUG3VbIszbWCba\ne4Ws8Yy0CAvz0ePVE2aySJE0aRQWZH2BjPIckeGE8AGxViS1JiiX+wruYJiLSzx9\n+ejlxtjjkUsMoDd3trSI/s5d\n-----END PRIVATE KEY-----\n","client_email":"maxun-999@maxun12345.iam.gserviceaccount.com","client_id":"108198347095941384494","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/maxun-999%40maxun12345.iam.gserviceaccount.com","universe_domain":"googleapis.com"}
|
||||
141
src/components/organisms/ApiKey.tsx
Normal file
141
src/components/organisms/ApiKey.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Paper,
|
||||
} from '@mui/material';
|
||||
import { ContentCopy, Visibility, Delete } from '@mui/icons-material';
|
||||
import styled from 'styled-components';
|
||||
import axios from 'axios';
|
||||
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
|
||||
const Container = styled(Box)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 50px;
|
||||
margin-left: 50px;
|
||||
`;
|
||||
|
||||
const ApiKeyManager = () => {
|
||||
const [apiKey, setApiKey] = useState<string | null>(null);
|
||||
const [apiKeyName, setApiKeyName] = useState<string>('Maxun API Key');
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [showKey, setShowKey] = useState<boolean>(false);
|
||||
const [copySuccess, setCopySuccess] = useState<boolean>(false);
|
||||
const { notify } = useGlobalInfoStore();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchApiKey = async () => {
|
||||
try {
|
||||
const { data } = await axios.get('http://localhost:8080/auth/api-key');
|
||||
setApiKey(data.api_key);
|
||||
} catch (error: any) {
|
||||
notify('error', `Failed to fetch API Key - ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchApiKey();
|
||||
}, []);
|
||||
|
||||
const generateApiKey = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await axios.post('http://localhost:8080/auth/generate-api-key');
|
||||
setApiKey(data.api_key);
|
||||
notify('success', `Generated API Key successfully`);
|
||||
} catch (error: any) {
|
||||
notify('error', `Failed to generate API Key - ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteApiKey = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.delete('http://localhost:8080/auth/delete-api-key');
|
||||
setApiKey(null);
|
||||
notify('success', 'API Key deleted successfully');
|
||||
} catch (error: any) {
|
||||
notify('error', `Failed to delete API Key - ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (apiKey) {
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
setCopySuccess(true);
|
||||
setTimeout(() => setCopySuccess(false), 2000);
|
||||
notify('info', 'Copied to clipboard');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <CircularProgress />;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h5">Manage Your API Key</Typography>
|
||||
|
||||
{apiKey ? (
|
||||
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>API Key Name</TableCell>
|
||||
<TableCell>API Key</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>{apiKeyName}</TableCell>
|
||||
<TableCell>{showKey ? `${apiKey?.substring(0, 10)}...` : '***************'}</TableCell>
|
||||
<TableCell>
|
||||
<Tooltip title="Copy API Key">
|
||||
<IconButton onClick={copyToClipboard}>
|
||||
<ContentCopy />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={showKey ? 'Hide API Key' : 'Show API Key'}>
|
||||
<IconButton onClick={() => setShowKey(!showKey)}>
|
||||
<Visibility />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete API Key">
|
||||
<IconButton onClick={deleteApiKey} color="error">
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<>
|
||||
<Typography>You haven't generated an API key yet.</Typography>
|
||||
<Button onClick={generateApiKey} variant="contained" color="primary">
|
||||
Generate API Key
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiKeyManager;
|
||||
@@ -47,6 +47,10 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
|
||||
alignItems: 'baseline',
|
||||
fontSize: 'medium',
|
||||
}} value="proxy" label="Proxy" />
|
||||
<Tab sx={{
|
||||
alignItems: 'baseline',
|
||||
fontSize: 'medium',
|
||||
}} value="apikey" label="Generate API Key" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { MainMenu } from "../components/organisms/MainMenu";
|
||||
import { Grid, Stack } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import { Recordings } from "../components/organisms/Recordings";
|
||||
import { Runs } from "../components/organisms/Runs";
|
||||
import ProxyForm from '../components/organisms/ProxyForm';
|
||||
import ApiKey from '../components/organisms/ApiKey';
|
||||
import { useGlobalInfoStore } from "../context/globalInfo";
|
||||
import { createRunForStoredRecording, interpretStoredRecording, notifyAboutAbort, scheduleStoredRecording } from "../api/storage";
|
||||
import { handleUploadCredentials } from "../api/integration"
|
||||
@@ -145,6 +146,8 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
||||
/>;
|
||||
case 'proxy':
|
||||
return <ProxyForm />;
|
||||
case 'apikey':
|
||||
return <ApiKey />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user