Further work towards the re-implementation of auth, changing how the appId is determined, now it mainly will use a header, and a cookie which will be written to store the current status of appId.

This commit is contained in:
Michael Drury 2020-11-03 13:45:49 +00:00
parent 871e9b123e
commit ac73b5c4aa
8 changed files with 67 additions and 47 deletions

View File

@ -1,15 +1,17 @@
import { store } from "./index"
import { get as svelteGet } from "svelte/store"
const apiCall = method => async ( const apiCall = method => async (
url, url,
body, body,
headers = { "Content-Type": "application/json" } headers = { "Content-Type": "application/json" }
) => { ) => {
const response = await fetch(url, { headers["x-budibase-app-id"] = svelteGet(store).appId
return await fetch(url, {
method: method, method: method,
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
headers, headers,
}) })
return response
} }
export const post = apiCall("POST") export const post = apiCall("POST")

View File

@ -84,8 +84,6 @@
<Button <Button
secondary secondary
on:click={() => { on:click={() => {
// reset cookies for this app
document.cookie = `budibase:${application}:local=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
window.open(`/${application}`) window.open(`/${application}`)
}}> }}>
Preview Preview

View File

@ -1,11 +1,12 @@
import {authenticate} from "./authenticate" import {authenticate} from "./authenticate"
// import appStore from "../state/store" import {getAppIdFromPath} from "../render/getAppId";
const apiCall = method => async ({ url, body }) => { const apiCall = method => async ({ url, body }) => {
const response = await fetch(url, { const response = await fetch(url, {
method: method, method: method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-budibase-app-id": getAppIdFromPath(),
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
credentials: "same-origin", credentials: "same-origin",
@ -36,9 +37,8 @@ const del = apiCall("DELETE")
const ERROR_MEMBER = "##error" const ERROR_MEMBER = "##error"
const error = message => { const error = message => {
const err = { [ERROR_MEMBER]: message }
// appStore.update(s => s["##error_message"], message) // appStore.update(s => s["##error_message"], message)
return err return {[ERROR_MEMBER]: message}
} }
const isSuccess = obj => !obj || !obj[ERROR_MEMBER] const isSuccess = obj => !obj || !obj[ERROR_MEMBER]
@ -81,6 +81,9 @@ const makeRowRequestBody = (parameters, state) => {
// then override with supplied parameters // then override with supplied parameters
for (let fieldName in parameters.fields) { for (let fieldName in parameters.fields) {
if (!parameters.fields.hasOwnProperty(fieldName)) {
continue
}
const field = parameters.fields[fieldName] const field = parameters.fields[fieldName]
// ensure fields sent are of the correct type // ensure fields sent are of the correct type

View File

@ -4,10 +4,10 @@ const bcrypt = require("../../utilities/bcrypt")
const env = require("../../environment") const env = require("../../environment")
const { getAPIKey } = require("../../utilities/usageQuota") const { getAPIKey } = require("../../utilities/usageQuota")
const { generateUserID } = require("../../db/utils") const { generateUserID } = require("../../db/utils")
const { getCookieName } = require("../../utilities") const { setCookie } = require("../../utilities")
exports.authenticate = async ctx => { exports.authenticate = async ctx => {
const appId = ctx.user.appId const appId = ctx.appId
if (!appId) ctx.throw(400, "No appId") if (!appId) ctx.throw(400, "No appId")
const { username, password } = ctx.request.body const { username, password } = ctx.request.body
@ -46,15 +46,7 @@ exports.authenticate = async ctx => {
expiresIn: "1 day", expiresIn: "1 day",
}) })
const expires = new Date() setCookie(ctx, appId, token)
expires.setDate(expires.getDate() + 1)
ctx.cookies.set(getCookieName(appId), token, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
delete dbUser.password delete dbUser.password
ctx.body = { ctx.body = {

View File

@ -9,7 +9,7 @@ const {
} = require("../utilities/accessLevels") } = require("../utilities/accessLevels")
const env = require("../environment") const env = require("../environment")
const { AuthTypes } = require("../constants") const { AuthTypes } = require("../constants")
const { getAppId, getCookieName } = require("../utilities") const { getAppId, getCookieName, setCookie } = require("../utilities")
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
if (ctx.path === "/_builder") { if (ctx.path === "/_builder") {
@ -17,7 +17,14 @@ module.exports = async (ctx, next) => {
return return
} }
const appId = getAppId(ctx) // do everything we can to make sure the appId is held correctly
// we hold it in state as a
let appId = getAppId(ctx)
if (appId) {
setCookie(ctx, "currentapp", appId)
} else {
appId = ctx.cookies.get(getCookieName("currentapp"))
}
const appToken = ctx.cookies.get(getCookieName(appId)) const appToken = ctx.cookies.get(getCookieName(appId))
const builderToken = ctx.cookies.get(getCookieName()) const builderToken = ctx.cookies.get(getCookieName())
@ -43,14 +50,12 @@ module.exports = async (ctx, next) => {
try { try {
const jwtPayload = jwt.verify(token, ctx.config.jwtSecret) const jwtPayload = jwt.verify(token, ctx.config.jwtSecret)
ctx.appId = appId
ctx.auth.apiKey = jwtPayload.apiKey ctx.auth.apiKey = jwtPayload.apiKey
ctx.user = { ctx.user = {
...jwtPayload, ...jwtPayload,
appId: jwtPayload.appId, appId: appId,
accessLevel: await getAccessLevel( accessLevel: await getAccessLevel(appId, jwtPayload.accessLevelId),
jwtPayload.appId,
jwtPayload.accessLevelId
),
} }
} catch (err) { } catch (err) {
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text) ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)

View File

@ -3,7 +3,7 @@ const env = require("../../environment")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { DocumentTypes, SEPARATOR } = require("../../db/utils") const { DocumentTypes, SEPARATOR } = require("../../db/utils")
const { getCookieName } = require("../index") const { setCookie } = require("../index")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
module.exports = async (ctx, appId, version) => { module.exports = async (ctx, appId, version) => {
@ -20,21 +20,13 @@ module.exports = async (ctx, appId, version) => {
expiresIn: "30 days", expiresIn: "30 days",
}) })
const expiry = new Date()
expiry.setDate(expiry.getDate() + 30)
// set the builder token // set the builder token
ctx.cookies.set(getCookieName(), token, { setCookie(ctx, "builder", token)
expires: expiry,
httpOnly: false,
overwrite: true,
})
// need to clear all app tokens or else unable to use the app in the builder // need to clear all app tokens or else unable to use the app in the builder
let allDbNames = await CouchDB.allDbs() let allDbNames = await CouchDB.allDbs()
allDbNames.map(dbName => { allDbNames.map(dbName => {
if (dbName.startsWith(APP_PREFIX)) { if (dbName.startsWith(APP_PREFIX)) {
ctx.cookies.set(getCookieName(dbName), "", { setCookie(ctx, dbName, "")
overwrite: true,
})
} }
}) })
} }

View File

@ -1,4 +1,7 @@
const env = require("../environment") const env = require("../environment")
const { DocumentTypes, SEPARATOR } = require("../db/utils")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms))
@ -17,26 +20,47 @@ exports.isDev = () => {
* @returns {string|undefined} If an appId was found it will be returned. * @returns {string|undefined} If an appId was found it will be returned.
*/ */
exports.getAppId = ctx => { exports.getAppId = ctx => {
let appId = env.CLOUD ? ctx.subdomains[1] : ctx.params.appId let appId = ctx.headers["x-budibase-app-id"]
if (!appId) {
appId = env.CLOUD ? ctx.subdomains[1] : ctx.params.appId
}
// look in body if can't find it in subdomain // look in body if can't find it in subdomain
if (!appId && ctx.request.body && ctx.request.body.appId) { if (!appId && ctx.request.body && ctx.request.body.appId) {
appId = ctx.request.body.appId appId = ctx.request.body.appId
} }
// if appId can't be determined from path param or subdomain let appPath =
if (!appId && ctx.request.headers.referer) { ctx.request.headers.referrer ||
const url = new URL(ctx.request.headers.referer) ctx.path.split("/").filter(subPath => subPath.startsWith(APP_PREFIX))
// remove leading and trailing slashes from appId if (!appId && appPath.length !== 0) {
appId = url.pathname.replace(/\//g, "") appId = appPath[0]
} }
return appId return appId
} }
/** /**
* Get the name of the cookie which is to be updated/retrieved * Get the name of the cookie which is to be updated/retrieved
* @param {string|undefined|null} appId OPTIONAL can specify the specific app if previewing etc * @param {string|undefined|null} name OPTIONAL can specify the specific app if previewing etc
* @returns {string} The name of the token trying to find * @returns {string} The name of the token trying to find
*/ */
exports.getCookieName = (appId = null) => { exports.getCookieName = (name = "builder") => {
let environment = env.CLOUD ? "cloud" : "local" let environment = env.CLOUD ? "cloud" : "local"
return `budibase:${appId ? appId : "builder"}:${environment}` return `budibase:${name}:${environment}`
}
/**
* Store a cookie for the request, has a hardcoded expiry.
* @param {object} ctx The request which is to be manipulated.
* @param {string} name The name of the cookie to set.
* @param {string|object} value The value of cookie which will be set.
*/
exports.setCookie = (ctx, name, value) => {
const expires = new Date()
expires.setDate(expires.getDate() + 1)
ctx.cookies.set(exports.getCookieName(name), value, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
} }

View File

@ -5,6 +5,10 @@ const apiCall = method => async (
"Content-Type": "application/json", "Content-Type": "application/json",
} }
) => { ) => {
const appId = location.pathname.split("/")[1]
if (appId) {
headers["x-budibase-app-id"] = appId
}
const response = await fetch(url, { const response = await fetch(url, {
method: method, method: method,
body: body && JSON.stringify(body), body: body && JSON.stringify(body),