Merge pull request #9914 from Budibase/fix/user-updateSelf-behaviour

Various fixes for update self behaviour
This commit is contained in:
Michael Drury 2023-03-07 17:15:08 +00:00 committed by GitHub
commit b1336ecd65
12 changed files with 68 additions and 63 deletions

View File

@ -1,8 +1,9 @@
import { get } from "svelte/store" import { get } from "svelte/store"
import { store } from "builderStore" import { store } from "builderStore"
import { users, auth } from "stores/portal" import { auth } from "stores/portal"
import analytics from "analytics" import analytics from "analytics"
import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps" import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps"
import { API } from "api"
const ONBOARDING_EVENT_PREFIX = "onboarding" const ONBOARDING_EVENT_PREFIX = "onboarding"
export const TOUR_STEP_KEYS = { export const TOUR_STEP_KEYS = {
@ -83,8 +84,7 @@ const getTours = () => {
// Mark the users onboarding as complete // Mark the users onboarding as complete
// Clear all tour related state // Clear all tour related state
if (get(auth).user) { if (get(auth).user) {
await users.save({ await API.updateSelf({
...get(auth).user,
onboardedAt: new Date().toISOString(), onboardedAt: new Date().toISOString(),
}) })
@ -114,8 +114,7 @@ const getTours = () => {
onComplete: async () => { onComplete: async () => {
// Push the onboarding forward // Push the onboarding forward
if (get(auth).user) { if (get(auth).user) {
await users.save({ await API.updateSelf({
...get(auth).user,
onboardedAt: new Date().toISOString(), onboardedAt: new Date().toISOString(),
}) })

View File

@ -13,6 +13,7 @@
await auth.updateSelf($values) await auth.updateSelf($values)
notifications.success("Information updated successfully") notifications.success("Information updated successfully")
} catch (error) { } catch (error) {
console.error(error)
notifications.error("Failed to update information") notifications.error("Failed to update information")
} }
} }

View File

@ -154,9 +154,14 @@ export function createAuthStore() {
await setInitInfo({}) await setInitInfo({})
}, },
updateSelf: async fields => { updateSelf: async fields => {
const newUser = { ...get(auth).user, ...fields } await API.updateSelf({ ...fields })
await API.updateSelf(newUser) // Refetch to enrich after update.
setUser(newUser) try {
const user = await API.fetchBuilderSelf()
setUser(user)
} catch (error) {
setUser(null)
}
}, },
forgotPassword: async email => { forgotPassword: async email => {
const tenantId = get(store).tenantId const tenantId = get(store).tenantId

View File

@ -17,6 +17,7 @@ export interface UpdateSelfRequest {
lastName?: string lastName?: string
password?: string password?: string
forceResetPassword?: boolean forceResetPassword?: boolean
onboardedAt?: string
} }
export interface UpdateSelfResponse { export interface UpdateSelfResponse {

View File

@ -1,10 +1,3 @@
export interface UpdateSelf {
firstName?: string
lastName?: string
password?: string
forceResetPassword?: boolean
}
export interface SaveUserOpts { export interface SaveUserOpts {
hashPassword?: boolean hashPassword?: boolean
requirePassword?: boolean requirePassword?: boolean

View File

@ -10,12 +10,7 @@ import {
} from "@budibase/backend-core" } from "@budibase/backend-core"
import env from "../../../environment" import env from "../../../environment"
import { groups } from "@budibase/pro" import { groups } from "@budibase/pro"
import { import { UpdateSelfRequest, UpdateSelfResponse, UserCtx } from "@budibase/types"
UpdateSelfRequest,
UpdateSelfResponse,
UpdateSelf,
UserCtx,
} from "@budibase/types"
const { getCookie, clearCookie, newid } = utils const { getCookie, clearCookie, newid } = utils
function newTestApiKey() { function newTestApiKey() {
@ -122,15 +117,14 @@ export async function getSelf(ctx: any) {
export async function updateSelf( export async function updateSelf(
ctx: UserCtx<UpdateSelfRequest, UpdateSelfResponse> ctx: UserCtx<UpdateSelfRequest, UpdateSelfResponse>
) { ) {
const body = ctx.request.body const update = ctx.request.body
const update: UpdateSelf = {
firstName: body.firstName,
lastName: body.lastName,
password: body.password,
forceResetPassword: body.forceResetPassword,
}
const user = await userSdk.updateSelf(ctx.user._id!, update) let user = await userSdk.getUser(ctx.user._id!)
user = {
...user,
...update,
}
user = await userSdk.save(user, { requirePassword: false })
if (update.password) { if (update.password) {
// Log all other sessions out apart from the current one // Log all other sessions out apart from the current one

View File

@ -11,7 +11,7 @@ router
.get("/api/global/self", controller.getSelf) .get("/api/global/self", controller.getSelf)
.post( .post(
"/api/global/self", "/api/global/self",
users.buildUserSaveValidation(true), users.buildSelfSaveValidation(),
controller.updateSelf controller.updateSelf
) )

View File

@ -18,30 +18,26 @@ describe("/api/global/self", () => {
}) })
describe("update", () => { describe("update", () => {
it("should update self", async () => { it("should reject updates with forbidden keys", async () => {
const user = await config.createUser() const user = await config.createUser()
await config.createSession(user) await config.createSession(user)
delete user.password delete user.password
const res = await config.api.self.updateSelf(user)
const dbUser = await config.getUser(user.email) await config.api.self.updateSelf(user, user).expect(400)
user._rev = dbUser._rev
user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
expect(res.body._id).toBe(user._id)
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.updated).toBeCalledWith(dbUser)
expect(events.user.passwordUpdated).not.toBeCalled()
}) })
it("should update password", async () => { it("should update password", async () => {
const user = await config.createUser() const user = await config.createUser()
await config.createSession(user) await config.createSession(user)
user.password = "newPassword" const res = await config.api.self
const res = await config.api.self.updateSelf(user) .updateSelf(user, {
password: "newPassword",
})
.expect(200)
const dbUser = await config.getUser(user.email) const dbUser = await config.getUser(user.email)
user._rev = dbUser._rev user._rev = dbUser._rev
user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString() user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
expect(res.body._id).toBe(user._id) expect(res.body._id).toBe(user._id)
@ -51,4 +47,22 @@ describe("/api/global/self", () => {
expect(events.user.passwordUpdated).toBeCalledWith(dbUser) expect(events.user.passwordUpdated).toBeCalledWith(dbUser)
}) })
}) })
it("should update onboarding", async () => {
const user = await config.createUser()
await config.createSession(user)
const res = await config.api.self
.updateSelf(user, {
onboardedAt: "2023-03-07T14:10:54.869Z",
})
.expect(200)
const dbUser = await config.getUser(user.email)
user._rev = dbUser._rev
user.dayPassRecordedAt = mocks.date.MOCK_DATE.toISOString()
expect(dbUser.onboardedAt).toBe("2023-03-07T14:10:54.869Z")
expect(res.body._id).toBe(user._id)
})
}) })

View File

@ -128,7 +128,7 @@ router
.get("/api/global/users/self", selfController.getSelf) .get("/api/global/users/self", selfController.getSelf)
.post( .post(
"/api/global/users/self", "/api/global/users/self",
users.buildUserSaveValidation(true), users.buildUserSaveValidation(),
selfController.updateSelf selfController.updateSelf
) )

View File

@ -17,13 +17,22 @@ let schema: any = {
roles: Joi.object().pattern(/.*/, Joi.string()).required().unknown(true), roles: Joi.object().pattern(/.*/, Joi.string()).required().unknown(true),
} }
export const buildUserSaveValidation = (isSelf = false) => { export const buildSelfSaveValidation = () => {
if (!isSelf) { schema = {
schema = { password: Joi.string().optional(),
...schema, forceResetPassword: Joi.boolean().optional(),
_id: Joi.string(), firstName: Joi.string().allow("").optional(),
_rev: Joi.string(), lastName: Joi.string().allow("").optional(),
} onboardedAt: Joi.string().optional(),
}
return auth.joiValidator.body(Joi.object(schema).required().unknown(false))
}
export const buildUserSaveValidation = () => {
schema = {
...schema,
_id: Joi.string(),
_rev: Joi.string(),
} }
return auth.joiValidator.body(Joi.object(schema).required().unknown(true)) return auth.joiValidator.body(Joi.object(schema).required().unknown(true))
} }

View File

@ -29,7 +29,6 @@ import {
PlatformUserByEmail, PlatformUserByEmail,
RowResponse, RowResponse,
SearchUsersRequest, SearchUsersRequest,
UpdateSelf,
User, User,
SaveUserOpts, SaveUserOpts,
} from "@budibase/types" } from "@budibase/types"
@ -227,15 +226,6 @@ export async function isPreventPasswordActions(user: User) {
return !!(account && isSSOAccount(account)) return !!(account && isSSOAccount(account))
} }
export async function updateSelf(id: string, data: UpdateSelf) {
let user = await getUser(id)
user = {
...user,
...data,
}
return save(user)
}
export const save = async ( export const save = async (
user: User, user: User,
opts: SaveUserOpts = {} opts: SaveUserOpts = {}

View File

@ -7,13 +7,12 @@ export class SelfAPI extends TestAPI {
super(config) super(config)
} }
updateSelf = (user: User) => { updateSelf = (user: User, update: any) => {
return this.request return this.request
.post(`/api/global/self`) .post(`/api/global/self`)
.send(user) .send(update)
.set(this.config.authHeaders(user)) .set(this.config.authHeaders(user))
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200)
} }
getSelf = (user: User) => { getSelf = (user: User) => {