Files
webstudio/apps/builder/app/shared/context.server.ts
T
Ivan Starkov 2e235bf215 fix: Permission error when opening marketplace projects (marketplace projects need to add any view permission link) (#3460)
## Description

- [x] - Todo cursor and other styles issues

ref #3446

We now display the marketplace project link only if it has a shared link
with view permission. If there is no shared link, we show disabled
buttons with a tooltip.

<img width="564" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/f1d98c9b-22a5-4706-b9f5-e2749c07f6e7">

<img width="732" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/02848dc7-ef99-4a4a-91a6-9e6d2d41ed7f">


## Steps for reproduction

Open marketplace, see one has link the other is not

<img width="300" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/90b2f352-305b-415a-86c5-056280e17000">

Check that link is working in incognito


## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example`
and the `builder/env-check.js` if mandatory
2024-06-01 09:34:24 +03:00

130 lines
3.6 KiB
TypeScript

import type { AppContext } from "@webstudio-is/trpc-interface/index.server";
import env from "~/env/env.server";
import { authenticator } from "~/services/auth.server";
import { trpcSharedClient } from "~/services/trpc.server";
import { entryApi } from "./entri/entri-api.server";
import {
getTokenPlanFeatures,
getUserPlanFeatures,
} from "./db/user-plan-features.server";
import { staticEnv } from "~/env/env.static.server";
const createAuthorizationContext = async (
request: Request
): Promise<AppContext["authorization"]> => {
const url = new URL(request.url);
const authToken =
url.searchParams.get("authToken") ??
request.headers.get("x-auth-token") ??
url.hostname;
const user = await authenticator.isAuthenticated(request);
const isServiceCall =
request.headers.has("Authorization") &&
request.headers.get("Authorization") === env.TRPC_SERVER_API_TOKEN;
const context: AppContext["authorization"] = {
userId: user?.id,
authToken,
isServiceCall,
authorizeTrpc: trpcSharedClient.authorize,
};
return context;
};
const createDomainContext = (request: Request) => {
const context: AppContext["domain"] = {
domainTrpc: trpcSharedClient.domain,
};
return context;
};
const getRequestOrigin = (request: Request) => {
const url = new URL(request.url);
// vercel overwrites x-forwarded-host on edge level even if our header is set
// as workaround we use custom header x-forwarded-ws-host to get the original host
url.host =
request.headers.get("x-forwarded-ws-host") ??
request.headers.get("x-forwarded-host") ??
url.host;
return url.origin;
};
const createDeploymentContext = (request: Request) => {
const context: AppContext["deployment"] = {
deploymentTrpc: trpcSharedClient.deployment,
env: {
BUILDER_ORIGIN: `${getRequestOrigin(request)}`,
GITHUB_REF_NAME: staticEnv.GITHUB_REF_NAME ?? "undefined",
GITHUB_SHA: staticEnv.GITHUB_SHA ?? undefined,
},
};
return context;
};
const createEntriContext = () => {
return {
entryApi,
};
};
const createUserPlanContext = async (request: Request) => {
const url = new URL(request.url);
const authToken = url.searchParams.get("authToken");
// When a shared link is accessed, identified by the presence of an authToken,
// the system retrieves the plan features associated with the project owner's account.
if (authToken !== null) {
const planFeatures = await getTokenPlanFeatures(authToken);
return planFeatures;
}
const user = await authenticator.isAuthenticated(request);
const planFeatures = user?.id ? getUserPlanFeatures(user.id) : undefined;
return planFeatures;
};
const createTrpcCache = () => {
const proceduresMaxAge = new Map<string, number>();
const setMaxAge = (path: string, value: number) => {
proceduresMaxAge.set(
path,
Math.min(proceduresMaxAge.get(path) ?? Number.MAX_SAFE_INTEGER, value)
);
};
const getMaxAge = (path: string) => proceduresMaxAge.get(path);
return {
setMaxAge,
getMaxAge,
};
};
/**
* argument buildEnv==="prod" only if we are loading project with production build
*/
export const createContext = async (request: Request): Promise<AppContext> => {
const authorization = await createAuthorizationContext(request);
const domain = createDomainContext(request);
const deployment = createDeploymentContext(request);
const entri = createEntriContext();
const userPlanFeatures = await createUserPlanContext(request);
const trpcCache = createTrpcCache();
return {
authorization,
domain,
deployment,
entri,
userPlanFeatures,
trpcCache,
};
};