@shivaedev/server 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -105
- package/dist/appHost.js +1 -1
- package/dist/appHost.js.map +1 -1
- package/dist/appHostHttp.d.ts +40 -41
- package/dist/appHostHttp.d.ts.map +1 -1
- package/dist/appHostHttp.js +3 -3
- package/dist/appHostHttp.js.map +1 -1
- package/dist/development.d.ts.map +1 -1
- package/dist/development.js +2 -1
- package/dist/development.js.map +1 -1
- package/dist/hostChanges.d.ts +2 -1
- package/dist/hostChanges.d.ts.map +1 -1
- package/dist/hostChanges.js +39 -5
- package/dist/hostChanges.js.map +1 -1
- package/dist/hostRpc.d.ts +1 -1
- package/dist/hostRpc.d.ts.map +1 -1
- package/dist/hostRpc.js +4 -2
- package/dist/hostRpc.js.map +1 -1
- package/dist/hostRpcRoutes.d.ts.map +1 -1
- package/dist/hostRpcRoutes.js +6 -2
- package/dist/hostRpcRoutes.js.map +1 -1
- package/dist/nodeHttpServer.js +1 -1
- package/dist/nodeHttpServer.js.map +1 -1
- package/dist/socketRpc.d.ts +6 -0
- package/dist/socketRpc.d.ts.map +1 -0
- package/dist/socketRpc.js +27 -0
- package/dist/socketRpc.js.map +1 -0
- package/package.json +6 -6
- package/src/appHost.ts +1 -1
- package/src/appHostHttp.ts +7 -7
- package/src/development.ts +2 -1
- package/src/hostChanges.ts +56 -9
- package/src/hostRpc.ts +4 -2
- package/src/hostRpcRoutes.ts +17 -2
- package/src/nodeHttpServer.ts +1 -1
- package/src/socketRpc.ts +36 -0
package/dist/hostChanges.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Option, Stream } from "effect";
|
|
2
|
+
import { Headers } from "effect/http";
|
|
3
|
+
import { CurrentViewer } from "@shivaedev/contract/readPolicy.ts";
|
|
4
|
+
import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
|
|
5
|
+
import { resolveSession } from "@shivaedev/identity/server/session.ts";
|
|
2
6
|
import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
|
|
3
|
-
export function hostChanges(group, changesService) {
|
|
7
|
+
export function hostChanges(group, changesService, sessions) {
|
|
4
8
|
return Layer.effectContext(Effect.gen(function* () {
|
|
5
9
|
const changes = yield* Effect.serviceOption(changesService);
|
|
6
10
|
const services = yield* Effect.context();
|
|
7
11
|
const handlers = new Map();
|
|
12
|
+
function subscribe(rpc, queries) {
|
|
13
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
14
|
+
const { subscription, context } = yield* resolveChanges(rpc, queries);
|
|
15
|
+
const viewer = yield* CurrentViewer;
|
|
16
|
+
const authorized = authorizeSession(sessions, rpc._tag, viewer, subscription.authorized);
|
|
17
|
+
const stream = Option.isSome(changes)
|
|
18
|
+
? changes.value.subscribe({ ...subscription, authorized }, context)
|
|
19
|
+
: Stream.concat(Stream.succeed({ _tag: "Resync" }), Stream.never);
|
|
20
|
+
if (sessions === undefined || Option.isNone(viewer)) {
|
|
21
|
+
return stream;
|
|
22
|
+
}
|
|
23
|
+
return Stream.interruptWhen(stream, sessionRevoked(sessions, rpc._tag, viewer.value.id));
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
8
26
|
for (const rpc of group.requests.values()) {
|
|
9
27
|
if (!rpc._tag.endsWith(".$changes")) {
|
|
10
28
|
continue;
|
|
@@ -14,12 +32,28 @@ export function hostChanges(group, changesService) {
|
|
|
14
32
|
handlers.set(rpc.key, provided);
|
|
15
33
|
continue;
|
|
16
34
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
handlers.set(rpc.key, {
|
|
36
|
+
context: services,
|
|
37
|
+
handler: ({ queries }) => subscribe(rpc, queries),
|
|
38
|
+
tag: rpc._tag,
|
|
39
|
+
});
|
|
21
40
|
}
|
|
22
41
|
return Context.makeUnsafe(handlers);
|
|
23
42
|
}));
|
|
24
43
|
}
|
|
44
|
+
function authorizeSession(sessions, tag, viewer, policy) {
|
|
45
|
+
if (sessions === undefined) {
|
|
46
|
+
return policy;
|
|
47
|
+
}
|
|
48
|
+
return resolveSession(sessions, Headers.empty, tag).pipe(Effect.map((current) => Option.isNone(viewer) || (Option.isSome(current) && current.value.user.id === viewer.value.id)), Effect.catch(() => Effect.succeed(false)), Effect.flatMap((allowed) => (allowed ? policy : Effect.succeed(false))));
|
|
49
|
+
}
|
|
50
|
+
function sessionRevoked(sessions, tag, id) {
|
|
51
|
+
return Effect.gen(function* () {
|
|
52
|
+
yield* Effect.sleep(sessions.streamRevalidationInterval ?? "30 seconds");
|
|
53
|
+
const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
54
|
+
if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
|
|
55
|
+
return yield* new Unauthorized({ message: "Authentication required" });
|
|
56
|
+
}
|
|
57
|
+
}).pipe(Effect.forever);
|
|
58
|
+
}
|
|
25
59
|
//# sourceMappingURL=hostChanges.js.map
|
package/dist/hostChanges.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostChanges.js","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"hostChanges.js","sourceRoot":"","sources":["../src/hostChanges.ts"],"names":[],"mappings":"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"}
|
package/dist/hostRpc.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ declare class HostedReadPolicy extends HostedReadPolicy_base {
|
|
|
7
7
|
}
|
|
8
8
|
export type { HostedReadPolicy };
|
|
9
9
|
export declare const rpcCacheControl: HttpEffect.PreResponseHandler;
|
|
10
|
-
export declare const hostRpc: <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R
|
|
10
|
+
export declare const hostRpc: <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, websocket?: boolean | undefined) => Effect.Effect<Effect.Effect<HttpServerResponse.HttpServerResponse, never, import("effect/http/HttpServerRequest").HttpServerRequest | import("effect/Scope").Scope>, never, import("effect/rpc/RpcSerialization").RpcSerialization | import("effect/Scope").Scope | Exclude<Rpc.Middleware<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ServicesServer<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy> | Exclude<Rpc.ToHandler<Rpc.AddMiddleware<Rpc.AddMiddleware<R, typeof HostedReadPolicy>, typeof import("@shivaedev/identity/middleware.ts").RequestTracing>>, HostedReadPolicy>>;
|
|
11
11
|
//# sourceMappingURL=hostRpc.d.ts.map
|
package/dist/hostRpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostRpc.d.ts","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAa,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;;AAG7F,cAAM,gBAAiB,SAAQ,qBAE7B;CAAG;AAEL,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,eAAe,EAAE,UAAU,CAAC,kBACoD,CAAC;AAE9F,eAAO,MAAM,OAAO,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"hostRpc.d.ts","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,UAAU,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAa,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;;AAG7F,cAAM,gBAAiB,SAAQ,qBAE7B;CAAG;AAEL,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC,eAAO,MAAM,eAAe,EAAE,UAAU,CAAC,kBACoD,CAAC;AAE9F,eAAO,MAAM,OAAO,GAAgC,CAAC,SAAS,GAAG,CAAC,GAAG,21BAWnE,CAAC"}
|
package/dist/hostRpc.js
CHANGED
|
@@ -9,12 +9,14 @@ class HostedReadPolicy extends RpcMiddleware.Service()("@shivaedev/server/Hosted
|
|
|
9
9
|
}) {
|
|
10
10
|
}
|
|
11
11
|
export const rpcCacheControl = (_, response) => Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "private, no-store"));
|
|
12
|
-
export const hostRpc = Effect.fnUntraced(function* (group) {
|
|
12
|
+
export const hostRpc = Effect.fnUntraced(function* (group, websocket = false) {
|
|
13
13
|
const guarded = group.middleware(HostedReadPolicy);
|
|
14
14
|
// Keep the policy inside identity middleware so it sees the request's viewer.
|
|
15
15
|
for (const rpc of guarded.requests.values()) {
|
|
16
16
|
Object.assign(rpc, { middlewares: new Set([HostedReadPolicy, ...rpc.middlewares]) });
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
const traced = tracedGroup(guarded);
|
|
19
|
+
const handler = websocket ? RpcServer.toHttpEffectWebsocket(traced) : RpcServer.toHttpEffect(traced);
|
|
20
|
+
return yield* handler.pipe(Effect.provideService(HostedReadPolicy, (effect, request) => Effect.flatMap(CurrentViewer, (viewer) => authorizeReads(effect, request, viewer))));
|
|
19
21
|
});
|
|
20
22
|
//# sourceMappingURL=hostRpc.js.map
|
package/dist/hostRpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostRpc.js","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAA2B,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,gBAAiB,SAAQ,aAAa,CAAC,OAAO,EAAoB,CAAC,oCAAoC,EAAE;IAC9G,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;CACzD,CAAC;CAAG;AAIL,MAAM,CAAC,MAAM,eAAe,GAAkC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAC7E,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAqB,KAA2B;
|
|
1
|
+
{"version":3,"file":"hostRpc.js","sourceRoot":"","sources":["../src/hostRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAA2B,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,gBAAiB,SAAQ,aAAa,CAAC,OAAO,EAAoB,CAAC,oCAAoC,EAAE;IAC9G,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;CACzD,CAAC;CAAG;AAIL,MAAM,CAAC,MAAM,eAAe,GAAkC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAC7E,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAqB,KAA2B,EAAE,SAAS,GAAG,KAAK;IACpH,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACnD,8EAA8E;IAC9E,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACrG,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CACzB,MAAM,CAAC,cAAc,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAChJ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostRpcRoutes.d.ts","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAmC,UAAU,EAAyC,MAAM,aAAa,CAAC;AAEjH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE3G,OAAO,EAA+C,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAEvH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"hostRpcRoutes.d.ts","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAmC,UAAU,EAAyC,MAAM,aAAa,CAAC;AAEjH,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAE3G,OAAO,EAA+C,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAEvH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAGnE,wBAAgB,aAAa,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,SAAS,YAAY,EACnH,OAAO,EAAE;IACR,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC;CACxC,GAAG,UAAU,CAAC,QAAQ,CAAC,EACxB,cAAc,EAAE,OAAO,UAAU,EACjC,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,EAC3F,SAAS,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC;;;;;;;;0CA6CjD"}
|
package/dist/hostRpcRoutes.js
CHANGED
|
@@ -9,21 +9,25 @@ import { boundedRpcRequest } from "#boundedRpcRequest.ts";
|
|
|
9
9
|
import { hostChanges } from "#hostChanges.ts";
|
|
10
10
|
import { hostRpc, rpcCacheControl } from "#hostRpc.ts";
|
|
11
11
|
import { redactingRpcSerialization } from "#redactingRpcSerialization.ts";
|
|
12
|
+
import { socketRpc } from "#socketRpc.ts";
|
|
12
13
|
export function hostRpcRoutes(options, changesService, { bodySize, trustedProxy, nativeOrigins }, sensitive) {
|
|
13
14
|
const middleware = options.policy === "loopback"
|
|
14
15
|
? requestTracingLayer({ sensitive })
|
|
15
16
|
: Layer.mergeAll(authenticatedLayer(options.sessions), maybeAuthenticatedLayer(options.sessions), requestTracingLayer({ sensitive }));
|
|
16
17
|
return HttpRouter.use((router) => Effect.gen(function* () {
|
|
17
18
|
const wire = redactingRpcSerialization(yield* RpcSerialization.RpcSerialization, sensitive, sensitive.maskFailure);
|
|
18
|
-
const
|
|
19
|
+
const httpGroup = options.group.omit(...[...options.group.requests.keys()].filter((tag) => tag.endsWith(".$changes")));
|
|
20
|
+
const handler = yield* hostRpc(httpGroup).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
|
|
19
21
|
const guarded = localRpcGuard(handler, trustedProxy, nativeOrigins);
|
|
22
|
+
const websocket = yield* hostRpc(options.group, true).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
|
|
20
23
|
yield* router.add("OPTIONS", options.rpcPath ?? "/rpc", HttpServerResponse.text("Local same-origin requests only", {
|
|
21
24
|
status: HttpStatus.fromLiteral("Forbidden"),
|
|
22
25
|
}));
|
|
26
|
+
yield* router.add("GET", options.rpcPath ?? "/rpc", socketRpc(websocket, options, trustedProxy, nativeOrigins).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire)));
|
|
23
27
|
yield* router.add("POST", options.rpcPath ?? "/rpc", (request) => {
|
|
24
28
|
HttpEffect.appendPreResponseHandlerUnsafe(request, rpcCacheControl);
|
|
25
29
|
return guarded.pipe(Effect.provideService(HttpIncomingMessage.MaxBodySize, ByteSize.bytes(bodySize)), Effect.provideService(HttpServerRequest.HttpServerRequest, boundedRpcRequest(request)), Effect.provideService(RpcSerialization.RpcSerialization, wire));
|
|
26
30
|
});
|
|
27
|
-
})).pipe(Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService).pipe(Layer.provideMerge(options.handlers)))), Layer.provide(RpcSerialization.layerNdjson));
|
|
31
|
+
})).pipe(Layer.provide(Layer.merge(middleware, hostChanges(options.group, changesService, options.policy === "loopback" ? undefined : options.sessions).pipe(Layer.provideMerge(options.handlers)))), Layer.provide(RpcSerialization.layerNdjson));
|
|
28
32
|
}
|
|
29
33
|
//# sourceMappingURL=hostRpcRoutes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hostRpcRoutes.js","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjH,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAA2B,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAqB,MAAM,uCAAuC,CAAC;AACvH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"hostRpcRoutes.js","sourceRoot":"","sources":["../src/hostRpcRoutes.ts"],"names":[],"mappings":"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"}
|
package/dist/nodeHttpServer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import
|
|
2
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
3
3
|
import { Config, Effect, Layer } from "effect";
|
|
4
4
|
export function nodeHttpServer(host, config) {
|
|
5
5
|
return Layer.unwrap(Effect.gen(function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeHttpServer.js","sourceRoot":"","sources":["../src/nodeHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"nodeHttpServer.js","sourceRoot":"","sources":["../src/nodeHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,cAAc,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE9D,MAAM,UAAU,cAAc,CAC7B,IAAY,EACZ,MAIC;IAED,OAAO,KAAK,CAAC,MAAM,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxF,OAAO,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE;YACzC,uBAAuB,EAAE,MAAM,EAAE,uBAAuB;YACxD,IAAI;YACJ,IAAI;YACJ,GAAG,CAAC,MAAM,EAAE,yBAAyB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,MAAM,CAAC,yBAAyB,EAAE,CAAC;SAC3H,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect, type Scope } from "effect";
|
|
2
|
+
import { HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
3
|
+
import { type SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
4
|
+
import type { HostPolicy } from "#hostOptions.ts";
|
|
5
|
+
export declare function socketRpc<TSession extends SessionShape>(handler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>, policy: HostPolicy<TSession>, trustedProxy: string | undefined, nativeOrigins: readonly string[]): Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>;
|
|
6
|
+
//# sourceMappingURL=socketRpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketRpc.d.ts","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAW,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,wBAAgB,SAAS,CAAC,QAAQ,SAAS,YAAY,EACtD,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,kBAAkB,EAAE,KAAK,EAAE,iBAAiB,CAAC,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,EACvH,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,aAAa,EAAE,SAAS,MAAM,EAAE,kHAiBhC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
3
|
+
import * as HttpStatus from "effect/http/HttpStatus";
|
|
4
|
+
import { localTransport } from "@shivaedev/identity/server/local.ts";
|
|
5
|
+
import { resolveSession } from "@shivaedev/identity/server/session.ts";
|
|
6
|
+
export function socketRpc(handler, policy, trustedProxy, nativeOrigins) {
|
|
7
|
+
return Effect.gen(function* () {
|
|
8
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
9
|
+
if (!localTransport(trustedProxy, nativeOrigins)(request)) {
|
|
10
|
+
return HttpServerResponse.text("Local same-origin requests only", { status: HttpStatus.fromLiteral("Forbidden") });
|
|
11
|
+
}
|
|
12
|
+
if (policy.policy !== "loopback") {
|
|
13
|
+
yield* resolveSession(policy.sessions, Headers.empty, "$connect");
|
|
14
|
+
}
|
|
15
|
+
return yield* handler;
|
|
16
|
+
}).pipe(Effect.catchTags({
|
|
17
|
+
AuthUnavailable: authUnavailable,
|
|
18
|
+
Forbidden: forbidden,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
function authUnavailable() {
|
|
22
|
+
return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") }));
|
|
23
|
+
}
|
|
24
|
+
function forbidden() {
|
|
25
|
+
return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Forbidden") }));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=socketRpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketRpc.js","sourceRoot":"","sources":["../src/socketRpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAqB,MAAM,uCAAuC,CAAC;AAG1F,MAAM,UAAU,SAAS,CACxB,OAAuH,EACvH,MAA4B,EAC5B,YAAgC,EAChC,aAAgC;IAEhC,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAC;QAC3D,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,OAAO,kBAAkB,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACpH,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,KAAK,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC;IACvB,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,SAAS,CAAC;QAChB,eAAe,EAAE,eAAe;QAChC,SAAS,EAAE,SAAS;KACpB,CAAC,CACF,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACvB,OAAO,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3G,CAAC;AACD,SAAS,SAAS;IACjB,OAAO,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Effect application host: HTTP and RPC serving, health, OTLP export, database and migrations, and development mode",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@shivaedev/changes": "0.5.0",
|
|
48
|
-
"@shivaedev/contract": "0.
|
|
48
|
+
"@shivaedev/contract": "0.20.0",
|
|
49
49
|
"@shivaedev/errors": "0.1.0",
|
|
50
|
-
"@shivaedev/http": "0.1.
|
|
51
|
-
"@shivaedev/identity": "0.3.
|
|
50
|
+
"@shivaedev/http": "0.1.1",
|
|
51
|
+
"@shivaedev/identity": "0.3.2",
|
|
52
52
|
"@shivaedev/local-postgres": "0.7.0",
|
|
53
53
|
"@shivaedev/sql": "0.24.0"
|
|
54
54
|
},
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"@effect/sql-pg": "4.0.5",
|
|
58
58
|
"@effect/vitest": "4.0.5",
|
|
59
59
|
"@shivaedev/jobs": "0.22.0",
|
|
60
|
-
"@shivaedev/quality": "0.
|
|
60
|
+
"@shivaedev/quality": "0.44.1",
|
|
61
61
|
"@shivaedev/test": "0.10.0",
|
|
62
62
|
"@shivaedev/test-sql": "0.10.2",
|
|
63
|
-
"@shivaedev/test-story": "0.
|
|
63
|
+
"@shivaedev/test-story": "0.23.0",
|
|
64
64
|
"@types/node": "24.19.2",
|
|
65
65
|
"@types/pg": "8.20.0",
|
|
66
66
|
"better-auth": "1.7.12",
|
package/src/appHost.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
|
|
2
2
|
import { Context, type Effect, Layer } from "effect";
|
|
3
3
|
import type { Rpc } from "effect/rpc";
|
|
4
4
|
import type { makeLiveServer } from "@shivaedev/contract/makeLiveServer.ts";
|
package/src/appHostHttp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
2
2
|
import { Effect, Layer } from "effect";
|
|
3
|
-
import { HttpRouter, HttpStaticServer } from "effect/http";
|
|
3
|
+
import { HttpRouter, HttpServer, HttpStaticServer } from "effect/http";
|
|
4
4
|
import type { Rpc } from "effect/rpc";
|
|
5
5
|
import type { SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
6
6
|
import type { AppChanges, AppHttpHostOptions } from "#appHost.ts";
|
|
@@ -61,11 +61,11 @@ export function appHostHttp<
|
|
|
61
61
|
Layer.provide(nativeClientOrigins(nativeOrigins, trustedProxy, false, options.rpcPath)),
|
|
62
62
|
Layer.provide(nativeClientOrigins(nativeOrigins, trustedProxy, options.nativeOrigins === undefined ? "preflight" : true)),
|
|
63
63
|
);
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
);
|
|
64
|
+
return Layer.unwrap(
|
|
65
|
+
HttpRouter.toHttpEffect(httpRoutes).pipe(
|
|
66
|
+
Effect.map((handler) => HttpServer.serve(handler).pipe(HttpServer.withLogAddress, Layer.provideMerge(server))),
|
|
67
|
+
),
|
|
68
|
+
).pipe(Layer.provideMerge(application), Layer.provideMerge(NodeHttpServer.layerHttpServices));
|
|
69
69
|
}),
|
|
70
70
|
).pipe(Layer.provide(redactingDiagnostics(sensitive)), Layer.provide(otlpTracing), Layer.provide(Layer.succeed(RedactionPolicy, sensitive)));
|
|
71
71
|
}
|
package/src/development.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import
|
|
2
|
+
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
3
|
+
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
3
4
|
import { Effect } from "effect";
|
|
4
5
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
5
6
|
|
package/src/hostChanges.ts
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Option, Stream } from "effect";
|
|
2
|
+
import { Headers } from "effect/http";
|
|
2
3
|
import type { Rpc, RpcGroup } from "effect/rpc";
|
|
4
|
+
import { CurrentViewer, type Viewer } from "@shivaedev/contract/readPolicy.ts";
|
|
3
5
|
import type { QueryReference } from "@shivaedev/contract/serverMetadata.ts";
|
|
6
|
+
import { Unauthorized } from "@shivaedev/errors/taxonomy.ts";
|
|
7
|
+
import { resolveSession, type SessionPolicy, type SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
4
8
|
import { resolveChanges } from "@shivaedev/identity/server/sessionKeys.ts";
|
|
5
9
|
import type { AppChanges } from "./appHost.ts";
|
|
6
10
|
|
|
7
|
-
export function hostChanges<R extends Rpc.Any
|
|
11
|
+
export function hostChanges<R extends Rpc.Any, TSession extends SessionShape>(
|
|
12
|
+
group: RpcGroup.RpcGroup<R>,
|
|
13
|
+
changesService: typeof AppChanges,
|
|
14
|
+
sessions?: SessionPolicy<TSession>,
|
|
15
|
+
) {
|
|
8
16
|
return Layer.effectContext(
|
|
9
17
|
Effect.gen(function* () {
|
|
10
18
|
const changes = yield* Effect.serviceOption(changesService);
|
|
11
19
|
const services = yield* Effect.context<never>();
|
|
12
20
|
const handlers = new Map<string, unknown>();
|
|
21
|
+
function subscribe(rpc: Rpc.Any, queries: readonly QueryReference[]) {
|
|
22
|
+
return Stream.unwrap(
|
|
23
|
+
Effect.gen(function* () {
|
|
24
|
+
const { subscription, context } = yield* resolveChanges(rpc, queries);
|
|
25
|
+
const viewer = yield* CurrentViewer;
|
|
26
|
+
const authorized = authorizeSession(sessions, rpc._tag, viewer, subscription.authorized);
|
|
27
|
+
const stream = Option.isSome(changes)
|
|
28
|
+
? changes.value.subscribe({ ...subscription, authorized }, context)
|
|
29
|
+
: Stream.concat(Stream.succeed({ _tag: "Resync" } as const), Stream.never);
|
|
30
|
+
if (sessions === undefined || Option.isNone(viewer)) {
|
|
31
|
+
return stream;
|
|
32
|
+
}
|
|
33
|
+
return Stream.interruptWhen(stream, sessionRevoked(sessions, rpc._tag, viewer.value.id));
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
13
37
|
for (const rpc of group.requests.values()) {
|
|
14
38
|
if (!rpc._tag.endsWith(".$changes")) {
|
|
15
39
|
continue;
|
|
@@ -19,16 +43,39 @@ export function hostChanges<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, chan
|
|
|
19
43
|
handlers.set(rpc.key, provided);
|
|
20
44
|
continue;
|
|
21
45
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
handlers.set(rpc.key, { context: services, handler, tag: rpc._tag });
|
|
46
|
+
handlers.set(rpc.key, {
|
|
47
|
+
context: services,
|
|
48
|
+
handler: ({ queries }: { readonly queries: readonly QueryReference[] }) => subscribe(rpc, queries),
|
|
49
|
+
tag: rpc._tag,
|
|
50
|
+
});
|
|
30
51
|
}
|
|
31
52
|
return Context.makeUnsafe<Rpc.ToHandler<Extract<R, { readonly _tag: `${string}.$changes` }>>>(handlers);
|
|
32
53
|
}),
|
|
33
54
|
);
|
|
34
55
|
}
|
|
56
|
+
|
|
57
|
+
function authorizeSession<TSession extends SessionShape>(
|
|
58
|
+
sessions: SessionPolicy<TSession> | undefined,
|
|
59
|
+
tag: string,
|
|
60
|
+
viewer: Option.Option<Viewer>,
|
|
61
|
+
policy: Effect.Effect<boolean>,
|
|
62
|
+
) {
|
|
63
|
+
if (sessions === undefined) {
|
|
64
|
+
return policy;
|
|
65
|
+
}
|
|
66
|
+
return resolveSession(sessions, Headers.empty, tag).pipe(
|
|
67
|
+
Effect.map((current) => Option.isNone(viewer) || (Option.isSome(current) && current.value.user.id === viewer.value.id)),
|
|
68
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
69
|
+
Effect.flatMap((allowed) => (allowed ? policy : Effect.succeed(false))),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sessionRevoked<TSession extends SessionShape>(sessions: SessionPolicy<TSession>, tag: string, id: string) {
|
|
74
|
+
return Effect.gen(function* () {
|
|
75
|
+
yield* Effect.sleep(sessions.streamRevalidationInterval ?? "30 seconds");
|
|
76
|
+
const current = yield* resolveSession(sessions, Headers.empty, tag).pipe(Effect.catchTag("AuthUnavailable", () => Effect.succeed(undefined)));
|
|
77
|
+
if (current !== undefined && (Option.isNone(current) || current.value.user.id !== id)) {
|
|
78
|
+
return yield* new Unauthorized({ message: "Authentication required" });
|
|
79
|
+
}
|
|
80
|
+
}).pipe(Effect.forever);
|
|
81
|
+
}
|
package/src/hostRpc.ts
CHANGED
|
@@ -14,13 +14,15 @@ export type { HostedReadPolicy };
|
|
|
14
14
|
export const rpcCacheControl: HttpEffect.PreResponseHandler = (_, response) =>
|
|
15
15
|
Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "private, no-store"));
|
|
16
16
|
|
|
17
|
-
export const hostRpc = Effect.fnUntraced(function* <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R
|
|
17
|
+
export const hostRpc = Effect.fnUntraced(function* <R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, websocket = false) {
|
|
18
18
|
const guarded = group.middleware(HostedReadPolicy);
|
|
19
19
|
// Keep the policy inside identity middleware so it sees the request's viewer.
|
|
20
20
|
for (const rpc of guarded.requests.values()) {
|
|
21
21
|
Object.assign(rpc, { middlewares: new Set([HostedReadPolicy, ...rpc.middlewares]) });
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
const traced = tracedGroup(guarded);
|
|
24
|
+
const handler = websocket ? RpcServer.toHttpEffectWebsocket(traced) : RpcServer.toHttpEffect(traced);
|
|
25
|
+
return yield* handler.pipe(
|
|
24
26
|
Effect.provideService(HostedReadPolicy, (effect, request) => Effect.flatMap(CurrentViewer, (viewer) => authorizeReads(effect, request, viewer))),
|
|
25
27
|
);
|
|
26
28
|
});
|
package/src/hostRpcRoutes.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { hostRpc, rpcCacheControl } from "#hostRpc.ts";
|
|
|
14
14
|
import type { hostTransport } from "#hostTransport.ts";
|
|
15
15
|
import { redactingRpcSerialization } from "#redactingRpcSerialization.ts";
|
|
16
16
|
import type { schemaSensitiveKeys } from "#schemaSensitiveKeys.ts";
|
|
17
|
+
import { socketRpc } from "#socketRpc.ts";
|
|
17
18
|
|
|
18
19
|
export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequirements, TSession extends SessionShape>(
|
|
19
20
|
options: {
|
|
@@ -32,8 +33,10 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
|
|
|
32
33
|
return HttpRouter.use((router) =>
|
|
33
34
|
Effect.gen(function* () {
|
|
34
35
|
const wire = redactingRpcSerialization(yield* RpcSerialization.RpcSerialization, sensitive, sensitive.maskFailure);
|
|
35
|
-
const
|
|
36
|
+
const httpGroup = options.group.omit(...[...options.group.requests.keys()].filter((tag) => tag.endsWith(".$changes")));
|
|
37
|
+
const handler = yield* hostRpc(httpGroup).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
|
|
36
38
|
const guarded = localRpcGuard(handler, trustedProxy, nativeOrigins);
|
|
39
|
+
const websocket = yield* hostRpc(options.group, true).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire));
|
|
37
40
|
yield* router.add(
|
|
38
41
|
"OPTIONS",
|
|
39
42
|
options.rpcPath ?? "/rpc",
|
|
@@ -41,6 +44,11 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
|
|
|
41
44
|
status: HttpStatus.fromLiteral("Forbidden"),
|
|
42
45
|
}),
|
|
43
46
|
);
|
|
47
|
+
yield* router.add(
|
|
48
|
+
"GET",
|
|
49
|
+
options.rpcPath ?? "/rpc",
|
|
50
|
+
socketRpc(websocket, options, trustedProxy, nativeOrigins).pipe(Effect.provideService(RpcSerialization.RpcSerialization, wire)),
|
|
51
|
+
);
|
|
44
52
|
yield* router.add("POST", options.rpcPath ?? "/rpc", (request) => {
|
|
45
53
|
HttpEffect.appendPreResponseHandlerUnsafe(request, rpcCacheControl);
|
|
46
54
|
return guarded.pipe(
|
|
@@ -51,7 +59,14 @@ export function hostRpcRoutes<TRpcs extends Rpc.Any, THandlers, TError, TRequire
|
|
|
51
59
|
});
|
|
52
60
|
}),
|
|
53
61
|
).pipe(
|
|
54
|
-
Layer.provide(
|
|
62
|
+
Layer.provide(
|
|
63
|
+
Layer.merge(
|
|
64
|
+
middleware,
|
|
65
|
+
hostChanges(options.group, changesService, options.policy === "loopback" ? undefined : options.sessions).pipe(
|
|
66
|
+
Layer.provideMerge(options.handlers),
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
55
70
|
Layer.provide(RpcSerialization.layerNdjson),
|
|
56
71
|
);
|
|
57
72
|
}
|
package/src/nodeHttpServer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import
|
|
2
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
3
3
|
import { Config, type Duration, Effect, Layer } from "effect";
|
|
4
4
|
|
|
5
5
|
export function nodeHttpServer(
|
package/src/socketRpc.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Effect, type Scope } from "effect";
|
|
2
|
+
import { Headers, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
3
|
+
import * as HttpStatus from "effect/http/HttpStatus";
|
|
4
|
+
import { localTransport } from "@shivaedev/identity/server/local.ts";
|
|
5
|
+
import { resolveSession, type SessionShape } from "@shivaedev/identity/server/session.ts";
|
|
6
|
+
import type { HostPolicy } from "#hostOptions.ts";
|
|
7
|
+
|
|
8
|
+
export function socketRpc<TSession extends SessionShape>(
|
|
9
|
+
handler: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest | Scope.Scope>,
|
|
10
|
+
policy: HostPolicy<TSession>,
|
|
11
|
+
trustedProxy: string | undefined,
|
|
12
|
+
nativeOrigins: readonly string[],
|
|
13
|
+
) {
|
|
14
|
+
return Effect.gen(function* () {
|
|
15
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
16
|
+
if (!localTransport(trustedProxy, nativeOrigins)(request)) {
|
|
17
|
+
return HttpServerResponse.text("Local same-origin requests only", { status: HttpStatus.fromLiteral("Forbidden") });
|
|
18
|
+
}
|
|
19
|
+
if (policy.policy !== "loopback") {
|
|
20
|
+
yield* resolveSession(policy.sessions, Headers.empty, "$connect");
|
|
21
|
+
}
|
|
22
|
+
return yield* handler;
|
|
23
|
+
}).pipe(
|
|
24
|
+
Effect.catchTags({
|
|
25
|
+
AuthUnavailable: authUnavailable,
|
|
26
|
+
Forbidden: forbidden,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function authUnavailable() {
|
|
32
|
+
return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") }));
|
|
33
|
+
}
|
|
34
|
+
function forbidden() {
|
|
35
|
+
return Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Forbidden") }));
|
|
36
|
+
}
|