Merge branch 'master' of github.com:ONLYOFFICE/CommunityServer-AspNetCore
This commit is contained in:
commit
4ff81ac474
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
@ -8,11 +7,9 @@ using System.Threading.Tasks;
|
||||
|
||||
using ASC.Core;
|
||||
using ASC.Security.Cryptography;
|
||||
using ASC.Web.Studio.Core;
|
||||
using ASC.Web.Studio.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@ -26,52 +23,18 @@ namespace ASC.Api.Core.Auth
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var Request = QueryHelpers.ParseQuery(Context.Request.Headers["confirm"]);
|
||||
_ = Request.TryGetValue("type", out var type);
|
||||
var _type = typeof(ConfirmType).TryParseEnum(type, ConfirmType.EmpInvite);
|
||||
var emailValidationKeyModel = EmailValidationKeyModel.FromRequest(Context.Request);
|
||||
|
||||
if (SecurityContext.IsAuthenticated && _type != ConfirmType.EmailChange)
|
||||
if (SecurityContext.IsAuthenticated && emailValidationKeyModel.Type != ConfirmType.EmailChange)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
|
||||
}
|
||||
|
||||
_ = Request.TryGetValue("key", out var key);
|
||||
_ = Request.TryGetValue("emplType", out var emplType);
|
||||
_ = Request.TryGetValue("email", out var _email);
|
||||
var validInterval = SetupInfo.ValidEmailKeyInterval;
|
||||
|
||||
EmailValidationKeyProvider.ValidationResult checkKeyResult;
|
||||
switch (_type)
|
||||
{
|
||||
case ConfirmType.EmpInvite:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + emplType, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.LinkInvite:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_type + emplType, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.EmailChange:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + SecurityContext.CurrentAccount.ID, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.PasswordChange:
|
||||
var userHash = Request.TryGetValue("p", out var p) && p == "1";
|
||||
var hash = string.Empty;
|
||||
|
||||
if (userHash)
|
||||
{
|
||||
var tenantId = CoreContext.TenantManager.GetCurrentTenant().TenantId;
|
||||
hash = CoreContext.Authentication.GetUserPasswordHash(tenantId, CoreContext.UserManager.GetUserByEmail(tenantId, _email).ID);
|
||||
}
|
||||
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)), key, validInterval);
|
||||
break;
|
||||
default:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type, key, validInterval);
|
||||
break;
|
||||
}
|
||||
var checkKeyResult = emailValidationKeyModel.Validate();
|
||||
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.Role, _type.ToString())
|
||||
new Claim(ClaimTypes.Role, emailValidationKeyModel.Type.ToString())
|
||||
};
|
||||
|
||||
if (!SecurityContext.IsAuthenticated)
|
||||
|
@ -125,7 +125,14 @@ namespace ASC.Common.Caching
|
||||
var cr = c.Consume(Cts[channelName].Token);
|
||||
if (cr != null && cr.Value != null && !(new Guid(cr.Key.Id.ToByteArray())).Equals(Key) && Actions.TryGetValue(channelName, out var act))
|
||||
{
|
||||
act(cr.Value);
|
||||
try
|
||||
{
|
||||
act(cr.Value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Kafka onmessage", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ConsumeException e)
|
||||
|
@ -29,7 +29,12 @@ using System.Text;
|
||||
using ASC.Common.Logging;
|
||||
using ASC.Common.Utils;
|
||||
using ASC.Core;
|
||||
|
||||
using ASC.Core.Users;
|
||||
using ASC.Web.Studio.Utility;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
||||
|
||||
namespace ASC.Security.Cryptography
|
||||
{
|
||||
public class EmailValidationKeyProvider
|
||||
@ -42,8 +47,18 @@ namespace ASC.Security.Cryptography
|
||||
}
|
||||
|
||||
private static readonly ILog log = LogManager.GetLogger("ASC.KeyValidation.EmailSignature");
|
||||
private static readonly DateTime _from = new DateTime(2010, 01, 01, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static readonly DateTime _from = new DateTime(2010, 01, 01, 0, 0, 0, DateTimeKind.Utc);
|
||||
internal static readonly TimeSpan ValidInterval;
|
||||
|
||||
static EmailValidationKeyProvider()
|
||||
{
|
||||
if (!TimeSpan.TryParse(ConfigurationManager.AppSettings["email:validinterval"], out var validInterval))
|
||||
{
|
||||
validInterval = TimeSpan.FromDays(7);
|
||||
}
|
||||
|
||||
ValidInterval = validInterval;
|
||||
}
|
||||
public static string GetEmailKey(string email)
|
||||
{
|
||||
return GetEmailKey(CoreContext.TenantManager.GetCurrentTenant().TenantId, email);
|
||||
@ -72,7 +87,7 @@ namespace ASC.Security.Cryptography
|
||||
log.Fatal("Failed to format tenant specific email", e);
|
||||
return email.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ValidationResult ValidateEmailKey(string email, string key)
|
||||
{
|
||||
@ -120,5 +135,82 @@ namespace ASC.Security.Cryptography
|
||||
Array.Copy(salt, 0, alldata, data.Length, salt.Length);
|
||||
return Hasher.Hash(alldata, HashAlg.SHA256);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmailValidationKeyModel
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public EmployeeType? EmplType { get; set; }
|
||||
public string Email { get; set; }
|
||||
public Guid? UiD { get; set; }
|
||||
public ConfirmType Type { get; set; }
|
||||
public int? P { get; set; }
|
||||
|
||||
public ValidationResult Validate()
|
||||
{
|
||||
ValidationResult checkKeyResult;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case ConfirmType.EmpInvite:
|
||||
checkKeyResult = ValidateEmailKey(Email + Type + EmplType, Key, ValidInterval);
|
||||
break;
|
||||
case ConfirmType.LinkInvite:
|
||||
checkKeyResult = ValidateEmailKey(Type.ToString() + EmplType.ToString(), Key, ValidInterval);
|
||||
break;
|
||||
case ConfirmType.EmailChange:
|
||||
checkKeyResult = ValidateEmailKey(Email + Type + SecurityContext.CurrentAccount.ID, Key, ValidInterval);
|
||||
break;
|
||||
case ConfirmType.PasswordChange:
|
||||
var hash = string.Empty;
|
||||
|
||||
if (P == 1)
|
||||
{
|
||||
var tenantId = CoreContext.TenantManager.GetCurrentTenant().TenantId;
|
||||
hash = CoreContext.Authentication.GetUserPasswordHash(tenantId, UiD.Value);
|
||||
}
|
||||
|
||||
checkKeyResult = ValidateEmailKey(Email + Type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)) + UiD, Key, ValidInterval);
|
||||
break;
|
||||
default:
|
||||
checkKeyResult = ValidateEmailKey(Email + Type, Key, ValidInterval);
|
||||
break;
|
||||
}
|
||||
|
||||
return checkKeyResult;
|
||||
}
|
||||
|
||||
public static EmailValidationKeyModel FromRequest(HttpRequest httpRequest)
|
||||
{
|
||||
var Request = QueryHelpers.ParseQuery(httpRequest.Headers["confirm"]);
|
||||
|
||||
_ = Request.TryGetValue("type", out var type);
|
||||
_ = Enum.TryParse<ConfirmType>(type, out var confirmType);
|
||||
|
||||
_ = Request.TryGetValue("key", out var key);
|
||||
|
||||
_ = Request.TryGetValue("p", out var pkey);
|
||||
_ = int.TryParse(pkey, out var p);
|
||||
|
||||
_ = Request.TryGetValue("emplType", out var emplType);
|
||||
_ = Enum.TryParse<EmployeeType>(emplType, out var employeeType);
|
||||
|
||||
_ = Request.TryGetValue("email", out var _email);
|
||||
_ = Request.TryGetValue("uid", out var userIdKey);
|
||||
_ = Guid.TryParse(userIdKey, out var userId);
|
||||
|
||||
return new EmailValidationKeyModel
|
||||
{
|
||||
Key = key,
|
||||
Type = confirmType,
|
||||
Email = _email,
|
||||
EmplType = employeeType,
|
||||
UiD = userId,
|
||||
P = p
|
||||
};
|
||||
}
|
||||
|
||||
public void Deconstruct(out string key, out string email, out EmployeeType? employeeType, out Guid? userId, out ConfirmType confirmType, out int? p)
|
||||
=> (key, email, employeeType, userId, confirmType, p) = (Key, Email, EmplType, UiD, Type, P);
|
||||
}
|
||||
}
|
28
common/ASC.Core.Common/Users/ConfirmTypeEnum.cs
Normal file
28
common/ASC.Core.Common/Users/ConfirmTypeEnum.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace ASC.Web.Studio.Utility
|
||||
{
|
||||
// emp-invite - confirm ivite by email
|
||||
// portal-suspend - confirm portal suspending - Tenant.SetStatus(TenantStatus.Suspended)
|
||||
// portal-continue - confirm portal continuation - Tenant.SetStatus(TenantStatus.Active)
|
||||
// portal-remove - confirm portal deletation - Tenant.SetStatus(TenantStatus.RemovePending)
|
||||
// DnsChange - change Portal Address and/or Custom domain name
|
||||
public enum ConfirmType
|
||||
{
|
||||
EmpInvite,
|
||||
LinkInvite,
|
||||
PortalSuspend,
|
||||
PortalContinue,
|
||||
PortalRemove,
|
||||
DnsChange,
|
||||
PortalOwnerChange,
|
||||
Activation,
|
||||
EmailChange,
|
||||
EmailActivation,
|
||||
PasswordChange,
|
||||
ProfileRemove,
|
||||
PhoneActivation,
|
||||
PhoneAuth,
|
||||
Auth,
|
||||
TfaActivation,
|
||||
TfaAuth
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { BrowserRouter, Switch } from "react-router-dom";
|
||||
import { Router, Switch } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import PeopleLayout from "./components/Layout/index";
|
||||
import Home from "./components/pages/Home";
|
||||
@ -10,6 +10,7 @@ import ProfileAction from './components/pages/ProfileAction';
|
||||
import GroupAction from './components/pages/GroupAction';
|
||||
import { Error404 } from "./components/pages/Error";
|
||||
import Reassign from './components/pages/Reassign';
|
||||
import history from './history';
|
||||
|
||||
/*const Profile = lazy(() => import("./components/pages/Profile"));
|
||||
const ProfileAction = lazy(() => import("./components/pages/ProfileAction"));
|
||||
@ -18,13 +19,13 @@ const GroupAction = lazy(() => import("./components/pages/GroupAction"));*/
|
||||
const App = ({ settings }) => {
|
||||
const { homepage } = settings;
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Router history={history}>
|
||||
<PeopleLayout>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<Switch>
|
||||
<PrivateRoute exact path={homepage} component={Home} />
|
||||
<PrivateRoute exact path={[homepage, `${homepage}/filter`]} component={Home} />
|
||||
<PrivateRoute
|
||||
path={`${homepage}/view/:userId`}
|
||||
component={Profile}
|
||||
@ -59,7 +60,7 @@ const App = ({ settings }) => {
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</PeopleLayout>
|
||||
</BrowserRouter>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,19 +1,30 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { FilterInput } from "asc-web-components";
|
||||
import { fetchPeople } from "../../../../../store/people/actions";
|
||||
import find from "lodash/find";
|
||||
import result from "lodash/result";
|
||||
import { isAdmin } from "../../../../../store/auth/selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { typeGuest, typeUser, department } from './../../../../../helpers/customNames';
|
||||
|
||||
const getSortData = ( t ) => {
|
||||
return [
|
||||
{ key: "firstname", label: t('ByFirstNameSorting') },
|
||||
{ key: "lastname", label: t('ByLastNameSorting') }
|
||||
];
|
||||
};
|
||||
import { withTranslation } from "react-i18next";
|
||||
import {
|
||||
typeGuest,
|
||||
typeUser,
|
||||
department
|
||||
} from "./../../../../../helpers/customNames";
|
||||
import { withRouter } from "react-router";
|
||||
import Filter from "../../../../../store/people/filter";
|
||||
import {
|
||||
EMPLOYEE_STATUS,
|
||||
ACTIVATION_STATUS,
|
||||
ROLE,
|
||||
GROUP,
|
||||
SEARCH,
|
||||
SORT_BY,
|
||||
SORT_ORDER,
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
} from "../../../../../helpers/constants";
|
||||
import { getFilterByLocation } from "../../../../../helpers/converters";
|
||||
|
||||
const getEmployeeStatus = filterValues => {
|
||||
const employeeStatus = result(
|
||||
@ -59,51 +70,47 @@ const getGroup = filterValues => {
|
||||
return groupId || null;
|
||||
};
|
||||
|
||||
const SectionFilterContent = ({
|
||||
fetchPeople,
|
||||
filter,
|
||||
onLoading,
|
||||
user,
|
||||
groups
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const selectedFilterData = {
|
||||
filterValues: [],
|
||||
sortDirection: filter.sortOrder === "ascending" ? "asc" : "desc",
|
||||
sortId: filter.sortBy
|
||||
class SectionFilterContent extends React.Component {
|
||||
componentDidMount() {
|
||||
const { location, filter, onLoading, fetchPeople } = this.props;
|
||||
|
||||
const newFilter = getFilterByLocation(location);
|
||||
|
||||
if(!newFilter || newFilter.equals(filter)) return;
|
||||
|
||||
onLoading(true);
|
||||
fetchPeople(newFilter).finally(() => onLoading(false));
|
||||
}
|
||||
|
||||
onFilter = data => {
|
||||
const { onLoading, fetchPeople, filter } = this.props;
|
||||
|
||||
const employeeStatus = getEmployeeStatus(data.filterValues);
|
||||
const activationStatus = getActivationStatus(data.filterValues);
|
||||
const role = getRole(data.filterValues);
|
||||
const group = getGroup(data.filterValues);
|
||||
const search = data.inputValue || null;
|
||||
const sortBy = data.sortId;
|
||||
const sortOrder =
|
||||
data.sortDirection === "desc" ? "descending" : "ascending";
|
||||
|
||||
const newFilter = filter.clone();
|
||||
newFilter.page = 0;
|
||||
newFilter.sortBy = sortBy;
|
||||
newFilter.sortOrder = sortOrder;
|
||||
newFilter.employeeStatus = employeeStatus;
|
||||
newFilter.activationStatus = activationStatus;
|
||||
newFilter.role = role;
|
||||
newFilter.search = search;
|
||||
newFilter.group = group;
|
||||
|
||||
onLoading(true);
|
||||
fetchPeople(newFilter).finally(() => onLoading(false));
|
||||
};
|
||||
|
||||
selectedFilterData.inputValue = filter.search;
|
||||
getData = () => {
|
||||
const { user, groups, t } = this.props;
|
||||
|
||||
if (filter.employeeStatus) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: `${filter.employeeStatus}`,
|
||||
group: "filter-status"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.activationStatus) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: `${filter.activationStatus}`,
|
||||
group: "filter-email"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.role) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: filter.role,
|
||||
group: "filter-type"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.group) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: filter.group,
|
||||
group: "filter-group"
|
||||
});
|
||||
}
|
||||
|
||||
const getData = useCallback(() => {
|
||||
const options = !isAdmin(user)
|
||||
? []
|
||||
: [
|
||||
@ -126,7 +133,12 @@ const SectionFilterContent = ({
|
||||
];
|
||||
|
||||
const groupOptions = groups.map(group => {
|
||||
return { key: group.id, inSubgroup: true, group: "filter-group", label: group.name };
|
||||
return {
|
||||
key: group.id,
|
||||
inSubgroup: true,
|
||||
group: "filter-group",
|
||||
label: group.name
|
||||
};
|
||||
});
|
||||
|
||||
const filterOptions = [
|
||||
@ -137,10 +149,10 @@ const SectionFilterContent = ({
|
||||
label: t("Email"),
|
||||
isHeader: true
|
||||
},
|
||||
{
|
||||
key: "1",
|
||||
group: "filter-email",
|
||||
label: t("LblActive")
|
||||
{
|
||||
key: "1",
|
||||
group: "filter-email",
|
||||
label: t("LblActive")
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
@ -153,66 +165,114 @@ const SectionFilterContent = ({
|
||||
label: t("UserType"),
|
||||
isHeader: true
|
||||
},
|
||||
{ key: "admin", group: "filter-type", label: t("Administrator")},
|
||||
{ key: "user", group: "filter-type", label: t('CustomTypeUser', { typeUser })},
|
||||
{ key: "guest", group: "filter-type", label: t('CustomTypeGuest', { typeGuest }) },
|
||||
{ key: "admin", group: "filter-type", label: t("Administrator") },
|
||||
{
|
||||
key: "user",
|
||||
group: "filter-type",
|
||||
label: t("CustomTypeUser", { typeUser })
|
||||
},
|
||||
{
|
||||
key: "guest",
|
||||
group: "filter-type",
|
||||
label: t("CustomTypeGuest", { typeGuest })
|
||||
},
|
||||
{
|
||||
key: "filter-other",
|
||||
group: "filter-other",
|
||||
label: t("LblOther"),
|
||||
isHeader: true
|
||||
},
|
||||
{ key: "filter-type-group", group: "filter-other", subgroup: 'filter-group', label: t('CustomDepartment', { department }), defaultSelectLabel: t("DefaultSelectLabel") },
|
||||
{
|
||||
key: "filter-type-group",
|
||||
group: "filter-other",
|
||||
subgroup: "filter-group",
|
||||
label: t("CustomDepartment", { department }),
|
||||
defaultSelectLabel: t("DefaultSelectLabel")
|
||||
},
|
||||
...groupOptions
|
||||
];
|
||||
|
||||
//console.log("getData (filterOptions)", filterOptions);
|
||||
|
||||
return filterOptions;
|
||||
};
|
||||
|
||||
}, [user, groups, t]);
|
||||
getSortData = () => {
|
||||
const { t } = this.props;
|
||||
|
||||
const onFilter = useCallback(
|
||||
data => {
|
||||
console.log(data);
|
||||
return [
|
||||
{ key: "firstname", label: t("ByFirstNameSorting") },
|
||||
{ key: "lastname", label: t("ByLastNameSorting") }
|
||||
];
|
||||
};
|
||||
|
||||
const newFilter = filter.clone();
|
||||
newFilter.page = 0;
|
||||
newFilter.sortBy = data.sortId;
|
||||
newFilter.sortOrder =
|
||||
data.sortDirection === "desc" ? "descending" : "ascending";
|
||||
newFilter.employeeStatus = getEmployeeStatus(data.filterValues);
|
||||
newFilter.activationStatus = getActivationStatus(data.filterValues);
|
||||
newFilter.role = getRole(data.filterValues);
|
||||
newFilter.search = data.inputValue || null;
|
||||
newFilter.group = getGroup(data.filterValues);
|
||||
getSelectedFilterData = () => {
|
||||
const { filter } = this.props;
|
||||
const selectedFilterData = {
|
||||
filterValues: [],
|
||||
sortDirection: filter.sortOrder === "ascending" ? "asc" : "desc",
|
||||
sortId: filter.sortBy
|
||||
};
|
||||
|
||||
onLoading(true);
|
||||
fetchPeople(newFilter).finally(() => onLoading(false));
|
||||
},
|
||||
[onLoading, fetchPeople, filter]
|
||||
);
|
||||
return (
|
||||
<FilterInput
|
||||
getFilterData={getData}
|
||||
getSortData={getSortData.bind(this, t)}
|
||||
selectedFilterData={selectedFilterData}
|
||||
onFilter={onFilter}
|
||||
directionAscLabel={t("DirectionAscLabel")}
|
||||
directionDescLabel={t("DirectionDescLabel")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
selectedFilterData.inputValue = filter.search;
|
||||
|
||||
if (filter.employeeStatus) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: `${filter.employeeStatus}`,
|
||||
group: "filter-status"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.activationStatus) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: `${filter.activationStatus}`,
|
||||
group: "filter-email"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.role) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: filter.role,
|
||||
group: "filter-type"
|
||||
});
|
||||
}
|
||||
|
||||
if (filter.group) {
|
||||
selectedFilterData.filterValues.push({
|
||||
key: filter.group,
|
||||
group: "filter-group"
|
||||
});
|
||||
}
|
||||
|
||||
return selectedFilterData;
|
||||
};
|
||||
|
||||
render() {
|
||||
const selectedFilterData = this.getSelectedFilterData();
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<FilterInput
|
||||
getFilterData={this.getData}
|
||||
getSortData={this.getSortData}
|
||||
selectedFilterData={selectedFilterData}
|
||||
onFilter={this.onFilter}
|
||||
directionAscLabel={t("DirectionAscLabel")}
|
||||
directionDescLabel={t("DirectionDescLabel")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.auth.user,
|
||||
groups: state.people.groups,
|
||||
filter: state.people.filter
|
||||
filter: state.people.filter,
|
||||
settings: state.auth.settings
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ fetchPeople }
|
||||
)(SectionFilterContent);
|
||||
)(withRouter(withTranslation()(SectionFilterContent)));
|
||||
|
@ -1,18 +1,31 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { PageLayout, Loader } from "asc-web-components";
|
||||
import { PageLayout, Loader, toastr } from "asc-web-components";
|
||||
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
||||
import { SectionHeaderContent, SectionBodyContent } from './Section';
|
||||
import { fetchProfile } from '../../../store/profile/actions';
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
|
||||
class Profile extends React.Component {
|
||||
class PureProfile extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
queryString: `${props.location.search.slice(1)}`
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { match, fetchProfile } = this.props;
|
||||
const { match, fetchProfile, t } = this.props;
|
||||
const { userId } = match.params;
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
if (linkParams.email_change && linkParams.email_change === "success"){
|
||||
toastr.success(t('ChangeEmailSuccess'));
|
||||
}
|
||||
|
||||
fetchProfile(userId);
|
||||
}
|
||||
@ -32,8 +45,7 @@ class Profile extends React.Component {
|
||||
|
||||
const { profile } = this.props;
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
{profile
|
||||
profile
|
||||
?
|
||||
<PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
@ -53,12 +65,15 @@ class Profile extends React.Component {
|
||||
sectionBodyContent={
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
}
|
||||
/>}
|
||||
</I18nextProvider>
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const ProfileContainer = withTranslation()(PureProfile);
|
||||
|
||||
const Profile = (props) => <I18nextProvider i18n={i18n}><ProfileContainer {...props} /></I18nextProvider>;
|
||||
|
||||
Profile.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
|
@ -21,6 +21,7 @@
|
||||
"DeleteSelfProfile": "Delete profile",
|
||||
"EditButton": "Edit",
|
||||
"Actions": "Actions",
|
||||
"ChangeEmailSuccess": "Mail has been successfully changed",
|
||||
|
||||
"PhoneChange": "Change phone",
|
||||
"PhoneLbl": "Phone",
|
||||
|
@ -1,5 +1,14 @@
|
||||
export const AUTH_KEY = "asc_auth_key";
|
||||
export const GUID_EMPTY = "00000000-0000-0000-0000-000000000000";
|
||||
export const EMPLOYEE_STATUS = "employeestatus";
|
||||
export const ACTIVATION_STATUS = "activationstatus";
|
||||
export const ROLE = "role";
|
||||
export const GROUP = "group";
|
||||
export const SEARCH = "search";
|
||||
export const SORT_BY = "sortby";
|
||||
export const SORT_ORDER = "sortorder";
|
||||
export const PAGE = "page";
|
||||
export const PAGE_COUNT = "pagecount";
|
||||
|
||||
/**
|
||||
* Enum for employee activation status.
|
||||
|
67
products/ASC.People/Client/src/helpers/converters.js
Normal file
67
products/ASC.People/Client/src/helpers/converters.js
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
EMPLOYEE_STATUS,
|
||||
ACTIVATION_STATUS,
|
||||
ROLE,
|
||||
GROUP,
|
||||
SEARCH,
|
||||
SORT_BY,
|
||||
SORT_ORDER,
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
} from "./constants";
|
||||
import Filter from "../store/people/filter";
|
||||
|
||||
export function getObjectByLocation(location) {
|
||||
if (!location.search || !location.search.length) return null;
|
||||
|
||||
const searchUrl = location.search.substring(1);
|
||||
const object = JSON.parse(
|
||||
'{"' +
|
||||
decodeURI(searchUrl)
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/&/g, '","')
|
||||
.replace(/=/g, '":"') +
|
||||
'"}'
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
export function getFilterByLocation(location) {
|
||||
const urlFilter = getObjectByLocation(location);
|
||||
|
||||
if(!urlFilter) return null;
|
||||
|
||||
const defaultFilter = Filter.getDefault();
|
||||
|
||||
const employeeStatus =
|
||||
(urlFilter[EMPLOYEE_STATUS] && +urlFilter[EMPLOYEE_STATUS]) ||
|
||||
defaultFilter.employeeStatus;
|
||||
const activationStatus =
|
||||
(urlFilter[ACTIVATION_STATUS] && +urlFilter[ACTIVATION_STATUS]) ||
|
||||
defaultFilter.activationStatus;
|
||||
const role = urlFilter[ROLE] || defaultFilter.role;
|
||||
const group = urlFilter[GROUP] || defaultFilter.group;
|
||||
const search = urlFilter[SEARCH] || defaultFilter.search;
|
||||
const sortBy = urlFilter[SORT_BY] || defaultFilter.sortBy;
|
||||
const sortOrder = urlFilter[SORT_ORDER] || defaultFilter.sortOrder;
|
||||
const page = (urlFilter[PAGE] && +urlFilter[PAGE]) || defaultFilter.page;
|
||||
const pageCount =
|
||||
(urlFilter[PAGE_COUNT] && +urlFilter[PAGE_COUNT]) ||
|
||||
defaultFilter.pageCount;
|
||||
|
||||
const newFilter = new Filter(
|
||||
page,
|
||||
pageCount,
|
||||
defaultFilter.total,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
employeeStatus,
|
||||
activationStatus,
|
||||
role,
|
||||
search,
|
||||
group
|
||||
);
|
||||
|
||||
return newFilter;
|
||||
}
|
3
products/ASC.People/Client/src/history.js
Normal file
3
products/ASC.People/Client/src/history.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
export default createBrowserHistory();
|
@ -50,6 +50,7 @@
|
||||
"RemoveData",
|
||||
"DeleteSelfProfile",
|
||||
"EditButton",
|
||||
"ChangeEmailSuccess",
|
||||
"Actions"
|
||||
]
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as api from "../services/api";
|
||||
import { setGroups, fetchPeopleAsync } from "../people/actions";
|
||||
import setAuthorizationToken from "../../store/services/setAuthorizationToken";
|
||||
import { getFilterByLocation } from "../../helpers/converters";
|
||||
|
||||
export const LOGIN_POST = "LOGIN_POST";
|
||||
export const SET_CURRENT_USER = "SET_CURRENT_USER";
|
||||
@ -59,7 +60,9 @@ export async function getUserInfo(dispatch) {
|
||||
|
||||
dispatch(setGroups(groupResp.data.response));
|
||||
|
||||
await fetchPeopleAsync(dispatch);
|
||||
const newFilter = getFilterByLocation(window.location);
|
||||
|
||||
await fetchPeopleAsync(dispatch, newFilter);
|
||||
|
||||
return dispatch(setIsLoaded(true));
|
||||
}
|
||||
|
@ -1,5 +1,18 @@
|
||||
import * as api from "../services/api";
|
||||
import Filter from "./filter";
|
||||
import history from "../../history";
|
||||
import config from "../../../package.json";
|
||||
import {
|
||||
EMPLOYEE_STATUS,
|
||||
ACTIVATION_STATUS,
|
||||
ROLE,
|
||||
GROUP,
|
||||
SEARCH,
|
||||
SORT_BY,
|
||||
SORT_ORDER,
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
} from "../../helpers/constants";
|
||||
|
||||
export const SET_GROUPS = "SET_GROUPS";
|
||||
export const SET_USERS = "SET_USERS";
|
||||
@ -74,6 +87,49 @@ export function deselectUser(user) {
|
||||
}
|
||||
|
||||
export function setFilter(filter) {
|
||||
const defaultFilter = Filter.getDefault();
|
||||
const params = [];
|
||||
|
||||
if (filter.employeeStatus) {
|
||||
params.push(`${EMPLOYEE_STATUS}=${filter.employeeStatus}`);
|
||||
}
|
||||
|
||||
if (filter.activationStatus) {
|
||||
params.push(`${ACTIVATION_STATUS}=${filter.activationStatus}`);
|
||||
}
|
||||
|
||||
if (filter.role) {
|
||||
params.push(`${ROLE}=${filter.role}`);
|
||||
}
|
||||
|
||||
if (filter.group) {
|
||||
params.push(`${GROUP}=${filter.group}`);
|
||||
}
|
||||
|
||||
if (filter.search) {
|
||||
params.push(`${SEARCH}=${filter.search}`);
|
||||
}
|
||||
|
||||
if (filter.page > 0) {
|
||||
params.push(`${PAGE}=${filter.page + 1}`);
|
||||
}
|
||||
|
||||
if (filter.pageCount !== defaultFilter.pageCount) {
|
||||
params.push(`${PAGE_COUNT}=${filter.pageCount}`);
|
||||
}
|
||||
|
||||
if (
|
||||
params.length > 0 ||
|
||||
(filter.sortBy !== defaultFilter.sortBy ||
|
||||
filter.sortOrder !== defaultFilter.sortOrder)
|
||||
) {
|
||||
params.push(`${SORT_BY}=${filter.sortBy}`);
|
||||
params.push(`${SORT_ORDER}=${filter.sortOrder}`);
|
||||
}
|
||||
|
||||
if (params.length > 0) {
|
||||
history.push(`${config.homepage}/filter?${params.join("&")}`);
|
||||
}
|
||||
return {
|
||||
type: SET_FILTER,
|
||||
filter
|
||||
@ -89,8 +145,9 @@ export function setSelectorUsers(users) {
|
||||
|
||||
export function fetchSelectorUsers() {
|
||||
return dispatch => {
|
||||
api.getSelectorUserList()
|
||||
.then(res => dispatch(setSelectorUsers(res.data.response)));
|
||||
api
|
||||
.getSelectorUserList()
|
||||
.then(res => dispatch(setSelectorUsers(res.data.response)));
|
||||
};
|
||||
}
|
||||
|
||||
@ -102,6 +159,7 @@ export function fetchPeople(filter) {
|
||||
|
||||
export function fetchPeopleByFilter(dispatch, filter) {
|
||||
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
||||
|
||||
return api.getUserList(filterData).then(res => {
|
||||
filterData.total = res.data.total;
|
||||
dispatch(setFilter(filterData));
|
||||
|
@ -61,7 +61,7 @@ class Filter {
|
||||
activationStatus,
|
||||
role,
|
||||
search,
|
||||
group,
|
||||
group
|
||||
} = this;
|
||||
|
||||
let dtoFilter = {
|
||||
@ -122,6 +122,21 @@ class Filter {
|
||||
this.group
|
||||
);
|
||||
}
|
||||
|
||||
equals(filter) {
|
||||
const equals =
|
||||
this.employeeStatus === filter.employeeStatus &&
|
||||
this.activationStatus === filter.activationStatus &&
|
||||
this.role === filter.role &&
|
||||
this.group === filter.group &&
|
||||
this.search === filter.search &&
|
||||
this.sortBy === filter.sortBy &&
|
||||
this.sortOrder === filter.sortOrder &&
|
||||
this.page === filter.page &&
|
||||
this.pageCount === filter.pageCount;
|
||||
|
||||
return equals;
|
||||
}
|
||||
}
|
||||
|
||||
export default Filter;
|
||||
|
@ -7,6 +7,7 @@ using ASC.Web.Api.Models;
|
||||
using ASC.Web.Api.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static ASC.Security.Cryptography.EmailValidationKeyProvider;
|
||||
|
||||
namespace ASC.Web.Api.Controllers
|
||||
{
|
||||
@ -38,6 +39,13 @@ namespace ASC.Web.Api.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Create("confirm", false)]
|
||||
public ValidationResult CheckConfirm([FromBody]EmailValidationKeyModel model)
|
||||
{
|
||||
return model.Validate();
|
||||
}
|
||||
|
||||
private static UserInfo GetUser(int tenantId, string userName, string password)
|
||||
{
|
||||
var user = CoreContext.UserManager.GetUsers(
|
||||
|
@ -1,48 +1,67 @@
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { Switch, Redirect, Route } from "react-router-dom";
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import PublicRoute from "../../../helpers/publicRoute";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
// import ChangeEmailForm from "./sub-components/changeEmail";
|
||||
|
||||
const CreateUserForm = lazy(() => import("./sub-components/createUser"));
|
||||
const ChangePasswordForm = lazy(() => import("./sub-components/changePassword"));
|
||||
const ActivateEmailForm = lazy(() => import("./sub-components/activateEmail"));
|
||||
const ChangeEmailForm = lazy(() => import("./sub-components/changeEmail"));
|
||||
const ChangePhoneForm = lazy(() => import("./sub-components/changePhone"));
|
||||
|
||||
const Confirm = ({ match }) => {
|
||||
//console.log("Confirm render");
|
||||
const ConfirmType = (props) => {
|
||||
switch (props.type) {
|
||||
case 'LinkInvite':
|
||||
return <PublicRoute
|
||||
component={CreateUserForm}
|
||||
/>;
|
||||
case 'EmailActivation':
|
||||
return <Route
|
||||
component={ActivateEmailForm}
|
||||
/>;
|
||||
case 'EmailChange':
|
||||
return <Route
|
||||
component={ChangeEmailForm}
|
||||
/>;
|
||||
case 'PasswordChange':
|
||||
return <Route
|
||||
component={ChangePasswordForm}
|
||||
/>;
|
||||
case 'PhoneActivation':
|
||||
return <Route
|
||||
component={ChangePhoneForm}
|
||||
/>;
|
||||
default:
|
||||
return <Redirect to={{ pathname: "/" }} />;
|
||||
}
|
||||
}
|
||||
class Confirm extends React.Component {
|
||||
|
||||
return (
|
||||
constructor(props) {
|
||||
const queryParams = props.location.search.slice(1).split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
super(props);
|
||||
this.state = {
|
||||
type: linkParams.type
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log("Confirm render");
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<Switch>
|
||||
<PublicRoute
|
||||
path={`${match.path}/type=LinkInvite`}
|
||||
component={CreateUserForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=EmailActivation`}
|
||||
component={ActivateEmailForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=PasswordChange`}
|
||||
component={ChangePasswordForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=PhoneActivation`}
|
||||
component={ChangePhoneForm}
|
||||
/>
|
||||
<Redirect to={{ pathname: "/" }} />
|
||||
</Switch>
|
||||
<ConfirmType type={this.state.type} />
|
||||
</Suspense>
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
</I18nextProvider >
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Confirm;
|
||||
|
@ -16,6 +16,7 @@
|
||||
"PassworResetTitle": "Now you can create a new password.", "_comment":"SYNTAX ERROR 'Passwor' Reset Title",
|
||||
"PasswordCustomMode": "Password",
|
||||
"ImportContactsOkButton": "OK",
|
||||
"LoadingProcessing": "Loading...",
|
||||
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
|
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { withRouter } from "react-router";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { PageLayout, Loader } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { logout, changeEmail } from '../../../../store/auth/actions';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
class ChangeEmail extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
queryString: props.location.search.slice(1)
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(){
|
||||
const { logout, changeEmail, userId, isLoaded } = this.props;
|
||||
if (isLoaded){
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
// logout();
|
||||
const email = decodeURIComponent(linkParams.email);
|
||||
changeEmail(userId, {email}, this.state.queryString)
|
||||
.then((res) => {
|
||||
console.log('change client email success', res)
|
||||
window.location.href = `${window.location.origin}/products/people/view/@self?email_change=success`;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('change client email error', e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('Change email render');
|
||||
return (
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChangeEmail.propTypes = {
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
const ChangeEmailForm = (props) => (<PageLayout sectionBodyContent={<ChangeEmail {...props} />} />);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLoaded: state.auth.isLoaded,
|
||||
userId: state.auth.user.id
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { logout, changeEmail })(withRouter(withTranslation()(ChangeEmailForm)));
|
@ -64,10 +64,11 @@ const Form = props => {
|
||||
setIsLoading(true);
|
||||
console.log("changePassword onSubmit", match, location, history);
|
||||
|
||||
const str = location.search.split("&");
|
||||
const userId = str[1].slice(4);
|
||||
const key = `type=PasswordChange&${location.search.slice(1)}`;
|
||||
const userId = "f305ea37-da05-11e9-89de-e0cb4e43b8c0"; //TODO: Find real userId by key
|
||||
|
||||
changePassword(userId, password, key)
|
||||
changePassword(userId, {password}, key)
|
||||
.then(() => {
|
||||
console.log("UPDATE PASSWORD");
|
||||
history.push("/");
|
||||
@ -149,6 +150,7 @@ const Form = props => {
|
||||
tooltipPasswordTitle="Password must contain:"
|
||||
tooltipPasswordLength={tooltipPasswordLength}
|
||||
placeholder={t("PasswordCustomMode")}
|
||||
maxLength={30}
|
||||
|
||||
//isAutoFocussed={true}
|
||||
//autocomple="current-password"
|
||||
|
@ -10,6 +10,7 @@ export const LOGOUT = 'LOGOUT';
|
||||
export const SET_PASSWORD_SETTINGS = 'SET_PASSWORD_SETTINGS';
|
||||
export const SET_IS_CONFIRM_LOADED = 'SET_IS_CONFIRM_LOADED';
|
||||
export const SET_NEW_PASSWORD = 'SET_NEW_PASSWORD';
|
||||
export const SET_NEW_EMAIL = 'SET_NEW_EMAIL';
|
||||
|
||||
export function setCurrentUser(user) {
|
||||
return {
|
||||
@ -66,6 +67,13 @@ export function setNewPasswordSettings(password) {
|
||||
};
|
||||
};
|
||||
|
||||
export function setNewEmail(email) {
|
||||
return {
|
||||
type: SET_NEW_EMAIL,
|
||||
email
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export function getUserInfo(dispatch) {
|
||||
return api.getUser()
|
||||
@ -140,9 +148,18 @@ export function checkResponseError(res) {
|
||||
export function changePassword(userId, password, key) {
|
||||
return dispatch => {
|
||||
return api.changePassword(userId, password, key)
|
||||
.then(res => {
|
||||
//checkResponseError(res);
|
||||
dispatch(setNewPasswordSettings(res.data.response));
|
||||
})
|
||||
.then(res => {
|
||||
//checkResponseError(res);
|
||||
dispatch(setNewPasswordSettings(res.data.response));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function changeEmail(userId, email, key) {
|
||||
return dispatch => {
|
||||
return api.changePassword(userId, email, key)
|
||||
.then(res => {
|
||||
dispatch(setNewEmail(res.data.response.email));
|
||||
})
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_IS_CONFIRM_LOADED, SET_NEW_PASSWORD } from './actions';
|
||||
import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_IS_CONFIRM_LOADED, SET_NEW_PASSWORD, SET_NEW_EMAIL } from './actions';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import config from "../../../package.json";
|
||||
|
||||
@ -60,6 +60,10 @@ const authReducer = (state = initialState, action) => {
|
||||
return Object.assign({}, state, {
|
||||
password: action.password
|
||||
});
|
||||
case SET_NEW_EMAIL:
|
||||
return Object.assign({}, state, {
|
||||
email: action.email
|
||||
});
|
||||
case LOGOUT:
|
||||
return initialState;
|
||||
default:
|
||||
|
@ -15,17 +15,17 @@ export function getModulesList() {
|
||||
return IS_FAKE
|
||||
? fakeApi.getModulesList()
|
||||
: axios
|
||||
.get(`${API_URL}/modules`)
|
||||
.then(res => {
|
||||
const modules = res.data.response;
|
||||
return axios.all(
|
||||
modules.map(m => axios.get(`${window.location.origin}/${m}`))
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.map(d => d.data.response);
|
||||
return Promise.resolve({ data: { response } });
|
||||
});
|
||||
.get(`${API_URL}/modules`)
|
||||
.then(res => {
|
||||
const modules = res.data.response;
|
||||
return axios.all(
|
||||
modules.map(m => axios.get(`${window.location.origin}/${m}`))
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.map(d => d.data.response);
|
||||
return Promise.resolve({ data: { response } });
|
||||
});
|
||||
}
|
||||
|
||||
export function getUser() {
|
||||
@ -44,8 +44,8 @@ export function getPasswordSettings(key) {
|
||||
return IS_FAKE
|
||||
? fakeApi.getPasswordSettings()
|
||||
: axios.get(`${API_URL}/settings/security/password`, {
|
||||
headers: { confirm: key }
|
||||
});
|
||||
headers: { confirm: key }
|
||||
});
|
||||
}
|
||||
|
||||
export function createUser(data, key) {
|
||||
@ -63,10 +63,9 @@ export function validateConfirmLink(link) {
|
||||
}
|
||||
|
||||
export function changePassword(userId, password, key) {
|
||||
const IS_FAKE = true;
|
||||
return IS_FAKE
|
||||
? fakeApi.changePassword()
|
||||
: axios.put(`${API_URL}/${userId}/password`, {password}, {
|
||||
headers: { confirm: key }
|
||||
: axios.put(`${API_URL}/people/${userId}/password`, password, {
|
||||
headers: { confirm: key }
|
||||
});
|
||||
}
|
||||
}
|
@ -150,12 +150,7 @@ export function validateConfirmLink(link) {
|
||||
}
|
||||
|
||||
export function changePassword() {
|
||||
const data = {
|
||||
//minLength: 12,
|
||||
//upperCase: true,
|
||||
//digits: true,
|
||||
//specSymbols: true
|
||||
};
|
||||
const data = { password: "password" };
|
||||
|
||||
return fakeResponse(data);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-components",
|
||||
"version": "1.0.97",
|
||||
"version": "1.0.98",
|
||||
"description": "Ascensio System SIA component library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/asc-web-components.js",
|
||||
|
@ -1,8 +1,25 @@
|
||||
import React, { memo } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ModalDialog from '../modal-dialog'
|
||||
import Button from '../button'
|
||||
import AvatarEditorBody from './sub-components/avatar-editor-body'
|
||||
import Aside from "../layout/sub-components/aside";
|
||||
import IconButton from '../icon-button'
|
||||
import styled from 'styled-components'
|
||||
import { desktop } from '../../utils/device';
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
const Header = styled.div`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const StyledAside = styled(Aside)`
|
||||
padding: 10px;
|
||||
|
||||
.aside-save-button{
|
||||
margin-top: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
class AvatarEditor extends React.Component {
|
||||
constructor(props) {
|
||||
@ -14,7 +31,9 @@ class AvatarEditor extends React.Component {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height:0
|
||||
height: 0,
|
||||
|
||||
displayType: this.props.displayType !== 'auto' ? this.props.displayType : window.innerWidth <= desktop.match(/\d+/)[0] ? 'aside' : 'modal'
|
||||
}
|
||||
|
||||
this.onClose = this.onClose.bind(this);
|
||||
@ -24,19 +43,29 @@ class AvatarEditor extends React.Component {
|
||||
this.onLoadFile = this.onLoadFile.bind(this);
|
||||
this.onPositionChange = this.onPositionChange.bind(this);
|
||||
|
||||
this.onDeleteImage = this.onDeleteImage.bind(this)
|
||||
|
||||
this.onDeleteImage = this.onDeleteImage.bind(this);
|
||||
this.throttledResize = throttle(this.resize, 300);
|
||||
|
||||
}
|
||||
onImageChange(file){
|
||||
if(typeof this.props.onImageChange === 'function') this.props.onImageChange(file);
|
||||
resize = () => {
|
||||
if (this.props.displayType === "auto") {
|
||||
const type = window.innerWidth <= desktop.match(/\d+/)[0] ? 'aside' : 'modal';
|
||||
if (type !== this.state.displayType)
|
||||
this.setState({
|
||||
displayType: type
|
||||
});
|
||||
}
|
||||
}
|
||||
onDeleteImage(){
|
||||
onImageChange(file) {
|
||||
if (typeof this.props.onImageChange === 'function') this.props.onImageChange(file);
|
||||
}
|
||||
onDeleteImage() {
|
||||
this.setState({
|
||||
isContainsFile: false
|
||||
})
|
||||
if(typeof this.props.onDeleteImage === 'function') this.props.onDeleteImage();
|
||||
if (typeof this.props.onDeleteImage === 'function') this.props.onDeleteImage();
|
||||
}
|
||||
onPositionChange(data){
|
||||
onPositionChange(data) {
|
||||
this.setState(data);
|
||||
}
|
||||
onLoadFileError(error) {
|
||||
@ -53,10 +82,10 @@ class AvatarEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
onLoadFile(file) {
|
||||
if(typeof this.props.onLoadFile === 'function') this.props.onLoadFile(file);
|
||||
if (typeof this.props.onLoadFile === 'function') this.props.onLoadFile(file);
|
||||
this.setState({ isContainsFile: true });
|
||||
}
|
||||
|
||||
|
||||
onSaveButtonClick() {
|
||||
this.props.onSave(this.state.isContainsFile, {
|
||||
x: this.state.x,
|
||||
@ -76,12 +105,59 @@ class AvatarEditor extends React.Component {
|
||||
this.setState({ visible: this.props.visible });
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.throttledResize);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.throttledResize);
|
||||
}
|
||||
render() {
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
visible={this.state.visible}
|
||||
headerContent={this.props.headerLabel}
|
||||
bodyContent={
|
||||
this.state.displayType === "modal" ?
|
||||
<ModalDialog
|
||||
visible={this.state.visible}
|
||||
headerContent={this.props.headerLabel}
|
||||
bodyContent={
|
||||
<AvatarEditorBody
|
||||
onImageChange={this.onImageChange}
|
||||
onPositionChange={this.onPositionChange}
|
||||
onLoadFileError={this.onLoadFileError}
|
||||
onLoadFile={this.onLoadFile}
|
||||
deleteImage={this.onDeleteImage}
|
||||
maxSize={this.props.maxSize * 1000000} // megabytes to bytes
|
||||
accept={this.props.accept}
|
||||
image={this.props.image}
|
||||
chooseFileLabel={this.props.chooseFileLabel}
|
||||
unknownTypeError={this.props.unknownTypeError}
|
||||
maxSizeFileError={this.props.maxSizeFileError}
|
||||
unknownError={this.props.unknownError}
|
||||
/>
|
||||
}
|
||||
footerContent={[
|
||||
<Button
|
||||
key="SaveBtn"
|
||||
label={this.props.saveButtonLabel}
|
||||
primary={true}
|
||||
onClick={this.onSaveButtonClick}
|
||||
/>
|
||||
]}
|
||||
onClose={this.props.onClose}
|
||||
/>
|
||||
:
|
||||
<StyledAside
|
||||
visible={this.state.visible}
|
||||
scale={true}
|
||||
>
|
||||
<Header>
|
||||
<IconButton
|
||||
iconName={"ArrowPathIcon"}
|
||||
color="#A3A9AE"
|
||||
size="16"
|
||||
onClick={this.onClose}
|
||||
/>
|
||||
</Header>
|
||||
|
||||
<AvatarEditorBody
|
||||
onImageChange={this.onImageChange}
|
||||
onPositionChange={this.onPositionChange}
|
||||
@ -96,23 +172,17 @@ class AvatarEditor extends React.Component {
|
||||
maxSizeFileError={this.props.maxSizeFileError}
|
||||
unknownError={this.props.unknownError}
|
||||
/>
|
||||
}
|
||||
footerContent={[
|
||||
|
||||
<Button
|
||||
key="SaveBtn"
|
||||
className="aside-save-button"
|
||||
label={this.props.saveButtonLabel}
|
||||
primary={true}
|
||||
onClick={this.onSaveButtonClick}
|
||||
/>,
|
||||
<Button
|
||||
key="CancelBtn"
|
||||
label={this.props.cancelButtonLabel}
|
||||
onClick={this.onClose}
|
||||
style={{ marginLeft: "8px" }}
|
||||
/>
|
||||
]}
|
||||
onClose={this.props.onClose}
|
||||
/>
|
||||
</StyledAside>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -134,7 +204,9 @@ AvatarEditor.propTypes = {
|
||||
onImageChange: PropTypes.func,
|
||||
unknownTypeError: PropTypes.string,
|
||||
maxSizeFileError: PropTypes.string,
|
||||
unknownError: PropTypes.string
|
||||
unknownError: PropTypes.string,
|
||||
displayType: PropTypes.oneOf(['auto', 'modal', 'aside']),
|
||||
|
||||
};
|
||||
|
||||
AvatarEditor.defaultProps = {
|
||||
@ -145,6 +217,7 @@ AvatarEditor.defaultProps = {
|
||||
cancelButtonLabel: 'Cancel',
|
||||
maxSizeErrorLabel: 'File is too big',
|
||||
accept: ['image/png', 'image/jpeg'],
|
||||
displayType: 'auto'
|
||||
};
|
||||
|
||||
export default AvatarEditor;
|
||||
|
@ -5,7 +5,7 @@ import ReactAvatarEditor from 'react-avatar-editor'
|
||||
import PropTypes from 'prop-types'
|
||||
import { default as ASCAvatar } from '../../avatar/index'
|
||||
import accepts from 'attr-accept'
|
||||
import {Text} from '../../text'
|
||||
import { Text } from '../../text'
|
||||
import { tablet } from '../../../utils/device';
|
||||
|
||||
const StyledErrorContainer = styled.div`
|
||||
@ -58,7 +58,8 @@ const StyledAvatarContainer = styled.div`
|
||||
text-align: center;
|
||||
|
||||
.custom-range{
|
||||
width: 300px;
|
||||
width: 100%;
|
||||
display: block
|
||||
}
|
||||
.avatar-container{
|
||||
display: inline-block;
|
||||
@ -66,10 +67,11 @@ const StyledAvatarContainer = styled.div`
|
||||
}
|
||||
.editor-container{
|
||||
display: inline-block;
|
||||
width: calc(100% - 170px);
|
||||
width: auto;
|
||||
padding: 0 30px;
|
||||
position: relative;
|
||||
@media ${tablet} {
|
||||
width: 100%
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -109,7 +111,7 @@ class AvatarEditorBody extends React.Component {
|
||||
|
||||
this.onDropAccepted = this.onDropAccepted.bind(this);
|
||||
this.onDropRejected = this.onDropRejected.bind(this);
|
||||
|
||||
|
||||
|
||||
this.onPositionChange = this.onPositionChange.bind(this);
|
||||
|
||||
|
@ -11,18 +11,18 @@ const StyledAside = styled.aside`
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateX(${props => (props.visible ? "0" : "240px")});
|
||||
transform: translateX(${props => (props.visible ? "0" : props.scale ? "100%" : "240px")});
|
||||
transition: transform 0.3s ease-in-out;
|
||||
width: 240px;
|
||||
width: ${props => (props.scale ? "100%" : "240px")};
|
||||
z-index: 400;
|
||||
`;
|
||||
|
||||
const Aside = React.memo(props => {
|
||||
//console.log("Aside render");
|
||||
const { visible, children } = props;
|
||||
const { visible, children, scale, className} = props;
|
||||
|
||||
return (
|
||||
<StyledAside visible={visible}>
|
||||
<StyledAside visible={visible} scale={scale} className={className}>
|
||||
<Scrollbar>{children}</Scrollbar>
|
||||
</StyledAside>
|
||||
);
|
||||
@ -32,10 +32,15 @@ Aside.displayName = "Aside";
|
||||
|
||||
Aside.propTypes = {
|
||||
visible: PropTypes.bool,
|
||||
scale: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
])
|
||||
};
|
||||
Aside.defaultProps = {
|
||||
scale: false,
|
||||
};
|
||||
|
||||
export default Aside;
|
||||
|
@ -124,7 +124,7 @@ namespace ASC.Web.Studio.Core.Notify
|
||||
public void UserPasswordChange(int tenantId, UserInfo userInfo)
|
||||
{
|
||||
var hash = Hasher.Base64Hash(CoreContext.Authentication.GetUserPasswordHash(tenantId, userInfo.ID));
|
||||
var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(tenantId, userInfo.Email, ConfirmType.PasswordChange, hash);
|
||||
var confirmationUrl = CommonLinkUtility.GetConfirmationUrl(tenantId, userInfo.Email, ConfirmType.PasswordChange, hash + userInfo.ID, userInfo.ID);
|
||||
|
||||
static string greenButtonText() => WebstudioNotifyPatternResource.ButtonChangePassword;
|
||||
|
||||
|
@ -31,6 +31,7 @@ using System.Threading;
|
||||
using ASC.Common.Caching;
|
||||
using ASC.Common.Utils;
|
||||
using ASC.Core;
|
||||
using ASC.Core.Configuration;
|
||||
using ASC.Notify;
|
||||
using ASC.Notify.Model;
|
||||
using ASC.Notify.Patterns;
|
||||
@ -44,7 +45,7 @@ namespace ASC.Web.Studio.Core.Notify
|
||||
private readonly INotifyClient client;
|
||||
private readonly ICacheNotify<NotifyItem> cache;
|
||||
|
||||
private static string EMailSenderName { get { return ASC.Core.Configuration.Constants.NotifyEMailSenderSysName; } }
|
||||
private static string EMailSenderName { get { return Constants.NotifyEMailSenderSysName; } }
|
||||
|
||||
public StudioNotifyServiceSender()
|
||||
{
|
||||
@ -56,7 +57,6 @@ namespace ASC.Web.Studio.Core.Notify
|
||||
public void OnMessage(NotifyItem item)
|
||||
{
|
||||
CoreContext.TenantManager.SetCurrentTenant(item.TenantId);
|
||||
SecurityContext.AuthenticateMe(item.TenantId, Guid.Parse(item.UserId));
|
||||
CultureInfo culture = null;
|
||||
|
||||
var tenant = CoreContext.TenantManager.GetCurrentTenant(false);
|
||||
@ -65,10 +65,14 @@ namespace ASC.Web.Studio.Core.Notify
|
||||
culture = tenant.GetCulture();
|
||||
}
|
||||
|
||||
var user = CoreContext.UserManager.GetUsers(item.TenantId, SecurityContext.CurrentAccount.ID);
|
||||
if (!string.IsNullOrEmpty(user.CultureName))
|
||||
if (Guid.TryParse(item.UserId, out var userId) && !userId.Equals(Constants.Guest.ID) && !userId.Equals(Guid.Empty))
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(user.CultureName);
|
||||
SecurityContext.AuthenticateMe(item.TenantId, userId);
|
||||
var user = CoreContext.UserManager.GetUsers(item.TenantId, userId);
|
||||
if (!string.IsNullOrEmpty(user.CultureName))
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(user.CultureName);
|
||||
}
|
||||
}
|
||||
|
||||
if (culture != null && !Equals(Thread.CurrentThread.CurrentCulture, culture))
|
||||
|
@ -66,32 +66,6 @@ namespace ASC.Web.Studio.Utility
|
||||
Storage = 21
|
||||
}
|
||||
|
||||
// emp-invite - confirm ivite by email
|
||||
// portal-suspend - confirm portal suspending - Tenant.SetStatus(TenantStatus.Suspended)
|
||||
// portal-continue - confirm portal continuation - Tenant.SetStatus(TenantStatus.Active)
|
||||
// portal-remove - confirm portal deletation - Tenant.SetStatus(TenantStatus.RemovePending)
|
||||
// DnsChange - change Portal Address and/or Custom domain name
|
||||
public enum ConfirmType
|
||||
{
|
||||
EmpInvite,
|
||||
LinkInvite,
|
||||
PortalSuspend,
|
||||
PortalContinue,
|
||||
PortalRemove,
|
||||
DnsChange,
|
||||
PortalOwnerChange,
|
||||
Activation,
|
||||
EmailChange,
|
||||
EmailActivation,
|
||||
PasswordChange,
|
||||
ProfileRemove,
|
||||
PhoneActivation,
|
||||
PhoneAuth,
|
||||
Auth,
|
||||
TfaActivation,
|
||||
TfaAuth
|
||||
}
|
||||
|
||||
public static class CommonLinkUtility
|
||||
{
|
||||
private static readonly Regex RegFilePathTrim = new Regex("/[^/]*\\.aspx", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
@ -515,7 +489,7 @@ namespace ASC.Web.Studio.Utility
|
||||
{
|
||||
var validationKey = EmailValidationKeyProvider.GetEmailKey(tenantId, email + confirmType + (postfix ?? ""));
|
||||
|
||||
var link = $"confirm/type={confirmType}?key={validationKey}";
|
||||
var link = $"confirm?type={confirmType}&key={validationKey}";
|
||||
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user