diff --git a/.gitignore b/.gitignore index 71246f7..dd4875c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ NodeWarden-compat/ .codex-upstream/bitwarden-browser/ .reasonix/ +.upstream/ # Compatibility analysis documents BITWARDEN_COMPATIBILITY_ANALYSIS.md diff --git a/src/handlers/admin.ts b/src/handlers/admin.ts index e3f4384..8b3ae8d 100644 --- a/src/handlers/admin.ts +++ b/src/handlers/admin.ts @@ -277,12 +277,21 @@ export async function handleAdminDeleteAllInvites( env: Env, actorUser: User ): Promise { - void request; if (!isAdmin(actorUser)) { return errorResponse('Forbidden', 403); } const storage = new StorageService(env.DB); + const url = new URL(request.url); + if (url.searchParams.get('scope') === 'invalid') { + const deleted = await storage.deleteInvalidInvites(); + await writeAuditLog(storage, actorUser.id, 'admin.invite.delete_invalid', 'invite', null, { + deleted, + }, request); + + return jsonResponse({ deleted }, 200); + } + const deleted = await storage.deleteAllInvites(); await writeAuditLog(storage, actorUser.id, 'admin.invite.delete_all', 'invite', null, { deleted, diff --git a/src/services/storage-admin-repo.ts b/src/services/storage-admin-repo.ts index fd29188..2c9caba 100644 --- a/src/services/storage-admin-repo.ts +++ b/src/services/storage-admin-repo.ts @@ -159,6 +159,15 @@ export async function deleteInvite(db: D1Database, code: string): Promise 0; } +export async function deleteInvalidInvites(db: D1Database): Promise { + const now = new Date().toISOString(); + const result = await db + .prepare("DELETE FROM invites WHERE status != 'active' OR expires_at <= ?") + .bind(now) + .run(); + return Number(result.meta.changes ?? 0); +} + export async function deleteAllInvites(db: D1Database): Promise { const result = await db.prepare('DELETE FROM invites').run(); return Number(result.meta.changes ?? 0); diff --git a/src/services/storage.ts b/src/services/storage.ts index cb295d6..5ab76fa 100644 --- a/src/services/storage.ts +++ b/src/services/storage.ts @@ -25,6 +25,7 @@ import { assignInviteUsedBy as assignStoredInviteUsedBy, createInvite as createStoredInvite, deleteInvite as deleteStoredInvite, + deleteInvalidInvites as deleteStoredInvalidInvites, deleteAllInvites as deleteStoredInvites, getInvite as findStoredInvite, listAuditLogs as listStoredAuditLogs, @@ -333,6 +334,10 @@ export class StorageService { return deleteStoredInvite(this.db, code); } + async deleteInvalidInvites(): Promise { + return deleteStoredInvalidInvites(this.db); + } + async deleteAllInvites(): Promise { return deleteStoredInvites(this.db); } diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 282a818..65c0b26 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -2036,6 +2036,7 @@ export default function App() { onRemoveAllDevices: accountSecurityActions.openRemoveAllDevices, onRefreshAdmin: adminActions.refreshAdmin, onCreateInvite: adminActions.createInvite, + onDeleteInvalidInvites: adminActions.deleteInvalidInvites, onDeleteAllInvites: adminActions.deleteAllInvites, onToggleUserStatus: adminActions.toggleUserStatus, onDeleteUser: adminActions.deleteUser, diff --git a/webapp/src/components/AdminPage.tsx b/webapp/src/components/AdminPage.tsx index 844cf53..2909039 100644 --- a/webapp/src/components/AdminPage.tsx +++ b/webapp/src/components/AdminPage.tsx @@ -13,6 +13,7 @@ interface AdminPageProps { error: string; onRefresh: () => void; onCreateInvite: (hours: number) => Promise; + onDeleteInvalidInvites: () => Promise; onDeleteAllInvites: () => Promise; onToggleUserStatus: (userId: string, currentStatus: 'active' | 'banned') => Promise; onDeleteUser: (userId: string) => Promise; @@ -134,7 +135,10 @@ export default function AdminPage(props: AdminPageProps) {

{t('txt_invites')}

+