Skip to content

Commit c3227e3

Browse files
committed
feat: store date and name of keys
1 parent ce5a80a commit c3227e3

5 files changed

Lines changed: 130 additions & 91 deletions

File tree

src/db/file/users.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'fs';
22
import Datastore from '@seald-io/nedb';
3-
import { User } from '../types';
3+
import { PublicKeyRecord, User } from '../types';
44

55
const COMPACTION_INTERVAL = 1000 * 60 * 60 * 24; // once per day
66

@@ -138,60 +138,72 @@ export const getUsers = (query: any = {}) => {
138138
});
139139
};
140140

141-
export const getPublicKeys = (username: string): Promise<string[]> => {
141+
export const getPublicKeys = (username: string): Promise<PublicKeyRecord[]> => {
142142
return findUser(username).then((user) => {
143143
if (!user) {
144144
throw new Error('User not found');
145145
}
146-
return user.publicKeys || [];
146+
return Array.isArray(user.publicKeys) ? user.publicKeys : [];
147147
});
148148
};
149149

150-
export const addPublicKey = function (username: string, publicKey: string) {
151-
return new Promise<User>((resolve, reject) => {
150+
export const addPublicKey = (username: string, record: PublicKeyRecord): Promise<User> =>
151+
new Promise<User>((resolve, reject) => {
152152
findUser(username)
153153
.then((user) => {
154154
if (!user) {
155155
reject(new Error('User not found'));
156156
return;
157157
}
158-
if (!user.publicKeys) {
159-
user.publicKeys = [];
160-
}
161-
if (!user.publicKeys.includes(publicKey)) {
162-
user.publicKeys.push(publicKey);
163-
exports.updateUser(user).then(resolve).catch(reject);
164-
} else {
165-
resolve(user);
166-
}
158+
if (!Array.isArray(user.publicKeys)) user.publicKeys = [];
159+
160+
const exists = user.publicKeys.some((r) => r.key === record.key);
161+
if (exists) return resolve(user);
162+
163+
user.publicKeys.push({
164+
...record,
165+
addedAt: record.addedAt ?? new Date().toISOString(),
166+
});
167+
168+
updateUser(user)
169+
.then(() => resolve(user))
170+
.catch(reject);
167171
})
168172
.catch(reject);
169173
});
170-
};
171174

172-
export const removePublicKey = function (username: string, publicKey: string) {
175+
export const removePublicKey = function (username: string, canonicalKey: string) {
173176
return new Promise<User>((resolve, reject) => {
174177
findUser(username)
175178
.then((user) => {
176179
if (!user) {
177180
reject(new Error('User not found'));
178181
return;
179182
}
180-
if (!user.publicKeys) {
183+
184+
if (!Array.isArray(user.publicKeys)) {
181185
user.publicKeys = [];
182-
resolve(user);
183-
return;
186+
return resolve(user);
184187
}
185-
user.publicKeys = user.publicKeys.filter((key) => key !== publicKey);
186-
exports.updateUser(user).then(resolve).catch(reject);
188+
189+
const before = user.publicKeys.length;
190+
user.publicKeys = user.publicKeys.filter((rec) => rec.key !== canonicalKey);
191+
192+
if (user.publicKeys.length === before) {
193+
return reject(new Error('SSH key not found'));
194+
}
195+
196+
updateUser(user)
197+
.then(() => resolve(user))
198+
.catch(reject);
187199
})
188200
.catch(reject);
189201
});
190202
};
191203

192-
export const findUserBySSHKey = function (sshKey: string) {
193-
return new Promise<User | null>((resolve, reject) => {
194-
db.findOne({ publicKeys: sshKey }, (err, doc) => {
204+
export const findUserBySSHKey = (sshKey: string): Promise<User | null> =>
205+
new Promise((resolve, reject) => {
206+
db.findOne({ 'publicKeys.key': sshKey }, (err, doc) => {
195207
if (err) {
196208
reject(err);
197209
} else {
@@ -203,4 +215,3 @@ export const findUserBySSHKey = function (sshKey: string) {
203215
}
204216
});
205217
});
206-
};

src/db/mongo/users.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { User } from '../types';
1+
import { PublicKeyRecord, User } from '../types';
22

33
const connect = require('./helper').connect;
44
const collectionName = 'users';
@@ -48,31 +48,44 @@ export const updateUser = async (user: User) => {
4848
await collection.updateOne({ username: user.username }, { $set: user }, options);
4949
};
5050

51-
export const addPublicKey = async (username: string, publicKey: string) => {
51+
export const addPublicKey = async (username: string, record: PublicKeyRecord) => {
5252
const collection = await connect(collectionName);
53+
5354
return collection.updateOne(
54-
{ username: username.toLowerCase() },
55-
{ $addToSet: { publicKeys: publicKey } },
55+
{
56+
username: username.toLowerCase(),
57+
'publicKeys.key': { $ne: record.key },
58+
},
59+
{
60+
$push: {
61+
publicKeys: {
62+
key: record.key,
63+
name: record.name,
64+
addedAt: record.addedAt ?? new Date().toISOString(),
65+
},
66+
},
67+
},
5668
);
5769
};
5870

59-
export const removePublicKey = async (username: string, publicKey: string) => {
71+
export const removePublicKey = async (username: string, canonicalKey: string) => {
6072
const collection = await connect(collectionName);
73+
6174
return collection.updateOne(
6275
{ username: username.toLowerCase() },
63-
{ $pull: { publicKeys: publicKey } },
76+
{ $pull: { publicKeys: { key: canonicalKey } } },
6477
);
6578
};
6679

67-
export const findUserBySSHKey = async function (sshKey: string) {
80+
export const findUserBySSHKey = async (sshKey: string): Promise<User | null> => {
6881
const collection = await connect(collectionName);
69-
return collection.findOne({ publicKeys: { $eq: sshKey } });
82+
return collection.findOne({ 'publicKeys.key': sshKey });
7083
};
7184

72-
export const getPublicKeys = async function (username: string): Promise<string[]> {
73-
const user = await findUser(username);
85+
export const getPublicKeys = async (username: string): Promise<PublicKeyRecord[]> => {
86+
const user = await findUser(username.toLowerCase());
7487
if (!user) {
7588
throw new Error('User not found');
7689
}
77-
return user.publicKeys || [];
90+
return Array.isArray(user.publicKeys) ? user.publicKeys : [];
7891
};

src/db/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export type Repo = {
1515
_id: string;
1616
};
1717

18+
export type PublicKeyRecord = {
19+
key: string;
20+
name: string;
21+
addedAt: string;
22+
};
23+
1824
export type User = {
1925
_id: string;
2026
username: string;
@@ -23,7 +29,7 @@ export type User = {
2329
email: string;
2430
admin: boolean;
2531
oidcId: string | null;
26-
publicKeys: string[];
32+
publicKeys: PublicKeyRecord[];
2733
};
2834

2935
export type Push = {

src/service/routes/users.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ router.get('/', async (req, res) => {
2323
for (const user of users) {
2424
delete user.password;
2525
if (user.publicKeys) {
26-
user.publicKeys = user.publicKeys.map((key) => key.trim());
26+
user.publicKeys = user.publicKeys.map((rec) => rec.key.trim());
2727
}
2828
}
2929
res.send(users);
@@ -53,7 +53,10 @@ router.post('/:username/ssh-keys', async (req, res) => {
5353
return;
5454
}
5555

56-
const { publicKey } = req.body;
56+
const { publicKey, name } = req.body;
57+
58+
if (!name || typeof name !== 'string')
59+
return res.status(400).json({ error: 'Key name is required' });
5760
if (!publicKey) {
5861
res.status(400).json({ error: 'Public key is required' });
5962
return;
@@ -67,7 +70,12 @@ router.post('/:username/ssh-keys', async (req, res) => {
6770
}
6871

6972
try {
70-
await db.addPublicKey(targetUsername, canonicalKey);
73+
const record = {
74+
key: canonicalKey,
75+
name: name.trim(),
76+
addedAt: new Date().toISOString(),
77+
};
78+
await db.addPublicKey(targetUsername, record);
7179
res.status(201).json({ message: 'SSH key added successfully' });
7280
} catch (error) {
7381
console.error('Error adding SSH key:', error);
@@ -127,17 +135,16 @@ router.delete('/:username/ssh-keys/fingerprint', async (req, res) => {
127135

128136
try {
129137
const keys = await db.getPublicKeys(targetUsername);
130-
console.log(`Found ${keys} keys for user ${targetUsername}`);
131-
const keyToDelete = keys.find((k) => {
132-
const keyFingerprint = fingerprintSHA256(k);
138+
const keyToDelete = keys.find((rec) => {
139+
const keyFingerprint = fingerprintSHA256(rec.key);
133140
return keyFingerprint === fingerprint;
134141
});
135142

136143
if (!keyToDelete) {
137144
return res.status(404).json({ error: 'SSH key not found for supplied fingerprint' });
138145
}
139146

140-
await db.removePublicKey(targetUsername, keyToDelete);
147+
await db.removePublicKey(targetUsername, keyToDelete.key);
141148
res.status(200).json({ message: 'SSH key removed successfully' });
142149
} catch (err) {
143150
console.error('Error removing SSH key:', err);
@@ -159,7 +166,11 @@ router.get('/:username/ssh-keys', async (req, res) => {
159166

160167
try {
161168
const keys = await db.getPublicKeys(targetUsername);
162-
const result = keys.map((k) => fingerprintSHA256(k));
169+
const result = keys.map((rec) => ({
170+
name: rec.name,
171+
fingerprint: fingerprintSHA256(rec.key),
172+
addedAt: rec.addedAt,
173+
}));
163174

164175
res.status(200).json({ publicKeys: result });
165176
} catch (err) {

0 commit comments

Comments
 (0)