Prefer app url resolution over referer (#9218)

* Prefer app url resolution over referer

* Update lockfiles
This commit is contained in:
Rory Powell 2023-01-11 10:24:57 +00:00 committed by GitHub
parent cca09eaba1
commit e95222c0f4
6 changed files with 141 additions and 14 deletions

View File

@ -1,7 +1,8 @@
import { structures } from "../../../tests"
import * as utils from "../../utils"
import * as events from "../../events"
import { DEFAULT_TENANT_ID } from "../../constants"
import * as db from "../../db"
import { DEFAULT_TENANT_ID, Header } from "../../constants"
import { doInTenant } from "../../context"
describe("utils", () => {
@ -14,4 +15,95 @@ describe("utils", () => {
})
})
})
describe("getAppIdFromCtx", () => {
it("gets appId from header", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
ctx.request.headers = {
[Header.APP_ID]: expected,
}
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("gets appId from body", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
ctx.request.body = {
appId: expected,
}
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("gets appId from path", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
ctx.path = `/apps/${expected}`
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("gets appId from url", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
const app = structures.apps.app(expected)
// set custom url
const appUrl = "custom-url"
app.url = `/${appUrl}`
ctx.path = `/app/${appUrl}`
// save the app
const database = db.getDB(expected)
await database.put(app)
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("doesn't get appId from url when previewing", async () => {
const ctx = structures.koa.newContext()
const appId = db.generateAppID()
const app = structures.apps.app(appId)
// set custom url
const appUrl = "preview"
app.url = `/${appUrl}`
ctx.path = `/app/${appUrl}`
// save the app
const database = db.getDB(appId)
await database.put(app)
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(undefined)
})
it("gets appId from referer", async () => {
const ctx = structures.koa.newContext()
const expected = db.generateAppID()
ctx.request.headers = {
referer: `http://test.com/builder/app/${expected}/design/screen_123/screens`,
}
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(expected)
})
it("doesn't get appId from referer when not builder", async () => {
const ctx = structures.koa.newContext()
const appId = db.generateAppID()
ctx.request.headers = {
referer: `http://test.com/foo/app/${appId}/bar`,
}
const actual = await utils.getAppIdFromCtx(ctx)
expect(actual).toBe(undefined)
})
})
})

View File

@ -25,13 +25,16 @@ const jwt = require("jsonwebtoken")
const APP_PREFIX = DocumentType.APP + SEPARATOR
const PROD_APP_PREFIX = "/app/"
const BUILDER_PREVIEW_PATH = "/app/preview"
const BUILDER_REFERER_PREFIX = "/builder/app/"
function confirmAppId(possibleAppId: string | undefined) {
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
? possibleAppId
: undefined
}
async function resolveAppUrl(ctx: Ctx) {
export async function resolveAppUrl(ctx: Ctx) {
const appUrl = ctx.path.split("/")[2]
let possibleAppUrl = `/${appUrl.toLowerCase()}`
@ -75,7 +78,7 @@ export function isServingApp(ctx: Ctx) {
*/
export async function getAppIdFromCtx(ctx: Ctx) {
// look in headers
const options = [ctx.headers[Header.APP_ID]]
const options = [ctx.request.headers[Header.APP_ID]]
let appId
for (let option of options) {
appId = confirmAppId(option as string)
@ -95,15 +98,23 @@ export async function getAppIdFromCtx(ctx: Ctx) {
appId = confirmAppId(pathId)
}
// look in the referer
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
if (!appId && refererId) {
appId = confirmAppId(refererId)
// lookup using custom url - prod apps only
// filter out the builder preview path which collides with the prod app path
// to ensure we don't load all apps excessively
const isBuilderPreview = ctx.path.startsWith(BUILDER_PREVIEW_PATH)
const isViewingProdApp =
ctx.path.startsWith(PROD_APP_PREFIX) && !isBuilderPreview
if (!appId && isViewingProdApp) {
appId = confirmAppId(await resolveAppUrl(ctx))
}
// look in the url - prod app
if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) {
appId = confirmAppId(await resolveAppUrl(ctx))
// look in the referer - builder only
// make sure this is performed after prod app url resolution, in case the
// referer header is present from a builder redirect
const referer = ctx.request.headers.referer
if (!appId && referer?.includes(BUILDER_REFERER_PREFIX)) {
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
appId = confirmAppId(refererId)
}
return appId

View File

@ -0,0 +1,21 @@
import { generator } from "."
import { App } from "@budibase/types"
import { DEFAULT_TENANT_ID, DocumentType } from "../../../src/constants"
export function app(id: string): App {
return {
_id: DocumentType.APP_METADATA,
appId: id,
type: "",
version: "0.0.1",
componentLibraries: [],
name: generator.name(),
url: `/custom-url`,
instance: {
_id: id,
},
tenantId: DEFAULT_TENANT_ID,
status: "",
template: undefined,
}
}

View File

@ -3,7 +3,8 @@ export * from "./common"
import Chance from "chance"
export const generator = new Chance()
export * as koa from "./koa"
export * as accounts from "./accounts"
export * as apps from "./apps"
export * as koa from "./koa"
export * as licenses from "./licenses"
export * as plugins from "./plugins"

View File

@ -5,9 +5,11 @@ export const newContext = (): BBContext => {
const ctx = createMockContext()
return {
...ctx,
path: "/",
cookies: createMockCookies(),
request: {
...ctx.request,
headers: {},
body: {},
},
}

View File

@ -3645,9 +3645,9 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
json5@^2.2.1:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
version "2.2.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
jsonc-parser@^3.2.0:
version "3.2.0"