refactor(passkeys): list passkeys via tRPC instead of better-auth client atom

This commit is contained in:
Mauricio Siu 2026-08-02 16:43:03 -06:00
parent 7b0bcee652
commit 81051ca965
3 changed files with 32 additions and 5 deletions

View File

@ -16,10 +16,17 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { authClient } from "@/lib/auth-client";
import { api } from "@/utils/api";
export const ManagePasskeys = () => {
const utils = api.useUtils();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const { data: passkeys, isPending, refetch } = authClient.useListPasskeys();
const { data: passkeys, isLoading } = api.user.listPasskeys.useQuery(
undefined,
{
enabled: isDialogOpen,
},
);
const [name, setName] = useState("");
const [isAdding, setIsAdding] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
@ -39,7 +46,7 @@ export const ManagePasskeys = () => {
toast.success("Passkey added successfully");
setName("");
refetch();
utils.user.listPasskeys.invalidate();
} catch {
toast.error("Failed to add passkey");
} finally {
@ -58,7 +65,7 @@ export const ManagePasskeys = () => {
}
toast.success("Passkey removed");
refetch();
utils.user.listPasskeys.invalidate();
} catch {
toast.error("Failed to remove passkey");
} finally {
@ -84,7 +91,7 @@ export const ManagePasskeys = () => {
</DialogHeader>
<div className="space-y-4">
{isPending ? (
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[10vh]">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />

View File

@ -3,6 +3,7 @@ import {
createOrganizationUserWithCredentials,
findNotificationById,
findOrganizationById,
findPasskeysByUserId,
findUserById,
getDokployUrl,
getUserByToken,
@ -173,6 +174,9 @@ export const userRouter = createTRPCRouter({
getPermissions: protectedProcedure.query(async ({ ctx }) => {
return resolvePermissions(ctx);
}),
listPasskeys: protectedProcedure.query(async ({ ctx }) => {
return findPasskeysByUserId(ctx.user.id);
}),
haveRootAccess: protectedProcedure.query(async ({ ctx }) => {
if (!IS_CLOUD) {
return false;

View File

@ -4,11 +4,12 @@ import {
apikey,
invitation,
member,
passkey,
user,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { and, eq } from "drizzle-orm";
import { and, desc, eq } from "drizzle-orm";
import { auth } from "../lib/auth";
export type User = typeof user.$inferSelect;
@ -396,6 +397,21 @@ export const findMemberById = async (
return result;
};
export const findPasskeysByUserId = async (userId: string) => {
return db.query.passkey.findMany({
where: eq(passkey.userId, userId),
columns: {
id: true,
name: true,
deviceType: true,
backedUp: true,
createdAt: true,
aaguid: true,
},
orderBy: [desc(passkey.createdAt)],
});
};
export const createOrganizationUserWithCredentials = async ({
organizationId,
email,