show user idp in devices

This commit is contained in:
miloschwartz
2026-04-28 21:27:11 -07:00
parent 7e374baee9
commit 2203ebf723
5 changed files with 90 additions and 15 deletions

View File

@@ -1,8 +1,8 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { db, olms, users } from "@server/db"; import { db, idp, idpOidcConfig, olms, users } from "@server/db";
import { clients, currentFingerprint } from "@server/db"; import { clients, currentFingerprint } from "@server/db";
import { eq, and } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -236,6 +236,9 @@ export type GetClientResponse = NonNullable<
lastSeen: number | null; lastSeen: number | null;
} | null; } | null;
posture: PostureData | null; posture: PostureData | null;
userType: string | null;
idpName: string | null;
idpVariant: string | null;
}; };
registry.registerPath({ registry.registerPath({
@@ -337,6 +340,30 @@ export async function getClient(
: maskPostureDataWithPlaceholder(rawPosture) : maskPostureDataWithPlaceholder(rawPosture)
: null; : null;
let userType: string | null = null;
let idpName: string | null = null;
let idpVariant: string | null = null;
if (client.clients.userId) {
const [idpRow] = await db
.select({
userType: users.type,
idpName: idp.name,
idpVariant: idpOidcConfig.variant
})
.from(users)
.leftJoin(idp, eq(users.idpId, idp.idpId))
.leftJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idp.idpId))
.where(eq(users.userId, client.clients.userId))
.limit(1);
if (idpRow) {
userType = idpRow.userType;
idpName = idpRow.idpName;
idpVariant = idpRow.idpVariant;
}
}
const data: GetClientResponse = { const data: GetClientResponse = {
...client.clients, ...client.clients,
name: clientName, name: clientName,
@@ -347,7 +374,10 @@ export async function getClient(
userName: client.user?.name ?? null, userName: client.user?.name ?? null,
userUsername: client.user?.username ?? null, userUsername: client.user?.username ?? null,
fingerprint: fingerprintData, fingerprint: fingerprintData,
posture: postureData posture: postureData,
userType,
idpName,
idpVariant
}; };
return response<GetClientResponse>(res, { return response<GetClientResponse>(res, {

View File

@@ -3,6 +3,8 @@ import {
clients, clients,
currentFingerprint, currentFingerprint,
db, db,
idp,
idpOidcConfig,
olms, olms,
orgs, orgs,
roleClients, roleClients,
@@ -165,6 +167,9 @@ function queryUserDevicesBase() {
userId: clients.userId, userId: clients.userId,
username: users.username, username: users.username,
userEmail: users.email, userEmail: users.email,
userType: users.type,
idpName: idp.name,
idpVariant: idpOidcConfig.variant,
niceId: clients.niceId, niceId: clients.niceId,
agent: olms.agent, agent: olms.agent,
approvalState: clients.approvalState, approvalState: clients.approvalState,
@@ -184,6 +189,8 @@ function queryUserDevicesBase() {
.leftJoin(orgs, eq(clients.orgId, orgs.orgId)) .leftJoin(orgs, eq(clients.orgId, orgs.orgId))
.leftJoin(olms, eq(clients.clientId, olms.clientId)) .leftJoin(olms, eq(clients.clientId, olms.clientId))
.leftJoin(users, eq(clients.userId, users.userId)) .leftJoin(users, eq(clients.userId, users.userId))
.leftJoin(idp, eq(users.idpId, idp.idpId))
.leftJoin(idpOidcConfig, eq(idpOidcConfig.idpId, idp.idpId))
.leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId)); .leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId));
} }

View File

@@ -96,6 +96,9 @@ export default async function ClientsPage(props: ClientsPageProps) {
userId: client.userId, userId: client.userId,
username: client.username, username: client.username,
userEmail: client.userEmail, userEmail: client.userEmail,
userType: client.userType ?? null,
idpName: client.idpName ?? null,
idpVariant: client.idpVariant ?? null,
niceId: client.niceId, niceId: client.niceId,
agent: client.agent, agent: client.agent,
archived: Boolean(client.archived), archived: Boolean(client.archived),

View File

@@ -8,6 +8,7 @@ import {
InfoSections, InfoSections,
InfoSectionTitle InfoSectionTitle
} from "@app/components/InfoSection"; } from "@app/components/InfoSection";
import IdpTypeBadge from "@app/components/IdpTypeBadge";
import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -36,7 +37,24 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
{userDisplayName ? t("user") : t("identifier")} {userDisplayName ? t("user") : t("identifier")}
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{userDisplayName || client.niceId} <div className="flex flex-wrap items-center gap-2">
<span>{userDisplayName || client.niceId}</span>
{userDisplayName &&
(client.userType ?? "internal") !==
"internal" && (
<IdpTypeBadge
type={client.userType ?? "oidc"}
name={
client.idpName?.trim()
? client.idpName
: t("idpNameInternal")
}
variant={
client.idpVariant ?? undefined
}
/>
)}
</div>
</InfoSectionContent> </InfoSectionContent>
</InfoSection> </InfoSection>
<InfoSection> <InfoSection>

View File

@@ -35,6 +35,7 @@ import { useMemo, useState, useTransition } from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import ClientDownloadBanner from "./ClientDownloadBanner"; import ClientDownloadBanner from "./ClientDownloadBanner";
import { ColumnFilterButton } from "./ColumnFilterButton"; import { ColumnFilterButton } from "./ColumnFilterButton";
import IdpTypeBadge from "./IdpTypeBadge";
import { Badge } from "./ui/badge"; import { Badge } from "./ui/badge";
import { ControlledDataTable } from "./ui/controlled-data-table"; import { ControlledDataTable } from "./ui/controlled-data-table";
@@ -52,6 +53,9 @@ export type ClientRow = {
userId: string | null; userId: string | null;
username: string | null; username: string | null;
userEmail: string | null; userEmail: string | null;
userType: string | null;
idpName: string | null;
idpVariant: string | null;
niceId: string; niceId: string;
agent: string | null; agent: string | null;
approvalState: "approved" | "pending" | "denied" | null; approvalState: "approved" | "pending" | "denied" | null;
@@ -370,17 +374,30 @@ export default function UserDevicesTable({
cell: ({ row }) => { cell: ({ row }) => {
const r = row.original; const r = row.original;
return r.userId ? ( return r.userId ? (
<Link <div className="flex items-center gap-2">
href={`/${r.orgId}/settings/access/users/${r.userId}`} <Link
> href={`/${r.orgId}/settings/access/users/${r.userId}`}
<Button variant="outline" size="sm"> >
{getUserDisplayName({ <Button variant="outline" size="sm">
email: r.userEmail, {getUserDisplayName({
username: r.username email: r.userEmail,
}) || r.userId} username: r.username
<ArrowUpRight className="ml-2 h-3 w-3" /> }) || r.userId}
</Button> <ArrowUpRight className="ml-2 h-3 w-3" />
</Link> </Button>
</Link>
{(r.userType ?? "internal") !== "internal" && (
<IdpTypeBadge
type={r.userType ?? "oidc"}
name={
r.idpName?.trim()
? r.idpName
: t("idpNameInternal")
}
variant={r.idpVariant ?? undefined}
/>
)}
</div>
) : ( ) : (
"-" "-"
); );