Sync global-info users to fix login + prevent double password hashing

This commit is contained in:
Rory Powell 2022-08-04 14:49:56 +01:00
parent a53bc1ec07
commit 1b6b877546
9 changed files with 138 additions and 12 deletions

View File

@ -37,4 +37,8 @@ export const DEFINITIONS: MigrationDefinition[] = [
type: MigrationType.INSTALLATION, type: MigrationType.INSTALLATION,
name: MigrationName.EVENT_INSTALLATION_BACKFILL, name: MigrationName.EVENT_INSTALLATION_BACKFILL,
}, },
{
type: MigrationType.GLOBAL,
name: MigrationName.GLOBAL_INFO_SYNC_USERS,
},
] ]

View File

@ -46,6 +46,7 @@ export enum MigrationName {
EVENT_APP_BACKFILL = "event_app_backfill", EVENT_APP_BACKFILL = "event_app_backfill",
EVENT_GLOBAL_BACKFILL = "event_global_backfill", EVENT_GLOBAL_BACKFILL = "event_global_backfill",
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill", EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
} }
export interface MigrationDefinition { export interface MigrationDefinition {

View File

@ -0,0 +1,13 @@
const { migrate, MIGRATIONS } = require("../../../migrations")
export const runMigrations = async (ctx: any) => {
const options = ctx.request.body
// don't await as can take a while, just return
migrate(options)
ctx.status = 200
}
export const fetchDefinitions = async (ctx: any) => {
ctx.body = MIGRATIONS
ctx.status = 200
}

View File

@ -106,7 +106,10 @@ router
if (ctx.publicEndpoint) { if (ctx.publicEndpoint) {
return next() return next()
} }
if ((!ctx.isAuthenticated || !ctx.user.budibaseAccess) && !ctx.internal) { if (
(!ctx.isAuthenticated || (ctx.user && !ctx.user.budibaseAccess)) &&
!ctx.internal
) {
ctx.throw(403, "Unauthorized - no public worker access") ctx.throw(403, "Unauthorized - no public worker access")
} }
return next() return next()

View File

@ -12,6 +12,7 @@ const tenantsRoutes = require("./system/tenants")
const statusRoutes = require("./system/status") const statusRoutes = require("./system/status")
const selfRoutes = require("./global/self") const selfRoutes = require("./global/self")
const licenseRoutes = require("./global/license") const licenseRoutes = require("./global/license")
const migrationRoutes = require("./system/migrations")
let userGroupRoutes = api.groups let userGroupRoutes = api.groups
exports.routes = [ exports.routes = [
@ -29,4 +30,5 @@ exports.routes = [
selfRoutes, selfRoutes,
licenseRoutes, licenseRoutes,
userGroupRoutes, userGroupRoutes,
migrationRoutes,
] ]

View File

@ -0,0 +1,19 @@
import Router from "@koa/router"
import * as migrationsController from "../../controllers/system/migrations"
import { auth } from "@budibase/backend-core"
const router = new Router()
router
.post(
"/api/system/migrations/run",
auth.internalApi,
migrationsController.runMigrations
)
.get(
"/api/system/migrations/definitions",
auth.internalApi,
migrationsController.fetchDefinitions
)
export = router

View File

@ -0,0 +1,20 @@
import { User } from "@budibase/types"
import * as sdk from "../../sdk"
/**
* Date:
* Aug 2022
*
* Description:
* Re-sync the global-db users to the global-info db users
*/
export const run = async (globalDb: any) => {
const users = (await sdk.users.allUsers()) as User[]
const promises = []
for (let user of users) {
promises.push(
sdk.users.addTenant(user.tenantId, user._id as string, user.email)
)
}
await Promise.all(promises)
}

View File

@ -0,0 +1,74 @@
import { migrations, redis } from "@budibase/backend-core"
import { Migration, MigrationOptions, MigrationName } from "@budibase/types"
import env from "../environment"
// migration functions
import * as syncUserInfo from "./functions/globalInfoSyncUsers"
/**
* Populate the migration function and additional configuration from
* the static migration definitions.
*/
export const buildMigrations = () => {
const definitions = migrations.DEFINITIONS
const workerMigrations: Migration[] = []
for (const definition of definitions) {
switch (definition.name) {
case MigrationName.GLOBAL_INFO_SYNC_USERS: {
// only needed in cloud
if (!env.SELF_HOSTED) {
workerMigrations.push({
...definition,
fn: syncUserInfo.run,
})
}
break
}
}
}
return workerMigrations
}
export const MIGRATIONS = buildMigrations()
export const migrate = async (options?: MigrationOptions) => {
if (env.SELF_HOSTED) {
await migrateWithLock(options)
} else {
await migrations.runMigrations(MIGRATIONS, options)
}
}
const migrateWithLock = async (options?: MigrationOptions) => {
// get a new lock client
const redlock = await redis.clients.getMigrationsRedlock()
// lock for 15 minutes
const ttl = 1000 * 60 * 15
let migrationLock
// acquire lock
try {
migrationLock = await redlock.lock("migrations", ttl)
} catch (e: any) {
if (e.name === "LockError") {
return
} else {
throw e
}
}
// run migrations
try {
await migrations.runMigrations(MIGRATIONS, options)
} finally {
// release lock
try {
await migrationLock.unlock()
} catch (e) {
console.error("unable to release migration lock")
}
}
}

View File

@ -106,7 +106,6 @@ const buildUser = async (
opts: SaveUserOpts = { opts: SaveUserOpts = {
hashPassword: true, hashPassword: true,
requirePassword: true, requirePassword: true,
bulkCreate: false,
}, },
tenantId: string, tenantId: string,
dbUser?: any dbUser?: any
@ -185,15 +184,7 @@ export const save = async (
dbUser = await db.get(_id) dbUser = await db.get(_id)
} }
let builtUser = await buildUser( let builtUser = await buildUser(user, opts, tenantId, dbUser)
user,
{
hashPassword: true,
requirePassword: user.requirePassword,
},
tenantId,
dbUser
)
// make sure we set the _id field for a new user // make sure we set the _id field for a new user
if (!_id) { if (!_id) {
@ -298,7 +289,6 @@ export const bulkCreate = async (
{ {
hashPassword: true, hashPassword: true,
requirePassword: user.requirePassword, requirePassword: user.requirePassword,
bulkCreate: false,
}, },
tenantId tenantId
) )