From 3864bb7115ce70b57156b63460c235a904123186 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Tue, 7 Oct 2025 01:16:28 -0700 Subject: [PATCH] onboarding key expiry view --- bin/core/src/api/read/onboarding_key.rs | 29 +++++++++-- bin/core/src/connection/server.rs | 9 +++- client/core/rs/src/entities/onboarding_key.rs | 9 ++-- client/core/ts/src/types.ts | 4 +- frontend/public/client/types.d.ts | 4 +- frontend/src/pages/settings/onboarding.tsx | 50 +++++++++++++------ 6 files changed, 75 insertions(+), 30 deletions(-) diff --git a/bin/core/src/api/read/onboarding_key.rs b/bin/core/src/api/read/onboarding_key.rs index 577eba7ae..f9924bdcf 100644 --- a/bin/core/src/api/read/onboarding_key.rs +++ b/bin/core/src/api/read/onboarding_key.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use anyhow::{Context, anyhow}; use database::mungos::find::find_collect; use komodo_client::api::read::{ @@ -22,9 +24,28 @@ impl Resolve for ListOnboardingKeys { .status_code(StatusCode::FORBIDDEN), ); } - find_collect(&db_client().onboarding_keys, None, None) - .await - .context("Failed to query database for Server onboarding keys") - .map_err(Into::into) + + let mut keys = + find_collect(&db_client().onboarding_keys, None, None) + .await + .context( + "Failed to query database for Server onboarding keys", + )?; + + // No expiry keys first, followed + keys.sort_by(|a, b| { + if a.expires == b.expires { + Ordering::Equal + } else if a.expires == 0 { + Ordering::Less + } else if b.expires == 0 { + Ordering::Greater + } else { + // Descending + b.expires.cmp(&a.expires) + } + }); + + Ok(keys) } } diff --git a/bin/core/src/connection/server.rs b/bin/core/src/connection/server.rs index 456984b86..d61f214fe 100644 --- a/bin/core/src/connection/server.rs +++ b/bin/core/src/connection/server.rs @@ -11,6 +11,7 @@ use komodo_client::{ api::write::{CreateBuilder, CreateServer, UpdateResourceMeta}, entities::{ builder::{PartialBuilderConfig, PartialServerBuilderConfig}, + komodo_timestamp, onboarding_key::OnboardingKey, server::{PartialServerConfig, Server}, user::system_user, @@ -344,10 +345,14 @@ impl PublicKeyValidator for CreationKeyValidator { .await .context("Failed to query database for Server onboarding keys")? .context("Matching Server onboarding key not found")?; - if onboarding_key.enabled { + // Check enabled and not expired. + if onboarding_key.enabled + && (onboarding_key.expires == 0 + || onboarding_key.expires > komodo_timestamp()) + { Ok(onboarding_key) } else { - Err(anyhow!("Onboarding key is disabled")) + Err(anyhow!("Onboarding key is invalid")) } } } diff --git a/client/core/rs/src/entities/onboarding_key.rs b/client/core/rs/src/entities/onboarding_key.rs index 93f4c8b9a..2d8e2eb13 100644 --- a/client/core/rs/src/entities/onboarding_key.rs +++ b/client/core/rs/src/entities/onboarding_key.rs @@ -25,6 +25,11 @@ pub struct OnboardingKey { #[serde(default)] pub enabled: bool, + /// Expiry of key, or 0 if never expires + #[serde(default)] + #[cfg_attr(feature = "mongo", index)] + pub expires: I64, + /// Name associated with the api key for management #[serde(default)] pub name: String, @@ -37,10 +42,6 @@ pub struct OnboardingKey { #[serde(default)] pub created_at: I64, - /// Expiry of key, or 0 if never expires - #[serde(default)] - pub expires: I64, - /// Default tags to give to Servers created with this key. #[serde(default)] pub tags: Vec, diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 7020e0e13..b630431c3 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -1159,14 +1159,14 @@ export interface OnboardingKey { public_key: string; /** Disable the onboarding key when not in use. */ enabled?: boolean; + /** Expiry of key, or 0 if never expires */ + expires?: I64; /** Name associated with the api key for management */ name?: string; /** The [Server](crate::entities::server::Server) ids onboarded by this Creation Key */ onboarded?: string[]; /** Timestamp of key creation */ created_at?: I64; - /** Expiry of key, or 0 if never expires */ - expires?: I64; /** Default tags to give to Servers created with this key. */ tags?: string[]; /** diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 9720f0864..0e7ce33c1 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -1283,14 +1283,14 @@ export interface OnboardingKey { public_key: string; /** Disable the onboarding key when not in use. */ enabled?: boolean; + /** Expiry of key, or 0 if never expires */ + expires?: I64; /** Name associated with the api key for management */ name?: string; /** The [Server](crate::entities::server::Server) ids onboarded by this Creation Key */ onboarded?: string[]; /** Timestamp of key creation */ created_at?: I64; - /** Expiry of key, or 0 if never expires */ - expires?: I64; /** Default tags to give to Servers created with this key. */ tags?: string[]; /** diff --git a/frontend/src/pages/settings/onboarding.tsx b/frontend/src/pages/settings/onboarding.tsx index d5054b103..4396ba937 100644 --- a/frontend/src/pages/settings/onboarding.tsx +++ b/frontend/src/pages/settings/onboarding.tsx @@ -27,6 +27,8 @@ import { fmt_date_with_minutes } from "@lib/formatting"; import { Switch } from "@ui/switch"; import { ResourceSelector, TagSelector } from "@components/resources/common"; import { Types } from "komodo_client"; +import { ColumnDef } from "@tanstack/react-table"; +import { Badge } from "@ui/badge"; export const Onboarding = () => { useSetTitle("Onboarding"); @@ -40,7 +42,11 @@ export const Onboarding = () => { toast({ title: "Updated onboarding key" }); }, }); - const columns = useMemo( + const columns: ( + | ColumnDef + | false + | undefined + )[] = useMemo( () => [ { size: 150, @@ -81,7 +87,7 @@ export const Onboarding = () => { header: "Tags", cell: ({ row }) => ( mutate({ public_key: row.original.public_key, tags }) } @@ -112,10 +118,19 @@ export const Onboarding = () => { header: ({ column }) => ( ), - cell: ({ row }) => - row.original.expires - ? fmt_date_with_minutes(new Date(row.original.expires)) - : "Never", + cell: ({ + row: { + original: { expires }, + }, + }) => ( + + {expires ? fmt_date_with_minutes(new Date(expires)) : "Never"} + + ), }, { size: 100, @@ -123,12 +138,15 @@ export const Onboarding = () => { header: ({ column }) => ( ), - cell: ({ row }) => ( + cell: ({ + row: { + original: { public_key, expires, enabled }, + }, + }) => ( - mutate({ public_key: row.original.public_key, enabled }) - } + checked={expires && expires <= Date.now() ? false : enabled} + onCheckedChange={(enabled) => mutate({ public_key, enabled })} + disabled={!!expires && expires <= Date.now()} /> ), }, @@ -159,14 +177,14 @@ export const Onboarding = () => { const ONE_DAY_MS = 1000 * 60 * 60 * 24; -type ExpiresOptions = "90 days" | "180 days" | "1 year" | "never"; +type ExpiresOptions = "1 day" | "7 days" | "30 days" | "never"; const CreateKey = () => { const { toast } = useToast(); const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [privateKey, setPrivateKey] = useState(""); - const [expires, setExpires] = useState("never"); + const [expires, setExpires] = useState("1 day"); const [submitted, setSubmitted] = useState<{ private_key: string }>(); const invalidate = useInvalidate(); const { mutate, isPending } = useWrite("CreateOnboardingKey", { @@ -178,9 +196,9 @@ const CreateKey = () => { }); const now = Date.now(); const expiresOptions: Record = { - "90 days": now + ONE_DAY_MS * 90, - "180 days": now + ONE_DAY_MS * 180, - "1 year": now + ONE_DAY_MS * 365, + "1 day": now + ONE_DAY_MS, + "7 days": now + ONE_DAY_MS * 7, + "30 days": now + ONE_DAY_MS * 90, never: 0, }; const submit = () =>