You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/app/server/lib/TagChecker.ts

75 lines
2.9 KiB

import {tbind} from 'app/common/tbind';
import {NextFunction, Request, RequestHandler, Response} from 'express';
export type RequestWithTag = Request & {tag: string|null};
/**
*
* Middleware to handle a /v/TAG/ prefix on urls.
*
*/
export class TagChecker {
// Use app.use(tagChecker.inspectTag) to strip /v/TAG/ from urls (if it is present).
// If the tag is present and matches what is expected, then `tag` is set on the request.
// If the tag is present but does not match what is expected, a 400 response is returned.
// If the tag is absent, `tag` is not set on the request.
public readonly inspectTag: RequestHandler = tbind(this._inspectTag, this);
// Use app.get('/path', tagChecker.requireTag, ...) to serve something only if the tag was
// present in the url. If the tag was not present, the route will not match and express will
// look further.
public readonly requireTag: RequestHandler = tbind(this._requireTag, this);
// pass in the tag to expect.
public constructor(public tag: string) {
}
// Like requireTag but for use wrapping other handlers in app.use().
// Whatever it wraps will be skipped if that tag was not set.
// See https://github.com/expressjs/express/issues/2591
public withTag(handler: RequestHandler) {
return function fn(req: Request, resp: Response, next: NextFunction) {
if (!(req as RequestWithTag).tag) { return next(); }
return handler(req, resp, next);
};
}
// Removes tag from url if present.
// Returns [remainder, tagInUrl, isMatch]
private _removeTag(url: string): [string, string|null, boolean] {
if (url.startsWith('/v/')) {
const taggedUrl = url.match(/^\/v\/([a-zA-Z0-9.\-_]+)(\/.*)/);
if (taggedUrl) {
const tag = taggedUrl[1];
// Turn off tag matching as we transition to serving
// static resources from CDN. We don't have version-sensitive
// routing, so under ordinary operation landing page html served
// by one home server could have its assets served by another home server.
// Once the CDN is active, those asset requests won't reach the home
// servers. TODO: turn tag matching back on when tag mismatches
// imply a bug.
return [taggedUrl[2], tag, true /* tag === this.tag */];
}
}
return [url, null, true];
}
private async _inspectTag(req: Request, resp: Response, next: NextFunction) {
const [newUrl, urlTag, isOk] = this._removeTag(req.url);
if (!isOk) {
return resp.status(400).send({error: "Tag mismatch",
expected: this.tag,
received: urlTag});
}
req.url = newUrl;
(req as RequestWithTag).tag = urlTag;
return next();
}
private async _requireTag(req: Request, resp: Response, next: NextFunction) {
if ((req as RequestWithTag).tag) { return next(); }
return next('route');
}
}