onboarding key expiry view

This commit is contained in:
mbecker20
2025-10-07 01:16:28 -07:00
parent cea8601246
commit 3864bb7115
6 changed files with 75 additions and 30 deletions

View File

@@ -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<ReadArgs> 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)
}
}

View File

@@ -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"))
}
}
}

View File

@@ -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<String>,

View File

@@ -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[];
/**

View File

@@ -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[];
/**

View File

@@ -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<Types.OnboardingKey, unknown>
| false
| undefined
)[] = useMemo(
() => [
{
size: 150,
@@ -81,7 +87,7 @@ export const Onboarding = () => {
header: "Tags",
cell: ({ row }) => (
<TagSelector
tags={row.original.tags}
tags={row.original.tags!}
set={(tags) =>
mutate({ public_key: row.original.public_key, tags })
}
@@ -112,10 +118,19 @@ export const Onboarding = () => {
header: ({ column }) => (
<SortableHeader column={column} title="Expires" />
),
cell: ({ row }) =>
row.original.expires
? fmt_date_with_minutes(new Date(row.original.expires))
: "Never",
cell: ({
row: {
original: { expires },
},
}) => (
<Badge
variant={
expires && expires <= Date.now() ? "destructive" : "secondary"
}
>
{expires ? fmt_date_with_minutes(new Date(expires)) : "Never"}
</Badge>
),
},
{
size: 100,
@@ -123,12 +138,15 @@ export const Onboarding = () => {
header: ({ column }) => (
<SortableHeader column={column} title="Enabled" />
),
cell: ({ row }) => (
cell: ({
row: {
original: { public_key, expires, enabled },
},
}) => (
<Switch
checked={row.original.enabled}
onCheckedChange={(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<ExpiresOptions>("never");
const [expires, setExpires] = useState<ExpiresOptions>("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<ExpiresOptions, number> = {
"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 = () =>