diff --git a/matrixgw_backend/src/controllers/tokens_controller.rs b/matrixgw_backend/src/controllers/tokens_controller.rs index 49350e3..997fbca 100644 --- a/matrixgw_backend/src/controllers/tokens_controller.rs +++ b/matrixgw_backend/src/controllers/tokens_controller.rs @@ -1,7 +1,7 @@ use crate::controllers::HttpResult; use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; -use crate::users::{APIToken, BaseAPIToken}; -use actix_web::HttpResponse; +use crate::users::{APIToken, APITokenID, BaseAPIToken}; +use actix_web::{HttpResponse, web}; /// Create a new token pub async fn create(auth: AuthExtractor) -> HttpResult { @@ -34,3 +34,15 @@ pub async fn get_list(auth: AuthExtractor) -> HttpResult { .collect::>(), )) } + +#[derive(serde::Deserialize)] +pub struct TokenIDInPath { + id: APITokenID, +} + +/// Delete an API access token +pub async fn delete(auth: AuthExtractor, path: web::Path) -> HttpResult { + let token = APIToken::load(&auth.user.email, &path.id).await?; + token.delete(&auth.user.email).await?; + Ok(HttpResponse::Accepted().finish()) +} diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 7004740..4d5ec16 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -115,6 +115,10 @@ async fn main() -> std::io::Result<()> { // API Tokens controller .route("/api/token", web::post().to(tokens_controller::create)) .route("/api/tokens", web::get().to(tokens_controller::get_list)) + .route( + "/api/token/{id}", + web::delete().to(tokens_controller::delete), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? diff --git a/matrixgw_frontend/src/api/TokensApi.ts b/matrixgw_frontend/src/api/TokensApi.ts index 268a2ae..acddaf6 100644 --- a/matrixgw_frontend/src/api/TokensApi.ts +++ b/matrixgw_frontend/src/api/TokensApi.ts @@ -43,4 +43,16 @@ export class TokensApi { }) ).data; } + + /** + * Delete a token + */ + static async Delete(t: Token): Promise { + return ( + await APIClient.exec({ + uri: `/token/${t.id}`, + method: "DELETE", + }) + ).data; + } } diff --git a/matrixgw_frontend/src/routes/APITokensRoute.tsx b/matrixgw_frontend/src/routes/APITokensRoute.tsx index 423c367..af72f09 100644 --- a/matrixgw_frontend/src/routes/APITokensRoute.tsx +++ b/matrixgw_frontend/src/routes/APITokensRoute.tsx @@ -2,7 +2,7 @@ import AddIcon from "@mui/icons-material/Add"; import RefreshIcon from "@mui/icons-material/Refresh"; import { Alert, AlertTitle, IconButton, Tooltip } from "@mui/material"; import type { GridColDef } from "@mui/x-data-grid"; -import { DataGrid } from "@mui/x-data-grid"; +import { DataGrid, GridActionsCellItem } from "@mui/x-data-grid"; import { QRCodeCanvas } from "qrcode.react"; import React from "react"; import { APIClient } from "../api/ApiClient"; @@ -12,6 +12,11 @@ import { AsyncWidget } from "../widgets/AsyncWidget"; import { CopyTextChip } from "../widgets/CopyTextChip"; import { MatrixGWRouteContainer } from "../widgets/MatrixGWRouteContainer"; import { TimeWidget } from "../widgets/TimeWidget"; +import DeleteIcon from "@mui/icons-material/DeleteOutlined"; +import { useSnackbar } from "../hooks/contexts_provider/SnackbarProvider"; +import { useConfirm } from "../hooks/contexts_provider/ConfirmDialogProvider"; +import { useAlert } from "../hooks/contexts_provider/AlertDialogProvider"; +import { time } from "../utils/DateUtils"; export function APITokensRoute(): React.ReactElement { const count = React.useRef(0); @@ -127,6 +132,30 @@ function TokensListGrid(p: { list: Token[]; onReload: () => void; }): React.ReactElement { + const snackbar = useSnackbar(); + const confirm = useConfirm(); + const alert = useAlert(); + + // Delete a token + const handleDeleteClick = (token: Token) => async () => { + try { + if ( + !(await confirm( + `Do you really want to delete the token named '${token.name}' ?` + )) + ) + return; + + await TokensApi.Delete(token); + p.onReload(); + + snackbar("The token was successfully deleted!"); + } catch (e) { + console.error(e); + alert(`Failed to delete API token! ${e}`); + } + }; + const columns: GridColDef<(typeof p.list)[number]>[] = [ { field: "id", headerName: "ID", flex: 1 }, { @@ -159,7 +188,18 @@ function TokensListGrid(p: { headerName: "Last usage", flex: 3, renderCell(params) { - return ; + return ( + + + + ); }, }, { @@ -167,7 +207,18 @@ function TokensListGrid(p: { headerName: "Max inactivity", flex: 3, renderCell(params) { - return ; + return ( + + + + ); }, }, { @@ -175,7 +226,18 @@ function TokensListGrid(p: { headerName: "Expiration", flex: 3, renderCell(params) { - return ; + return ( + + + + ); }, }, { @@ -184,6 +246,24 @@ function TokensListGrid(p: { flex: 2, type: "boolean", }, + { + field: "actions", + type: "actions", + headerName: "Actions", + flex: 2, + cellClassName: "actions", + getActions: ({ row }) => { + return [ + } + label="Delete" + onClick={handleDeleteClick(row)} + color="inherit" + />, + ]; + }, + }, ]; if (p.list.length === 0)