1
0
Fork 0
mirror of synced 2024-07-02 21:10:43 +12:00

Swapping over everything to use the new user ID and updating everything after some end to end testing.

This commit is contained in:
mike12345567 2021-04-20 17:17:44 +01:00
parent 1f8925ceb8
commit b4c8bf81f7
17 changed files with 163 additions and 119 deletions

View file

@ -1,5 +1,9 @@
let Pouch
module.exports.setDB = pouch => { module.exports.setDB = pouch => {
module.exports.CouchDB = pouch Pouch = pouch
} }
module.exports.CouchDB = null module.exports.getDB = dbName => {
return new Pouch(dbName)
}

View file

@ -23,14 +23,6 @@ const SEPARATOR = "_"
exports.SEPARATOR = SEPARATOR exports.SEPARATOR = SEPARATOR
/**
* Generates a new global user ID.
* @returns {string} The new user ID which the user doc can be stored under.
*/
exports.generateUserID = () => {
return `${DocumentTypes.USER}${SEPARATOR}${newid()}`
}
/** /**
* Generates a new group ID. * Generates a new group ID.
* @returns {string} The new group ID which the group doc can be stored under. * @returns {string} The new group ID which the group doc can be stored under.
@ -50,10 +42,18 @@ exports.getGroupParams = (id = "", otherProps = {}) => {
} }
} }
/**
* Generates a new global user ID.
* @returns {string} The new user ID which the user doc can be stored under.
*/
exports.generateGlobalUserID = () => {
return `${DocumentTypes.USER}${SEPARATOR}${newid()}`
}
/** /**
* Gets parameters for retrieving users. * Gets parameters for retrieving users.
*/ */
exports.getUserParams = (globalId = "", otherProps = {}) => { exports.getGlobalUserParams = (globalId = "", otherProps = {}) => {
if (!globalId) { if (!globalId) {
globalId = "" globalId = ""
} }

View file

@ -1,9 +1,24 @@
const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils") const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils")
const { CouchDB } = require("./index") const { getDB } = require("./index")
function DesignDoc() {
return {
_id: "_design/database",
// view collation information, read before writing any complex views:
// https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification
views: {},
}
}
exports.createUserEmailView = async () => { exports.createUserEmailView = async () => {
const db = new CouchDB(StaticDatabases.GLOBAL.name) const db = getDB(StaticDatabases.GLOBAL.name)
const designDoc = await db.get("_design/database") let designDoc
try {
designDoc = await db.get("_design/database")
} catch (err) {
// no design doc, make one
designDoc = DesignDoc()
}
const view = { const view = {
// if using variables in a map function need to inject them before use // if using variables in a map function need to inject them before use
map: `function(doc) { map: `function(doc) {

View file

@ -2,7 +2,7 @@ const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy const JwtStrategy = require("passport-jwt").Strategy
// const GoogleStrategy = require("passport-google-oauth").Strategy // const GoogleStrategy = require("passport-google-oauth").Strategy
const database = require("./db") const { setDB, getDB } = require("./db")
const { StaticDatabases } = require("./db/utils") const { StaticDatabases } = require("./db/utils")
const { jwt, local, authenticated } = require("./middleware") const { jwt, local, authenticated } = require("./middleware")
const { Cookies, UserStatus } = require("./constants") const { Cookies, UserStatus } = require("./constants")
@ -13,14 +13,14 @@ const {
getCookie, getCookie,
clearCookie, clearCookie,
isClient, isClient,
getGlobalUserByEmail,
} = require("./utils") } = require("./utils")
const { const {
generateUserID, generateGlobalUserID,
getUserParams, getGlobalUserParams,
generateGroupID, generateGroupID,
getGroupParams, getGroupParams,
} = require("./db/utils") } = require("./db/utils")
const { getGlobalUserByEmail } = require("./utils")
// Strategies // Strategies
passport.use(new LocalStrategy(local.options, local.authenticate)) passport.use(new LocalStrategy(local.options, local.authenticate))
@ -30,7 +30,7 @@ passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
passport.serializeUser((user, done) => done(null, user)) passport.serializeUser((user, done) => done(null, user))
passport.deserializeUser(async (user, done) => { passport.deserializeUser(async (user, done) => {
const db = new database.CouchDB(StaticDatabases.GLOBAL.name) const db = getDB(StaticDatabases.GLOBAL.name)
try { try {
const user = await db.get(user._id) const user = await db.get(user._id)
@ -43,14 +43,14 @@ passport.deserializeUser(async (user, done) => {
module.exports = { module.exports = {
init(pouch) { init(pouch) {
database.setDB(pouch) setDB(pouch)
}, },
passport, passport,
Cookies, Cookies,
UserStatus, UserStatus,
StaticDatabases, StaticDatabases,
generateUserID, generateGlobalUserID,
getUserParams, getGlobalUserParams,
generateGroupID, generateGroupID,
getGroupParams, getGroupParams,
hash, hash,

View file

@ -1,7 +1,5 @@
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { UserStatus } = require("../../constants") const { UserStatus } = require("../../constants")
const database = require("../../db")
const { StaticDatabases, generateUserID } = require("../../db/utils")
const { compare } = require("../../hashing") const { compare } = require("../../hashing")
const env = require("../../environment") const env = require("../../environment")
const { getGlobalUserByEmail } = require("../../utils") const { getGlobalUserByEmail } = require("../../utils")
@ -21,11 +19,8 @@ exports.authenticate = async function(email, password, done) {
if (!email) return done(null, false, "Email Required.") if (!email) return done(null, false, "Email Required.")
if (!password) return done(null, false, "Password Required.") if (!password) return done(null, false, "Password Required.")
let dbUser const dbUser = await getGlobalUserByEmail(email)
try { if (dbUser == null) {
dbUser = await getGlobalUserByEmail(email)
} catch (err) {
console.error("User not found", err)
return done(null, false, { message: "User not found" }) return done(null, false, { message: "User not found" })
} }

View file

@ -2,7 +2,7 @@ const { DocumentTypes, SEPARATOR, ViewNames, StaticDatabases } = require("./db/u
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 { createUserEmailView } = require("./db/views")
const { CouchDB } = require("./db") const { getDB } = require("./db")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
@ -101,19 +101,23 @@ exports.isClient = ctx => {
} }
exports.getGlobalUserByEmail = async email => { exports.getGlobalUserByEmail = async email => {
const db = new CouchDB(StaticDatabases.GLOBAL.name) const db = getDB(StaticDatabases.GLOBAL.name)
try { try {
let users = (await db.query( let users = (await db.query(
`database/${ViewNames.USER_BY_EMAIL}`, `database/${ViewNames.USER_BY_EMAIL}`,
{ {
key: email key: email,
include_docs: true,
}) })
).rows ).rows
users = users.map(user => user.doc)
return users.length <= 1 ? users[0] : users return users.length <= 1 ? users[0] : users
} catch (err) { } catch (err) {
if (err != null && err.name === "not_found") { if (err != null && err.name === "not_found") {
await createUserEmailView() await createUserEmailView()
return exports.getGlobalUserByEmail(email) return exports.getGlobalUserByEmail(email)
} else {
throw err
} }
} }
} }

View file

@ -21,14 +21,7 @@
async function createTestUser() { async function createTestUser() {
try { try {
await auth.createUser({ await auth.firstUser()
email: "test@test.com",
password: "test",
roles: {},
builder: {
global: true,
},
})
notifier.success("Test user created") notifier.success("Test user created")
} catch (err) { } catch (err) {
console.error(err) console.error(err)

View file

@ -151,8 +151,8 @@
const user = { const user = {
roleId: $createAppStore.values.roleId, roleId: $createAppStore.values.roleId,
} }
const userResp = await api.post(`/api/users/metadata`, user) const userResp = await api.post(`/api/users/metadata/self`, user)
const json = await userResp.json() await userResp.json()
$goto(`./${appJson._id}`) $goto(`./${appJson._id}`)
} catch (error) { } catch (error) {
console.error(error) console.error(error)

View file

@ -30,11 +30,24 @@ export function createAuthStore() {
}, },
logout: async () => { logout: async () => {
const response = await api.post(`/api/admin/auth/logout`) const response = await api.post(`/api/admin/auth/logout`)
if (response.status !== 200) {
throw "Unable to create logout"
}
await response.json() await response.json()
set({ user: null }) set({ user: null })
}, },
createUser: async user => { createUser: async user => {
const response = await api.post(`/api/admin/users`, user) const response = await api.post(`/api/admin/users`, user)
if (response.status !== 200) {
throw "Unable to create user"
}
await response.json()
},
firstUser: async () => {
const response = await api.post(`/api/admin/users/first`)
if (response.status !== 200) {
throw "Unable to create test user"
}
await response.json() await response.json()
}, },
} }

View file

@ -43,6 +43,10 @@ exports.createMetadata = async function(ctx) {
const db = new CouchDB(appId) const db = new CouchDB(appId)
const { roleId } = ctx.request.body const { roleId } = ctx.request.body
if (ctx.request.body._id) {
return exports.updateMetadata(ctx)
}
// check role valid // check role valid
const role = await getRole(appId, roleId) const role = await getRole(appId, roleId)
if (!role) ctx.throw(400, "Invalid Role") if (!role) ctx.throw(400, "Invalid Role")
@ -66,20 +70,26 @@ exports.createMetadata = async function(ctx) {
} }
} }
exports.updateSelfMetadata = async function(ctx) {
// overwrite the ID with current users
ctx.request.body._id = ctx.user.userId
// make sure no stale rev
delete ctx.request.body._rev
await exports.updateMetadata(ctx)
}
exports.updateMetadata = async function(ctx) { exports.updateMetadata = async function(ctx) {
const appId = ctx.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const user = ctx.request.body const user = ctx.request.body
const globalUser = await saveGlobalUser( const globalUser = await saveGlobalUser(ctx, appId, {
ctx, ...user,
appId, _id: getGlobalIDFromUserMetadataID(user._id),
getGlobalIDFromUserMetadataID(user._id), })
ctx.request.body
)
const metadata = { const metadata = {
...globalUser, ...globalUser,
_id: user._id || generateUserMetadataID(globalUser._id), _id: user._id || generateUserMetadataID(globalUser._id),
_rev: ctx.request.body._rev, _rev: user._rev,
} }
ctx.body = await db.put(metadata) ctx.body = await db.put(metadata)
} }

View file

@ -31,6 +31,12 @@ router
usage, usage,
controller.createMetadata controller.createMetadata
) )
.post(
"/api/users/metadata/self",
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
usage,
controller.updateSelfMetadata
)
.delete( .delete(
"/api/users/metadata/:id", "/api/users/metadata/:id",
authorized(PermissionTypes.USER, PermissionLevels.WRITE), authorized(PermissionTypes.USER, PermissionLevels.WRITE),

View file

@ -2,6 +2,10 @@ const { getAppId, setCookie, getCookie, Cookies } = require("@budibase/auth")
const { getRole } = require("../utilities/security/roles") const { getRole } = require("../utilities/security/roles")
const { getGlobalUsers } = require("../utilities/workerRequests") const { getGlobalUsers } = require("../utilities/workerRequests")
const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles") const { BUILTIN_ROLE_IDS } = require("../utilities/security/roles")
const {
getGlobalIDFromUserMetadataID,
generateUserMetadataID,
} = require("../db/utils")
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
// try to get the appID from the request // try to get the appID from the request
@ -26,7 +30,8 @@ module.exports = async (ctx, next) => {
appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC) appCookie.roleId === BUILTIN_ROLE_IDS.PUBLIC)
) { ) {
// Different App ID means cookie needs reset, or if the same public user has logged in // Different App ID means cookie needs reset, or if the same public user has logged in
const globalUser = await getGlobalUsers(ctx, requestAppId, ctx.user.email) const globalId = getGlobalIDFromUserMetadataID(ctx.user.userId)
const globalUser = await getGlobalUsers(ctx, requestAppId, globalId)
updateCookie = true updateCookie = true
appId = requestAppId appId = requestAppId
if (globalUser.roles && globalUser.roles[requestAppId]) { if (globalUser.roles && globalUser.roles[requestAppId]) {
@ -36,18 +41,24 @@ module.exports = async (ctx, next) => {
appId = appCookie.appId appId = appCookie.appId
roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC roleId = appCookie.roleId || BUILTIN_ROLE_IDS.PUBLIC
} }
if (appId) { // nothing more to do
if (!appId) {
return next()
}
ctx.appId = appId ctx.appId = appId
if (roleId) { if (roleId) {
ctx.roleId = roleId ctx.roleId = roleId
const userId = ctx.user ? generateUserMetadataID(ctx.user.userId) : null
ctx.user = { ctx.user = {
...ctx.user, ...ctx.user,
_id: ctx.user ? ctx.user.userId : null, // override userID with metadata one
_id: userId,
userId,
role: await getRole(appId, roleId), role: await getRole(appId, roleId),
} }
} }
} if (updateCookie) {
if (updateCookie && appId) {
setCookie(ctx, { appId, roleId }, Cookies.CurrentApp) setCookie(ctx, { appId, roleId }, Cookies.CurrentApp)
} }
return next() return next()

View file

@ -91,8 +91,10 @@ exports.getGlobalUsers = async (ctx, appId = null, globalId = null) => {
return users return users
} }
exports.saveGlobalUser = async (ctx, appId, body, globalId = null) => { exports.saveGlobalUser = async (ctx, appId, body) => {
const globalUser = await exports.getGlobalUsers(ctx, appId, globalId) const globalUser = body._id
? await exports.getGlobalUsers(ctx, appId, body._id)
: {}
const roles = globalUser.roles || {} const roles = globalUser.roles || {}
if (body.roleId) { if (body.roleId) {
roles[appId] = body.roleId roles[appId] = body.roleId

View file

@ -31,15 +31,13 @@ exports.fetch = async function(ctx) {
include_docs: true, include_docs: true,
}) })
) )
const groups = response.rows.map(row => row.doc) ctx.body = response.rows.map(row => row.doc)
ctx.body = groups
} }
exports.find = async function(ctx) { exports.find = async function(ctx) {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
try { try {
const record = await db.get(ctx.params.id) ctx.body = await db.get(ctx.params.id)
ctx.body = record
} catch (err) { } catch (err) {
ctx.throw(err.status, err) ctx.throw(err.status, err)
} }

View file

@ -1,27 +1,42 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const { const {
hash, hash,
generateUserID, generateGlobalUserID,
getUserParams, getGlobalUserParams,
StaticDatabases, StaticDatabases,
getGlobalUserByEmail,
} = require("@budibase/auth") } = require("@budibase/auth")
const { UserStatus } = require("../../../constants") const { UserStatus } = require("../../../constants")
const FIRST_USER_EMAIL = "test@test.com"
const FIRST_USER_PASSWORD = "test"
const GLOBAL_DB = StaticDatabases.GLOBAL.name const GLOBAL_DB = StaticDatabases.GLOBAL.name
exports.userSave = async ctx => { exports.userSave = async ctx => {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const { email, password, _id } = ctx.request.body const { email, password, _id } = ctx.request.body
const hashedPassword = password ? await hash(password) : null
let user = { // make sure another user isn't using the same email
...ctx.request.body, const dbUser = await getGlobalUserByEmail(email)
_id: generateUserID(email), if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
password: hashedPassword, ctx.throw(400, "Email address already in use.")
} }
let dbUser
// in-case user existed already // get the password, make sure one is defined
if (_id) { let hashedPassword
dbUser = await db.get(_id) if (password) {
hashedPassword = await hash(password)
} else if (dbUser) {
hashedPassword = dbUser.password
} else {
ctx.throw(400, "Password must be specified.")
}
let user = {
...dbUser,
...ctx.request.body,
_id: _id || generateGlobalUserID(),
password: hashedPassword,
} }
// add the active status to a user if its not provided // add the active status to a user if its not provided
if (user.status == null) { if (user.status == null) {
@ -29,7 +44,7 @@ exports.userSave = async ctx => {
} }
try { try {
const response = await db.post({ const response = await db.post({
password: hashedPassword || dbUser.password, password: hashedPassword,
...user, ...user,
}) })
ctx.body = { ctx.body = {
@ -46,12 +61,24 @@ exports.userSave = async ctx => {
} }
} }
exports.firstUser = async ctx => {
ctx.request.body = {
email: FIRST_USER_EMAIL,
password: FIRST_USER_PASSWORD,
roles: {},
builder: {
global: true,
},
}
await exports.userSave(ctx)
}
exports.userDelete = async ctx => { exports.userDelete = async ctx => {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const dbUser = await db.get(generateUserID(ctx.params.email)) const dbUser = await db.get(ctx.params.id)
await db.remove(dbUser._id, dbUser._rev) await db.remove(dbUser._id, dbUser._rev)
ctx.body = { ctx.body = {
message: `User ${ctx.params.email} deleted.`, message: `User ${ctx.params.id} deleted.`,
} }
} }
@ -59,7 +86,7 @@ exports.userDelete = async ctx => {
exports.userFetch = async ctx => { exports.userFetch = async ctx => {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const response = await db.allDocs( const response = await db.allDocs(
getUserParams(null, { getGlobalUserParams(null, {
include_docs: true, include_docs: true,
}) })
) )
@ -78,7 +105,7 @@ exports.userFind = async ctx => {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
let user let user
try { try {
user = await db.get(generateUserID(ctx.params.email)) user = await db.get(ctx.params.id)
} catch (err) { } catch (err) {
// no user found, just return nothing // no user found, just return nothing
user = {} user = {}

View file

@ -32,8 +32,9 @@ router
authenticated, authenticated,
controller.userSave controller.userSave
) )
.delete("/api/admin/users/:email", authenticated, controller.userDelete) .post("/api/admin/users/first", controller.firstUser)
.delete("/api/admin/users/:id", authenticated, controller.userDelete)
.get("/api/admin/users", authenticated, controller.userFetch) .get("/api/admin/users", authenticated, controller.userFetch)
.get("/api/admin/users/:email", authenticated, controller.userFind) .get("/api/admin/users/:id", authenticated, controller.userFind)
module.exports = router module.exports = router

View file

@ -1,35 +0,0 @@
exports.StaticDatabases = {
USER: {
name: "user-db",
},
}
const DocumentTypes = {
USER: "us",
APP: "app",
}
exports.DocumentTypes = DocumentTypes
const UNICODE_MAX = "\ufff0"
const SEPARATOR = "_"
/**
* Generates a new user ID based on the passed in email.
* @param {string} email The email which the ID is going to be built up of.
* @returns {string} The new user ID which the user doc can be stored under.
*/
exports.generateUserID = email => {
return `${DocumentTypes.USER}${SEPARATOR}${email}`
}
/**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/
exports.getUserParams = (email = "", otherProps = {}) => {
return {
...otherProps,
startkey: `${DocumentTypes.USER}${SEPARATOR}${email}`,
endkey: `${DocumentTypes.USER}${SEPARATOR}${email}${UNICODE_MAX}`,
}
}