Adding tenancy to the API key, making the authenticated middleware aware of new user API keys, using a view to lookup the user by API key.
This commit is contained in:
parent
417ea349ab
commit
249b2dbba8
|
@ -30,6 +30,7 @@ const UNICODE_MAX = "\ufff0"
|
||||||
|
|
||||||
exports.ViewNames = {
|
exports.ViewNames = {
|
||||||
USER_BY_EMAIL: "by_email",
|
USER_BY_EMAIL: "by_email",
|
||||||
|
BY_API_KEY: "by_api_key",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.StaticDatabases = StaticDatabases
|
exports.StaticDatabases = StaticDatabases
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { DocumentTypes, ViewNames } = require("./utils")
|
const { DocumentTypes, ViewNames } = require("./utils")
|
||||||
|
const { getGlobalDB } = require("../tenancy")
|
||||||
|
|
||||||
function DesignDoc() {
|
function DesignDoc() {
|
||||||
return {
|
return {
|
||||||
|
@ -9,7 +10,8 @@ function DesignDoc() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createUserEmailView = async db => {
|
exports.createUserEmailView = async () => {
|
||||||
|
const db = getGlobalDB()
|
||||||
let designDoc
|
let designDoc
|
||||||
try {
|
try {
|
||||||
designDoc = await db.get("_design/database")
|
designDoc = await db.get("_design/database")
|
||||||
|
@ -31,3 +33,51 @@ exports.createUserEmailView = async db => {
|
||||||
}
|
}
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.createApiKeyView = async () => {
|
||||||
|
const db = getGlobalDB()
|
||||||
|
let designDoc
|
||||||
|
try {
|
||||||
|
designDoc = await db.get("_design/database")
|
||||||
|
} catch (err) {
|
||||||
|
designDoc = DesignDoc()
|
||||||
|
}
|
||||||
|
const view = {
|
||||||
|
map: `function(doc) {
|
||||||
|
if (doc._id.startsWith("${DocumentTypes.DEV_INFO}") && doc.apiKey) {
|
||||||
|
emit(doc.apiKey, doc.userId)
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
}
|
||||||
|
designDoc.views = {
|
||||||
|
...designDoc.views,
|
||||||
|
[ViewNames.BY_API_KEY]: view,
|
||||||
|
}
|
||||||
|
await db.put(designDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.queryGlobalView = async (viewName, params, db = null) => {
|
||||||
|
const CreateFuncByName = {
|
||||||
|
[ViewNames.USER_BY_EMAIL]: exports.createUserEmailView,
|
||||||
|
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
|
||||||
|
}
|
||||||
|
// can pass DB in if working with something specific
|
||||||
|
if (!db) {
|
||||||
|
db = getGlobalDB()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let response = (await db.query(`database/${viewName}`, params)).rows
|
||||||
|
response = response.map(resp =>
|
||||||
|
params.include_docs ? resp.doc : resp.value
|
||||||
|
)
|
||||||
|
return response.length <= 1 ? response[0] : response
|
||||||
|
} catch (err) {
|
||||||
|
if (err != null && err.name === "not_found") {
|
||||||
|
const createFunc = CreateFuncByName[viewName]
|
||||||
|
await createFunc()
|
||||||
|
return exports.queryGlobalView(viewName, params)
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ const { getUser } = require("../cache/user")
|
||||||
const { getSession, updateSessionTTL } = require("../security/sessions")
|
const { getSession, updateSessionTTL } = require("../security/sessions")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db")
|
||||||
|
const { getGlobalDB } = require("../tenancy")
|
||||||
|
|
||||||
function finalise(
|
function finalise(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -16,6 +18,26 @@ function finalise(
|
||||||
ctx.version = version
|
ctx.version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkApiKey(apiKey, populateUser) {
|
||||||
|
if (apiKey === env.INTERNAL_API_KEY) {
|
||||||
|
return { valid: true }
|
||||||
|
}
|
||||||
|
const tenantId = apiKey.split(SEPARATOR)[0]
|
||||||
|
const db = getGlobalDB(tenantId)
|
||||||
|
const userId = await queryGlobalView(
|
||||||
|
ViewNames.BY_API_KEY,
|
||||||
|
{
|
||||||
|
key: apiKey,
|
||||||
|
},
|
||||||
|
db
|
||||||
|
)
|
||||||
|
if (userId) {
|
||||||
|
return { valid: true, user: await getUser(userId, tenantId, populateUser) }
|
||||||
|
} else {
|
||||||
|
throw "Invalid API key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This middleware is tenancy aware, so that it does not depend on other middlewares being used.
|
* This middleware is tenancy aware, so that it does not depend on other middlewares being used.
|
||||||
* The tenancy modules should not be used here and it should be assumed that the tenancy context
|
* The tenancy modules should not be used here and it should be assumed that the tenancy context
|
||||||
|
@ -79,9 +101,19 @@ module.exports = (
|
||||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||||
const tenantId = ctx.request.headers[Headers.TENANT_ID]
|
const tenantId = ctx.request.headers[Headers.TENANT_ID]
|
||||||
// this is an internal request, no user made it
|
// this is an internal request, no user made it
|
||||||
if (!authenticated && apiKey && apiKey === env.INTERNAL_API_KEY) {
|
if (!authenticated && apiKey) {
|
||||||
authenticated = true
|
const populateUser = opts.populateUser ? opts.populateUser(ctx) : null
|
||||||
internal = true
|
const { valid, user: foundUser } = await checkApiKey(
|
||||||
|
apiKey,
|
||||||
|
populateUser
|
||||||
|
)
|
||||||
|
if (valid && foundUser) {
|
||||||
|
authenticated = true
|
||||||
|
user = foundUser
|
||||||
|
} else if (valid) {
|
||||||
|
authenticated = true
|
||||||
|
internal = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!user && tenantId) {
|
if (!user && tenantId) {
|
||||||
user = { tenantId }
|
user = { tenantId }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
exports.lookupApiKey = async () => {}
|
|
@ -6,7 +6,7 @@ const {
|
||||||
} = require("./db/utils")
|
} = require("./db/utils")
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { options } = require("./middleware/passport/jwt")
|
const { options } = require("./middleware/passport/jwt")
|
||||||
const { createUserEmailView } = require("./db/views")
|
const { queryGlobalView } = require("./db/views")
|
||||||
const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants")
|
const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants")
|
||||||
const {
|
const {
|
||||||
getGlobalDB,
|
getGlobalDB,
|
||||||
|
@ -139,25 +139,11 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
const db = getGlobalDB()
|
|
||||||
|
|
||||||
try {
|
return queryGlobalView(ViewNames.USER_BY_EMAIL, {
|
||||||
let users = (
|
key: email.toLowerCase(),
|
||||||
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
include_docs: true,
|
||||||
key: email.toLowerCase(),
|
})
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
).rows
|
|
||||||
users = users.map(user => user.doc)
|
|
||||||
return users.length <= 1 ? users[0] : users
|
|
||||||
} catch (err) {
|
|
||||||
if (err != null && err.name === "not_found") {
|
|
||||||
await createUserEmailView(db)
|
|
||||||
return exports.getGlobalUserByEmail(email)
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.saveUser = async (
|
exports.saveUser = async (
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
|
||||||
const { generateDevInfoID } = require("@budibase/backend-core/db")
|
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
||||||
const { newid } = require("@budibase/backend-core/utils")
|
const { newid } = require("@budibase/backend-core/utils")
|
||||||
|
|
||||||
|
function newApiKey() {
|
||||||
|
return `${getTenantId()}${SEPARATOR}${newid()}`
|
||||||
|
}
|
||||||
|
|
||||||
function cleanupDevInfo(info) {
|
function cleanupDevInfo(info) {
|
||||||
// user doesn't need to aware of dev doc info
|
// user doesn't need to aware of dev doc info
|
||||||
delete info._id
|
delete info._id
|
||||||
|
@ -16,9 +20,9 @@ exports.generateAPIKey = async ctx => {
|
||||||
try {
|
try {
|
||||||
devInfo = await db.get(id)
|
devInfo = await db.get(id)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
devInfo = { _id: id }
|
devInfo = { _id: id, userId: ctx.user._id }
|
||||||
}
|
}
|
||||||
devInfo.apiKey = newid()
|
devInfo.apiKey = newApiKey()
|
||||||
await db.put(devInfo)
|
await db.put(devInfo)
|
||||||
ctx.body = cleanupDevInfo(devInfo)
|
ctx.body = cleanupDevInfo(devInfo)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +36,8 @@ exports.fetchAPIKey = async ctx => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
devInfo = {
|
devInfo = {
|
||||||
_id: id,
|
_id: id,
|
||||||
apiKey: newid(),
|
userId: ctx.user._id,
|
||||||
|
apiKey: newApiKey(),
|
||||||
}
|
}
|
||||||
await db.put(devInfo)
|
await db.put(devInfo)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue