mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 01:25:00 +02:00
Fix password bugs (#2235)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Vendored
+15
@@ -24,6 +24,21 @@
|
||||
"cwd": "${workspaceRoot}/server/server",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"name": "Debug Account",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/index.ts"],
|
||||
"env": {
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
"SERVER_SECRET": "secret",
|
||||
"TRANSACTOR_URL": "ws:/localhost:3333"
|
||||
},
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceRoot}/pods/account",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
|
||||
+8
-2
@@ -1,6 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.30 (upcoming)
|
||||
## 0.6.31 (upcoming)
|
||||
|
||||
Core:
|
||||
|
||||
- Fix password change settings
|
||||
- Fix settings collapse
|
||||
|
||||
## 0.6.30
|
||||
|
||||
Core:
|
||||
|
||||
@@ -38,7 +45,6 @@ Tracker:
|
||||
- Roadmap
|
||||
- Context menus (Priority/Status/Assignee)
|
||||
|
||||
|
||||
Chunter:
|
||||
|
||||
- Reactions on messages
|
||||
|
||||
+14
-1
@@ -23,9 +23,10 @@ import {
|
||||
dropWorkspace,
|
||||
getAccount,
|
||||
getWorkspace,
|
||||
setRole,
|
||||
listAccounts,
|
||||
listWorkspaces,
|
||||
replacePassword,
|
||||
setRole,
|
||||
upgradeWorkspace
|
||||
} from '@anticrm/account'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
@@ -94,6 +95,18 @@ program
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('reset-account <email>')
|
||||
.description('create user and corresponding account in master database')
|
||||
.option('-p, --password <password>', 'new user password')
|
||||
.action(async (email: string, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
console.log(`update account ${email} ${cmd.first as string} ${cmd.last as string}...`)
|
||||
await replacePassword(db, email, cmd.password)
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('assign-workspace <email> <workspace>')
|
||||
.description('assign workspace')
|
||||
|
||||
@@ -121,8 +121,9 @@
|
||||
{/if}
|
||||
{#if format === 'password'}
|
||||
<input
|
||||
id="userPassword"
|
||||
bind:this={input}
|
||||
type="passsword"
|
||||
type="Password"
|
||||
bind:value
|
||||
placeholder={phTraslate}
|
||||
{style}
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import login from '@anticrm/login'
|
||||
import {
|
||||
getMetadata,
|
||||
OK,
|
||||
PlatformError,
|
||||
Request,
|
||||
Response,
|
||||
getMetadata,
|
||||
OK,
|
||||
serialize,
|
||||
Status,
|
||||
unknownError,
|
||||
unknownStatus
|
||||
} from '@anticrm/platform'
|
||||
import login from '@anticrm/login'
|
||||
import { fetchMetadataLocalStorage, getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
|
||||
export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
@@ -437,7 +437,7 @@ export async function changePassword (oldPassword: string, password: string): Pr
|
||||
params: [oldPassword, password]
|
||||
}
|
||||
|
||||
await fetch(accountsUrl, {
|
||||
const response = await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
@@ -445,6 +445,10 @@ export async function changePassword (oldPassword: string, password: string): Pr
|
||||
},
|
||||
body: serialize(request)
|
||||
})
|
||||
const resp = await response.json()
|
||||
if (resp.error !== undefined) {
|
||||
throw new PlatformError(resp.error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function leaveWorkspace (email: string): Promise<void> {
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"User": "User",
|
||||
"Maintainer": "Maintainer",
|
||||
"Owner": "Owner",
|
||||
"Role": "Role"
|
||||
"Role": "Role",
|
||||
"FailedToSave": "Failed to update password"
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@
|
||||
"User": "Пользователь",
|
||||
"Maintainer": "Maintainer",
|
||||
"Owner": "Владелец",
|
||||
"Role": "Роль"
|
||||
"Role": "Роль",
|
||||
"FailedToSave": "Не удалось обновить пароль"
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,15 @@
|
||||
import presentation from '@anticrm/presentation'
|
||||
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
||||
import { changePassword } from '@anticrm/login-resources'
|
||||
import Error from './icons/Error.svelte'
|
||||
import plugin from '../plugin'
|
||||
|
||||
let oldPassword: string = ''
|
||||
let password: string = ''
|
||||
let password2: string = ''
|
||||
let label = presentation.string.Save
|
||||
let saved = false
|
||||
let error = false
|
||||
|
||||
$: disabled =
|
||||
password.length === 0 || oldPassword.length === 0 || oldPassword === password || password !== password2 || saved
|
||||
@@ -37,8 +40,16 @@
|
||||
console.log(e)
|
||||
label = presentation.string.Save
|
||||
saved = false
|
||||
error = true
|
||||
}
|
||||
}
|
||||
|
||||
function updateSaved (p1: string, p2: string, p3: string): void {
|
||||
saved = false
|
||||
label = presentation.string.Save
|
||||
error = false
|
||||
}
|
||||
$: updateSaved(oldPassword, password, password2)
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
@@ -48,6 +59,12 @@
|
||||
</div>
|
||||
<div class="flex-row-stretch flex-grow p-10">
|
||||
<div class="flex-grow flex-col">
|
||||
{#if error}
|
||||
<div class="flex-row-center gap-2">
|
||||
<Icon icon={Error} size={'medium'} />
|
||||
<Label label={plugin.string.FailedToSave} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-grow flex-col">
|
||||
<div>
|
||||
<EditBox
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
import { EmployeeAccount } from '@anticrm/contact'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
|
||||
export let visibileNav = true
|
||||
let category: SettingsCategory | undefined
|
||||
let categoryId: string = ''
|
||||
|
||||
@@ -86,44 +87,54 @@
|
||||
</script>
|
||||
|
||||
<div class="flex h-full">
|
||||
<div class="antiPanel-navigator filled indent">
|
||||
<div class="antiNav-header">
|
||||
<span class="fs-title overflow-label">
|
||||
<Label label={setting.string.Settings} />
|
||||
</span>
|
||||
{#if visibileNav}
|
||||
<div class="antiPanel-navigator filled indent">
|
||||
<div class="antiNav-header">
|
||||
<span class="fs-title overflow-label">
|
||||
<Label label={setting.string.Settings} />
|
||||
</span>
|
||||
</div>
|
||||
{#each categories as category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== category.group}
|
||||
<div class="antiNav-divider short line" />
|
||||
{/if}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
expandable={category._id === setting.ids.Setting}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="signout">
|
||||
<CategoryElement icon={setting.icon.Signout} label={setting.string.Signout} on:click={signOut} />
|
||||
<CategoryElement
|
||||
icon={login.icon.InviteWorkspace}
|
||||
label={setting.string.InviteWorkspace}
|
||||
on:click={inviteWorkspace}
|
||||
/>
|
||||
<CategoryElement
|
||||
icon={setting.icon.SelectWorkspace}
|
||||
label={setting.string.SelectWorkspace}
|
||||
on:click={selectWorkspace}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#each categories as category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== category.group}
|
||||
<div class="antiNav-divider short line" />
|
||||
{/if}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
expandable={category._id === setting.ids.Setting}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="signout">
|
||||
<CategoryElement icon={setting.icon.Signout} label={setting.string.Signout} on:click={signOut} />
|
||||
<CategoryElement
|
||||
icon={login.icon.InviteWorkspace}
|
||||
label={setting.string.InviteWorkspace}
|
||||
on:click={inviteWorkspace}
|
||||
/>
|
||||
<CategoryElement
|
||||
icon={setting.icon.SelectWorkspace}
|
||||
label={setting.string.SelectWorkspace}
|
||||
on:click={selectWorkspace}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="antiPanel-component border-left filled">
|
||||
{#if category}
|
||||
<Component is={category.component} />
|
||||
<Component
|
||||
is={category.component}
|
||||
props={{
|
||||
visibileNav,
|
||||
onFirstCategory: () => {
|
||||
selectCategory(categories[0].name)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { AccountRole, getCurrentAccount } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import setting, { SettingsCategory } from '@anticrm/setting'
|
||||
import { Component, getCurrentLocation, Label, location, navigate } from '@anticrm/ui'
|
||||
import ui, { Button, Component, getCurrentLocation, IconBack, Label, location, navigate } from '@anticrm/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import CategoryElement from './CategoryElement.svelte'
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
let categories: SettingsCategory[] = []
|
||||
const account = getCurrentAccount() as EmployeeAccount
|
||||
|
||||
export let visibileNav = true
|
||||
export let onFirstCategory: () => void
|
||||
|
||||
const settingsQuery = createQuery()
|
||||
settingsQuery.query(
|
||||
setting.class.WorkspaceSettingCategory,
|
||||
@@ -58,23 +61,34 @@
|
||||
</script>
|
||||
|
||||
<div class="flex h-full">
|
||||
<div class="antiPanel-navigator filled indent">
|
||||
<div class="antiNav-header">
|
||||
<span class="fs-title overflow-label">
|
||||
<Label label={setting.string.WorkspaceSetting} />
|
||||
</span>
|
||||
{#if visibileNav}
|
||||
<div class="antiPanel-navigator filled indent">
|
||||
<div class="antiNav-header">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Button
|
||||
kind={'link'}
|
||||
icon={IconBack}
|
||||
label={ui.string.Back}
|
||||
on:click={() => onFirstCategory()}
|
||||
size={'small'}
|
||||
/>
|
||||
<span class="fs-title overflow-label">
|
||||
<Label label={setting.string.WorkspaceSetting} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="antiPanel-component border-left filled">
|
||||
{#if category}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="7.275" stroke="#EE7A7A" stroke-width="1.2" />
|
||||
<path
|
||||
d="M9.51371 11.6902L9.51636 11.7875H9.61367H10.4137H10.511L10.5136 11.6902L10.6886 5.27772L10.6914 5.17499H10.5887H9.43867H9.33591L9.33871 5.27772L9.51371 11.6902ZM10.0012 14.375C10.4929 14.375 10.9012 13.9812 10.9012 13.475C10.9012 12.9687 10.4929 12.575 10.0012 12.575C9.50947 12.575 9.10117 12.9687 9.10117 13.475C9.10117 13.9812 9.50947 14.375 10.0012 14.375Z"
|
||||
fill="#EE7A7A"
|
||||
stroke="#EE7A7A"
|
||||
stroke-width="0.2"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 623 B |
@@ -47,6 +47,7 @@ export default mergeIds(settingId, setting, {
|
||||
User: '' as IntlString,
|
||||
Maintainer: '' as IntlString,
|
||||
Owner: '' as IntlString,
|
||||
Role: '' as IntlString
|
||||
Role: '' as IntlString,
|
||||
FailedToSave: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
let templates: MessageTemplate[] = []
|
||||
let selected: Ref<MessageTemplate> | undefined
|
||||
let newTemplate: Data<MessageTemplate> | undefined = undefined
|
||||
let loading = true
|
||||
|
||||
query.query(templatesPlugin.class.MessageTemplate, {}, (t) => {
|
||||
templates = t
|
||||
@@ -19,6 +20,7 @@
|
||||
selected = undefined
|
||||
newTemplate = undefined
|
||||
}
|
||||
loading = false
|
||||
})
|
||||
|
||||
const Mode = {
|
||||
@@ -79,7 +81,9 @@
|
||||
<div class="ac-column">
|
||||
<div id="create-template" class="flex-between trans-title mb-3">
|
||||
<Label label={templatesPlugin.string.TemplatesHeader} />
|
||||
<CircleButton icon={IconAdd} on:click={addTemplate} />
|
||||
{#if !loading}
|
||||
<CircleButton icon={IconAdd} on:click={addTemplate} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex-col overflow-y-auto">
|
||||
|
||||
@@ -488,14 +488,14 @@
|
||||
}}
|
||||
>
|
||||
{#if currentApplication && currentApplication.component}
|
||||
<Component is={currentApplication.component} props={{ currentSpace }} />
|
||||
<Component is={currentApplication.component} props={{ currentSpace, visibileNav }} />
|
||||
{:else if specialComponent}
|
||||
<Component
|
||||
is={specialComponent.component}
|
||||
props={{ model: navigatorModel, ...specialComponent.componentProps, currentSpace }}
|
||||
props={{ model: navigatorModel, ...specialComponent.componentProps, currentSpace, visibileNav }}
|
||||
/>
|
||||
{:else if currentView?.component !== undefined}
|
||||
<Component is={currentView.component} props={{ ...currentView.componentProps, currentView }} />
|
||||
<Component is={currentView.component} props={{ ...currentView.componentProps, currentView, visibileNav }} />
|
||||
{:else}
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} />
|
||||
{/if}
|
||||
|
||||
@@ -543,6 +543,21 @@ export async function changePassword (db: Db, token: string, oldPassword: string
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function replacePassword (db: Db, email: string, password: string): Promise<void> {
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.InvalidPassword, { account: email }))
|
||||
}
|
||||
const salt = randomBytes(32)
|
||||
const hash = hashWithSalt(password, salt)
|
||||
|
||||
await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: account._id }, { $set: { salt, hash } })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user