renamed passkey to security key to stay aligned with the UI and other backend naming.

This commit is contained in:
Adrian Astles 2025-07-05 21:51:31 +08:00
parent 6ccc05b183
commit 5009906385
13 changed files with 158 additions and 118 deletions

View file

@ -491,10 +491,24 @@ export const idpOrg = pgTable("idpOrg", {
orgMapping: varchar("orgMapping")
});
export const securityKeys = pgTable("webauthnCredentials", {
credentialId: varchar("credentialId").primaryKey(),
userId: varchar("userId").notNull().references(() => users.userId, {
onDelete: "cascade"
}),
publicKey: varchar("publicKey").notNull(),
signCount: integer("signCount").notNull(),
transports: varchar("transports"),
name: varchar("name"),
lastUsed: varchar("lastUsed").notNull(),
dateCreated: varchar("dateCreated").notNull(),
securityKeyName: varchar("securityKeyName")
});
export const webauthnChallenge = pgTable("webauthnChallenge", {
sessionId: varchar("sessionId").primaryKey(),
challenge: varchar("challenge").notNull(),
passkeyName: varchar("passkeyName"),
securityKeyName: varchar("securityKeyName"),
userId: varchar("userId").references(() => users.userId, {
onDelete: "cascade"
}),

View file

@ -7,12 +7,37 @@ import type { Database as BetterSqlite3Database } from "better-sqlite3";
const migrationsFolder = path.join("server/migrations");
const dropAllTables = (sqlite: BetterSqlite3Database) => {
console.log("Dropping all existing tables...");
// Disable foreign key checks
sqlite.pragma('foreign_keys = OFF');
// Get all tables
const tables = sqlite.prepare(`
SELECT name FROM sqlite_master
WHERE type='table'
AND name NOT LIKE 'sqlite_%'
`).all() as { name: string }[];
// Drop each table
for (const table of tables) {
console.log(`Dropping table: ${table.name}`);
sqlite.prepare(`DROP TABLE IF EXISTS "${table.name}"`).run();
}
// Re-enable foreign key checks
sqlite.pragma('foreign_keys = ON');
};
const runMigrations = async () => {
console.log("Running migrations...");
try {
// Initialize the database file with a valid SQLite header
const sqlite = new Database(location) as BetterSqlite3Database;
sqlite.pragma('foreign_keys = ON');
// Drop all existing tables first
dropAllTables(sqlite);
// Run the migrations
migrate(db as any, {

View file

@ -135,7 +135,7 @@ export const users = sqliteTable("user", {
.default(false)
});
export const passkeys = sqliteTable("webauthnCredentials", {
export const securityKeys = sqliteTable("webauthnCredentials", {
credentialId: text("credentialId").primaryKey(),
userId: text("userId").notNull().references(() => users.userId, {
onDelete: "cascade"
@ -151,7 +151,7 @@ export const passkeys = sqliteTable("webauthnCredentials", {
export const webauthnChallenge = sqliteTable("webauthnChallenge", {
sessionId: text("sessionId").primaryKey(),
challenge: text("challenge").notNull(),
passkeyName: text("passkeyName"),
securityKeyName: text("securityKeyName"),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),

View file

@ -6,10 +6,10 @@ export * from "./requestTotpSecret";
export * from "./disable2fa";
export * from "./verifyEmail";
export * from "./requestEmailVerificationCode";
export * from "./changePassword";
export * from "./requestPasswordReset";
export * from "./resetPassword";
export * from "./checkResourceSession";
export * from "./requestPasswordReset";
export * from "./setServerAdmin";
export * from "./initialSetupComplete";
export * from "./passkey";
export * from "./changePassword";
export * from "./checkResourceSession";
export * from "./securityKey";

View file

@ -4,7 +4,7 @@ import {
serializeSessionCookie
} from "@server/auth/sessions/app";
import { db } from "@server/db";
import { users, passkeys } from "@server/db";
import { users, securityKeys } from "@server/db";
import HttpCode from "@server/types/HttpCode";
import response from "@server/lib/response";
import { eq, and } from "drizzle-orm";
@ -35,6 +35,7 @@ export type LoginBody = z.infer<typeof loginBodySchema>;
export type LoginResponse = {
codeRequested?: boolean;
emailVerificationRequired?: boolean;
useSecurityKey?: boolean;
};
export const dynamic = "force-dynamic";
@ -91,15 +92,15 @@ export async function login(
const existingUser = existingUserRes[0];
// Check if user has passkeys registered
const userPasskeys = await db
// Check if user has security keys registered
const userSecurityKeys = await db
.select()
.from(passkeys)
.where(eq(passkeys.userId, existingUser.userId));
.from(securityKeys)
.where(eq(securityKeys.userId, existingUser.userId));
if (userPasskeys.length > 0) {
return response<{ usePasskey: boolean }>(res, {
data: { usePasskey: true },
if (userSecurityKeys.length > 0) {
return response<{ useSecurityKey: boolean }>(res, {
data: { useSecurityKey: true },
success: true,
error: false,
message: "Please use your security key to sign in",

View file

@ -4,7 +4,7 @@ import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import { db } from "@server/db";
import { User, passkeys, users, webauthnChallenge } from "@server/db";
import { User, securityKeys, users, webauthnChallenge } from "@server/db";
import { eq, and, lt } from "drizzle-orm";
import { response } from "@server/lib";
import logger from "@server/logger";
@ -55,14 +55,14 @@ setInterval(async () => {
await db
.delete(webauthnChallenge)
.where(lt(webauthnChallenge.expiresAt, now));
logger.debug("Cleaned up expired passkey challenges");
logger.debug("Cleaned up expired security key challenges");
} catch (error) {
logger.error("Failed to clean up expired passkey challenges", error);
logger.error("Failed to clean up expired security key challenges", error);
}
}, 5 * 60 * 1000);
// Helper functions for challenge management
async function storeChallenge(sessionId: string, challenge: string, passkeyName?: string, userId?: string) {
async function storeChallenge(sessionId: string, challenge: string, securityKeyName?: string, userId?: string) {
const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes
// Delete any existing challenge for this session
@ -72,7 +72,7 @@ async function storeChallenge(sessionId: string, challenge: string, passkeyName?
await db.insert(webauthnChallenge).values({
sessionId,
challenge,
passkeyName,
securityKeyName,
userId,
expiresAt
});
@ -102,7 +102,7 @@ async function clearChallenge(sessionId: string) {
await db.delete(webauthnChallenge).where(eq(webauthnChallenge.sessionId, sessionId));
}
export const registerPasskeyBody = z.object({
export const registerSecurityKeyBody = z.object({
name: z.string().min(1),
password: z.string().min(1)
}).strict();
@ -119,7 +119,7 @@ export const verifyAuthenticationBody = z.object({
credential: z.any()
}).strict();
export const deletePasskeyBody = z.object({
export const deleteSecurityKeyBody = z.object({
password: z.string().min(1)
}).strict();
@ -128,7 +128,7 @@ export async function startRegistration(
res: Response,
next: NextFunction
): Promise<any> {
const parsedBody = registerPasskeyBody.safeParse(req.body);
const parsedBody = registerSecurityKeyBody.safeParse(req.body);
if (!parsedBody.success) {
return next(
@ -142,12 +142,12 @@ export async function startRegistration(
const { name, password } = parsedBody.data;
const user = req.user as User;
// Only allow internal users to use passkeys
// Only allow internal users to use security keys
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Passkeys are only available for internal users"
"Security keys are only available for internal users"
)
);
}
@ -170,13 +170,13 @@ export async function startRegistration(
});
}
// Get existing passkeys for user
const existingPasskeys = await db
// Get existing security keys for user
const existingSecurityKeys = await db
.select()
.from(passkeys)
.where(eq(passkeys.userId, user.userId));
.from(securityKeys)
.where(eq(securityKeys.userId, user.userId));
const excludeCredentials = existingPasskeys.map(key => ({
const excludeCredentials = existingSecurityKeys.map(key => ({
id: Buffer.from(key.credentialId, 'base64').toString('base64url'),
type: 'public-key' as const,
transports: key.transports ? JSON.parse(key.transports) as AuthenticatorTransport[] : undefined
@ -237,12 +237,12 @@ export async function verifyRegistration(
const { credential } = parsedBody.data;
const user = req.user as User;
// Only allow internal users to use passkeys
// Only allow internal users to use security keys
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Passkeys are only available for internal users"
"Security keys are only available for internal users"
)
);
}
@ -279,14 +279,14 @@ export async function verifyRegistration(
);
}
// Store the passkey in the database
await db.insert(passkeys).values({
// Store the security key in the database
await db.insert(securityKeys).values({
credentialId: Buffer.from(registrationInfo.credentialID).toString('base64'),
userId: user.userId,
publicKey: Buffer.from(registrationInfo.credentialPublicKey).toString('base64'),
signCount: registrationInfo.counter || 0,
transports: credential.response.transports ? JSON.stringify(credential.response.transports) : null,
name: challengeData.passkeyName,
name: challengeData.securityKeyName,
lastUsed: new Date().toISOString(),
dateCreated: new Date().toISOString()
});
@ -298,7 +298,7 @@ export async function verifyRegistration(
data: null,
success: true,
error: false,
message: "Passkey registered successfully",
message: "Security key registered successfully",
status: HttpCode.OK
});
} catch (error) {
@ -312,34 +312,34 @@ export async function verifyRegistration(
}
}
export async function listPasskeys(
export async function listSecurityKeys(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
const user = req.user as User;
// Only allow internal users to use passkeys
// Only allow internal users to use security keys
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Passkeys are only available for internal users"
"Security keys are only available for internal users"
)
);
}
try {
const userPasskeys = await db
const userSecurityKeys = await db
.select()
.from(passkeys)
.where(eq(passkeys.userId, user.userId));
.from(securityKeys)
.where(eq(securityKeys.userId, user.userId));
return response<typeof userPasskeys>(res, {
data: userPasskeys,
return response<typeof userSecurityKeys>(res, {
data: userSecurityKeys,
success: true,
error: false,
message: "Passkeys retrieved successfully",
message: "Security keys retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
@ -347,13 +347,13 @@ export async function listPasskeys(
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to retrieve passkeys"
"Failed to retrieve security keys"
)
);
}
}
export async function deletePasskey(
export async function deleteSecurityKey(
req: Request,
res: Response,
next: NextFunction
@ -362,7 +362,7 @@ export async function deletePasskey(
const credentialId = decodeURIComponent(encodedCredentialId);
const user = req.user as User;
const parsedBody = deletePasskeyBody.safeParse(req.body);
const parsedBody = deleteSecurityKeyBody.safeParse(req.body);
if (!parsedBody.success) {
return next(
@ -375,12 +375,12 @@ export async function deletePasskey(
const { password } = parsedBody.data;
// Only allow internal users to use passkeys
// Only allow internal users to use security keys
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Passkeys are only available for internal users"
"Security keys are only available for internal users"
)
);
}
@ -404,17 +404,17 @@ export async function deletePasskey(
}
await db
.delete(passkeys)
.delete(securityKeys)
.where(and(
eq(passkeys.credentialId, credentialId),
eq(passkeys.userId, user.userId)
eq(securityKeys.credentialId, credentialId),
eq(securityKeys.userId, user.userId)
));
return response<null>(res, {
data: null,
success: true,
error: false,
message: "Passkey deleted successfully",
message: "Security key deleted successfully",
status: HttpCode.OK
});
} catch (error) {
@ -422,7 +422,7 @@ export async function deletePasskey(
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to delete passkey"
"Failed to delete security key"
)
);
}
@ -454,7 +454,7 @@ export async function startAuthentication(
}> = [];
let userId;
// If email is provided, get passkeys for that specific user
// If email is provided, get security keys for that specific user
if (email) {
const [user] = await db
.select()
@ -473,27 +473,27 @@ export async function startAuthentication(
userId = user.userId;
const userPasskeys = await db
const userSecurityKeys = await db
.select()
.from(passkeys)
.where(eq(passkeys.userId, user.userId));
.from(securityKeys)
.where(eq(securityKeys.userId, user.userId));
if (userPasskeys.length === 0) {
if (userSecurityKeys.length === 0) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"No passkeys registered for this user"
"No security keys registered for this user"
)
);
}
allowCredentials = userPasskeys.map(key => ({
allowCredentials = userSecurityKeys.map(key => ({
id: Buffer.from(key.credentialId, 'base64'),
type: 'public-key' as const,
transports: key.transports ? JSON.parse(key.transports) as AuthenticatorTransport[] : undefined
}));
} else {
// If no email provided, allow any passkey (for resident key authentication)
// If no email provided, allow any security key (for resident key authentication)
allowCredentials = [];
}
@ -570,15 +570,15 @@ export async function verifyAuthentication(
);
}
// Find the passkey in database
// Find the security key in database
const credentialId = Buffer.from(credential.id, 'base64').toString('base64');
const [passkey] = await db
const [securityKey] = await db
.select()
.from(passkeys)
.where(eq(passkeys.credentialId, credentialId))
.from(securityKeys)
.where(eq(securityKeys.credentialId, credentialId))
.limit(1);
if (!passkey) {
if (!securityKey) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
@ -591,14 +591,14 @@ export async function verifyAuthentication(
const [user] = await db
.select()
.from(users)
.where(eq(users.userId, passkey.userId))
.where(eq(users.userId, securityKey.userId))
.limit(1);
if (!user || user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"User not found or not authorized for passkey authentication"
"User not found or not authorized for security key authentication"
)
);
}
@ -609,10 +609,10 @@ export async function verifyAuthentication(
expectedOrigin: origin,
expectedRPID: rpID,
authenticator: {
credentialID: Buffer.from(passkey.credentialId, 'base64'),
credentialPublicKey: Buffer.from(passkey.publicKey, 'base64'),
counter: passkey.signCount,
transports: passkey.transports ? JSON.parse(passkey.transports) as AuthenticatorTransport[] : undefined
credentialID: Buffer.from(securityKey.credentialId, 'base64'),
credentialPublicKey: Buffer.from(securityKey.publicKey, 'base64'),
counter: securityKey.signCount,
transports: securityKey.transports ? JSON.parse(securityKey.transports) as AuthenticatorTransport[] : undefined
},
requireUserVerification: false
});
@ -630,12 +630,12 @@ export async function verifyAuthentication(
// Update sign count
await db
.update(passkeys)
.update(securityKeys)
.set({
signCount: authenticationInfo.newCounter,
lastUsed: new Date().toISOString()
})
.where(eq(passkeys.credentialId, credentialId));
.where(eq(securityKeys.credentialId, credentialId));
// Create session for the user
const { createSession, generateSessionToken, serializeSessionCookie } = await import("@server/auth/sessions/app");

View file

@ -789,35 +789,35 @@ authRouter.post("/idp/:idpId/oidc/validate-callback", idp.validateOidcCallback);
authRouter.put("/set-server-admin", auth.setServerAdmin);
authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
// Passkey routes
// Security Key routes
authRouter.post(
"/passkey/register/start",
"/security-key/register/start",
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Allow 5 passkey registrations per 15 minutes per IP
keyGenerator: (req) => `passkeyRegister:${req.ip}:${req.user?.userId}`,
max: 5, // Allow 5 security key registrations per 15 minutes per IP
keyGenerator: (req) => `securityKeyRegister:${req.ip}:${req.user?.userId}`,
handler: (req, res, next) => {
const message = `You can only register ${5} passkeys every ${15} minutes. Please try again later.`;
const message = `You can only register ${5} security keys every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
}
}),
verifySessionUserMiddleware,
auth.startRegistration
);
authRouter.post("/passkey/register/verify", verifySessionUserMiddleware, auth.verifyRegistration);
authRouter.post("/security-key/register/verify", verifySessionUserMiddleware, auth.verifyRegistration);
authRouter.post(
"/passkey/authenticate/start",
"/security-key/authenticate/start",
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Allow 10 authentication attempts per 15 minutes per IP
keyGenerator: (req) => `passkeyAuth:${req.ip}`,
keyGenerator: (req) => `securityKeyAuth:${req.ip}`,
handler: (req, res, next) => {
const message = `You can only attempt passkey authentication ${10} times every ${15} minutes. Please try again later.`;
const message = `You can only attempt security key authentication ${10} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
}
}),
auth.startAuthentication
);
authRouter.post("/passkey/authenticate/verify", auth.verifyAuthentication);
authRouter.get("/passkey/list", verifySessionUserMiddleware, auth.listPasskeys);
authRouter.delete("/passkey/:credentialId", verifySessionUserMiddleware, auth.deletePasskey);
authRouter.post("/security-key/authenticate/verify", auth.verifyAuthentication);
authRouter.get("/security-key/list", verifySessionUserMiddleware, auth.listSecurityKeys);
authRouter.delete("/security-key/:credentialId", verifySessionUserMiddleware, auth.deleteSecurityKey);

View file

@ -8,7 +8,7 @@ export default async function migration() {
try {
db.transaction((trx) => {
trx.run(sql`CREATE TABLE 'passkey' (
trx.run(sql`CREATE TABLE 'securityKey' (
'credentialId' text PRIMARY KEY NOT NULL,
'userId' text NOT NULL,
'publicKey' text NOT NULL,

View file

@ -14,7 +14,7 @@ export default async function migration() {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE IF NOT EXISTS passkey (
CREATE TABLE IF NOT EXISTS securityKey (
credentialId TEXT PRIMARY KEY,
userId TEXT NOT NULL,
publicKey TEXT NOT NULL,
@ -28,9 +28,9 @@ export default async function migration() {
`);
})(); // executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Created passkey table`);
console.log(`Created securityKey table`);
} catch (e) {
console.error("Unable to create passkey table");
console.error("Unable to create securityKey table");
console.error(e);
throw e;
}

View file

@ -14,22 +14,22 @@ export default async function migration() {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE IF NOT EXISTS passkeyChallenge (
CREATE TABLE IF NOT EXISTS securityKeyChallenge (
sessionId TEXT PRIMARY KEY,
challenge TEXT NOT NULL,
passkeyName TEXT,
securityKeyName TEXT,
userId TEXT,
expiresAt INTEGER NOT NULL,
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_passkeyChallenge_expiresAt ON passkeyChallenge(expiresAt);
CREATE INDEX IF NOT EXISTS idx_securityKeyChallenge_expiresAt ON securityKeyChallenge(expiresAt);
`);
})(); // executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Created passkeyChallenge table`);
console.log(`Created securityKeyChallenge table`);
} catch (e) {
console.error("Unable to create passkeyChallenge table");
console.error("Unable to create securityKeyChallenge table");
console.error(e);
throw e;
}

View file

@ -6,21 +6,21 @@ export default async function migrate() {
// Rename the table
await db.run(`
ALTER TABLE passkeyChallenge RENAME TO webauthnChallenge;
ALTER TABLE securityKeyChallenge RENAME TO webauthnChallenge;
`);
console.log("Successfully renamed table");
// Rename the index
await db.run(`
DROP INDEX IF EXISTS idx_passkeyChallenge_expiresAt;
DROP INDEX IF EXISTS idx_securityKeyChallenge_expiresAt;
CREATE INDEX IF NOT EXISTS idx_webauthnChallenge_expiresAt ON webauthnChallenge(expiresAt);
`);
console.log("Successfully updated index");
console.log(`Renamed passkeyChallenge table to webauthnChallenge`);
console.log(`Renamed securityKeyChallenge table to webauthnChallenge`);
return true;
} catch (error: any) {
console.error("Unable to rename passkeyChallenge table:", error);
console.error("Unable to rename securityKeyChallenge table:", error);
console.error("Error details:", error.message);
return false;
}

View file

@ -4,8 +4,8 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Button } from "@app/components/ui/button";
import { Input } from "@app/components/ui/input";
import {
Form,
FormControl,
@ -13,15 +13,15 @@ import {
FormItem,
FormLabel,
FormMessage
} from "@/components/ui/form";
} from "@app/components/ui/form";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
} from "@app/components/ui/card";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { LoginResponse } from "@server/routers/auth";
import { useRouter } from "next/navigation";
import { AxiosResponse } from "axios";
@ -120,8 +120,8 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
setError(null);
const data = res.data.data;
if (data?.usePasskey) {
await initiateSecurityKeyAuth();
if (data?.useSecurityKey) {
setShowSecurityKeyPrompt(true);
return;
}
@ -197,7 +197,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
const email = form.getValues().email;
// Start WebAuthn authentication
const startRes = await api.post("/auth/passkey/authenticate/start", {
const startRes = await api.post("/auth/security-key/authenticate/start", {
email: email || undefined
});
@ -216,7 +216,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
// Verify authentication
const verifyRes = await api.post(
"/auth/passkey/authenticate/verify",
"/auth/security-key/authenticate/verify",
{ credential },
{
headers: {
@ -355,9 +355,9 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
pattern={
REGEXP_ONLY_DIGITS_AND_CHARS
}
onChange={(e) => {
field.onChange(e);
if (e.length === 6) {
onChange={(value: string) => {
field.onChange(value);
if (value.length === 6) {
mfaForm.handleSubmit(onSubmit)();
}
}}

View file

@ -108,7 +108,7 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
const loadSecurityKeys = async () => {
try {
const response = await api.get("/auth/passkey/list");
const response = await api.get("/auth/security-key/list");
setSecurityKeys(response.data.data);
} catch (error) {
toast({
@ -132,7 +132,7 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
}
setIsRegistering(true);
const startRes = await api.post("/auth/passkey/register/start", {
const startRes = await api.post("/auth/security-key/register/start", {
name: values.name,
password: values.password,
});
@ -152,7 +152,7 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
try {
const credential = await startRegistration(options);
await api.post("/auth/passkey/register/verify", {
await api.post("/auth/security-key/register/verify", {
credential,
});
@ -217,7 +217,7 @@ export default function SecurityKeyForm({ open, setOpen }: SecurityKeyFormProps)
try {
const encodedCredentialId = encodeURIComponent(selectedSecurityKey.credentialId);
await api.delete(`/auth/passkey/${encodedCredentialId}`, {
await api.delete(`/auth/security-key/${encodedCredentialId}`, {
data: {
password: values.password,
}