Prefer app url resolution over referer (#9218)
* Prefer app url resolution over referer * Update lockfiles
This commit is contained in:
parent
cca09eaba1
commit
e95222c0f4
|
@ -1,7 +1,8 @@
|
||||||
import { structures } from "../../../tests"
|
import { structures } from "../../../tests"
|
||||||
import * as utils from "../../utils"
|
import * as utils from "../../utils"
|
||||||
import * as events from "../../events"
|
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"
|
import { doInTenant } from "../../context"
|
||||||
|
|
||||||
describe("utils", () => {
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,13 +25,16 @@ const jwt = require("jsonwebtoken")
|
||||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||||
const PROD_APP_PREFIX = "/app/"
|
const PROD_APP_PREFIX = "/app/"
|
||||||
|
|
||||||
|
const BUILDER_PREVIEW_PATH = "/app/preview"
|
||||||
|
const BUILDER_REFERER_PREFIX = "/builder/app/"
|
||||||
|
|
||||||
function confirmAppId(possibleAppId: string | undefined) {
|
function confirmAppId(possibleAppId: string | undefined) {
|
||||||
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||||
? possibleAppId
|
? possibleAppId
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveAppUrl(ctx: Ctx) {
|
export async function resolveAppUrl(ctx: Ctx) {
|
||||||
const appUrl = ctx.path.split("/")[2]
|
const appUrl = ctx.path.split("/")[2]
|
||||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||||
|
|
||||||
|
@ -75,7 +78,7 @@ export function isServingApp(ctx: Ctx) {
|
||||||
*/
|
*/
|
||||||
export async function getAppIdFromCtx(ctx: Ctx) {
|
export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
// look in headers
|
// look in headers
|
||||||
const options = [ctx.headers[Header.APP_ID]]
|
const options = [ctx.request.headers[Header.APP_ID]]
|
||||||
let appId
|
let appId
|
||||||
for (let option of options) {
|
for (let option of options) {
|
||||||
appId = confirmAppId(option as string)
|
appId = confirmAppId(option as string)
|
||||||
|
@ -95,15 +98,23 @@ export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
appId = confirmAppId(pathId)
|
appId = confirmAppId(pathId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// look in the referer
|
// lookup using custom url - prod apps only
|
||||||
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
|
// filter out the builder preview path which collides with the prod app path
|
||||||
if (!appId && refererId) {
|
// to ensure we don't load all apps excessively
|
||||||
appId = confirmAppId(refererId)
|
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
|
// look in the referer - builder only
|
||||||
if (!appId && ctx.path.startsWith(PROD_APP_PREFIX)) {
|
// make sure this is performed after prod app url resolution, in case the
|
||||||
appId = confirmAppId(await resolveAppUrl(ctx))
|
// 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
|
return appId
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ export * from "./common"
|
||||||
import Chance from "chance"
|
import Chance from "chance"
|
||||||
export const generator = new Chance()
|
export const generator = new Chance()
|
||||||
|
|
||||||
export * as koa from "./koa"
|
|
||||||
export * as accounts from "./accounts"
|
export * as accounts from "./accounts"
|
||||||
|
export * as apps from "./apps"
|
||||||
|
export * as koa from "./koa"
|
||||||
export * as licenses from "./licenses"
|
export * as licenses from "./licenses"
|
||||||
export * as plugins from "./plugins"
|
export * as plugins from "./plugins"
|
||||||
|
|
|
@ -5,9 +5,11 @@ export const newContext = (): BBContext => {
|
||||||
const ctx = createMockContext()
|
const ctx = createMockContext()
|
||||||
return {
|
return {
|
||||||
...ctx,
|
...ctx,
|
||||||
|
path: "/",
|
||||||
cookies: createMockCookies(),
|
cookies: createMockCookies(),
|
||||||
request: {
|
request: {
|
||||||
...ctx.request,
|
...ctx.request,
|
||||||
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3645,9 +3645,9 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
|
||||||
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
|
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
|
||||||
|
|
||||||
json5@^2.2.1:
|
json5@^2.2.1:
|
||||||
version "2.2.3"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||||
|
|
||||||
jsonc-parser@^3.2.0:
|
jsonc-parser@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
|
|
Loading…
Reference in New Issue