Skip to content

Commit 8503958

Browse files
committed
feat: start drafting roomySink and streamplaceSource.
1 parent aabedf4 commit 8503958

File tree

16 files changed

+3052
-510
lines changed

16 files changed

+3052
-510
lines changed

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "iso-repo"]
2+
path = iso-repo
3+
url = git@github.com:zicklag/iso-repo.git
4+
branch = fix-invocation

actors/actors.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
import { actor, event, setup } from "rivetkit";
2-
import { operatorAuth } from "./actors/operatorAuth";
1+
import { setup } from "rivetkit";
32

4-
const counter = actor({
5-
state: { count: 0 },
6-
events: {
7-
count: event<number>(),
8-
},
9-
actions: {
10-
increment: (c, amount: number) => {
11-
c.state.count += amount;
12-
c.broadcast("count", c.state.count);
13-
return c.state.count;
14-
},
15-
},
16-
});
3+
import { operatorAuth } from "./actors/operatorAuth";
4+
import { roomySink } from "./actors/roomySink";
5+
import { streamplaceSource } from "./actors/streamplaceSource";
176

187
export const registry = setup({
19-
use: { counter, operatorAuth },
8+
use: { operatorAuth, roomySink, streamplaceSource },
209
});

actors/actors/operatorAuth.ts

Lines changed: 153 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -3,155 +3,170 @@ import { EdDSASigner } from "iso-signatures/signers/eddsa.js";
33
import { Store } from "iso-ucan/store";
44
import { type } from "arktype";
55
import { capabilities, kvDriver, verifierResolver } from "../ucan";
6-
import { Capability } from "iso-ucan/capability";
76
import { IdResolver, MemoryCache } from "@atproto/identity";
87
import { verifyJwt } from "@atproto/xrpc-server";
9-
import { DID } from "iso-ucan/types";
108
import { Invocation } from "iso-ucan/invocation";
9+
import { Delegation } from "iso-ucan/delegation";
1110

1211
const ActorCreateInput = type({
13-
adminDids: "string[]",
12+
adminDids: "string[]",
1413
});
15-
1614
const ConnParams = type({
17-
clientDid: "string",
18-
serviceAuthToken: "string",
19-
}).or(type.undefined);
20-
21-
type State = { privateKey: string; adminDids: string[] };
15+
clientDid: "string",
16+
serviceAuthToken: "string",
17+
})
18+
.or(type.undefined)
19+
.or(type.null);
20+
type State = {
21+
privateKey: string;
22+
adminDids: string[];
23+
};
2224
type ConnState = { did: string; clientDid: string } | undefined;
2325
type Vars = {
24-
signer: EdDSASigner;
25-
store: Store;
26-
idResolver: IdResolver;
26+
signer: EdDSASigner;
27+
store: Store;
28+
idResolver: IdResolver;
2729
};
2830

