@rivetkit/core
Version:
592 lines (492 loc) • 19.5 kB
text/typescript
import { describe, expect, test } from "vitest";
import type { ActorError } from "@/client/errors";
import type { DriverTestConfig } from "../mod";
import { setupDriverTest } from "../utils";
export function runActorAuthTests(driverTestConfig: DriverTestConfig) {
describe("Actor Authentication Tests", () => {
describe("Basic Authentication", () => {
test("should allow access with valid auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Create client with valid auth params
const instance = client.authActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
// This should succeed with valid API key
const authData = await instance.getUserAuth();
if (driverTestConfig.clientType === "inline") {
// Inline clients don't have auth data
expect(authData).toBeUndefined();
} else {
// HTTP clients should have auth data
expect(authData).toEqual({
userId: "user123",
token: "valid-api-key",
});
}
// Should be able to call actions
const requests = await instance.getRequests();
expect(requests).toBe(1);
});
test("should deny access with invalid auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// This should fail without proper authorization
const instance = client.authActor.getOrCreate();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication
const requests = await instance.getRequests();
expect(typeof requests).toBe("number");
} else {
// HTTP clients should enforce authentication
try {
await instance.getRequests();
expect.fail("Expected authentication error");
} catch (error) {
expect((error as ActorError).code).toBe("missing_auth");
}
}
});
test("should expose auth data on connection", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.authActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
// Auth data should be available via c.conn.auth
const authData = await instance.getUserAuth();
if (driverTestConfig.clientType === "inline") {
// Inline clients don't have auth data
expect(authData).toBeUndefined();
} else {
// HTTP clients should have auth data
expect(authData).toBeDefined();
expect((authData as any).userId).toBe("user123");
expect((authData as any).token).toBe("valid-api-key");
}
});
});
describe("Intent-Based Authentication", () => {
test("should allow get operations for any role", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const createdInstance = await client.intentAuthActor.create(["foo"], {
params: { role: "admin" },
});
const actorId = await createdInstance.resolve();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication
const instance = client.intentAuthActor.getForId(actorId);
const value = await instance.getValue();
expect(value).toBe(0);
} else {
// HTTP clients - actions require user or admin role
const instance = client.intentAuthActor.getForId(actorId, {
params: { role: "user" }, // Actions require user or admin role
});
const value = await instance.getValue();
expect(value).toBe(0);
}
});
test("should require admin role for create operations", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const instance = client.intentAuthActor.getOrCreate(undefined, {
params: { role: "user" },
});
const value = await instance.getValue();
expect(value).toBe(0);
} else {
// HTTP clients should enforce authentication
try {
const instance = client.intentAuthActor.getOrCreate(undefined, {
params: { role: "user" },
});
await instance.getValue();
expect.fail("Expected permission error for create operation");
} catch (error) {
expect((error as ActorError).code).toBe("insufficient_permissions");
expect((error as ActorError).message).toContain(
"Admin role required",
);
}
}
});
test("should allow actions for user and admin roles", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const createdInstance = await client.intentAuthActor.create(["foo"], {
params: { role: "admin" },
});
const actorId = await createdInstance.resolve();
// This should fail - actions require user or admin role
const instance = client.intentAuthActor.getForId(actorId, {
params: { role: "guest" },
});
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const result = await instance.setValue(42);
expect(result).toBe(42);
} else {
// HTTP clients should enforce authentication
try {
await instance.setValue(42);
expect.fail("Expected permission error for action");
} catch (error) {
expect((error as ActorError).code).toBe("insufficient_permissions");
expect((error as ActorError).message).toContain(
"User or admin role required",
);
}
}
});
});
describe("Public Access", () => {
test("should allow access with empty onAuth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Public actor should allow access without authentication
const instance = client.publicActor.getOrCreate();
const visitors = await instance.visit();
expect(visitors).toBe(1);
// Should be able to call multiple times
const visitors2 = await instance.visit();
expect(visitors2).toBe(2);
});
test("should deny access without onAuth defined", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Actor without onAuth should be blocked
const instance = client.noAuthActor.getOrCreate();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const value = await instance.getValue();
expect(value).toBe(42);
} else {
// HTTP clients should enforce authentication
try {
await instance.getValue();
expect.fail(
"Expected access to be denied for actor without onAuth",
);
} catch (error) {
expect((error as ActorError).code).toBe("forbidden");
}
}
});
});
describe("Async Authentication", () => {
test("should handle promise-based auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.asyncAuthActor.getOrCreate(undefined, {
params: { token: "valid" },
});
// Should succeed with valid token
const result = await instance.increment();
expect(result).toBe(1);
// Auth data should be available
const authData = await instance.getAuthData();
if (driverTestConfig.clientType === "inline") {
// Inline clients don't have auth data
expect(authData).toBeUndefined();
} else {
// HTTP clients should have auth data
expect(authData).toBeDefined();
expect((authData as any).userId).toBe("user-valid");
expect((authData as any).validated).toBe(true);
}
});
test("should handle async auth failures", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.asyncAuthActor.getOrCreate();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const result = await instance.increment();
expect(result).toBe(1);
} else {
// HTTP clients should enforce authentication
try {
await instance.increment();
expect.fail("Expected async auth failure");
} catch (error) {
expect((error as ActorError).code).toBe("missing_token");
}
}
});
});
describe("Authentication Across Transports", () => {
if (driverTestConfig.transport === "websocket") {
test("should authenticate WebSocket connections", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Test WebSocket connection auth
const instance = client.authActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
// Should be able to establish connection and call actions
const authData = await instance.getUserAuth();
expect(authData).toBeDefined();
expect((authData as any).userId).toBe("user123");
});
}
test("should authenticate HTTP actions", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Test HTTP action auth
const instance = client.authActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
// Actions should require authentication
const requests = await instance.getRequests();
expect(typeof requests).toBe("number");
});
});
describe("Error Handling", () => {
test("should handle auth errors gracefully", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.authActor.getOrCreate();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const requests = await instance.getRequests();
expect(typeof requests).toBe("number");
} else {
// HTTP clients should enforce authentication
try {
await instance.getRequests();
expect.fail("Expected authentication error");
} catch (error) {
// Error should be properly structured
const actorError = error as ActorError;
expect(actorError.code).toBeDefined();
expect(actorError.message).toBeDefined();
}
}
});
test("should preserve error details for debugging", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.asyncAuthActor.getOrCreate();
if (driverTestConfig.clientType === "inline") {
// Inline clients bypass authentication - should succeed
const result = await instance.increment();
expect(result).toBe(1);
} else {
// HTTP clients should enforce authentication
try {
await instance.increment();
expect.fail("Expected token error");
} catch (error) {
const actorError = error as ActorError;
expect(actorError.code).toBe("missing_token");
expect(actorError.message).toBe("Token required");
}
}
});
});
describe("Raw HTTP Authentication", () => {
test("should allow raw HTTP access with valid auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Create actor with valid auth
const instance = client.rawHttpAuthActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
// Raw HTTP request should succeed
const response = await instance.fetch("api/auth-info");
expect(response.ok).toBe(true);
const data = (await response.json()) as any;
expect(data.message).toBe("Authenticated request");
expect(data.requestCount).toBe(1);
// Regular actions should also work
const count = await instance.getRequestCount();
expect(count).toBe(1);
});
test("should deny raw HTTP access without auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Create actor without auth
const instance = client.rawHttpAuthActor.getOrCreate();
// All clients should now enforce authentication for raw endpoints
const response = await instance.fetch("api/protected");
if (driverTestConfig.clientType === "inline") {
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
} else {
expect(response.ok).toBe(false);
expect(response.status).toBe(400);
}
// Check error details
try {
const errorData = (await response.json()) as any;
expect(errorData.c || errorData.code).toBe("missing_auth");
} catch {
// Response might be CBOR encoded, status code check is sufficient
}
});
test("should deny raw HTTP for actors without onAuth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawHttpNoAuthActor.getOrCreate();
// All clients should now enforce authentication for raw endpoints
const response = await instance.fetch("api/test");
if (driverTestConfig.clientType === "inline") {
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
} else {
expect(response.ok).toBe(false);
expect(response.status).toBe(403);
}
// Check error details
try {
const errorData = (await response.json()) as any;
expect(errorData.c || errorData.code).toBe("forbidden");
} catch {
// Response might be CBOR encoded, status code check is sufficient
}
});
test("should allow public raw HTTP access", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawHttpPublicActor.getOrCreate();
// Should work without auth
const response = await instance.fetch("api/visit");
expect(response.ok).toBe(true);
const data = (await response.json()) as any;
expect(data.message).toBe("Welcome visitor!");
expect(data.count).toBe(1);
// Second request
const response2 = await instance.fetch("api/visit");
const data2 = (await response2.json()) as any;
expect(data2.count).toBe(2);
});
test("should handle custom auth in onFetch", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawHttpCustomAuthActor.getOrCreate();
// Request without auth should fail
const response1 = await instance.fetch("api/data");
expect(response1.ok).toBe(false);
expect(response1.status).toBe(401);
const error1 = (await response1.json()) as any;
expect(error1.error).toBe("Unauthorized");
// Request with wrong token should fail
const response2 = await instance.fetch("api/data", {
headers: {
Authorization: "Bearer wrong-token",
},
});
expect(response2.ok).toBe(false);
expect(response2.status).toBe(403);
// Request with correct token should succeed
const response3 = await instance.fetch("api/data", {
headers: {
Authorization: "Bearer custom-token",
},
});
expect(response3.ok).toBe(true);
const data = (await response3.json()) as any;
expect(data.message).toBe("Authorized!");
expect(data.authorized).toBe(1);
// Check stats
const stats = await instance.getStats();
expect(stats.authorized).toBe(1);
expect(stats.unauthorized).toBe(2);
});
});
describe("Raw WebSocket Authentication", () => {
test("should allow raw WebSocket access with valid auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
// Create actor with valid auth
const instance = client.rawWebSocketAuthActor.getOrCreate(undefined, {
params: { apiKey: "valid-api-key" },
});
const ws = await instance.websocket();
// Wait for welcome message
const welcomePromise = new Promise((resolve, reject) => {
ws.addEventListener("message", (event: any) => {
const data = JSON.parse(event.data);
if (data.type === "welcome") {
resolve(data);
}
});
ws.addEventListener("close", () => reject("closed"));
});
const welcomeData = (await welcomePromise) as any;
expect(welcomeData.message).toBe("Authenticated WebSocket connection");
expect(welcomeData.connectionCount).toBe(1);
ws.close();
});
test("should deny raw WebSocket access without auth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawWebSocketAuthActor.getOrCreate();
// All clients should now enforce authentication for raw endpoints
try {
await instance.websocket();
expect.fail("Expected authentication error");
} catch (error) {
// WebSocket connection failures may not always have structured error codes
expect(error).toBeDefined();
}
});
test("should deny raw WebSocket for actors without onAuth", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawWebSocketNoAuthActor.getOrCreate();
// All clients should now enforce authentication for raw endpoints
try {
await instance.websocket();
expect.fail("Expected forbidden error");
} catch (error) {
// WebSocket connection failures may not always have structured error codes
expect(error).toBeDefined();
}
});
test("should allow public raw WebSocket access", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawWebSocketPublicActor.getOrCreate();
// Should work without auth
const ws = await instance.websocket();
const welcomePromise = new Promise((resolve, reject) => {
ws.addEventListener("message", (event: any) => {
const data = JSON.parse(event.data);
if (data.type === "welcome") {
resolve(data);
}
});
ws.addEventListener("close", reject);
});
const welcomeData = (await welcomePromise) as any;
expect(welcomeData.message).toBe("Public WebSocket connection");
expect(welcomeData.visitorNumber).toBe(1);
ws.close();
});
test("should handle custom auth in onWebSocket", async (c) => {
const { client } = await setupDriverTest(c, driverTestConfig);
const instance = client.rawWebSocketCustomAuthActor.getOrCreate();
// WebSocket without token should be rejected
try {
const ws1 = await instance.websocket();
// Listen for error message before close
const errorPromise = new Promise((resolve, reject) => {
ws1.addEventListener("message", (event: any) => {
const data = JSON.parse(event.data);
if (data.type === "error") {
resolve(data);
}
});
ws1.addEventListener("close", reject);
});
const errorData = (await errorPromise) as any;
expect(errorData.type).toBe("error");
expect(errorData.message).toBe("Unauthorized");
} catch (error) {
// Some drivers might reject the connection immediately
expect(error).toBeDefined();
}
// WebSocket with correct token should succeed
const ws2 = await instance.websocket("?token=custom-ws-token");
const authPromise = new Promise((resolve, reject) => {
ws2.addEventListener("message", (event: any) => {
const data = JSON.parse(event.data);
if (data.type === "authorized") {
resolve(data);
}
});
ws2.addEventListener("close", reject);
});
const authData = (await authPromise) as any;
expect(authData.message).toBe("Welcome authenticated user!");
ws2.close();
// Check stats
const stats = await instance.getStats();
expect(stats.authorized).toBeGreaterThanOrEqual(1);
expect(stats.unauthorized).toBeGreaterThanOrEqual(1);
});
});
});
}