@webstudio-is/trpc-interface 0.91.0 → 0.260.2

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.
Files changed (61) hide show
  1. package/package.json +26 -25
  2. package/src/authorize/project.server.test.ts +443 -0
  3. package/src/authorize/project.server.ts +309 -121
  4. package/src/authorize/role.ts +18 -0
  5. package/src/context/context.server.ts +59 -24
  6. package/src/context/errors.server.ts +16 -0
  7. package/src/context/router.server.ts +19 -0
  8. package/src/index.server.ts +15 -3
  9. package/src/shared/client.ts +0 -2
  10. package/src/shared/deployment.ts +23 -6
  11. package/src/shared/domain.ts +3 -3
  12. package/src/shared/plan-client.server.ts +7 -0
  13. package/src/shared/plan-features.ts +7 -0
  14. package/src/shared/shared-router.ts +0 -2
  15. package/src/shared/trpc.ts +5 -1
  16. package/src/trpc-caller-link.test.ts +1 -1
  17. package/src/trpc-caller-link.ts +1 -2
  18. package/tsconfig.json +3 -0
  19. package/lib/authorize/authorization-token.server.js +0 -72
  20. package/lib/authorize/project.server.js +0 -103
  21. package/lib/cjs/authorize/authorization-token.server.js +0 -92
  22. package/lib/cjs/authorize/project.server.js +0 -123
  23. package/lib/cjs/context/context.server.js +0 -16
  24. package/lib/cjs/context/errors.server.js +0 -29
  25. package/lib/cjs/index.js +0 -18
  26. package/lib/cjs/index.server.js +0 -40
  27. package/lib/cjs/package.json +0 -1
  28. package/lib/cjs/shared/authorization-router.js +0 -184
  29. package/lib/cjs/shared/client.js +0 -63
  30. package/lib/cjs/shared/deployment.js +0 -51
  31. package/lib/cjs/shared/domain.js +0 -98
  32. package/lib/cjs/shared/shared-router.js +0 -32
  33. package/lib/cjs/shared/trpc.js +0 -31
  34. package/lib/cjs/trpc-caller-link.js +0 -46
  35. package/lib/context/context.server.js +0 -0
  36. package/lib/context/errors.server.js +0 -9
  37. package/lib/index.js +0 -1
  38. package/lib/index.server.js +0 -10
  39. package/lib/shared/authorization-router.js +0 -164
  40. package/lib/shared/client.js +0 -35
  41. package/lib/shared/deployment.js +0 -31
  42. package/lib/shared/domain.js +0 -78
  43. package/lib/shared/shared-router.js +0 -12
  44. package/lib/shared/trpc.js +0 -11
  45. package/lib/trpc-caller-link.js +0 -26
  46. package/lib/types/authorize/authorization-token.server.d.ts +0 -21
  47. package/lib/types/authorize/project.server.d.ts +0 -25
  48. package/lib/types/context/context.server.d.ts +0 -53
  49. package/lib/types/context/errors.server.d.ts +0 -1
  50. package/lib/types/index.d.ts +0 -1
  51. package/lib/types/index.server.d.ts +0 -7
  52. package/lib/types/shared/authorization-router.d.ts +0 -276
  53. package/lib/types/shared/client.d.ts +0 -8
  54. package/lib/types/shared/deployment.d.ts +0 -45
  55. package/lib/types/shared/domain.d.ts +0 -119
  56. package/lib/types/shared/shared-router.d.ts +0 -415
  57. package/lib/types/shared/trpc.d.ts +0 -48
  58. package/lib/types/trpc-caller-link.d.ts +0 -16
  59. package/lib/types/trpc-caller-link.test.d.ts +0 -49
  60. package/src/authorize/authorization-token.server.ts +0 -106
  61. package/src/shared/authorization-router.ts +0 -198
