User management events
This commit is contained in:
parent
957e90fe86
commit
d99f579bf6
|
@ -3,7 +3,6 @@ exports.Events = {
|
||||||
USER_CREATED: "user:created",
|
USER_CREATED: "user:created",
|
||||||
USER_UPDATED: "user:updated",
|
USER_UPDATED: "user:updated",
|
||||||
USER_DELETED: "user:deleted",
|
USER_DELETED: "user:deleted",
|
||||||
USER_PASSWORD_FORCE_RESET: "user:password:force:reset",
|
|
||||||
|
|
||||||
// USER / PERMISSIONS
|
// USER / PERMISSIONS
|
||||||
USER_PERMISSION_ADMIN_ASSIGNED: "user:admin:assigned",
|
USER_PERMISSION_ADMIN_ASSIGNED: "user:admin:assigned",
|
||||||
|
@ -15,9 +14,9 @@ exports.Events = {
|
||||||
USER_INVITED: "user:invited",
|
USER_INVITED: "user:invited",
|
||||||
USER_INVITED_ACCEPTED: "user:invite:accepted",
|
USER_INVITED_ACCEPTED: "user:invite:accepted",
|
||||||
|
|
||||||
// USER / SELF
|
// USER / PASSWORD
|
||||||
USER_SELF_UPDATED: "user:self:updated",
|
USER_PASSWORD_FORCE_RESET: "user:password:force:reset",
|
||||||
USER_SELF_PASSWORD_UPDATED: "user:self:password:updated",
|
USER_PASSWORD_UPDATED: "user:password:updated",
|
||||||
USER_PASSWORD_RESET_REQUESTED: "user:password:reset:requested",
|
USER_PASSWORD_RESET_REQUESTED: "user:password:reset:requested",
|
||||||
USER_PASSWORD_RESET: "user:password:reset",
|
USER_PASSWORD_RESET: "user:password:reset",
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,6 @@ exports.deleted = user => {
|
||||||
events.processEvent(Events.USER_DELETED, properties)
|
events.processEvent(Events.USER_DELETED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.passwordForceReset = user => {
|
|
||||||
const properties = {}
|
|
||||||
events.processEvent(Events.USER_PASSWORD_FORCE_RESET, properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PERMISSIONS
|
// PERMISSIONS
|
||||||
|
|
||||||
exports.permissionAdminAssigned = user => {
|
exports.permissionAdminAssigned = user => {
|
||||||
|
@ -48,39 +42,33 @@ exports.permissionBuilderRemoved = user => {
|
||||||
|
|
||||||
// INVITE
|
// INVITE
|
||||||
|
|
||||||
// TODO
|
exports.invited = userInfo => {
|
||||||
exports.invited = user => {
|
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_INVITED, properties)
|
events.processEvent(Events.USER_INVITED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.inviteAccepted = user => {
|
exports.inviteAccepted = user => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_INVITED_ACCEPTED, properties)
|
events.processEvent(Events.USER_INVITED_ACCEPTED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SELF
|
// PASSWORD
|
||||||
|
|
||||||
// TODO
|
exports.passwordForceReset = user => {
|
||||||
exports.selfUpdated = user => {
|
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_SELF_UPDATED, properties)
|
events.processEvent(Events.USER_PASSWORD_FORCE_RESET, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
exports.passwordUpdated = user => {
|
||||||
exports.selfPasswordUpdated = user => {
|
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_SELF_PASSWORD_UPDATED, properties)
|
events.processEvent(Events.USER_PASSWORD_UPDATED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.passwordResetRequested = user => {
|
exports.passwordResetRequested = user => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_PASSWORD_RESET_REQUESTED, properties)
|
events.processEvent(Events.USER_PASSWORD_RESET_REQUESTED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.passwordReset = user => {
|
exports.passwordReset = user => {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
events.processEvent(Events.USER_PASSWORD_RESET, properties)
|
events.processEvent(Events.USER_PASSWORD_RESET, properties)
|
||||||
|
|
|
@ -80,15 +80,14 @@ jest.mock("../../../events", () => {
|
||||||
created: jest.fn(),
|
created: jest.fn(),
|
||||||
updated: jest.fn(),
|
updated: jest.fn(),
|
||||||
deleted: jest.fn(),
|
deleted: jest.fn(),
|
||||||
passwordForceReset: jest.fn(),
|
|
||||||
permissionAdminAssigned: jest.fn(),
|
permissionAdminAssigned: jest.fn(),
|
||||||
permissionAdminRemoved: jest.fn(),
|
permissionAdminRemoved: jest.fn(),
|
||||||
permissionBuilderAssigned: jest.fn(),
|
permissionBuilderAssigned: jest.fn(),
|
||||||
permissionBuilderRemoved: jest.fn(),
|
permissionBuilderRemoved: jest.fn(),
|
||||||
invited: jest.fn(),
|
invited: jest.fn(),
|
||||||
inviteAccepted: jest.fn(),
|
inviteAccepted: jest.fn(),
|
||||||
selfUpdated: jest.fn(),
|
passwordForceReset: jest.fn(),
|
||||||
selfPasswordUpdated: jest.fn(),
|
passwordUpdated: jest.fn(),
|
||||||
passwordResetRequested: jest.fn(),
|
passwordResetRequested: jest.fn(),
|
||||||
passwordReset: jest.fn(),
|
passwordReset: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,20 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryGlobalView(ViewNames.USER_BY_EMAIL, {
|
const response = await queryGlobalView(ViewNames.USER_BY_EMAIL, {
|
||||||
key: email.toLowerCase(),
|
key: email.toLowerCase(),
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
for (let user of response) {
|
||||||
|
delete user.password
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete response.password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,7 @@ export const reset = async (ctx: any) => {
|
||||||
user,
|
user,
|
||||||
subject: "{{ company }} platform password reset",
|
subject: "{{ company }} platform password reset",
|
||||||
})
|
})
|
||||||
|
events.user.passwordResetRequested(user)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
@ -136,6 +137,9 @@ export const resetUpdate = async (ctx: any) => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "password reset successfully.",
|
message: "password reset successfully.",
|
||||||
}
|
}
|
||||||
|
// remove password from the user before sending events
|
||||||
|
delete user.password
|
||||||
|
events.user.passwordReset(user)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, "Cannot reset password.")
|
ctx.throw(400, "Cannot reset password.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ const { encrypt } = require("@budibase/backend-core/encryption")
|
||||||
const { newid } = require("@budibase/backend-core/utils")
|
const { newid } = require("@budibase/backend-core/utils")
|
||||||
const { users } = require("../../../sdk")
|
const { users } = require("../../../sdk")
|
||||||
const { Cookies } = require("@budibase/backend-core/constants")
|
const { Cookies } = require("@budibase/backend-core/constants")
|
||||||
|
const { events } = require("@budibase/backend-core")
|
||||||
|
|
||||||
function newApiKey() {
|
function newApiKey() {
|
||||||
return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
|
return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
|
||||||
|
@ -110,8 +111,10 @@ exports.getSelf = async ctx => {
|
||||||
exports.updateSelf = async ctx => {
|
exports.updateSelf = async ctx => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const user = await db.get(ctx.user._id)
|
const user = await db.get(ctx.user._id)
|
||||||
|
let passwordChange = false
|
||||||
if (ctx.request.body.password) {
|
if (ctx.request.body.password) {
|
||||||
// changing password
|
// changing password
|
||||||
|
passwordChange = true
|
||||||
ctx.request.body.password = await hash(ctx.request.body.password)
|
ctx.request.body.password = await hash(ctx.request.body.password)
|
||||||
// Log all other sessions out apart from the current one
|
// Log all other sessions out apart from the current one
|
||||||
await platformLogout({
|
await platformLogout({
|
||||||
|
@ -134,4 +137,11 @@ exports.updateSelf = async ctx => {
|
||||||
_id: response.id,
|
_id: response.id,
|
||||||
_rev: response.rev,
|
_rev: response.rev,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove the old password from the user before sending events
|
||||||
|
delete user.password
|
||||||
|
events.user.updated(user)
|
||||||
|
if (passwordChange) {
|
||||||
|
events.user.passwordUpdated(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ const {
|
||||||
users: usersCore,
|
users: usersCore,
|
||||||
tenancy,
|
tenancy,
|
||||||
db: dbUtils,
|
db: dbUtils,
|
||||||
|
events,
|
||||||
} = require("@budibase/backend-core")
|
} = require("@budibase/backend-core")
|
||||||
|
|
||||||
export const save = async (ctx: any) => {
|
export const save = async (ctx: any) => {
|
||||||
|
@ -121,6 +122,7 @@ export const invite = async (ctx: any) => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Invitation has been sent.",
|
message: "Invitation has been sent.",
|
||||||
}
|
}
|
||||||
|
events.user.invited(userInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inviteAccept = async (ctx: any) => {
|
export const inviteAccept = async (ctx: any) => {
|
||||||
|
@ -128,14 +130,16 @@ export const inviteAccept = async (ctx: any) => {
|
||||||
try {
|
try {
|
||||||
// info is an extension of the user object that was stored by global
|
// info is an extension of the user object that was stored by global
|
||||||
const { email, info }: any = await checkInviteCode(inviteCode)
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||||
ctx.body = await tenancy.doInTenant(info.tenantId, () => {
|
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
||||||
return users.save({
|
const user = await users.save({
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
...info,
|
...info,
|
||||||
})
|
})
|
||||||
|
events.user.inviteAccepted(user)
|
||||||
|
return user
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||||
|
|
|
@ -6,7 +6,6 @@ const { events } = require("@budibase/backend-core")
|
||||||
const TENANT_ID = structures.TENANT_ID
|
const TENANT_ID = structures.TENANT_ID
|
||||||
|
|
||||||
describe("/api/global/auth", () => {
|
describe("/api/global/auth", () => {
|
||||||
let code
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -20,16 +19,7 @@ describe("/api/global/auth", () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should logout", async () => {
|
const requestPasswordReset = async () => {
|
||||||
await request
|
|
||||||
.post("/api/global/auth/logout")
|
|
||||||
.set(config.defaultHeaders())
|
|
||||||
.expect(200)
|
|
||||||
expect(events.auth.logout.mock.calls.length).toBe(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to generate password reset email", async () => {
|
|
||||||
// initially configure settings
|
|
||||||
await config.saveSmtpConfig()
|
await config.saveSmtpConfig()
|
||||||
await config.saveSettingsConfig()
|
await config.saveSettingsConfig()
|
||||||
await config.createUser()
|
await config.createUser()
|
||||||
|
@ -40,16 +30,36 @@ describe("/api/global/auth", () => {
|
||||||
})
|
})
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
const emailCall = sendMailMock.mock.calls[0][0]
|
||||||
|
const parts = emailCall.html.split(`http://localhost:10000/builder/auth/reset?code=`)
|
||||||
|
const code = parts[1].split("\"")[0].split("&")[0]
|
||||||
|
return { code, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should logout", async () => {
|
||||||
|
await request
|
||||||
|
.post("/api/global/auth/logout")
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect(200)
|
||||||
|
expect(events.auth.logout.mock.calls.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to generate password reset email", async () => {
|
||||||
|
const { res, code } = await requestPasswordReset()
|
||||||
|
const user = await config.getUser("test@test.com")
|
||||||
|
|
||||||
expect(res.body).toEqual({ message: "Please check your email for a reset link." })
|
expect(res.body).toEqual({ message: "Please check your email for a reset link." })
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
expect(sendMailMock).toHaveBeenCalled()
|
||||||
const emailCall = sendMailMock.mock.calls[0][0]
|
|
||||||
// after this URL there should be a code
|
|
||||||
const parts = emailCall.html.split(`http://localhost:10000/builder/auth/reset?code=`)
|
|
||||||
code = parts[1].split("\"")[0].split("&")[0]
|
|
||||||
expect(code).toBeDefined()
|
expect(code).toBeDefined()
|
||||||
|
expect(events.user.passwordResetRequested).toBeCalledTimes(1)
|
||||||
|
expect(events.user.passwordResetRequested).toBeCalledWith(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow resetting user password with code", async () => {
|
it("should allow resetting user password with code", async () => {
|
||||||
|
const { code } = await requestPasswordReset()
|
||||||
|
const user = await config.getUser("test@test.com")
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/global/auth/${TENANT_ID}/reset/update`)
|
.post(`/api/global/auth/${TENANT_ID}/reset/update`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -59,6 +69,8 @@ describe("/api/global/auth", () => {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body).toEqual({ message: "password reset successfully." })
|
expect(res.body).toEqual({ message: "password reset successfully." })
|
||||||
|
expect(events.user.passwordReset).toBeCalledTimes(1)
|
||||||
|
expect(events.user.passwordReset).toBeCalledWith(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("oidc", () => {
|
describe("oidc", () => {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
jest.mock("nodemailer")
|
||||||
|
const { config, request } = require("../../../tests")
|
||||||
|
const { events, utils } = require("@budibase/backend-core")
|
||||||
|
|
||||||
|
describe("/api/global/self", () => {
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await config.beforeAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await config.afterAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateSelf = async (user) => {
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/global/self`)
|
||||||
|
.send(user)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
|
||||||
|
it("should update self", async () => {
|
||||||
|
const user = await config.createUser()
|
||||||
|
|
||||||
|
const res = await updateSelf(user)
|
||||||
|
|
||||||
|
delete user.password
|
||||||
|
expect(res.body._id).toBe(user._id)
|
||||||
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
expect(events.user.updated).toBeCalledWith(user)
|
||||||
|
expect(events.user.passwordUpdated).not.toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update password", async () => {
|
||||||
|
const user = await config.createUser()
|
||||||
|
const password = "newPassword"
|
||||||
|
user.password = password
|
||||||
|
|
||||||
|
const res = await updateSelf(user)
|
||||||
|
|
||||||
|
delete user.password
|
||||||
|
expect(res.body._id).toBe(user._id)
|
||||||
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
expect(events.user.updated).toBeCalledWith(user)
|
||||||
|
expect(events.user.passwordUpdated).toBeCalledTimes(1)
|
||||||
|
expect(events.user.passwordUpdated).toBeCalledWith(user)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -4,7 +4,6 @@ const sendMailMock = mocks.email.mock()
|
||||||
const { events } = require("@budibase/backend-core")
|
const { events } = require("@budibase/backend-core")
|
||||||
|
|
||||||
describe("/api/global/users", () => {
|
describe("/api/global/users", () => {
|
||||||
let code
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -14,8 +13,7 @@ describe("/api/global/users", () => {
|
||||||
await config.afterAll()
|
await config.afterAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to generate an invitation", async () => {
|
const sendUserInvite = async () => {
|
||||||
// initially configure settings
|
|
||||||
await config.saveSmtpConfig()
|
await config.saveSmtpConfig()
|
||||||
await config.saveSettingsConfig()
|
await config.saveSettingsConfig()
|
||||||
const res = await request
|
const res = await request
|
||||||
|
@ -26,16 +24,27 @@ describe("/api/global/users", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
|
||||||
const emailCall = sendMailMock.mock.calls[0][0]
|
const emailCall = sendMailMock.mock.calls[0][0]
|
||||||
// after this URL there should be a code
|
// after this URL there should be a code
|
||||||
const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=")
|
const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=")
|
||||||
code = parts[1].split("\"")[0].split("&")[0]
|
const code = parts[1].split("\"")[0].split("&")[0]
|
||||||
|
return { code, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should be able to generate an invitation", async () => {
|
||||||
|
const { code, res } = await sendUserInvite()
|
||||||
|
|
||||||
|
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
||||||
|
expect(sendMailMock).toHaveBeenCalled()
|
||||||
expect(code).toBeDefined()
|
expect(code).toBeDefined()
|
||||||
|
expect(events.user.invited).toBeCalledTimes(1)
|
||||||
|
expect(events.user.invited).toBeCalledWith({ tenantId: structures.TENANT_ID })
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to create new user from invite", async () => {
|
it("should be able to create new user from invite", async () => {
|
||||||
|
const { code } = await sendUserInvite()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/global/users/invite/accept`)
|
.post(`/api/global/users/invite/accept`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -48,6 +57,8 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.getUser("invite@test.com")
|
const user = await config.getUser("invite@test.com")
|
||||||
expect(user).toBeDefined()
|
expect(user).toBeDefined()
|
||||||
expect(user._id).toEqual(res.body._id)
|
expect(user._id).toEqual(res.body._id)
|
||||||
|
expect(events.user.inviteAccepted).toBeCalledTimes(1)
|
||||||
|
expect(events.user.inviteAccepted).toBeCalledWith(res.body)
|
||||||
})
|
})
|
||||||
|
|
||||||
const createUser = async (user) => {
|
const createUser = async (user) => {
|
||||||
|
@ -70,7 +81,7 @@ describe("/api/global/users", () => {
|
||||||
.send(user)
|
.send(user)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
// .expect(200)
|
.expect(200)
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +160,23 @@ describe("/api/global/users", () => {
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
||||||
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
||||||
|
expect(events.user.passwordForceReset).not.toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should be able to force reset password", async () => {
|
||||||
|
let user = structures.users.user({ email: "basic-password-update@test.com" })
|
||||||
|
await createUser(user)
|
||||||
|
jest.clearAllMocks()
|
||||||
|
|
||||||
|
user.forceResetPassword = true
|
||||||
|
user.password = "tempPassword"
|
||||||
|
await updateUser(user)
|
||||||
|
|
||||||
|
expect(events.user.created).not.toBeCalled()
|
||||||
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
|
||||||
|
expect(events.user.permissionAdminAssigned).not.toBeCalled()
|
||||||
|
expect(events.user.passwordForceReset).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to update a basic user to an admin user", async () => {
|
it("should be able to update a basic user to an admin user", async () => {
|
||||||
|
|
|
@ -52,6 +52,14 @@ export const handleSaveEvents = (user: any, existingUser: any) => {
|
||||||
if (isRemovingAdmin(user, existingUser)) {
|
if (isRemovingAdmin(user, existingUser)) {
|
||||||
events.user.permissionAdminRemoved(user)
|
events.user.permissionAdminRemoved(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!existingUser.forceResetPassword &&
|
||||||
|
user.forceResetPassword &&
|
||||||
|
user.password
|
||||||
|
) {
|
||||||
|
events.user.passwordForceReset(user)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
events.user.created(user)
|
events.user.created(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import * as apps from "../../utilities/appService"
|
import * as apps from "../../utilities/appService"
|
||||||
const { events } = require("@budibase/backend-core")
|
|
||||||
import * as eventHelpers from "./events"
|
import * as eventHelpers from "./events"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -125,7 +124,7 @@ export const save = async (
|
||||||
const putUserFn = () => {
|
const putUserFn = () => {
|
||||||
return db.put(user)
|
return db.put(user)
|
||||||
}
|
}
|
||||||
if (await eventHelpers.isAddingBuilder(user, dbUser)) {
|
if (eventHelpers.isAddingBuilder(user, dbUser)) {
|
||||||
response = await quotas.addDeveloper(putUserFn)
|
response = await quotas.addDeveloper(putUserFn)
|
||||||
} else {
|
} else {
|
||||||
response = await putUserFn()
|
response = await putUserFn()
|
||||||
|
|
Loading…
Reference in New Issue