diff --git a/server/src/models/User.ts b/server/src/models/User.ts index 9bc6affb..83156fd2 100644 --- a/server/src/models/User.ts +++ b/server/src/models/User.ts @@ -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 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, diff --git a/server/src/routes/api.ts b/server/src/routes/api.ts deleted file mode 100644 index cc417bfb..00000000 --- a/server/src/routes/api.ts +++ /dev/null @@ -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 }); - } -}); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 3effb0d2..c33409c2 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -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 }); + } +}); diff --git a/server/src/routes/service_account_credentials.json b/server/src/routes/service_account_credentials.json deleted file mode 100644 index 602fcbe0..00000000 --- a/server/src/routes/service_account_credentials.json +++ /dev/null @@ -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"} \ No newline at end of file diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx new file mode 100644 index 00000000..8133ae39 --- /dev/null +++ b/src/components/organisms/ApiKey.tsx @@ -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(null); + const [apiKeyName, setApiKeyName] = useState('Maxun API Key'); + const [loading, setLoading] = useState(true); + const [showKey, setShowKey] = useState(false); + const [copySuccess, setCopySuccess] = useState(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 ; + + return ( + + Manage Your API Key + + {apiKey ? ( + + + + + API Key Name + API Key + Actions + + + + + {apiKeyName} + {showKey ? `${apiKey?.substring(0, 10)}...` : '***************'} + + + + + + + + setShowKey(!showKey)}> + + + + + + + + + + + +
+
+ ) : ( + <> + You haven't generated an API key yet. + + + )} +
+ ); +}; + +export default ApiKeyManager; \ No newline at end of file diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index 50a9f849..eaa0e057 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -47,6 +47,10 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu alignItems: 'baseline', fontSize: 'medium', }} value="proxy" label="Proxy" /> + diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index a6a62258..e5d04b54 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -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 ; + case 'apikey': + return ; default: return null; }