Updating bb admin user creation so that it can be used incase in self host a user gets locked out, the environment variables can be used to create a simple user to access the system.
This commit is contained in:
parent
2be8940334
commit
ddd8482013
|
@ -3,7 +3,7 @@ import * as eventHelpers from "./events"
|
||||||
import * as accounts from "../accounts"
|
import * as accounts from "../accounts"
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import * as cache from "../cache"
|
import * as cache from "../cache"
|
||||||
import { getGlobalDB, getIdentity, getTenantId } from "../context"
|
import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
|
||||||
import * as dbUtils from "../db"
|
import * as dbUtils from "../db"
|
||||||
import { EmailUnavailableError, HTTPError } from "../errors"
|
import { EmailUnavailableError, HTTPError } from "../errors"
|
||||||
import * as platform from "../platform"
|
import * as platform from "../platform"
|
||||||
|
@ -11,12 +11,10 @@ import * as sessions from "../security/sessions"
|
||||||
import * as usersCore from "./users"
|
import * as usersCore from "./users"
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
AllDocsResponse,
|
|
||||||
BulkUserCreated,
|
BulkUserCreated,
|
||||||
BulkUserDeleted,
|
BulkUserDeleted,
|
||||||
isSSOAccount,
|
isSSOAccount,
|
||||||
isSSOUser,
|
isSSOUser,
|
||||||
RowResponse,
|
|
||||||
SaveUserOpts,
|
SaveUserOpts,
|
||||||
User,
|
User,
|
||||||
UserStatus,
|
UserStatus,
|
||||||
|
@ -488,6 +486,37 @@ export class UserDB {
|
||||||
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async createAdminUser(
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
tenantId: string,
|
||||||
|
opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean }
|
||||||
|
) {
|
||||||
|
const user: User = {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
roles: {},
|
||||||
|
builder: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
tenantId,
|
||||||
|
}
|
||||||
|
if (opts?.ssoId) {
|
||||||
|
user.ssoId = opts.ssoId
|
||||||
|
}
|
||||||
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
||||||
|
// stuck in a cycle
|
||||||
|
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
||||||
|
return await UserDB.save(user, {
|
||||||
|
hashPassword: opts?.hashPassword,
|
||||||
|
requirePassword: opts?.requirePassword,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async getGroups(groupIds: string[]) {
|
static async getGroups(groupIds: string[]) {
|
||||||
return await this.groups.getBulk(groupIds)
|
return await this.groups.getBulk(groupIds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ function removeUserPassword(users: User | User[]) {
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isSupportedUserSearch = (query: SearchQuery) => {
|
export function isSupportedUserSearch(query: SearchQuery) {
|
||||||
const allowed = [
|
const allowed = [
|
||||||
{ op: SearchQueryOperators.STRING, key: "email" },
|
{ op: SearchQueryOperators.STRING, key: "email" },
|
||||||
{ op: SearchQueryOperators.EQUAL, key: "_id" },
|
{ op: SearchQueryOperators.EQUAL, key: "_id" },
|
||||||
|
@ -68,10 +68,10 @@ export const isSupportedUserSearch = (query: SearchQuery) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkGetGlobalUsersById = async (
|
export async function bulkGetGlobalUsersById(
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
opts?: GetOpts
|
opts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
let users = (
|
let users = (
|
||||||
await db.allDocs({
|
await db.allDocs({
|
||||||
|
@ -85,7 +85,7 @@ export const bulkGetGlobalUsersById = async (
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllUserIds = async () => {
|
export async function getAllUserIds() {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const startKey = `${DocumentType.USER}${SEPARATOR}`
|
const startKey = `${DocumentType.USER}${SEPARATOR}`
|
||||||
const response = await db.allDocs({
|
const response = await db.allDocs({
|
||||||
|
@ -95,7 +95,7 @@ export const getAllUserIds = async () => {
|
||||||
return response.rows.map(row => row.id)
|
return response.rows.map(row => row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
export async function bulkUpdateGlobalUsers(users: User[]) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
return (await db.bulkDocs(users)) as BulkDocsResponse
|
return (await db.bulkDocs(users)) as BulkDocsResponse
|
||||||
}
|
}
|
||||||
|
@ -113,10 +113,10 @@ export async function getById(id: string, opts?: GetOpts): Promise<User> {
|
||||||
* Given an email address this will use a view to search through
|
* Given an email address this will use a view to search through
|
||||||
* all the users to find one with this email address.
|
* all the users to find one with this email address.
|
||||||
*/
|
*/
|
||||||
export const getGlobalUserByEmail = async (
|
export async function getGlobalUserByEmail(
|
||||||
email: String,
|
email: String,
|
||||||
opts?: GetOpts
|
opts?: GetOpts
|
||||||
): Promise<User | undefined> => {
|
): Promise<User | undefined> {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
|
@ -139,11 +139,23 @@ export const getGlobalUserByEmail = async (
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchGlobalUsersByApp = async (
|
export async function doesUserExist(email: string) {
|
||||||
|
try {
|
||||||
|
const user = await getGlobalUserByEmail(email)
|
||||||
|
if (Array.isArray(user) || user != null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchGlobalUsersByApp(
|
||||||
appId: any,
|
appId: any,
|
||||||
opts: DatabaseQueryOpts,
|
opts: DatabaseQueryOpts,
|
||||||
getOpts?: GetOpts
|
getOpts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
if (typeof appId !== "string") {
|
if (typeof appId !== "string") {
|
||||||
throw new Error("Must provide a string based app ID")
|
throw new Error("Must provide a string based app ID")
|
||||||
}
|
}
|
||||||
|
@ -167,10 +179,10 @@ export const searchGlobalUsersByApp = async (
|
||||||
Return any user who potentially has access to the application
|
Return any user who potentially has access to the application
|
||||||
Admins, developers and app users with the explicitly role.
|
Admins, developers and app users with the explicitly role.
|
||||||
*/
|
*/
|
||||||
export const searchGlobalUsersByAppAccess = async (
|
export async function searchGlobalUsersByAppAccess(
|
||||||
appId: any,
|
appId: any,
|
||||||
opts?: { limit?: number }
|
opts?: { limit?: number }
|
||||||
) => {
|
) {
|
||||||
const roleSelector = `roles.${appId}`
|
const roleSelector = `roles.${appId}`
|
||||||
|
|
||||||
let orQuery: any[] = [
|
let orQuery: any[] = [
|
||||||
|
@ -205,7 +217,7 @@ export const searchGlobalUsersByAppAccess = async (
|
||||||
return resp.rows
|
return resp.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
export function getGlobalUserByAppPage(appId: string, user: User) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -215,11 +227,11 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
||||||
/**
|
/**
|
||||||
* Performs a starts with search on the global email view.
|
* Performs a starts with search on the global email view.
|
||||||
*/
|
*/
|
||||||
export const searchGlobalUsersByEmail = async (
|
export async function searchGlobalUsersByEmail(
|
||||||
email: string | unknown,
|
email: string | unknown,
|
||||||
opts: any,
|
opts: any,
|
||||||
getOpts?: GetOpts
|
getOpts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
if (typeof email !== "string") {
|
if (typeof email !== "string") {
|
||||||
throw new Error("Must provide a string to search by")
|
throw new Error("Must provide a string to search by")
|
||||||
}
|
}
|
||||||
|
@ -242,12 +254,12 @@ export const searchGlobalUsersByEmail = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_LIMIT = 8
|
const PAGE_LIMIT = 8
|
||||||
export const paginatedUsers = async ({
|
export async function paginatedUsers({
|
||||||
bookmark,
|
bookmark,
|
||||||
query,
|
query,
|
||||||
appId,
|
appId,
|
||||||
limit,
|
limit,
|
||||||
}: SearchUsersRequest = {}) => {
|
}: SearchUsersRequest = {}) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const pageSize = limit ?? PAGE_LIMIT
|
const pageSize = limit ?? PAGE_LIMIT
|
||||||
const pageLimit = pageSize + 1
|
const pageLimit = pageSize + 1
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import env from "./environment"
|
import env from "./environment"
|
||||||
import * as redis from "./utilities/redis"
|
import * as redis from "./utilities/redis"
|
||||||
|
import { generateApiKey, getChecklist } from "./utilities/workerRequests"
|
||||||
import {
|
import {
|
||||||
createAdminUser,
|
events,
|
||||||
generateApiKey,
|
installation,
|
||||||
getChecklist,
|
logging,
|
||||||
} from "./utilities/workerRequests"
|
tenancy,
|
||||||
import { events, installation, logging, tenancy } from "@budibase/backend-core"
|
users,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { watch } from "./watch"
|
import { watch } from "./watch"
|
||||||
import * as automations from "./automations"
|
import * as automations from "./automations"
|
||||||
|
@ -110,34 +112,37 @@ export async function startup(app?: any, server?: any) {
|
||||||
// check and create admin user if required
|
// check and create admin user if required
|
||||||
// this must be run after the api has been initialised due to
|
// this must be run after the api has been initialised due to
|
||||||
// the app user sync
|
// the app user sync
|
||||||
|
const bbAdminEmail = env.BB_ADMIN_USER_EMAIL,
|
||||||
|
bbAdminPassword = env.BB_ADMIN_USER_PASSWORD
|
||||||
if (
|
if (
|
||||||
env.SELF_HOSTED &&
|
env.SELF_HOSTED &&
|
||||||
!env.MULTI_TENANCY &&
|
!env.MULTI_TENANCY &&
|
||||||
env.BB_ADMIN_USER_EMAIL &&
|
bbAdminEmail &&
|
||||||
env.BB_ADMIN_USER_PASSWORD
|
bbAdminPassword
|
||||||
) {
|
) {
|
||||||
const checklist = await getChecklist()
|
const tenantId = tenancy.getTenantId()
|
||||||
if (!checklist?.adminUser?.checked) {
|
await tenancy.doInTenant(tenantId, async () => {
|
||||||
try {
|
const exists = await users.doesUserExist(bbAdminEmail)
|
||||||
const tenantId = tenancy.getTenantId()
|
const checklist = await getChecklist()
|
||||||
const user = await createAdminUser(
|
if (!checklist?.adminUser?.checked || !exists) {
|
||||||
env.BB_ADMIN_USER_EMAIL,
|
try {
|
||||||
env.BB_ADMIN_USER_PASSWORD,
|
const user = await users.UserDB.createAdminUser(
|
||||||
tenantId
|
bbAdminEmail,
|
||||||
)
|
bbAdminPassword,
|
||||||
// Need to set up an API key for automated integration tests
|
tenantId,
|
||||||
if (env.isTest()) {
|
{ hashPassword: true, requirePassword: true }
|
||||||
await generateApiKey(user._id)
|
)
|
||||||
}
|
// Need to set up an API key for automated integration tests
|
||||||
|
if (env.isTest()) {
|
||||||
|
await generateApiKey(user._id!)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log("Admin account automatically created for", bbAdminEmail)
|
||||||
"Admin account automatically created for",
|
} catch (e) {
|
||||||
env.BB_ADMIN_USER_EMAIL
|
logging.logAlert("Error creating initial admin user. Exiting.", e)
|
||||||
)
|
shutdown(server)
|
||||||
} catch (e) {
|
}
|
||||||
logging.logAlert("Error creating initial admin user. Exiting.", e)
|
|
||||||
shutdown(server)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,9 @@ export async function createAdminUser(
|
||||||
return checkResponse(response, "create admin user")
|
return checkResponse(response, "create admin user")
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getChecklist() {
|
export async function getChecklist(): Promise<{
|
||||||
|
adminUser: { checked: boolean }
|
||||||
|
}> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(env.WORKER_URL + "/api/global/configs/checklist"),
|
checkSlashesInUrl(env.WORKER_URL + "/api/global/configs/checklist"),
|
||||||
request(undefined, { method: "GET" })
|
request(undefined, { method: "GET" })
|
||||||
|
|
|
@ -120,28 +120,17 @@ export const adminUser = async (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user: User = {
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
roles: {},
|
|
||||||
builder: {
|
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
tenantId,
|
|
||||||
ssoId,
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
const finalUser = await userSdk.db.createAdminUser(
|
||||||
// stuck in a cycle
|
email,
|
||||||
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
password,
|
||||||
const finalUser = await userSdk.db.save(user, {
|
tenantId,
|
||||||
hashPassword,
|
{
|
||||||
requirePassword,
|
ssoId,
|
||||||
})
|
hashPassword,
|
||||||
|
requirePassword,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// events
|
// events
|
||||||
let account: CloudAccount | undefined
|
let account: CloudAccount | undefined
|
||||||
|
|
Loading…
Reference in New Issue