Add test coverage and fix webauthn e2e tests to use real sessions
- Add test:e2e:coverage script with Bun's built-in coverage support - Create bunfig.toml with coverage configuration (text + lcov reporters) - Fix webauthn tests to create real database sessions/login requests instead of mock context objects that bypass auth middleware - Add createUserAPIContext helper for cleaner test code - Update security tests to expect NOT_FOUND when accessing other user's passkeys Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,6 +34,9 @@ devenv.local.nix
|
|||||||
# TypeScript
|
# TypeScript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Test coverage
|
||||||
|
coverage/
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
9
apps/api-server/bunfig.toml
Normal file
9
apps/api-server/bunfig.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[test]
|
||||||
|
# Coverage reporters: text for console, lcov for CI/tooling integration
|
||||||
|
coverageReporter = ["text", "lcov"]
|
||||||
|
|
||||||
|
# Output directory for lcov.info file
|
||||||
|
coverageDir = "coverage"
|
||||||
|
|
||||||
|
# Don't count test files in coverage metrics
|
||||||
|
coverageSkipTestFiles = true
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint . --cache",
|
"lint": "eslint . --cache",
|
||||||
"clean": "rm -rf dist .eslintcache",
|
"clean": "rm -rf dist .eslintcache",
|
||||||
"test:e2e": "bun test src/__tests__/e2e --no-parallel",
|
"test:e2e": "bun test src/__tests__/e2e --no-parallel --coverage",
|
||||||
"test:unit": "bun test src/__tests__/unit"
|
"test:unit": "bun test src/__tests__/unit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -9,15 +9,13 @@
|
|||||||
|
|
||||||
import type { Database } from "@reviq/db-schema";
|
import type { Database } from "@reviq/db-schema";
|
||||||
import type { Kysely } from "kysely";
|
import type { Kysely } from "kysely";
|
||||||
import type {
|
import type { APIContext } from "../../context.js";
|
||||||
APIContext,
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
||||||
AuthenticatedContext,
|
|
||||||
LoginRequestContext,
|
|
||||||
} from "../../context.js";
|
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
||||||
import { call } from "@orpc/server";
|
import { call } from "@orpc/server";
|
||||||
import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
|
import { VirtualAuthenticator } from "@reviq/virtual-authenticator";
|
||||||
import { router } from "../../router.js";
|
import { router } from "../../router.js";
|
||||||
|
import { COOKIE_NAMES } from "../../utils/cookies.js";
|
||||||
|
import { hashToken } from "../../utils/crypto.js";
|
||||||
import { getUserPasskeys } from "../../utils/webauthn.js";
|
import { getUserPasskeys } from "../../utils/webauthn.js";
|
||||||
import { KNOWN_AAGUIDS, TEST_RP } from "../helpers/test-constants.js";
|
import { KNOWN_AAGUIDS, TEST_RP } from "../helpers/test-constants.js";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +26,9 @@ import {
|
|||||||
truncateAllTables,
|
truncateAllTables,
|
||||||
} from "../helpers/test-db.js";
|
} from "../helpers/test-db.js";
|
||||||
|
|
||||||
|
/** Session expiry duration: 24 hours in milliseconds */
|
||||||
|
const SESSION_EXPIRY_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
let db: Kysely<Database> | undefined;
|
let db: Kysely<Database> | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,67 +42,93 @@ function getDb(): Kysely<Database> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an API context (for public endpoints)
|
* Create an API context with optional session token
|
||||||
*/
|
*/
|
||||||
function createAPIContext(): APIContext {
|
function createAPIContext(sessionToken?: string): APIContext {
|
||||||
|
const reqHeaders = new Headers();
|
||||||
|
if (sessionToken) {
|
||||||
|
reqHeaders.set("cookie", `${COOKIE_NAMES.SESSION_TOKEN}=${sessionToken}`);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
db: getDb(),
|
db: getDb(),
|
||||||
origin: TEST_RP.origin,
|
origin: TEST_RP.origin,
|
||||||
allowedOrigins: [...TEST_RP.allowedOrigins],
|
allowedOrigins: [...TEST_RP.allowedOrigins],
|
||||||
rpName: TEST_RP.rpName,
|
rpName: TEST_RP.rpName,
|
||||||
reqHeaders: new Headers(),
|
reqHeaders,
|
||||||
resHeaders: new Headers(),
|
resHeaders: new Headers(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an authenticated context (for protected endpoints)
|
* Create a real session in the database and return the token
|
||||||
*/
|
*/
|
||||||
function createAuthenticatedContext(
|
async function createSession(userId: number): Promise<string> {
|
||||||
userId: number,
|
const token = "test-session-" + String(Date.now()) + String(Math.random());
|
||||||
email: string,
|
const tokenHashValue = await hashToken(token);
|
||||||
): AuthenticatedContext {
|
const expiresAt = new Date(Date.now() + SESSION_EXPIRY_MS);
|
||||||
const now = new Date();
|
|
||||||
return {
|
await getDb()
|
||||||
...createAPIContext(),
|
.insertInto("sessions")
|
||||||
user: {
|
.values({
|
||||||
id: userId,
|
user_id: userId,
|
||||||
email,
|
token_hash: tokenHashValue,
|
||||||
displayName: null,
|
ip_address: "127.0.0.1",
|
||||||
emailVerifiedAt: null,
|
user_agent: "test-agent",
|
||||||
isSuperuser: false,
|
expires_at: expiresAt,
|
||||||
},
|
trusted_mode: false,
|
||||||
session: {
|
})
|
||||||
id: "1",
|
.execute();
|
||||||
trustedMode: false,
|
|
||||||
createdAt: now,
|
return token;
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
method: "session",
|
|
||||||
sessionId: "1",
|
|
||||||
expiresAt: new Date(now.getTime() + 24 * 60 * 60 * 1000),
|
|
||||||
createdAt: now,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a login request context (for login flow endpoints)
|
* Create a login request in the database and return ID and token
|
||||||
*/
|
*/
|
||||||
function createLoginRequestContext(
|
async function createLoginRequest(
|
||||||
userId: number,
|
userId: number,
|
||||||
email: string,
|
email: string,
|
||||||
): LoginRequestContext {
|
): Promise<{ id: number; token: string }> {
|
||||||
return {
|
const token = "test-login-" + String(Date.now()) + String(Math.random());
|
||||||
...createAPIContext(),
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
|
||||||
loginRequestId: 1,
|
|
||||||
user: {
|
const result = await getDb()
|
||||||
id: userId,
|
.insertInto("login_requests")
|
||||||
|
.values({
|
||||||
|
user_id: userId,
|
||||||
email,
|
email,
|
||||||
displayName: null,
|
token,
|
||||||
emailVerifiedAt: null,
|
expires_at: expiresAt,
|
||||||
isSuperuser: false,
|
})
|
||||||
},
|
.returning("id")
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
return { id: result.id, token };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an authenticated API context for a user (creates session + context)
|
||||||
|
*/
|
||||||
|
async function createUserAPIContext(userId: number): Promise<APIContext> {
|
||||||
|
const sessionToken = await createSession(userId);
|
||||||
|
return createAPIContext(sessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an API context with login request cookie
|
||||||
|
*/
|
||||||
|
function createLoginRequestContext(loginToken: string): APIContext {
|
||||||
|
const reqHeaders = new Headers();
|
||||||
|
reqHeaders.set("cookie", `${COOKIE_NAMES.LOGIN_REQUEST_TOKEN}=${loginToken}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
db: getDb(),
|
||||||
|
origin: TEST_RP.origin,
|
||||||
|
allowedOrigins: [...TEST_RP.allowedOrigins],
|
||||||
|
rpName: TEST_RP.rpName,
|
||||||
|
reqHeaders,
|
||||||
|
resHeaders: new Headers(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +154,7 @@ async function registerPasskey(
|
|||||||
authenticator: VirtualAuthenticator,
|
authenticator: VirtualAuthenticator,
|
||||||
) {
|
) {
|
||||||
const apiCtx = createAPIContext();
|
const apiCtx = createAPIContext();
|
||||||
const authCtx = createAuthenticatedContext(userId, email);
|
const authCtx = await createUserAPIContext(userId);
|
||||||
|
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createRegistrationOptions,
|
router.auth.webauthn.createRegistrationOptions,
|
||||||
@@ -152,7 +179,8 @@ async function authenticate(
|
|||||||
email: string,
|
email: string,
|
||||||
authenticator: VirtualAuthenticator,
|
authenticator: VirtualAuthenticator,
|
||||||
) {
|
) {
|
||||||
const loginCtx = createLoginRequestContext(userId, email);
|
const { token: loginToken } = await createLoginRequest(userId, email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
|
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
@@ -234,8 +262,8 @@ describe("registration flow", () => {
|
|||||||
// Create credential with virtual authenticator
|
// Create credential with virtual authenticator
|
||||||
const response = authenticator.createCredential(options);
|
const response = authenticator.createCredential(options);
|
||||||
|
|
||||||
// Verify registration via router
|
// Verify registration via router (requires authenticated session)
|
||||||
const authCtx = createAuthenticatedContext(user.id, user.email);
|
const authCtx = await createUserAPIContext(user.id);
|
||||||
await call(
|
await call(
|
||||||
router.auth.webauthn.verifyRegistration,
|
router.auth.webauthn.verifyRegistration,
|
||||||
{ challengeId, response },
|
{ challengeId, response },
|
||||||
@@ -256,7 +284,7 @@ describe("registration flow", () => {
|
|||||||
});
|
});
|
||||||
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
||||||
const apiCtx = createAPIContext();
|
const apiCtx = createAPIContext();
|
||||||
const authCtx = createAuthenticatedContext(user.id, user.email);
|
const authCtx = await createUserAPIContext(user.id);
|
||||||
|
|
||||||
// Register first passkey via router
|
// Register first passkey via router
|
||||||
const { options: options1, challengeId: challengeId1 } = await call(
|
const { options: options1, challengeId: challengeId1 } = await call(
|
||||||
@@ -299,7 +327,7 @@ describe("registration flow", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const apiCtx = createAPIContext();
|
const apiCtx = createAPIContext();
|
||||||
const authCtx = createAuthenticatedContext(user.id, user.email);
|
const authCtx = await createUserAPIContext(user.id);
|
||||||
|
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createRegistrationOptions,
|
router.auth.webauthn.createRegistrationOptions,
|
||||||
@@ -325,7 +353,7 @@ describe("registration flow", () => {
|
|||||||
});
|
});
|
||||||
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
||||||
const apiCtx = createAPIContext();
|
const apiCtx = createAPIContext();
|
||||||
const authCtx = createAuthenticatedContext(user.id, user.email);
|
const authCtx = await createUserAPIContext(user.id);
|
||||||
|
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createRegistrationOptions,
|
router.auth.webauthn.createRegistrationOptions,
|
||||||
@@ -355,7 +383,7 @@ describe("registration flow", () => {
|
|||||||
});
|
});
|
||||||
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
const authenticator = new VirtualAuthenticator({ origin: TEST_RP.origin });
|
||||||
const apiCtx = createAPIContext();
|
const apiCtx = createAPIContext();
|
||||||
const authCtx = createAuthenticatedContext(user.id, user.email);
|
const authCtx = await createUserAPIContext(user.id);
|
||||||
|
|
||||||
// Create options via router
|
// Create options via router
|
||||||
const { options } = await call(
|
const { options } = await call(
|
||||||
@@ -399,7 +427,8 @@ describe("authentication flow", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create authentication options via router
|
// Create authentication options via router
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -427,7 +456,8 @@ describe("authentication flow", () => {
|
|||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
// Authenticate via router
|
// Authenticate via router
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options: authOptions, challengeId: authChallengeId } = await call(
|
const { options: authOptions, challengeId: authChallengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -460,7 +490,8 @@ describe("authentication flow", () => {
|
|||||||
expect(firstPasskey.lastUsedAt).toBeNull();
|
expect(firstPasskey.lastUsedAt).toBeNull();
|
||||||
|
|
||||||
// Authenticate via router
|
// Authenticate via router
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options: authOptions, challengeId: authChallengeId } = await call(
|
const { options: authOptions, challengeId: authChallengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -489,7 +520,8 @@ describe("authentication flow", () => {
|
|||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
// Authenticate via router
|
// Authenticate via router
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options: authOptions, challengeId: authChallengeId } = await call(
|
const { options: authOptions, challengeId: authChallengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -522,7 +554,8 @@ describe("authentication flow", () => {
|
|||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
// Create auth options via router
|
// Create auth options via router
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options: authOptions } = await call(
|
const { options: authOptions } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -585,7 +618,8 @@ describe("security tests", () => {
|
|||||||
authenticator.setSignCount(regResponse.id, 0);
|
authenticator.setSignCount(regResponse.id, 0);
|
||||||
|
|
||||||
// Create a new authentication challenge
|
// Create a new authentication challenge
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -624,7 +658,8 @@ describe("security tests", () => {
|
|||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
// Create authentication challenge
|
// Create authentication challenge
|
||||||
const loginCtx = createLoginRequestContext(user.id, user.email);
|
const { token: loginToken } = await createLoginRequest(user.id, user.email);
|
||||||
|
const loginCtx = createLoginRequestContext(loginToken);
|
||||||
const { options, challengeId } = await call(
|
const { options, challengeId } = await call(
|
||||||
router.auth.webauthn.createAuthenticationOptions,
|
router.auth.webauthn.createAuthenticationOptions,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -755,7 +790,7 @@ describe("passkey management", () => {
|
|||||||
await registerPasskey(user.id, user.email, authenticator2);
|
await registerPasskey(user.id, user.email, authenticator2);
|
||||||
|
|
||||||
// List passkeys via router handler
|
// List passkeys via router handler
|
||||||
const ctx = createAuthenticatedContext(user.id, user.email);
|
const ctx = await createUserAPIContext(user.id);
|
||||||
const passkeys = await call(router.me.passkeys.list, undefined, {
|
const passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
});
|
});
|
||||||
@@ -806,7 +841,7 @@ describe("passkey management", () => {
|
|||||||
|
|
||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
const ctx = createAuthenticatedContext(user.id, user.email);
|
const ctx = await createUserAPIContext(user.id);
|
||||||
let passkeys = await call(router.me.passkeys.list, undefined, {
|
let passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
});
|
});
|
||||||
@@ -842,8 +877,8 @@ describe("passkey management", () => {
|
|||||||
await registerPasskey(user1.id, user1.email, auth1);
|
await registerPasskey(user1.id, user1.email, auth1);
|
||||||
await registerPasskey(user2.id, user2.email, auth2);
|
await registerPasskey(user2.id, user2.email, auth2);
|
||||||
|
|
||||||
const ctx1 = createAuthenticatedContext(user1.id, user1.email);
|
const ctx1 = await createUserAPIContext(user1.id);
|
||||||
const ctx2 = createAuthenticatedContext(user2.id, user2.email);
|
const ctx2 = await createUserAPIContext(user2.id);
|
||||||
|
|
||||||
const user2Passkeys = await call(router.me.passkeys.list, undefined, {
|
const user2Passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx2,
|
context: ctx2,
|
||||||
@@ -853,12 +888,18 @@ describe("passkey management", () => {
|
|||||||
throw new Error("Expected user2 passkey to exist");
|
throw new Error("Expected user2 passkey to exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to rename user2's passkey using user1's context (should not work)
|
// Try to rename user2's passkey using user1's context (should throw NOT_FOUND)
|
||||||
|
try {
|
||||||
await call(
|
await call(
|
||||||
router.me.passkeys.rename,
|
router.me.passkeys.rename,
|
||||||
{ passkeyId: user2FirstPasskey.id, name: "Hacked Name" },
|
{ passkeyId: user2FirstPasskey.id, name: "Hacked Name" },
|
||||||
{ context: ctx1 },
|
{ context: ctx1 },
|
||||||
);
|
);
|
||||||
|
throw new Error("Expected rename to fail with NOT_FOUND");
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect((error as Error).message).toContain("Passkey not found");
|
||||||
|
}
|
||||||
|
|
||||||
// User2's passkey should be unchanged
|
// User2's passkey should be unchanged
|
||||||
const user2PasskeysAfter = await call(router.me.passkeys.list, undefined, {
|
const user2PasskeysAfter = await call(router.me.passkeys.list, undefined, {
|
||||||
@@ -880,7 +921,7 @@ describe("passkey management", () => {
|
|||||||
|
|
||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
const ctx = createAuthenticatedContext(user.id, user.email);
|
const ctx = await createUserAPIContext(user.id);
|
||||||
let passkeys = await call(router.me.passkeys.list, undefined, {
|
let passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
});
|
});
|
||||||
@@ -906,7 +947,7 @@ describe("passkey management", () => {
|
|||||||
await registerPasskey(user.id, user.email, auth1);
|
await registerPasskey(user.id, user.email, auth1);
|
||||||
await registerPasskey(user.id, user.email, auth2);
|
await registerPasskey(user.id, user.email, auth2);
|
||||||
|
|
||||||
const ctx = createAuthenticatedContext(user.id, user.email);
|
const ctx = await createUserAPIContext(user.id);
|
||||||
let passkeys = await call(router.me.passkeys.list, undefined, {
|
let passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
});
|
});
|
||||||
@@ -937,7 +978,7 @@ describe("passkey management", () => {
|
|||||||
|
|
||||||
await registerPasskey(user.id, user.email, authenticator);
|
await registerPasskey(user.id, user.email, authenticator);
|
||||||
|
|
||||||
const ctx = createAuthenticatedContext(user.id, user.email);
|
const ctx = await createUserAPIContext(user.id);
|
||||||
const passkeys = await call(router.me.passkeys.list, undefined, {
|
const passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
});
|
});
|
||||||
@@ -978,8 +1019,8 @@ describe("passkey management", () => {
|
|||||||
await registerPasskey(user1.id, user1.email, auth1);
|
await registerPasskey(user1.id, user1.email, auth1);
|
||||||
await registerPasskey(user2.id, user2.email, auth2);
|
await registerPasskey(user2.id, user2.email, auth2);
|
||||||
|
|
||||||
const ctx1 = createAuthenticatedContext(user1.id, user1.email);
|
const ctx1 = await createUserAPIContext(user1.id);
|
||||||
const ctx2 = createAuthenticatedContext(user2.id, user2.email);
|
const ctx2 = await createUserAPIContext(user2.id);
|
||||||
|
|
||||||
const user2Passkeys = await call(router.me.passkeys.list, undefined, {
|
const user2Passkeys = await call(router.me.passkeys.list, undefined, {
|
||||||
context: ctx2,
|
context: ctx2,
|
||||||
@@ -989,12 +1030,18 @@ describe("passkey management", () => {
|
|||||||
throw new Error("Expected user2 passkey to exist");
|
throw new Error("Expected user2 passkey to exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to delete user2's passkey using user1's context (should not affect user2)
|
// Try to delete user2's passkey using user1's context (should throw NOT_FOUND)
|
||||||
|
try {
|
||||||
await call(
|
await call(
|
||||||
router.me.passkeys.delete,
|
router.me.passkeys.delete,
|
||||||
{ passkeyId: user2FirstPasskey.id },
|
{ passkeyId: user2FirstPasskey.id },
|
||||||
{ context: ctx1 },
|
{ context: ctx1 },
|
||||||
);
|
);
|
||||||
|
throw new Error("Expected delete to fail with NOT_FOUND");
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect((error as Error).message).toContain("Passkey not found");
|
||||||
|
}
|
||||||
|
|
||||||
// User2's passkey should still exist
|
// User2's passkey should still exist
|
||||||
const user2PasskeysAfter = await call(router.me.passkeys.list, undefined, {
|
const user2PasskeysAfter = await call(router.me.passkeys.list, undefined, {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
\restrict NwR9NcSOK9D25dGgvUNdLvsNphDACAXsvkQ5NSmhpf6sLcFR570yQ96lhgCbCXf
|
\restrict KXTb98GlQCetYfS0eRd7LzGbBIiTxg53JFiqnSln3PIIhE3DD10jqFdLLY3AKZu
|
||||||
|
|
||||||
-- Dumped from database version 17.7
|
-- Dumped from database version 17.7
|
||||||
-- Dumped by pg_dump version 17.7
|
-- Dumped by pg_dump version 17.7
|
||||||
@@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public.user_devices
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict NwR9NcSOK9D25dGgvUNdLvsNphDACAXsvkQ5NSmhpf6sLcFR570yQ96lhgCbCXf
|
\unrestrict KXTb98GlQCetYfS0eRd7LzGbBIiTxg53JFiqnSln3PIIhE3DD10jqFdLLY3AKZu
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user