Merge pull request #935 from getmaxun/api-key-create

feat: display api key creation date
This commit is contained in:
Karishma Shukla
2026-01-04 18:11:19 +05:30
committed by GitHub
3 changed files with 29 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ interface UserAttributes {
password: string; password: string;
api_key_name?: string | null; api_key_name?: string | null;
api_key?: string | null; api_key?: string | null;
api_key_created_at?: Date | null;
proxy_url?: string | null; proxy_url?: string | null;
proxy_username?: string | null; proxy_username?: string | null;
proxy_password?: string | null; proxy_password?: string | null;
@@ -20,6 +21,7 @@ class User extends Model<UserAttributes, UserCreationAttributes> implements User
public password!: string; public password!: string;
public api_key_name!: string | null; public api_key_name!: string | null;
public api_key!: string | null; public api_key!: string | null;
public api_key_created_at!: Date | null;
public proxy_url!: string | null; public proxy_url!: string | null;
public proxy_username!: string | null; public proxy_username!: string | null;
public proxy_password!: string | null; public proxy_password!: string | null;
@@ -53,6 +55,10 @@ User.init(
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
}, },
api_key_created_at: {
type: DataTypes.DATE,
allowNull: true,
},
proxy_url: { proxy_url: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,

View File

@@ -255,8 +255,9 @@ router.post(
return res.status(400).json({ message: "API key already exists" }); return res.status(400).json({ message: "API key already exists" });
} }
const apiKey = genAPIKey(); const apiKey = genAPIKey();
const createdAt = new Date();
await user.update({ api_key: apiKey }); await user.update({ api_key: apiKey, api_key_created_at: createdAt })
capture("maxun-oss-api-key-created", { capture("maxun-oss-api-key-created", {
user_id: user.id, user_id: user.id,
@@ -266,6 +267,7 @@ router.post(
return res.status(200).json({ return res.status(200).json({
message: "API key generated successfully", message: "API key generated successfully",
api_key: apiKey, api_key: apiKey,
api_key_created_at: createdAt,
}); });
} catch (error) { } catch (error) {
return res return res
@@ -290,7 +292,7 @@ router.get(
const user = await User.findByPk(req.user.id, { const user = await User.findByPk(req.user.id, {
raw: true, raw: true,
attributes: ["api_key"], attributes: ["api_key", "api_key_created_at"]
}); });
if (!user) { if (!user) {
@@ -305,6 +307,7 @@ router.get(
ok: true, ok: true,
message: "API key fetched successfully", message: "API key fetched successfully",
api_key: user.api_key || null, api_key: user.api_key || null,
api_key_created_at: user.api_key_created_at || null,
}); });
} catch (error) { } catch (error) {
console.error('API Key fetch error:', error); console.error('API Key fetch error:', error);
@@ -336,7 +339,7 @@ router.delete(
return res.status(404).json({ message: "API Key not found" }); return res.status(404).json({ message: "API Key not found" });
} }
await User.update({ api_key: null }, { where: { id: req.user.id } }); await User.update({ api_key: null, api_key_created_at: null }, { where: { id: req.user.id } });
capture("maxun-oss-api-key-deleted", { capture("maxun-oss-api-key-deleted", {
user_id: user.id, user_id: user.id,

View File

@@ -34,6 +34,7 @@ const ApiKeyManager = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [apiKey, setApiKey] = useState<string | null>(null); const [apiKey, setApiKey] = useState<string | null>(null);
const [apiKeyName, setApiKeyName] = useState<string>(t('apikey.default_name')); const [apiKeyName, setApiKeyName] = useState<string>(t('apikey.default_name'));
const [apiKeyCreatedAt, setApiKeyCreatedAt] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [showKey, setShowKey] = useState<boolean>(false); const [showKey, setShowKey] = useState<boolean>(false);
const [copySuccess, setCopySuccess] = useState<boolean>(false); const [copySuccess, setCopySuccess] = useState<boolean>(false);
@@ -44,6 +45,7 @@ const ApiKeyManager = () => {
try { try {
const { data } = await axios.get(`${apiUrl}/auth/api-key`); const { data } = await axios.get(`${apiUrl}/auth/api-key`);
setApiKey(data.api_key); setApiKey(data.api_key);
setApiKeyCreatedAt(data.api_key_created_at);
} catch (error: any) { } catch (error: any) {
notify('error', t('apikey.notifications.fetch_error', { error: error.message })); notify('error', t('apikey.notifications.fetch_error', { error: error.message }));
} finally { } finally {
@@ -60,7 +62,7 @@ const ApiKeyManager = () => {
try { try {
const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`);
setApiKey(data.api_key); setApiKey(data.api_key);
setApiKeyCreatedAt(data.api_key_created_at);
notify('success', t('apikey.notifications.generate_success')); notify('success', t('apikey.notifications.generate_success'));
} catch (error: any) { } catch (error: any) {
notify('error', t('apikey.notifications.generate_error', { error: error.message })); notify('error', t('apikey.notifications.generate_error', { error: error.message }));
@@ -74,6 +76,7 @@ const ApiKeyManager = () => {
try { try {
await axios.delete(`${apiUrl}/auth/delete-api-key`); await axios.delete(`${apiUrl}/auth/delete-api-key`);
setApiKey(null); setApiKey(null);
setApiKeyCreatedAt(null);
notify('success', t('apikey.notifications.delete_success')); notify('success', t('apikey.notifications.delete_success'));
} catch (error: any) { } catch (error: any) {
notify('error', t('apikey.notifications.delete_error', { error: error.message })); notify('error', t('apikey.notifications.delete_error', { error: error.message }));
@@ -128,12 +131,13 @@ const ApiKeyManager = () => {
</Typography> </Typography>
{apiKey ? ( {apiKey ? (
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}> <TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
<Table> <Table sx={{ tableLayout: 'fixed', width: '100%' }}>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>{t('apikey.table.name')}</TableCell> <TableCell>{t('apikey.table.name')}</TableCell>
<TableCell>{t('apikey.table.key')}</TableCell> <TableCell>{t('apikey.table.key')}</TableCell>
<TableCell>{t('apikey.table.actions')}</TableCell> {apiKeyCreatedAt && <TableCell>Created On</TableCell>}
<TableCell align="center" sx={{ width: 160 }}>{t('apikey.table.actions')}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -144,7 +148,16 @@ const ApiKeyManager = () => {
{showKey ? `${apiKey?.substring(0, 10)}...` : '**********'} {showKey ? `${apiKey?.substring(0, 10)}...` : '**********'}
</Box> </Box>
</TableCell> </TableCell>
<TableCell> {apiKeyCreatedAt && (
<TableCell>
{new Date(apiKeyCreatedAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</TableCell>
)}
<TableCell align="right" sx={{ width: 160 }}>
<Tooltip title={t('apikey.actions.copy')}> <Tooltip title={t('apikey.actions.copy')}>
<IconButton onClick={copyToClipboard}> <IconButton onClick={copyToClipboard}>
<ContentCopy /> <ContentCopy />