feat: admin controls

This commit is contained in:
Zoe
2026-10-15 10:07:41 -05:00
parent b6f4d85ed7
commit ff7797d3f8
41 changed files with 612 additions and 427 deletions
+10 -12
View File
@@ -5,7 +5,7 @@ import { base64ToUint8Array, decrypt, deriveKey, encryptData, uint8ArrayToBase64
const emit = defineEmits(['confirm', 'cancel']);
const { user } = useAuth();
const { providers } = await useModels();
const { providers, refresh } = await useModels();
const INPUT_CLASS = 'w-full h-9 px-2.5 text-sm rounded-md bg-[var(--bg-container)] placeholder:text-[var(--text-tertiary)]';
@@ -45,18 +45,19 @@ const importKey = (jwk: JsonWebKey) =>
// one. The server never sees the plaintext keys.
const decryptProviderKeys = async (
oldJwk: JsonWebKey,
): Promise<Result<{ id: string; plaintext: string }[], string[]>> => {
): Promise<Result<{ id: string; originalApiKey: string; plaintext: string }[], string[]>> => {
const oldKey = await importKey(oldJwk);
const decrypted: { id: string; plaintext: string }[] = [];
const decrypted: { id: string; originalApiKey: string; plaintext: string }[] = [];
const failed: string[] = [];
for (const provider of providers.value) {
if (!provider.config.apiKey) continue;
try {
const plaintext = await decrypt(oldKey, base64ToUint8Array(provider.config.apiKey));
decrypted.push({ id: provider.id, plaintext });
const originalApiKey = provider.config.apiKey;
const plaintext = await decrypt(oldKey, base64ToUint8Array(originalApiKey));
decrypted.push({ id: provider.id, originalApiKey, plaintext });
} catch {
failed.push(provider.name);
}
@@ -101,9 +102,9 @@ const submit = async () => {
steps[1]!.state = 'active';
const newJwk = await deriveKey(form.next, userId);
const newKey = await importKey(newJwk);
const providerKeys: { id: string; apiKey: string }[] = [];
for (const { id, plaintext } of decrypted.data) {
providerKeys.push({ id, apiKey: uint8ArrayToBase64(await encryptData(newKey, plaintext)) });
const providerKeys: { id: string; originalApiKey: string; apiKey: string }[] = [];
for (const { id, originalApiKey, plaintext } of decrypted.data) {
providerKeys.push({ id, originalApiKey, apiKey: uint8ArrayToBase64(await encryptData(newKey, plaintext)) });
}
steps[1]!.state = 'done';
@@ -142,10 +143,7 @@ const submit = async () => {
// from here on this browser has to use the new key
localStorage.setItem('encryptionKey', JSON.stringify(newJwk));
const newKeys = new Map(providerKeys.map(p => [p.id, p.apiKey]));
providers.value = providers.value.map(p =>
newKeys.has(p.id) ? { ...p, config: { ...p.config, apiKey: newKeys.get(p.id)! } } : p
);
await refresh();
// the server signs the other devices out as part of the same request
steps[2]!.state = 'done';