Skip to content

Commit

Permalink
Merge cbb1ad6 into f95a536
Browse files Browse the repository at this point in the history
  • Loading branch information
jimrandomh committed Apr 17, 2024
2 parents f95a536 + cbb1ad6 commit cc93a03
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 169 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { Components, registerComponent } from "../../lib/vulcan-lib";
import { useLocation } from "../../lib/routeUtil";
import { hasForumEvents } from "../../lib/betas";

type BannerType = "frontpage" | "postpage";

Expand All @@ -13,6 +14,11 @@ export const ForumEventBanner = () => {
const {currentRoute} = useLocation();
const bannerType = bannerTypes[currentRoute?.name ?? ""];
const {ForumEventFrontpageBanner, ForumEventPostPageBanner} = Components;

if (!hasForumEvents) {
return null;
}

switch (bannerType) {
case "frontpage":
return (
Expand Down
10 changes: 5 additions & 5 deletions packages/lesswrong/lib/vulcan-lib/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,22 @@ type EmailRenderContextType = {

export const EmailRenderContext = React.createContext<EmailRenderContextType|null>(null);

const classNameProxy = (componentName: string) => {
const classNameProxy = (prefix: string) => {
return new Proxy({}, {
get: function(obj: any, prop: any) {
// Check that the prop is really a string. This isn't an error that comes
// up normally, but apparently React devtools will try to query for non-
// string properties sometimes when using the component debugger.
if (typeof prop === "string")
return `${componentName}-${prop}`;
return prefix+prop;
else
return `${componentName}-invalid`;
return prefix+'invalid';
}
});
}

const addClassnames = (componentName: string, styles: any) => {
const classesProxy = classNameProxy(componentName);
const classesProxy = classNameProxy(componentName+'-');
return (WrappedComponent: any) => forwardRef((props, ref) => {
const emailRenderContext = React.useContext(EmailRenderContext);
if (emailRenderContext?.isEmailRender) {
Expand All @@ -114,7 +114,7 @@ const addClassnames = (componentName: string, styles: any) => {
}

export const useStyles = (styles: (theme: ThemeType) => JssStyles, componentName: keyof ComponentTypes) => {
return classNameProxy(componentName);
return classNameProxy(componentName+'-');
};

// Register a component. Takes a name, a raw component, and ComponentOptions
Expand Down
28 changes: 22 additions & 6 deletions packages/lesswrong/lib/vulcan-users/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,23 +201,34 @@ export const userCanReadField = <N extends CollectionNameString>(
document: ObjectsByCollectionName[N],
): boolean => {
const canRead = field.canRead;
const userGroups = userGetGroups(user);
if (canRead) {
return userHasFieldPermissions(user, canRead, document);
return userHasFieldPermissions(user, userGroups, canRead, document);
}
return false;
};

const userHasFieldPermissions = <T extends DbObject>(user: UsersCurrent|DbUser|null, canRead: FieldPermissions, document: T): boolean => {
const userHasFieldPermissions = <T extends DbObject>(
user: UsersCurrent|DbUser|null,
userGroups: string[],
canRead: FieldPermissions,
document: T
): boolean => {
if (typeof canRead === 'string') {
// if canRead is just a string, we assume it's the name of a group and pass it to isMemberOf
return canRead === 'guests' || userIsMemberOf(user, canRead);
if (canRead === 'guests') return true;
for (let group of userGroups) {
if (group===canRead)
return true;
}
return false;
} else if (typeof canRead === 'function') {
// if canRead is a function, execute it with user and document passed. it must return a boolean
return canRead(user, document);
} else if (Array.isArray(canRead) && canRead.length > 0) {
// if canRead is an array, we do a recursion on every item and return true if one of the items return true
for (const group of canRead) {
if (userHasFieldPermissions(user, group, document)) {
if (userHasFieldPermissions(user, userGroups, group, document)) {
return true;
}
}
Expand Down Expand Up @@ -271,10 +282,15 @@ export const restrictViewableFieldsSingle = function <N extends CollectionNameSt
if (!doc) return {};
const schema = getSchema(collection);
const restrictedDocument: Partial<ObjectsByCollectionName[N]> = {};
const userGroups = userGetGroups(user);

for (const fieldName in doc) {
const fieldSchema = schema[fieldName];
if (fieldSchema && userCanReadField(user, fieldSchema, doc)) {
restrictedDocument[fieldName] = doc[fieldName];
if (fieldSchema) {
const canRead = fieldSchema.canRead;
if (canRead && userHasFieldPermissions(user, userGroups, canRead, doc)) {
restrictedDocument[fieldName] = doc[fieldName];
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/lesswrong/server/apolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { GraphQLError, GraphQLFormattedError } from 'graphql';
import { isDevelopment, getInstanceSettings, getServerPort, isProduction } from '../lib/executionEnvironment';
import { renderWithCache, getThemeOptionsFromReq } from './vulcan-lib/apollo-ssr/renderPage';

import { pickerMiddleware } from './vendor/picker';
import { pickerMiddleware, addStaticRoute } from './vulcan-lib/staticRoutes';
import voyagerMiddleware from 'graphql-voyager/middleware/express';
import { graphiqlMiddleware } from './vulcan-lib/apollo-server/graphiql';
import getPlaygroundConfig from './vulcan-lib/apollo-server/playground';
Expand All @@ -27,7 +27,6 @@ import { addAuthMiddlewares, expressSessionSecretSetting } from './authenticatio
import { addForumSpecificMiddleware } from './forumSpecificMiddleware';
import { addSentryMiddlewares, logGraphqlQueryStarted, logGraphqlQueryFinished } from './logging';
import { addClientIdMiddleware } from './clientIdMiddleware';
import { addStaticRoute } from './vulcan-lib/staticRoutes';
import { classesForAbTestGroups } from '../lib/abTestImpl';
import expressSession from 'express-session';
import MongoStore from './vendor/ConnectMongo/MongoStore';
Expand Down
7 changes: 6 additions & 1 deletion packages/lesswrong/server/repos/PageCacheRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ class PageCacheRepo extends AbstractRepo<"PageCache"> {
renderedAt: now,
expiresAt: new Date(now.getTime() + maxCacheAgeMs),
ttlMs: maxCacheAgeMs,
renderResult,

// Stringify renderResult before handing it to the postgres library. We
// do this because the string can be large, and if we pass it as a JSON
// object, the postgres library will stringify it in a slower way that
// adds bignum support (which we don't use).
renderResult: JSON.stringify(renderResult),
schemaVersion: 1,
createdAt: now,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/lesswrong/server/resolvers/revisionResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ augmentFieldsDict(Revisions, {
type: 'String!',
resolver: ({html}): string => {
if (!html) return ""
const truncatedHtml = truncate(sanitize(html), PLAINTEXT_HTML_TRUNCATION_LENGTH)
const truncatedHtml = truncate(html, PLAINTEXT_HTML_TRUNCATION_LENGTH)
return htmlToTextPlaintextDescription(truncatedHtml).substring(0, PLAINTEXT_DESCRIPTION_LENGTH);
}
}
Expand Down
149 changes: 0 additions & 149 deletions packages/lesswrong/server/vendor/picker.ts

This file was deleted.

75 changes: 70 additions & 5 deletions packages/lesswrong/server/vulcan-lib/staticRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,74 @@
import type { NextFunction } from 'express';
// Adapted from https://github.com/meteorhacks/picker
//
// Which is under the MIT License:
//
// Copyright (c) 2014 MeteorHacks PVT Ltd. hello@meteorhacks.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import type { IncomingMessage, ServerResponse } from 'http';
import { Picker } from '../vendor/picker';
import pathToRegexp from 'path-to-regexp';
import URL from 'url';
import type { NextFunction, ParamsDictionary, Query, Response } from 'express-serve-static-core';
import type { RequestHandler } from 'express';
const urlParse = URL.parse;

type Req = Parameters<RequestHandler>[0];
type Res = Response<any, Record<string, any>, number>;
type RouteCallback = (props: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void | Promise<void>;

let routes: (pathToRegexp.PathRegExp & { callback: any })[] = [];

function dispatch(req: Req, res: Res, next: NextFunction) {
for (const route of routes) {
var uri = req.url.replace(/\?.*/, '');
var m = uri.match(route);
if (!m) continue;

var params = buildParams(route.keys, m);
params.query = urlParse(req.url, true).query;

route.callback.call(null, params, req, res, next);
return;
}
next();
};

function buildParams(keys: pathToRegexp.Key[], m: RegExpMatchArray) {
var params: any = {};
for(var lc=1; lc<m.length; lc++) {
var key = keys[lc-1].name;
var value = m[lc];
params[key] = value;
}

return params;
};

export const pickerMiddleware: RequestHandler<ParamsDictionary, any, any, Query, Record<string, any>> = function(req, res, next) {
dispatch(req, res, next);
}

/// Add a route which renders by putting things into the http response body
/// directly, rather than using all the Meteor/Apollo/etc stuff.
export const addStaticRoute = (url: string, handler: (props: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void|Promise<void>) => {
Picker.route(url, handler);
}
export function addStaticRoute(path: pathToRegexp.Path, callback: RouteCallback) {
var regExp = pathToRegexp(path);
const regExpWithCallback = Object.assign(regExp, { callback });
routes.push(regExpWithCallback);
};

0 comments on commit cc93a03

Please sign in to comment.