mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-24 21:32:24 +02:00
Move security section
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -217,7 +217,7 @@ export function createModel (builder: Builder): void {
|
||||
name: 'security',
|
||||
label: setting.string.Security,
|
||||
icon: setting.icon.Password,
|
||||
component: setting.component.TwoFactorSettings,
|
||||
component: setting.component.SecuritySettings,
|
||||
group: 'settings-account',
|
||||
role: AccountRole.Guest,
|
||||
order: 1200
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
"SpaceMembersOnly": "Pouze členové prostoru",
|
||||
"Reset": "Resetovat",
|
||||
"Security": "Zabezpečení",
|
||||
"SecurityTabSessions": "Historie relací",
|
||||
"TwoFactorAuth": "Dvoufaktorové ověřování",
|
||||
"TwoFactorAuthDescription": "Dvoufaktorové ověřování přidává další vrstvu zabezpečení k vašemu účtu",
|
||||
"EnableTwoFactorAuth": "Povolit dvoufaktorové ověřování",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "Nur Bereichsmitglieder",
|
||||
"Reset": "Zurücksetzen",
|
||||
"Security": "Sicherheit",
|
||||
"SecurityTabSessions": "Sitzungsverlauf",
|
||||
"TwoFactorAuth": "Zweistufige Authentifizierung",
|
||||
"TwoFactorAuthDescription": "Zweistufige Authentifizierung fügt eine zusätzliche Sicherheitsebene zu Ihrem Konto hinzu",
|
||||
"EnableTwoFactorAuth": "Zweistufige Authentifizierung aktivieren",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"ShowInTitle": "Show in title",
|
||||
"SpaceMembersOnly": "Space members only",
|
||||
"Security": "Security",
|
||||
"SecurityTabSessions": "Session history",
|
||||
"TwoFactorAuth": "Two-factor authentication",
|
||||
"TwoFactorAuthDescription": "Two-factor authentication adds an extra layer of security to your account",
|
||||
"EnableTwoFactorAuth": "Enable two-factor authentication",
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"SpaceMembersOnly": "Solo miembros del espacio",
|
||||
"Reset": "Reiniciar",
|
||||
"Security": "Seguridad",
|
||||
"SecurityTabSessions": "Historial de sesiones",
|
||||
"TwoFactorAuth": "Autenticación de dos factores",
|
||||
"TwoFactorAuthDescription": "La autenticación de dos factores añade una capa adicional de seguridad a tu cuenta",
|
||||
"EnableTwoFactorAuth": "Habilitar autenticación de dos factores",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "Membres de l'espace uniquement",
|
||||
"Reset": "Réinitialiser",
|
||||
"Security": "Sécurité",
|
||||
"SecurityTabSessions": "Historique des sessions",
|
||||
"TwoFactorAuth": "Authentification à deux facteurs",
|
||||
"TwoFactorAuthDescription": "L'authentification à deux facteurs ajoute une couche de sécurité supplémentaire à votre compte",
|
||||
"EnableTwoFactorAuth": "Activer l'authentification à deux facteurs",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "Solo membri dello spazio",
|
||||
"Reset": "Reset",
|
||||
"Security": "Sicurezza",
|
||||
"SecurityTabSessions": "Cronologia sessioni",
|
||||
"TwoFactorAuth": "Autenticazione a due fattori",
|
||||
"TwoFactorAuthDescription": "L'autenticazione a due fattori aggiunge un ulteriore livello di sicurezza al tuo account",
|
||||
"EnableTwoFactorAuth": "Abilita autenticazione a due fattori",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "スペースメンバーのみ",
|
||||
"Reset": "リセット",
|
||||
"Security": "セキュリティ",
|
||||
"SecurityTabSessions": "セッション履歴",
|
||||
"TwoFactorAuth": "二要素認証",
|
||||
"TwoFactorAuthDescription": "二要素認証はアカウントにセキュリティの追加レイヤーを追加します",
|
||||
"EnableTwoFactorAuth": "二要素認証を有効にする",
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"SpaceMembersOnly": "Apenas membros do espaço",
|
||||
"Reset": "Reiniciar",
|
||||
"Security": "Segurança",
|
||||
"SecurityTabSessions": "Histórico de sessões",
|
||||
"TwoFactorAuth": "Autenticação de dois fatores",
|
||||
"TwoFactorAuthDescription": "A autenticação de dois fatores adiciona uma camada extra de segurança à sua conta",
|
||||
"EnableTwoFactorAuth": "Ativar autenticação de dois fatores",
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"SpaceMembersOnly": "Apenas membros do espaço",
|
||||
"Reset": "Reiniciar",
|
||||
"Security": "Segurança",
|
||||
"SecurityTabSessions": "Histórico de sessões",
|
||||
"TwoFactorAuth": "Autenticação de dois fatores",
|
||||
"TwoFactorAuthDescription": "A autenticação de dois fatores adiciona uma camada extra de segurança à sua conta",
|
||||
"EnableTwoFactorAuth": "Ativar autenticação de dois fatores",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"ShowInTitle": "Показывать в заголовке",
|
||||
"SpaceMembersOnly": "Только участники пространства",
|
||||
"Security": "Безопасность",
|
||||
"SecurityTabSessions": "История сессий",
|
||||
"TwoFactorAuth": "Двухфакторная аутентификация",
|
||||
"TwoFactorAuthDescription": "Двухфакторная аутентификация добавляет дополнительный уровень безопасности к вашей учетной записи",
|
||||
"EnableTwoFactorAuth": "Включить двухфакторную аутентификацию",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "Yalnızca alan üyeleri",
|
||||
"Reset": "Sıfırla",
|
||||
"Security": "Güvenlik",
|
||||
"SecurityTabSessions": "Oturum geçmişi",
|
||||
"TwoFactorAuth": "İki faktörlü kimlik doğrulama",
|
||||
"TwoFactorAuthDescription": "İki faktörlü kimlik doğrulama hesabınıza ek bir güvenlik katmanı ekler",
|
||||
"EnableTwoFactorAuth": "İki faktörlü kimlik doğrulamayı etkinleştir",
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
"SpaceMembersOnly": "仅限空间成员",
|
||||
"Reset": "重置",
|
||||
"Security": "安全",
|
||||
"SecurityTabSessions": "会话历史",
|
||||
"TwoFactorAuth": "双因素认证",
|
||||
"TwoFactorAuthDescription": "双因素认证为您的帐户增加额外的安全层",
|
||||
"EnableTwoFactorAuth": "启用双因素认证",
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<!--
|
||||
// Copyright © 2026 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import setting from '@hcengineering/setting'
|
||||
import view from '@hcengineering/view'
|
||||
import {
|
||||
Breadcrumb,
|
||||
defineSeparators,
|
||||
Header,
|
||||
NavItem,
|
||||
Scroller,
|
||||
Separator,
|
||||
twoPanelsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
import settingsRes from '../plugin'
|
||||
import SessionHistorySettings from './SessionHistorySettings.svelte'
|
||||
import TwoFactorSettings from './TwoFactorSettings.svelte'
|
||||
|
||||
let securityTab: 'twoFactor' | 'sessions' = 'twoFactor'
|
||||
|
||||
defineSeparators('securitySettings', twoPanelsSeparators)
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={setting.icon.Password} label={setting.string.Security} size={'large'} isCurrent />
|
||||
</Header>
|
||||
<div class="hulyComponent-content__container columns">
|
||||
<div class="hulyComponent-content__column navigation py-2">
|
||||
<Scroller shrink>
|
||||
<NavItem
|
||||
icon={setting.icon.Password}
|
||||
label={setting.string.TwoFactorAuth}
|
||||
selected={securityTab === 'twoFactor'}
|
||||
on:click={() => {
|
||||
securityTab = 'twoFactor'
|
||||
}}
|
||||
/>
|
||||
<NavItem
|
||||
icon={view.icon.Timeline}
|
||||
label={settingsRes.string.SecurityTabSessions}
|
||||
selected={securityTab === 'sessions'}
|
||||
on:click={() => {
|
||||
securityTab = 'sessions'
|
||||
}}
|
||||
/>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<Separator name={'securitySettings'} index={0} color={'var(--theme-divider-color)'} />
|
||||
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
{#if securityTab === 'twoFactor'}
|
||||
<TwoFactorSettings />
|
||||
{:else}
|
||||
<SessionHistorySettings />
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!--
|
||||
// Copyright © 2026 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import type { SecurityLoginHistoryEvent } from '@hcengineering/account-client'
|
||||
import { MessageBox } from '@hcengineering/presentation'
|
||||
import { Button, Label, showPopup } from '@hcengineering/ui'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import settingsRes from '../plugin'
|
||||
import { formatLocation, getShortUserAgent, maskIpAddress, shouldShowNotMeAction } from '../securityLoginActivity'
|
||||
import { getAccountClient } from '../utils'
|
||||
|
||||
/** Narrow account client for security APIs (params match server contract). */
|
||||
const accountClient = getAccountClient() as unknown as {
|
||||
getMySecurityLoginHistory: (params?: { limit?: number, redact?: boolean }) => Promise<SecurityLoginHistoryEvent[]>
|
||||
reportSecurityLoginConcern: (params?: { loginEventId?: string }) => Promise<void>
|
||||
}
|
||||
const recentActivityLimit = 20
|
||||
let loginHistory: SecurityLoginHistoryEvent[] = []
|
||||
let loginHistoryLoading = false
|
||||
let loginHistoryLoaded = false
|
||||
let loginHistoryError = false
|
||||
|
||||
async function loadRecentLoginActivity (): Promise<void> {
|
||||
loginHistoryLoading = true
|
||||
loginHistoryError = false
|
||||
try {
|
||||
loginHistory = await accountClient.getMySecurityLoginHistory({ limit: recentActivityLimit, redact: true })
|
||||
loginHistoryLoaded = true
|
||||
} catch (err: any) {
|
||||
loginHistoryError = true
|
||||
Analytics.handleError(err)
|
||||
} finally {
|
||||
loginHistoryLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleNotMeAction (event?: SecurityLoginHistoryEvent): void {
|
||||
showPopup(MessageBox, {
|
||||
label: settingsRes.string.NotMeDialogTitle,
|
||||
message: settingsRes.string.NotMeDialogMessage,
|
||||
okLabel: settingsRes.string.NotMeDialogAction,
|
||||
action: async () => {
|
||||
await accountClient.reportSecurityLoginConcern(event !== undefined ? { loginEventId: event.id } : {})
|
||||
Analytics.handleEvent('Settings:RecentLoginActivityNotMe', {
|
||||
eventId: event?.id ?? 'header-action',
|
||||
authMethod: event?.authMethod ?? 'unknown'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
void loadRecentLoginActivity()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex-col gap-2 max-w-240">
|
||||
<div class="flex-between">
|
||||
<h3 class="text-lg font-medium"><Label label={settingsRes.string.RecentLoginActivityTitle} /></h3>
|
||||
<Button
|
||||
label={settingsRes.string.NotMeAction}
|
||||
kind="secondary"
|
||||
on:click={() => {
|
||||
handleNotMeAction()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if loginHistoryLoading}
|
||||
<div class="login-placeholder"><Label label={settingsRes.string.RecentLoginActivityLoading} /></div>
|
||||
<div class="login-placeholder"><Label label={settingsRes.string.RecentLoginActivityLoading} /></div>
|
||||
<div class="login-placeholder"><Label label={settingsRes.string.RecentLoginActivityLoading} /></div>
|
||||
{:else if loginHistoryError}
|
||||
<div class="login-empty">
|
||||
<div><Label label={settingsRes.string.RecentLoginActivityError} /></div>
|
||||
<Button
|
||||
label={settingsRes.string.RecentLoginActivityRetry}
|
||||
kind="secondary"
|
||||
on:click={() => {
|
||||
void loadRecentLoginActivity()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if loginHistoryLoaded && loginHistory.length === 0}
|
||||
<div class="login-empty"><Label label={settingsRes.string.RecentLoginActivityEmpty} /></div>
|
||||
{:else}
|
||||
<div class="flex-col gap-2">
|
||||
{#each loginHistory as loginEvent (loginEvent.id)}
|
||||
<div class="login-activity-row">
|
||||
<div class="flex-between gap-2">
|
||||
<span class="text-sm">{new Date(loginEvent.eventTime).toLocaleString()}</span>
|
||||
<span class:login-success={loginEvent.success} class:login-failed={!loginEvent.success}>
|
||||
{#if loginEvent.success}
|
||||
<Label label={settingsRes.string.RecentLoginActivitySuccess} />
|
||||
{:else}
|
||||
<Label label={settingsRes.string.RecentLoginActivityFailure} />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm flex-row-center gap-1">
|
||||
<Label label={settingsRes.string.RecentLoginActivityMethod} />: {loginEvent.authMethod}
|
||||
</div>
|
||||
<div class="text-sm flex-row-center gap-1">
|
||||
<Label label={settingsRes.string.RecentLoginActivityIp} />: {maskIpAddress(loginEvent.ip)}
|
||||
</div>
|
||||
<div class="text-sm flex-row-center gap-1">
|
||||
<Label label={settingsRes.string.RecentLoginActivityLocation} />:
|
||||
{formatLocation({ city: loginEvent.city, country: loginEvent.country })}
|
||||
</div>
|
||||
<div class="text-sm flex-row-center gap-1">
|
||||
<Label label={settingsRes.string.RecentLoginActivityDevice} />:
|
||||
{getShortUserAgent(loginEvent.userAgent)}
|
||||
</div>
|
||||
{#if shouldShowNotMeAction(loginEvent)}
|
||||
<div class="mt-2">
|
||||
<Button
|
||||
label={settingsRes.string.NotMeAction}
|
||||
kind="secondary"
|
||||
on:click={() => {
|
||||
handleNotMeAction(loginEvent)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.login-activity-row {
|
||||
border: 1px solid var(--theme-divider-color, var(--divider-color));
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.login-placeholder {
|
||||
height: 2.25rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--theme-button-default, var(--button-default));
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.login-empty {
|
||||
color: var(--caption-color);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.login-success {
|
||||
color: var(--theme-positive-color, #2e7d32);
|
||||
}
|
||||
|
||||
.login-failed {
|
||||
color: var(--theme-danger-color, #d32f2f);
|
||||
}
|
||||
</style>
|
||||
@@ -17,7 +17,7 @@
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { OK, unknownError, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { Breadcrumb, Button, EditBox, Header, Label, Spinner, Status } from '@hcengineering/ui'
|
||||
import { Button, EditBox, Label, Spinner, Status } from '@hcengineering/ui'
|
||||
import { getAccountClient } from '../utils'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
|
||||
@@ -96,10 +96,6 @@
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={setting.icon.Password} label={setting.string.TwoFactorAuth} size="large" isCurrent />
|
||||
</Header>
|
||||
|
||||
<div class="flex-col p-6 gap-8 max-w-2xl">
|
||||
<div class="flex flex-between">
|
||||
<Label label={setting.string.TwoFactorAuthDescription} />
|
||||
|
||||
@@ -30,6 +30,7 @@ import Password from './components/Password.svelte'
|
||||
import Privacy from './components/Privacy.svelte'
|
||||
import Profile from './components/Profile.svelte'
|
||||
import Settings from './components/Settings.svelte'
|
||||
import SecuritySettings from './components/SecuritySettings.svelte'
|
||||
import TwoFactorSettings from './components/TwoFactorSettings.svelte'
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
@@ -171,7 +172,8 @@ export default async (): Promise<Resources> => ({
|
||||
AddEmailSocialId,
|
||||
EmployeeRefEditor,
|
||||
UserRoleSelect,
|
||||
TwoFactorSettings
|
||||
TwoFactorSettings,
|
||||
SecuritySettings
|
||||
},
|
||||
actionImpl: {
|
||||
DeleteMixin
|
||||
|
||||
@@ -178,6 +178,7 @@ export default mergeIds(settingId, setting, {
|
||||
NotMeAction: '' as IntlString,
|
||||
NotMeDialogTitle: '' as IntlString,
|
||||
NotMeDialogMessage: '' as IntlString,
|
||||
NotMeDialogAction: '' as IntlString
|
||||
NotMeDialogAction: '' as IntlString,
|
||||
SecurityTabSessions: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -246,7 +246,8 @@ export default plugin(settingId, {
|
||||
AddEmailSocialId: '' as AnyComponent,
|
||||
OfficeSettings: '' as AnyComponent,
|
||||
UserRoleSelect: '' as AnyComponent,
|
||||
TwoFactorSettings: '' as AnyComponent
|
||||
TwoFactorSettings: '' as AnyComponent,
|
||||
SecuritySettings: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Settings: '' as IntlString,
|
||||
|
||||
Reference in New Issue
Block a user