Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/better-auth-external-db/src/backend/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ interface Message {

export const chatRoom = actor({
// onAuth runs on the server & before connecting to the actor
onAuth: async (c: OnAuthOptions) => {
onAuth: async (opts: OnAuthOptions) => {
// Access Better Auth session
const authResult = await auth.api.getSession({
headers: c.request.headers,
headers: opts.request.headers,
});
if (!authResult) throw new Unauthorized();

Expand Down
25 changes: 1 addition & 24 deletions examples/tenant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,6 @@ This tenant system demonstrates:
- **Dashboard Stats**: Access to basic member statistics only
- **No Invoice Access**: Cannot view or manage billing information

## Security Features

### Authentication
```typescript
// Token-based authentication
createConnState: async (c, { params }) => {
const token = params.token;
const { userId, role } = await authenticate(token);
return { userId, role };
}
```

### Authorization
```typescript
// Server-side permission checks
getInvoices: (c) => {
if (c.conn.role !== "admin") {
throw new UserError("Permission denied: Admin role required");
}
return c.state.invoices;
}
```

### Data Isolation
- Organization-scoped data using actor keys
- User context stored in connection state
Expand Down Expand Up @@ -217,4 +194,4 @@ To test the role-based access control:

## License

Apache 2.0
Apache 2.0
15 changes: 11 additions & 4 deletions internal-docs/CONFIG_TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@

- All types must be included in `ActorTypes` so the user can hardcode types

- If using input parameters for inferring types, they must be raw parameters. e.g.:
- If using input parameters for inferring types, they must be raw parameters. They also must be the last parameter. e.g.:

```typescript
// It's hard for users to infer TConnParams
// DO NOT DO THIS:
// It's hard for users to infer TConnParams because they would have to import & use an extra type
onAuth: (opts: OnAuthOpts<TConnParams>) => TAuthData,
// Because you would have to import & use an extra type
onAuth: (opts: OnAuthOpts<MyConnParam>) => TAuthData,

// DO NOT DO THIS:
// If you only want to access `opts`, you'll have to also define `params`
onAuth: (params: TConnParams, opts: OnAuthOpts) => TAuthData,

// DO THIS:
// This allows you to not accept `params` and only access `opts`
onAuth: (opts: OnAuthOpts, params: TConnParams) => TAuthData,
```

- When inferring via return data, you must use a union. e.g.:
Expand Down
12 changes: 6 additions & 6 deletions packages/core/fixtures/driver-test-suite/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { actor, UserError } from "@rivetkit/core";
// Basic auth actor - requires API key
export const authActor = actor({
state: { requests: 0 },
onAuth: (params) => {
const apiKey = (params as any)?.apiKey;
onAuth: (opts, params: { apiKey?: string } | undefined) => {
const apiKey = params?.apiKey;
if (!apiKey) {
throw new UserError("API key required", { code: "missing_auth" });
}
Expand All @@ -27,9 +27,9 @@ export const authActor = actor({
// Intent-specific auth actor - checks different permissions for different intents
export const intentAuthActor = actor({
state: { value: 0 },
onAuth: (params, { request, intents }) => {
onAuth: ({ request, intents }, params: { role: string }) => {
console.log("intents", intents, params);
const role = (params as any)?.role;
const role = params.role;

if (intents.has("create") && role !== "admin") {
throw new UserError("Admin role required for create operations", {
Expand Down Expand Up @@ -80,8 +80,8 @@ export const noAuthActor = actor({
// Async auth actor - tests promise-based authentication
export const asyncAuthActor = actor({
state: { count: 0 },
onAuth: async (params) => {
const token = (params as any)?.token;
onAuth: async (opts, params: { token?: string } | undefined) => {
const token = params?.token;
if (!token) {
throw new UserError("Token required", { code: "missing_token" });
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/fixtures/driver-test-suite/conn-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { actor } from "@rivetkit/core";
export const counterWithParams = actor({
onAuth: () => {},
state: { count: 0, initializers: [] as string[] },
createConnState: (c, { params }: { params: { name?: string } }) => {
createConnState: (c, opts, params: { name?: string }) => {
return {
name: params?.name || "anonymous",
name: params.name || "anonymous",
};
},
onConnect: (c, conn) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/fixtures/driver-test-suite/conn-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const connStateActor = actor({
// Define connection state
createConnState: (
c,
{ params }: { params?: { username?: string; role?: string } },
opts,
params: { username?: string; role?: string },
): ConnState => {
return {
username: params?.username || "anonymous",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/fixtures/driver-test-suite/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export const counterWithLifecycle = actor({
count: 0,
events: [] as string[],
},
createConnState: (c, params: ConnParams) => ({
createConnState: (c, opts, params: ConnParams) => ({
joinTime: Date.now(),
}),
onStart: (c) => {
c.state.events.push("onStart");
},
onBeforeConnect: (c, params) => {
onBeforeConnect: (c, opts, params: ConnParams) => {
if (params?.trackLifecycle) c.state.events.push("onBeforeConnect");
},
onConnect: (c, conn) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/fixtures/driver-test-suite/raw-http-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export const rawHttpAuthActor = actor({
state: {
requestCount: 0,
},
onAuth: (params) => {
const apiKey = (params as any)?.apiKey;
onAuth: (opts, params: { apiKey?: string }) => {
const apiKey = params.apiKey;
if (!apiKey) {
throw new UserError("API key required", { code: "missing_auth" });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export const rawWebSocketAuthActor = actor({
connectionCount: 0,
messageCount: 0,
},
onAuth: (params) => {
const apiKey = (params as any)?.apiKey;
onAuth: (opts, params: { apiKey?: string }) => {
const apiKey = params.apiKey;
if (!apiKey) {
throw new UserError("API key required", { code: "missing_auth" });
}
Expand Down
10 changes: 1 addition & 9 deletions packages/core/fixtures/driver-test-suite/request-access-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@ import { actor } from "@rivetkit/core";
* onAuth runs on the HTTP server, not in the actor, so we test it separately
*/
export const requestAccessAuthActor = actor({
onAuth: ({
request,
intents,
params,
}: {
request: Request;
intents: Set<string>;
params?: { trackRequest?: boolean };
}) => {
onAuth: ({ request, intents }, params: { trackRequest?: boolean }) => {
if (params?.trackRequest) {
// Extract request info and return it as auth data
const headers: Record<string, string> = {};
Expand Down
10 changes: 2 additions & 8 deletions packages/core/fixtures/driver-test-suite/request-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ export const requestAccessActor = actor({
requestHeaders: {} as Record<string, string>,
},
},
createConnState: (
c,
{
params,
request,
}: { params?: { trackRequest?: boolean }; request?: Request },
) => {
createConnState: (c, { request }, params: { trackRequest?: boolean }) => {
// In createConnState, the state isn't available yet.

return {
Expand All @@ -60,7 +54,7 @@ export const requestAccessActor = actor({
c.state.createConnStateRequest = conn.state.requestInfo;
}
},
onBeforeConnect: (c, { request, params }) => {
onBeforeConnect: (c, { request }, params) => {
if (params?.trackRequest) {
if (request) {
c.state.onBeforeConnectRequest.hasRequest = true;
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/actor/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ type CreateConnState<
| {
createConnState: (
c: InitContext,
params: TConnParams,
opts: OnConnectOptions,
params: TConnParams,
) => TConnState | Promise<TConnState>;
}
| Record<never, never>;
Expand Down Expand Up @@ -221,8 +221,8 @@ type OnAuth<TConnParams, TAuthData> =
* @throws Throw an error to deny access to the actor
*/
onAuth: (
params: TConnParams,
opts: OnAuthOptions,
params: TConnParams,
) => TAuthData | Promise<TAuthData>;
}
| Record<never, never>;
Expand Down Expand Up @@ -376,8 +376,8 @@ interface BaseActorConfig<
TAuthData,
TDatabase
>,
params: TConnParams,
opts: OnConnectOptions,
params: TConnParams,
) => void | Promise<void>;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actor/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export class InvalidStateType extends ActorError {
msg += "Attempted to set invalid state.";
}
msg +=
" State must be CBOR serializable. Valid types include: null, undefined, boolean, string, number, BigInt, Date, RegExp, Error, typed arrays (Uint8Array, Int8Array, Float32Array, etc.), Map, Set, Array, and plain objects. (https://www.rivet.gg/docs/actors/state/#limitations)";
" Valid types include: null, undefined, boolean, string, number, BigInt, Date, RegExp, Error, typed arrays (Uint8Array, Int8Array, Float32Array, etc.), Map, Set, Array, and plain objects. (https://www.rivet.gg/docs/actors/state/#limitations)";
super("invalid_state_type", msg);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/actor/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,8 +728,8 @@ export class ActorInstance<
if (this.#config.onBeforeConnect) {
await this.#config.onBeforeConnect(
this.actorContext,
params,
onBeforeConnectOpts,
params,
);
}

Expand All @@ -745,8 +745,8 @@ export class ActorInstance<
undefined,
undefined
>,
params,
onBeforeConnectOpts,
params,
);
if (dataOrPromise instanceof Promise) {
connState = await deadline(
Expand Down
11 changes: 7 additions & 4 deletions packages/core/src/manager/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,13 @@ export async function authenticateRequest(
}

try {
const dataOrPromise = actorDefinition.config.onAuth(params, {
request: c.req.raw,
intents,
});
const dataOrPromise = actorDefinition.config.onAuth(
{
request: c.req.raw,
intents,
},
params,
);
if (dataOrPromise instanceof Promise) {
return await dataOrPromise;
} else {
Expand Down
Loading