2931
export const operatorAuth = actor({
30-
state: {
31-
privateKey: "",
32-
adminDids: [] as string[],
33-
},
34-
onCreate: async (c, rawInput) => {
35-
// This is a singleton actor that can only be created as "main"
36-
if (c.key.length != 1 || c.key[0] != "main") {
37-
console.error(
38-
'Cannot create operatorAuth actor with key other than ["main"]:',
39-
c.key,
40-
);
41-
// If this actor is not "main" immediately destroy it
42-
c.destroy();
43-
return;
44-
}
45-
46-
// Error if no admin DIDs were specified
47-
const input = ActorCreateInput(rawInput);
48-
if (input instanceof type.errors) {
49-
console.error("Invalid creation input to operatorAuth:", input.summary);
50-
c.destroy();
51-
return;
52-
}
53-
54-
// Set the admin IDS
55-
c.state.adminDids = input.adminDids;
56-
57-
// Generate a new signing key
58-
c.state.privateKey = (await EdDSASigner.generate()).export();
59-
},
60-
createVars: async (c) => {
61-
// This can happen if creation fails, but this lifecycle hook will still run
62-
if (c.aborted) return undefined as any;
63-
64-
return {
65-
signer: await EdDSASigner.import(c.state.privateKey),
66-
store: new Store(kvDriver(c.kv)),
67-
idResolver: new IdResolver({
68-
didCache: new MemoryCache(),
69-
}),
70-
};
71-
},
72-
73-
createConnState: async (c, rawParams?): Promise<ConnState> => {
74-
const { idResolver } = c.vars as Vars;
75-
76-
const params = ConnParams(rawParams);
77-
if (!params) return;
78-
79-
try {
80-
// Parse parameters
81-
if (params instanceof type.errors) {
82-
throw new UserError(
83-
`Failed to parse connection parameters: ${params.summary}`,
84-
);
85-
}
86-
87-
const jwt = params.serviceAuthToken;
88-
89-
const payload = await verifyJwt(
90-
jwt,
91-
null,
92-
null,
93-
async (did, forceRefresh) => {
94-
const atprotoData = await idResolver.did.resolveAtprotoData(
95-
did,
96-
forceRefresh,
97-
);
98-
return atprotoData.signingKey;
99-
},
100-
);
101-
const did = payload.iss;
102-
103-
if (!c.state.adminDids.includes(did)) {
104-
throw new UserError("User is not an admin.");
105-
}
106-
107-
return { did, clientDid: params.clientDid };
108-
} catch (e) {
109-
console.warn("Auth error", e);
110-
return undefined;
111-
}
112-
},
113-
114-
actions: {
115-
/** Get the signing key for the operator actor. */
116-
signingKey(c) {
117-
return c.vars.signer.toString();
118-
},
119-
requestEchoDelegation: async (c) => {
120-
if (!c.conn.state?.did) throw new UserError("Not authenticated");
121-
122-
const delegation = await capabilities.Echo.delegate({
123-
iss: c.vars.signer,
124-
aud: c.conn.state.clientDid as DID,
125-
store: c.vars.store,
126-
sub: c.vars.signer.did,
127-
pol: [],
128-
exp: Math.round(Date.now() / 1000) + 3600,
129-
});
130-
await c.vars.store.add([delegation]);
131-
132-
return { delegation: delegation.toString() };
133-
},
134-
echo: async ({ vars: { signer, store } }, rawInvocation: Uint8Array) => {
135-
const invocation = await Invocation.from({
136-
bytes: rawInvocation,
137-
audience: signer.verifiableDid,
138-
resolveProof: store.resolveProof.bind(store),
139-
verifierResolver,
140-
});
141-
142-
const args = capabilities.Echo.schema(invocation.payload.args as any);
143-
if (args instanceof type.errors) {
144-
throw new UserError(`Could not parse arguments: ${args.summary}`);
145-
}
146-
147-
return { content: args.content };
148-
},
149-
},
32+
state: {
33+
privateKey: "",
34+
adminDids: [] as string[],
35+
},
36+
37+
onCreate: async (c, rawInput) => {
38+
// This is a singleton actor that can only be created as "main"
39+
if (c.key.length != 1 || c.key[0] != "main") {
40+
console.error(
41+
'Cannot create operatorAuth actor with key other than ["main"]:',
42+
c.key,
43+
);
44+
// If this actor is not "main" immediately destroy it
45+
c.destroy();
46+
return;
47+
}
48+
49+
// Parse creation args
50+
const input = ActorCreateInput(rawInput);
51+
if (input instanceof type.errors) {
52+
console.error("Invalid creation input to operatorAuth:", input.summary);
53+
c.destroy();
54+
return;
55+
}
56+
57+
// Initialize state
58+
c.state.adminDids = input.adminDids;
59+
c.state.privateKey = (await EdDSASigner.generate()).export();
60+
},
61+
62+
createVars: async (c) => {
63+
// This can happen if creation fails, but this lifecycle hook will still run
64+
if (c.aborted) return undefined as any;
65+
66+
return {
67+
signer: await EdDSASigner.import(c.state.privateKey),
68+
store: new Store(kvDriver(c.kv)),
69+
idResolver: new IdResolver({
70+
didCache: new MemoryCache(),
71+
}),
72+
};
73+
},
74+
75+
createConnState: async (c, rawParams?): Promise<ConnState> => {
76+
const { idResolver } = c.vars as Vars;
77+
78+
// Parse the connection params or return an unauthenticated connection.
79+
const params = ConnParams(rawParams);
80+
if (!params) return;
81+
82+
try {
83+
if (params instanceof type.errors) {
84+
throw new UserError(
85+
`Failed to parse connection parameters: ${params.summary}`,
86+
);
87+
}
88+
89+
// Get the ATProto service auth JWT
90+
const jwt = params.serviceAuthToken;
91+
92+
// Verify the service auth JWT
93+
const payload = await verifyJwt(
94+
jwt,
95+
null,
96+
null,
97+
async (did, forceRefresh) => {
98+
const atprotoData = await idResolver.did.resolveAtprotoData(
99+
did,
100+
forceRefresh,
101+
);
102+
return atprotoData.signingKey;
103+
},
104+
);
105+
106+
// Get the authenticated DID
107+
const did = payload.iss;
108+
109+
// Make sure the user is an admin
110+
if (!c.state.adminDids.includes(did)) {
111+
throw new UserError("User is not an admin.");
112+
}
113+
114+
// Return authenticated connection
115+
return { did, clientDid: params.clientDid };
116+
} catch (e) {
117+
throw new UserError(`Authentication error: ${e}`);
118+
}
119+
},
120+
121+
actions: {
122+
/** Get the signing key for the operator actor. */
123+
signingKey(c) {
124+
return c.vars.signer.toString();
125+
},
126+
127+
/** Get the admin */
128+
requestAdminDelegations: async (c) => {
129+
if (!c.conn.state?.did) throw new UserError("Not authenticated");
130+
131+
const delegations = [
132+
// Create a delegation that allows all access to the operatorAuth actor
133+
await Delegation.create({
134+
iss: c.vars.signer,
135+
aud: c.conn.state.clientDid,
136+
sub: c.vars.signer.did,
137+
pol: [],
138+
exp: Math.round(Date.now() / 1000) + 3600, // Last one hour
139+
cmd: "/",
140+
}),
141+
];
142+
143+
await c.vars.store.add(delegations);
144+
145+
return { delegations: delegations.map((x) => x.toString()) };
146+
},
147+
148+
/** Simple test endpoint that demonstrates UCAN auth. */
149+
echo: async ({ vars: { signer, store } }, rawInvocation: Uint8Array) => {
150+
const invocation = await Invocation.from({
151+
bytes: rawInvocation,
152+
audience: signer.verifiableDid,
153+
resolveProof: store.resolveProof.bind(store),
154+
verifierResolver,
155+
});
156+
157+
const args = capabilities.Echo.schema(invocation.payload.args as any);
158+
if (args instanceof type.errors) {
159+
throw new UserError(`Could not parse arguments: ${args.summary}`);
160+
}
161+
162+
return { content: args.content };
163+
},
164+
},
150165
}) satisfies ActorDefinition<
151-
State,
152-
typeof ConnParams.infer,
153-
ConnState,
154-
Vars,
155-
typeof ActorCreateInput.infer,
156-
any
166+
State,
167+
typeof ConnParams.infer,
168+
ConnState,
169+
Vars,
170+
typeof ActorCreateInput.infer,
171+
any
157172
>;

0 commit comments

Comments
 (0)