Merge pull request #9914 from Budibase/fix/user-updateSelf-behaviour
Various fixes for update self behaviour
This commit is contained in:
commit
b1336ecd65
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
Loading…
Reference in New Issue