@@ -1,164 +0,0 @@
1
- import { z } from "zod";
2
- import { router, procedure } from "./trpc";
3
- import { prisma } from "@webstudio-is/prisma-client";
4
- const Relation = z.enum(["viewers", "editors", "builders", "owners"]);
5
- const AuthPermit = z.enum(["view", "edit", "build", "own"]);
6
- const DeleteCreateInput = z.discriminatedUnion("namespace", [
7
- z.object({
8
- namespace: z.literal("Project"),
9
- id: z.string(),
10
- relation: Relation,
11
- subjectSet: z.discriminatedUnion("namespace", [
12
- z.object({
13
- namespace: z.literal("User"),
14
- id: z.string()
15
- }),
16
- z.object({
17
- namespace: z.literal("Token"),
18
- id: z.string()
19
- }),
20
- z.object({
21
- namespace: z.literal("Email"),
22
- id: z.string(),
23
- relation: z.literal("owners")
24
- })
25
- ])
26
- }),
27
- z.object({
28
- namespace: z.literal("Email"),
29
- id: z.string(),
30
- relation: z.enum(["owners"]),
31
- subjectSet: z.object({
32
- namespace: z.literal("User"),
33
- id: z.string()
34
- })
35
- })
36
- ]);
37
- const authorizationRouter = router({
38
- /**
39
- * Relation expansion in authorize looks like a tree
40
- *
41
- * :#@Project:AliceProjectUUID#viewers
42
- * :#@Email:bob@bob.com#owner
43
- * :#@User:BobUUID️
44
- * :#@Token:LinkRandomSequence️
45
- *
46
- * We don't need the whole tree in UI and need only the leaf nodes.
47
- * i.e. @User:BobUUID️, @Token:LinkRandomSequence️ and the root relation i.e "viewers"
48
- */
49
- expandLeafNodes: procedure.input(
50
- z.object({
51
- namespace: z.literal("Project"),
52
- id: z.string()
53
- })
54
- ).output(
55
- z.array(
56
- z.object({
57
- // top level relation
58
- relation: Relation,
59
- // subjectSet
60
- namespace: z.enum(["Email", "User", "Token"]),
61
- id: z.string()
62
- })
63
- )
64
- ).query(async ({ input }) => {
65
- const { namespace, id } = input;
66
- if (namespace !== "Project") {
67
- throw new Error("Not implemented");
68
- }
69
- const ownerRow = await prisma.project.findUnique({
70
- where: {
71
- id
72
- },
73
- select: {
74
- userId: true
75
- }
76
- });
77
- const tokenRows = await prisma.authorizationToken.findMany({
78
- where: {
79
- projectId: id
80
- }
81
- });
82
- const leafSubjectSets = [];
83
- if (ownerRow !== null && ownerRow.userId !== null) {
84
- leafSubjectSets.push({
85
- namespace: "User",
86
- id: ownerRow.userId,
87
- relation: "owners"
88
- });
89
- }
90
- for (const tokenRow of tokenRows) {
91
- leafSubjectSets.push({
92
- namespace: "Token",
93
- id: tokenRow.token,
94
- relation: tokenRow.relation
95
- });
96
- }
97
- return leafSubjectSets;
98
- }),
99
- /**
100
- * Check if subject has permit on the resource
101
- */
102
- check: procedure.input(
103
- z.object({
104
- namespace: z.enum(["Project"]),
105
- id: z.string(),
106
- permit: AuthPermit,
107
- subjectSet: z.object({
108
- namespace: z.enum(["User", "Token"]),
109
- id: z.string()
110
- })
111
- })
112
- ).output(z.object({ allowed: z.boolean() })).query(async ({ input }) => {
113
- const { subjectSet } = input;
114
- if (subjectSet.namespace === "User") {
115
- const row = await prisma.project.findFirst({
116
- where: {
117
- id: input.id,
118
- userId: subjectSet.id
119
- },
120
- select: {
121
- id: true
122
- }
123
- });
124
- return { allowed: row !== null };
125
- }
126
- const permitToRelationRewrite = {
127
- view: ["viewers", "editors", "builders"],
128
- edit: ["editors", "builders"],
129
- build: ["builders"]
130
- };
131
- if (subjectSet.namespace === "Token" && input.permit !== "own") {
132
- const row = await prisma.authorizationToken.findFirst({
133
- where: {
134
- token: subjectSet.id,
135
- relation: {
136
- in: [...permitToRelationRewrite[input.permit]]
137
- }
138
- },
139
- select: { token: true }
140
- });
141
- return { allowed: row !== null };
142
- }
143
- return { allowed: false };
144
- }),
145
- /**
146
- * In OSS we extract owner relation from the Project table, and the rest from the authorizationToken table
147
- */
148
- create: procedure.input(DeleteCreateInput).mutation(async ({ input }) => {
149
- }),
150
- delete: procedure.input(DeleteCreateInput).mutation(async ({ input }) => {
151
- }),
152
- patch: procedure.input(
153
- z.array(
154
- z.object({
155
- action: z.enum(["insert", "delete"]),
156
- relationTuple: DeleteCreateInput
157
- })
158
- )
159
- ).mutation(async ({ input }) => {
160
- })
161
- });
162
- export {
163
- authorizationRouter
164
- };
@@ -1,35 +0,0 @@
1
- import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
2
- import {
3
- sharedRouter
4
- } from "./shared-router";
5
- import { callerLink } from "../trpc-caller-link";
6
- import fetch from "node-fetch";
7
- const createTrpcProxyServiceClient = (options) => {
8
- if (options !== void 0) {
9
- const remoteClient = createTRPCProxyClient({
10
- links: [
11
- httpBatchLink({
12
- url: options.url,
13
- fetch,
14
- headers: () => ({
15
- Authorization: options.token,
16
- // We use this header for SaaS preview service discovery proxy
17
- "x-branch-name": options.branchName
18
- })
19
- })
20
- ]
21
- });
22
- return remoteClient;
23
- }
24
- const client = createTRPCProxyClient({
25
- links: [
26
- callerLink({
27
- appRouter: sharedRouter
28
- })
29
- ]
30
- });
31
- return client;
32
- };
33
- export {
34
- createTrpcProxyServiceClient
35
- };
@@ -1,31 +0,0 @@
1
- import { z } from "zod";
2
- import { router, procedure } from "./trpc";
3
- const PublishInput = z.object({
4
- // used to load build data from the builder see routes/rest.build.$buildId.ts
5
- buildId: z.string(),
6
- builderApiOrigin: z.string(),
7
- // preview support
8
- branchName: z.string(),
9
- // action log helper (not used for deployment, but for action logs readablity)
10
- projectDomainName: z.string()
11
- });
12
- const Output = z.discriminatedUnion("success", [
13
- z.object({
14
- success: z.literal(true)
15
- }),
16
- z.object({
17
- success: z.literal(false),
18
- error: z.string()
19
- })
20
- ]);
21
- const deploymentRouter = router({
22
- publish: procedure.input(PublishInput).output(Output).mutation(async ({ input, ctx }) => {
23
- return {
24
- success: false,
25
- error: `Not implemented, use buildId=${input.buildId}`
26
- };
27
- })
28
- });
29
- export {
30
- deploymentRouter
31
- };
@@ -1,78 +0,0 @@
1
- import { z } from "zod";
2
- import { router, procedure } from "./trpc";
3
- const CreateInput = z.object({ domain: z.string(), txtRecord: z.string() });
4
- const Input = z.object({ domain: z.string() });
5
- const createOutput = (data) => z.discriminatedUnion("success", [
6
- z.object({ success: z.literal(true), data }),
7
- z.object({ success: z.literal(false), error: z.string() })
8
- ]);
9
- globalThis.dnsTxtEntries = globalThis.dnsTxtEntries ?? /* @__PURE__ */ new Map();
10
- globalThis.domainStates = globalThis.domainStates ?? /* @__PURE__ */ new Map();
11
- const domainRouter = router({
12
- /**
13
- * Verify TXT record and add custom domain entry to DNS
14
- */
15
- create: procedure.input(CreateInput).output(createOutput(z.optional(z.undefined()))).mutation(async ({ input, ctx }) => {
16
- const record = dnsTxtEntries.get(input.domain);
17
- if (record !== input.txtRecord) {
18
- dnsTxtEntries.set(input.domain, input.txtRecord);
19
- return {
20
- success: false,
21
- error: `TXT record does not match, expected "${input.txtRecord}" but got "${record ?? "undefined"}"`
22
- };
23
- }
24
- domainStates.set(input.domain, "pending");
25
- return { success: true };
26
- }),
27
- refresh: procedure.input(Input).output(createOutput(z.optional(z.undefined()))).mutation(async ({ input, ctx }) => {
28
- return { success: true };
29
- }),
30
- /**
31
- * Get status of verified domain
32
- */
33
- getStatus: procedure.input(Input).output(
34
- createOutput(
35
- z.discriminatedUnion("status", [
36
- z.object({ status: z.enum(["active", "pending"]) }),
37
- z.object({ status: z.enum(["error"]), error: z.string() })
38
- ])
39
- )
40
- ).query(async ({ input, ctx }) => {
41
- const domainState = domainStates.get(input.domain);
42
- if (domainState === void 0) {
43
- domainStates.set(input.domain, "pending");
44
- return {
45
- success: false,
46
- error: `Domain ${input.domain} is not active`
47
- };
48
- }
49
- if (domainState === "active") {
50
- setTimeout(() => {
51
- domainStates.set(input.domain, "error");
52
- });
53
- }
54
- if (domainState === "pending" || domainState === "error") {
55
- setTimeout(() => {
56
- domainStates.set(input.domain, "active");
57
- }, 5e3);
58
- }
59
- if (domainState === "error") {
60
- return {
61
- success: true,
62
- data: {
63
- status: "error",
64
- error: "Domain cname verification failed"
65
- }
66
- };
67
- }
68
- return {
69
- success: true,
70
- data: {
71
- status: domainState
72
- }
73
- };
74
- })
75
- });
76
- export {
77
- domainRouter
78
- };
@@ -1,12 +0,0 @@
1
- import { router } from "./trpc";
2
- import { authorizationRouter } from "./authorization-router";
3
- import { domainRouter } from "./domain";
4
- import { deploymentRouter } from "./deployment";
5
- const sharedRouter = router({
6
- authorize: authorizationRouter,
7
- domain: domainRouter,
8
- deployment: deploymentRouter
9
- });
10
- export {
11
- sharedRouter
12
- };
@@ -1,11 +0,0 @@
1
- import { initTRPC } from "@trpc/server";
2
- const createContext = async () => {
3
- return {};
4
- };
5
- const { router, procedure, middleware } = initTRPC.context().create();
6
- export {
7
- createContext,
8
- middleware,
9
- procedure,
10
- router
11
- };
@@ -1,26 +0,0 @@
1
- import { observable } from "@trpc/server/observable";
2
- import { TRPCClientError } from "@trpc/client";
3
- const callerLink = (opts) => {
4
- const { appRouter, createContext } = opts;
5
- return (runtime) => ({ op }) => observable((observer) => {
6
- const caller = appRouter.createCaller(createContext?.() ?? {});
7
- const { path, input } = op;
8
- const paths = path.split(".");
9
- let localCaller = caller;
10
- for (const functionName of paths) {
11
- localCaller = localCaller[functionName];
12
- }
13
- const promise = localCaller(input);
14
- promise.then((data) => {
15
- observer.next({
16
- result: { data }
17
- });
18
- observer.complete();
19
- }).catch((error) => observer.error(TRPCClientError.from(error)));
20
- return () => {
21
- };
22
- });
23
- };
24
- export {
25
- callerLink
26
- };
@@ -1,21 +0,0 @@
1
- import type { AppContext } from "../context/context.server";
2
- /**
3
- * For 3rd party authorization systems like Ory we need to register token for the project.
4
- *
5
- * We do that before the authorizationToken create (and out of the transaction),
6
- * so in case of an error we will have just stale records of non existed projects in authorization system.
7
- */
8
- export declare const registerToken: (props: {
9
- projectId: string;
10
- tokenId: string;
11
- relation: "viewers" | "editors" | "builders";
12
- }, context: AppContext) => Promise<void>;
13
- export declare const patchToken: (props: {
14
- projectId: string;
15
- tokenId: string;
16
- }, prevRelation: "viewers" | "editors" | "builders", nextRelation: "viewers" | "editors" | "builders", context: AppContext) => Promise<void>;
17
- export declare const unregisterToken: (props: {
18
- projectId: string;
19
- tokenId: string;
20
- relation: "viewers" | "editors";
21
- }, context: AppContext) => Promise<void>;
@@ -1,25 +0,0 @@
1
- import type { Project } from "@webstudio-is/prisma-client";
2
- import type { AuthPermit } from "../shared/authorization-router";
3
- import type { AppContext } from "../context/context.server";
4
- /**
5
- * For 3rd party authorization systems like Ory we need to register the project owner.
6
- *
7
- * We do that before the project create (and out of the transaction),
8
- * so in case of an error we will have just stale records of non existed projects in authorization system.
9
- */
10
- export declare const registerProjectOwner: (props: {
11
- projectId: string;
12
- }, context: AppContext) => Promise<void>;
13
- export declare const hasProjectPermit: (props: {
14
- projectId: Project["id"];
15
- permit: AuthPermit;
16
- }, context: AppContext) => Promise<boolean>;
17
- /**
18
- * Returns the first allowed permit from the list or undefined if none is allowed
19
- * @todo think about caching to authorizeTrpc.check.query
20
- * batching check queries would help too https://github.com/ory/keto/issues/812
21
- */
22
- export declare const getProjectPermit: <T extends "build" | "view" | "edit" | "own">(props: {
23
- projectId: string;
24
- permits: readonly T[];
25
- }, context: AppContext) => Promise<T | undefined>;
@@ -1,53 +0,0 @@
1
- import type { TrpcInterfaceClient } from "../shared/shared-router";
2
- /**
3
- * All necessary parameters for Authorization
4
- */
5
- type AuthorizationContext = {
6
- /**
7
- * userId of the current authenticated user
8
- */
9
- userId: string | undefined;
10
- /**
11
- * token URLSearchParams or hostname
12
- */
13
- authToken: string | undefined;
14
- /**
15
- * project list serves as a template and is accessible to everyone.
16
- */
17
- projectTemplates: string[];
18
- /**
19
- * Allow service 2 service communications to skip authorization for view calls
20
- */
21
- isServiceCall: boolean;
22
- authorizeTrpc: TrpcInterfaceClient["authorize"];
23
- };
24
- type DomainContext = {
25
- domainTrpc: TrpcInterfaceClient["domain"];
26
- };
27
- type EntriContext = {
28
- entryApi: {
29
- getEntriToken: () => Promise<{
30
- token: string;
31
- applicationId: string;
32
- }>;
33
- };
34
- };
35
- type DeploymentContext = {
36
- deploymentTrpc: TrpcInterfaceClient["deployment"];
37
- env: {
38
- BUILDER_ORIGIN: string;
39
- BRANCH_NAME: string;
40
- };
41
- };
42
- /**
43
- * AppContext is a global context that is passed to all trpc/api queries/mutations
44
- * "authorization" is made inside the namespace because eventually there will be
45
- * logging parameters, potentially "request" cache, etc.
46
- */
47
- export type AppContext = {
48
- authorization: AuthorizationContext;
49
- domain: DomainContext;
50
- deployment: DeploymentContext;
51
- entri: EntriContext;
52
- };
53
- export {};
@@ -1 +0,0 @@
1
- export declare const AuthorizationError: import("ts-custom-error").CustomErrorConstructor<import("ts-custom-error").CustomErrorProperties>;
@@ -1 +0,0 @@
1
- export * from "./index.server";
@@ -1,7 +0,0 @@
1
- export type { SharedRouter } from "./shared/shared-router";
2
- export { createTrpcProxyServiceClient } from "./shared/client";
3
- export type { AppContext } from "./context/context.server";
4
- export { AuthorizationError } from "./context/errors.server";
5
- export * as authorizeProject from "./authorize/project.server";
6
- export * as authorizeAuthorizationToken from "./authorize/authorization-token.server";
7
- export type { AuthPermit } from "./shared/authorization-router";