Merge branch 'release/v1.1.0' into bugfix/file-operations

This commit is contained in:
Alexey Safronov 2023-06-05 11:31:17 +04:00
commit 5296f8db73
44 changed files with 1944 additions and 1110 deletions

161
.github/workflows/build_packages.yml vendored Normal file
View File

@ -0,0 +1,161 @@
name: Build packages
on:
push:
branches:
- release/*
- develop
- hotfix/*
paths:
- build/install/deb**
- build/install/rpm**
workflow_dispatch:
env:
BRANCH_NAME: $(echo ${GITHUB_REF#refs/heads/})
PRODUCT_LOW: $(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' )
PRODUCT: "${{ github.event.repository.name }}"
BUILD_NUMBER: "${{ github.run_number }}"
jobs:
build_deb:
name: DEB packages
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Import GPG
uses: crazy-max/ghaction-import-gpg@v5
id: gpg_step
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASS }}
- name: Get files from repository
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Prepare build
id: get_vars
run: |
wget -O - https://dl.yarnpkg.com/debian/pubkey.gpg | \
sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/yarnkey.gpg --import
sudo chmod 644 /usr/share/keyrings/yarnkey.gpg
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" | \
sudo tee /etc/apt/sources.list.d/yarn.list
wget https://packages.microsoft.com/config/$(lsb_release -is | \
tr [:upper:] [:lower:])/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
wget -O - https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y dotnet-sdk-7.0 yarn nodejs dh-make rename dpkg-sig lintian
sudo npm install -g json
echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
echo "VERSION=$(echo "${{ github.ref }}" | grep -oP '\d+\.\d+\.\d+' || echo "1.0.0")" >> $GITHUB_OUTPUT
- name: Build
shell: bash
run: |
cd build/install/deb/
rename -f -v "s/product([^\/]*)$/${{ env.PRODUCT_LOW }}\$1/g" debian/* ../common/* ../common/logrotate/*
find ../ -type f -exec sed -i "s/{{product}}/${{ env.PRODUCT_LOW }}/g" {} ';'
sed -i "s/{{package_header_tag_version}}/${{ steps.get_vars.outputs.VERSION }}.$BUILD_NUMBER/g" \
debian/changelog debian/control
dpkg-buildpackage -uc -k${{ steps.gpg_step.outputs.fingerprint }}
- name: Upload to Nexus
run: |
for file in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/*.deb; do
echo $file
curl --verbose --user ${{ secrets.REPO_LOGIN }}:${{ secrets.REPO_PASS }} -H "Content-Type: multipart/form-data" \
--data-binary "@$file" ${{ secrets.REPO_URL_4TESTING_DEB }}
done
- name: Lint
run: |
lintian --suppress-tags=mismatched-override --profile debian /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/*.deb \
| tee -a file
if grep -qE '^(W:|E:)' file; then echo \
"::warning Noticedeb=lintian::$(cat file | awk '/^W:/ { ws += 1 } /^E:/ { es += 1 } END { print "Warnings:", ws, "Errors:", es }')"; fi
build_rpm:
name: RPM packages
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Import GPG
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASS }}
- name: Get files from repository
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Prepare build
id: get_vars
run: |
wget -O - https://dl.yarnpkg.com/debian/pubkey.gpg | sudo gpg --no-default-keyring --keyring \
gnupg-ring:/usr/share/keyrings/yarnkey.gpg --import
sudo chmod 644 /usr/share/keyrings/yarnkey.gpg
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" | \
sudo tee /etc/apt/sources.list.d/yarn.list
wget https://packages.microsoft.com/config/$(lsb_release -is | \
tr [:upper:] [:lower:])/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
wget -O - https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y dotnet-sdk-7.0 yarn nodejs dh-make rename python3-pip python3-rpm
sudo npm install -g json
sudo pip install rpmlint
echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
echo "VERSION=$(echo "${ GITHUB_REF##*/ }" | grep -oP '\d+\.\d+\.\d+' || echo "1.0.0")" >> $GITHUB_OUTPUT
- name: Build
run: |
cd build/install/rpm/SPECS
mv ./SOURCES/product.rpmlintrc ./SOURCES/${{ env.PRODUCT_LOW }}.rpmlintrc
wget https://github.com/ONLYOFFICE/${{ env.PRODUCT }}/archive/${{ env.BRANCH_NAME }}.tar.gz \
-O ./SOURCES/${{ env.PRODUCT }}-$(echo ${{ env.BRANCH_NAME }} | tr '/' '-').tar.gz
wget https://github.com/ONLYOFFICE/document-templates/archive/main/community-server.tar.gz \
-O ./SOURCES/document-templates-main-community-server.tar.gz
wget https://github.com/ONLYOFFICE/dictionaries/archive/master.tar.gz \
-O ./SOURCES/dictionaries-master.tar.gz
sed -i -e '/BuildRequires/d' product.spec
rpmbuild -D "packager Ascensio System SIA <support@onlyoffice.com>" -D "GIT_BRANCH $(echo ${{ env.BRANCH_NAME }} \
| tr '/' '-')" -D "_topdir $(pwd)" -D "version ${{ steps.get_vars.outputs.VERSION }}" \
-D "release ${{ env.BUILD_NUMBER }}" -ba product.spec
- name: Sign
run: |
cat << EOF >> $HOME/.rpmmacros
%_signature gpg
%_gpg_name ${{ secrets.GPG_KEY_NAME }}
%_gpg_path $HOME/.gnupg
%__gpg /usr/bin/gpg
EOF
gpg --export --armor --output onlyoffice-gpgkey.pub
rpm --import onlyoffice-gpgkey.pub
rpm --addsign /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm
- name: Upload
run: |
for file in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm; do
curl --verbose --user ${{ secrets.REPO_LOGIN }}:${{ secrets.REPO_PASS }} \
--upload-file "$file" ${{ secrets.REPO_URL_4TESTING_RPM }}
done
- name: Rpmlint
run: |
for package in /home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/RPMS/x86_64/*.rpm
do rpmlint --ignore-unused-rpmlintrc --rpmlintrc \
/home/runner/work/${{ env.PRODUCT }}/${{ env.PRODUCT }}/build/install/rpm/SPECS/SOURCES/${{ env.PRODUCT_LOW }}.rpmlintrc $package \
| tee -a file2
done
if grep -qE '^(W:|E:)' file2; then echo \
"::warning NoticeRpm=rpmLint::$(cat file2 | awk '/W:/ { ws += 1 } /E:/ { es += 1 } END { print "Warnings:", ws, "Errors:", es }')" ; fi

View File

@ -552,14 +552,14 @@ install_docker_compose () {
install_service python3
fi
if command_exists apt-get; then
apt-get -y update -qq
apt-get -y -q install python3-pip
elif command_exists yum; then
py3_version=$(python3 -c 'import sys; print(sys.version_info.minor)')
if [[ $py3_version -lt 6 ]]; then
curl -O https://bootstrap.pypa.io/pip/3.$py3_version/get-pip.py
else
curl -O https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py || true
rm get-pip.py
fi
fi
python3 get-pip.py
rm get-pip.py
python3 -m pip install --upgrade pip
python3 -m pip install docker-compose
@ -571,21 +571,6 @@ install_docker_compose () {
fi
}
install_jq () {
if command_exists apt-get; then
apt-get -y update
apt-get -y -q install jq
elif command_exists yum; then
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm || true
yum -y install jq
fi
if ! command_exists jq; then
echo "command jq not found"
exit 1;
fi
}
check_ports () {
RESERVED_PORTS=(3306 8092);
ARRAY_PORTS=();
@ -779,10 +764,6 @@ get_available_version () {
install_curl;
fi
if ! command_exists jq ; then
install_jq
fi
CREDENTIALS="";
AUTH_HEADER="";
TAGS_RESP="";
@ -953,6 +934,13 @@ download_files () {
install_service svn subversion
fi
if ! command_exists jq ; then
if command_exists yum; then
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm
fi
install_service jq
fi
svn export --force https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}/branches/${GIT_BRANCH}/build/install/docker/ ${BASE_DIR}
reconfigure STATUS ${STATUS}
@ -965,8 +953,8 @@ download_files () {
}
reconfigure () {
local VARIABLE_NAME=$1
local VARIABLE_VALUE=$2
local VARIABLE_NAME="$1"
local VARIABLE_VALUE="$2"
if [[ -n ${VARIABLE_VALUE} ]]; then
sed -i "s~${VARIABLE_NAME}=.*~${VARIABLE_NAME}=${VARIABLE_VALUE}~g" $BASE_DIR/.env

View File

@ -7,7 +7,7 @@ privacy-breach-generic var/www/{{product}}/services/*/node_modules/*
# Ignoring node_modules errors due to lack of ability to influence them
script-not-executable var/www/{{product}}/services/*/node_modules/*
# Ignoring node_modules errors due to lack of ability to influence them
unusual-interpreter var/www/{{product}}/services/*/node_modules/*
unusual-interpreter */node_modules/*
# The use of the /var/www directory is caused by its past history as the default document root
dir-or-file-in-var-www
@ -44,3 +44,12 @@ untranslatable-debconf-templates
# We use this to protect sensitive information (ie passwords) in the config file
non-standard-file-perm
# There are instances where temporary or future code sections need to be retained for documentation or future development purposes
no-code-sections
# Ignoring errors due to lack of ability to influence them
library-not-linked-against-libc
# Some file triggers a privacy concern, specifically references an image files .png
privacy-breach-generic

View File

@ -7,9 +7,6 @@ addFilter(r' W: non-standard-(uid|gid)')
# This is necessary to ensure that all child packages are updated correctly
addFilter(r'W: requires-on-release')
# The signature of packages occurs in further stages
addFilter(r'E: no-signature')
# The basic documentation comes with the common package
addFilter(r'W: no-documentation')
@ -45,3 +42,18 @@ addFilter(r'non-standard-executable-perm')
# We use this to protect sensitive information (ie passwords) in the config file
addFilter(r'non-readable')
# No one license from allowed pull AGPLv3, AGPLv3+ worked
addFilter(r'W: invalid-license AGPLv3')
# Сertain services require write access to the log directory. These services are launched under a user account that is different from the root user.
addFilter(r'logrotate-user-writable-log-dir')
# The use of the /var/www directory is caused by its past history as the default document root
addFilter(r'W: non-standard-dir-in-var www')
# Shared libraries centos7-librdkafka.so, libgrpc_csharp_ext.x64.so location of which is hardcoded
addFilter(r'W: binary-or-shlib-calls-gethostbyname')
# There are the same files, however on a different languages
addFilter(r'files-duplicate')

View File

@ -1,4 +1,5 @@
%package backup
Packager: %{packager}
Summary: Backup
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -8,6 +9,7 @@ AutoReqProv: no
Backup
%package common
Packager: %{packager}
Summary: Common
Group: Applications/Internet
Requires: logrotate
@ -15,6 +17,7 @@ Requires: logrotate
Common
%package files-services
Packager: %{packager}
Summary: Files-services
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -25,6 +28,7 @@ AutoReqProv: no
Files-services
%package notify
Packager: %{packager}
Summary: Notify
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -34,6 +38,7 @@ AutoReqProv: no
Notify
%package files
Packager: %{packager}
Summary: Files
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -43,6 +48,7 @@ AutoReqProv: no
Files
%package proxy
Packager: %{packager}
Summary: Proxy
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -53,6 +59,7 @@ AutoReqProv: no
Proxy
%package studio-notify
Packager: %{packager}
Summary: Studio-notify
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -62,6 +69,7 @@ AutoReqProv: no
Studio-notify
%package people-server
Packager: %{packager}
Summary: People-server
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -71,6 +79,7 @@ AutoReqProv: no
People-server
%package socket
Packager: %{packager}
Summary: Socket
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -80,6 +89,7 @@ AutoReqProv: no
Socket
%package studio
Packager: %{packager}
Summary: Studio
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -89,6 +99,7 @@ AutoReqProv: no
Studio
%package api
Packager: %{packager}
Summary: Api
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -98,6 +109,7 @@ AutoReqProv: no
Api
%package api-system
Packager: %{packager}
Summary: Api-system
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -107,6 +119,7 @@ AutoReqProv: no
Api-system
%package ssoauth
Packager: %{packager}
Summary: Ssoauth
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -116,6 +129,7 @@ AutoReqProv: no
Ssoauth
%package clear-events
Packager: %{packager}
Summary: Clear-events
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -125,6 +139,7 @@ AutoReqProv: no
Clear-events
%package backup-background
Packager: %{packager}
Summary: Backup-background
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -134,6 +149,7 @@ AutoReqProv: no
Backup-background
%package radicale
Packager: %{packager}
Summary: Radicale
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -143,6 +159,7 @@ AutoReqProv: no
Radicale
%package doceditor
Packager: %{packager}
Summary: Doceditor
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -152,6 +169,7 @@ AutoReqProv: no
Doceditor
%package migration-runner
Packager: %{packager}
Summary: Migration-runner
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -161,6 +179,7 @@ AutoReqProv: no
Migration-runner
%package login
Packager: %{packager}
Summary: Login
Group: Applications/Internet
Requires: %name-common = %version-%release
@ -170,6 +189,7 @@ AutoReqProv: no
Login
%package healthchecks
Packager: %{packager}
Summary: Healthchecks
Group: Applications/Internet
Requires: %name-common = %version-%release

View File

@ -14,6 +14,7 @@ AutoReqProv: no
URL: http://onlyoffice.com
Vendor: Ascensio System SIA
Packager: %{packager}
License: AGPLv3
Source0: https://github.com/ONLYOFFICE/%{product}/archive/%GIT_BRANCH.tar.gz#/%{sourcename}.tar.gz

View File

@ -434,7 +434,15 @@ public class DiscDataStore : BaseStorage
}
var entries = Directory.GetFiles(targetDir, "*.*", SearchOption.AllDirectories);
var size = entries.Select(entry => _crypt.GetFileSize(entry)).Sum();
var size = entries.Where(r =>
{
if (QuotaController == null || string.IsNullOrEmpty(QuotaController.ExcludePattern))
{
return true;
}
return !Path.GetFileName(r).StartsWith(QuotaController.ExcludePattern);
}
).Select(_crypt.GetFileSize).Sum();
var subDirs = Directory.GetDirectories(targetDir, "*", SearchOption.AllDirectories).ToList();
subDirs.Reverse();

View File

@ -457,7 +457,15 @@ public class GoogleCloudStorage : BaseStorage
await foreach (var obj in objToDel)
{
await storage.DeleteObjectAsync(_bucket, obj.Name);
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(obj.Name).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
}
}

View File

@ -36,4 +36,6 @@ public interface IQuotaController
void QuotaUsedSet(string module, string domain, string dataTag, long size);
void QuotaUsedCheck(long size);
string ExcludePattern { get; set; }
}

View File

@ -189,10 +189,10 @@ public class RackspaceCloudStorage : BaseStorage
return SaveAsync(domain, path, stream, string.Empty, string.Empty, ACL.Auto, contentEncoding, cacheDays);
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5,
@ -200,7 +200,7 @@ public class RackspaceCloudStorage : BaseStorage
{
var buffered = _tempStream.GetBuffered(stream);
if (EnableQuotaCheck(domain))
if (EnableQuotaCheck(domain))
{
QuotaController.QuotaUsedCheck(buffered.Length);
}
@ -482,7 +482,15 @@ public class RackspaceCloudStorage : BaseStorage
foreach (var obj in objToDel)
{
client.DeleteObject(_private_container, obj.Name);
await QuotaUsedDelete(domain, obj.Bytes);
if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(obj.Name).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, obj.Bytes);
}
}
}
}
@ -755,7 +763,7 @@ public class RackspaceCloudStorage : BaseStorage
var obj = client.ListObjects(_private_container, null, null, null, prefix, _region).FirstOrDefault();
var lastModificationDate = obj == null ? throw new FileNotFoundException("File not found" + prefix) : obj.LastModified.UtcDateTime;
var lastModificationDate = obj == null ? throw new FileNotFoundException("File not found" + prefix) : obj.LastModified.UtcDateTime;
var etag = '"' + lastModificationDate.Ticks.ToString("X8", CultureInfo.InvariantCulture) + '"';

View File

@ -495,7 +495,14 @@ public class S3Storage : BaseStorage
await client.DeleteObjectAsync(deleteRequest);
await QuotaUsedDelete(domain, s3Object.Size);
if (QuotaController != null)
{
if (string.IsNullOrEmpty(QuotaController.ExcludePattern) ||
!Path.GetFileName(s3Object.Key).StartsWith(QuotaController.ExcludePattern))
{
await QuotaUsedDelete(domain, s3Object.Size);
}
}
}
}

View File

@ -50,6 +50,7 @@ public class TenantQuotaController : IQuotaController
private readonly TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> _maxTotalSizeChecker;
private Lazy<long> _lazyCurrentSize;
private long _currentSize;
public string ExcludePattern { get; set; }
public TenantQuotaController(TenantManager tenantManager, AuthContext authContext, TenantQuotaFeatureChecker<MaxFileSizeFeature, long> maxFileSizeChecker, TenantQuotaFeatureChecker<MaxTotalSizeFeature, long> maxTotalSizeChecker)
{
@ -58,13 +59,14 @@ public class TenantQuotaController : IQuotaController
_maxTotalSizeChecker = maxTotalSizeChecker;
_authContext = authContext;
}
public void Init(int tenant)
public void Init(int tenant, string excludePattern = null)
{
_tenant = tenant;
_lazyCurrentSize = new Lazy<long>(() => _tenantManager.FindTenantQuotaRows(tenant)
.Where(r => UsedInQuota(r.Tag))
.Sum(r => r.Counter));
ExcludePattern = excludePattern;
}
public void QuotaUsedAdd(string module, string domain, string dataTag, long size, bool quotaCheckFileSize = true)

View File

@ -394,6 +394,15 @@ public class FactoryIndexer<T> : IFactoryIndexer where T : class, ISearchItem
Logger.ErrorUpdate(e);
}
}
public Task<bool> UpdateAsync(T data, UpdateAction action, Expression<Func<T, IList>> field, bool immediately = true)
{
var t = _serviceProvider.GetService<T>();
return !Support(t)
? Task.FromResult(false)
: Queue(() => _indexer.Update(data, action, field, immediately));
}
public void Update(T data, Expression<Func<Selector<T>, Selector<T>>> expression, bool immediately = true, params Expression<Func<T, object>>[] fields)
{

View File

@ -47,7 +47,7 @@
"oidc": {
"authority": ""
},
"server-root": ""
"server-root": ""
},
"license": {
"file": {
@ -296,7 +296,8 @@
"book-training-email": "training@onlyoffice.com",
"documentation-email": "documentation@onlyoffice.com",
"max-upload-size": 5242880,
"zendesk-key": ""
"zendesk-key": "",
"samesite": ""
},
"ConnectionStrings": {
"default": {
@ -342,7 +343,8 @@
"storageBucket": "",
"messagingSenderId": "",
"appId": "",
"measurementId": ""
"measurementId": "",
"databaseURL": ""
},
"debug-info": {
"enabled": "true"

View File

@ -1,6 +1,6 @@
{
"name": "docspace",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"workspaces": {
"packages": [

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/client",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"homepage": "",
"scripts": {

View File

@ -487,6 +487,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
"/files/trash/filter",
"/accounts",
"/accounts/changeOwner",
"/accounts/filter",
"/accounts/create/:type",

View File

@ -0,0 +1,165 @@
import FileReactSvgUrl from "PUBLIC_DIR/images/icons/24/file.svg?url";
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { isMobileOnly } from "react-device-detect";
import ModalDialog from "@docspace/components/modal-dialog";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import Textarea from "@docspace/components/textarea";
import IconButton from "@docspace/components/icon-button";
import toastr from "@docspace/components/toast/toastr";
import {
getCrashReport,
downloadJson,
getCurrentDate,
} from "SRC_DIR/helpers/crashReport";
const ModalDialogContainer = styled(ModalDialog)`
#modal-dialog {
width: auto;
max-width: 520px;
max-height: 560px;
}
.report-description {
margin-bottom: 16px;
}
.report-wrapper {
margin-top: 8px;
height: 48px;
display: flex;
gap: 16px;
align-items: center;
.report-filename {
display: flex;
}
.file-icon {
width: 24px;
user-select: none;
}
.icon-button {
cursor: pointer;
}
}
`;
const ReportDialog = (props) => {
const { t, ready } = useTranslation(["Common"]);
const { visible, onClose, error, user, version, FirebaseHelper } = props;
const [report, setReport] = useState({});
const [description, setDescription] = useState("");
useEffect(() => {
const report = getCrashReport(user.id, version, user.cultureName, error);
setReport(report);
console.log(report);
}, []);
const onChangeTextareaValue = (e) => {
setDescription(e.target.value);
};
const onClickDownload = () => {
downloadJson(report, fileTitle);
};
const onClickSend = async () => {
try {
const reportWithDescription = Object.assign(report, {
description: description,
});
await FirebaseHelper.sendCrashReport(reportWithDescription);
toastr.success(t("ErrorReportSuccess"));
onCloseAction();
} catch (e) {
console.error(e);
toastr.error(e);
}
};
const onCloseAction = () => {
setDescription("");
onClose();
};
const fileTitle = t("ErrorReport") + " " + getCurrentDate();
return (
<ModalDialogContainer
isLoading={!ready}
visible={visible}
onClose={onCloseAction}
displayType="modal"
>
<ModalDialog.Header>{t("ErrorReport")}</ModalDialog.Header>
<ModalDialog.Body>
<Text className="report-description" noSelect>
{t("ErrorReportDescription")}
</Text>
<Textarea
placeholder={t("RecoverDescribeYourProblemPlaceholder")}
value={description}
onChange={onChangeTextareaValue}
autoFocus
areaSelect
heightTextArea={72}
fontSize={13}
/>
<div className="report-wrapper">
<img src={FileReactSvgUrl} className="file-icon" />
<Text as="div" fontWeight={600} noSelect className="report-filename">
{fileTitle}
<Text fontWeight={600} noSelect color="#A3A9AE">
.json
</Text>
</Text>
<IconButton
className="icon-button"
iconName={DownloadReactSvgUrl}
size="16"
isfill={true}
onClick={onClickDownload}
/>
</div>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendButton"
label={t("SendButton")}
size="normal"
primary
scale={isMobileOnly}
onClick={onClickSend}
/>
<Button
key="CancelButton"
label={t("CancelButton")}
size="normal"
scale={isMobileOnly}
onClick={onCloseAction}
/>
</ModalDialog.Footer>
</ModalDialogContainer>
);
};
export default inject(({ auth }) => {
const { user } = auth.userStore;
const { firebaseHelper } = auth.settingsStore;
return {
user,
version: auth.version,
FirebaseHelper: firebaseHelper,
};
})(observer(ReportDialog));

View File

@ -29,6 +29,7 @@ import LogoutConnectionDialog from "./LogoutConnectionDialog";
import LogoutAllConnectionDialog from "./LogoutAllConnectionDialog";
import CreateRoomConfirmDialog from "./CreateRoomConfirmDialog";
import PortalRenamingDialog from "./PortalRenamingDialog";
import ReportDialog from "./ReportDialog";
export {
EmptyTrashDialog,
@ -62,4 +63,5 @@ export {
InviteUsersWarningDialog,
LogoutAllConnectionDialog,
PortalRenamingDialog,
ReportDialog,
};

View File

@ -0,0 +1,37 @@
import saveAs from "file-saver";
export const getCrashReport = (userId, version, language, error) => {
const currentTime = new Date();
const reportTime = currentTime.toTimeString();
const lsObject = JSON.stringify(window.localStorage) || "";
const report = {
url: window.origin,
userId: userId,
version: version,
platform: navigator?.platform,
userAgent: navigator?.userAgent,
language: language || "en",
errorMessage: error?.message,
errorStack: error?.stack,
localStorage: lsObject,
reportTime: reportTime,
};
return report;
};
export const downloadJson = (json, fileName) => {
const cleanJson = JSON.stringify(json);
const data = new Blob([cleanJson], { type: "application/json" });
const url = window.URL.createObjectURL(data);
saveAs(url, `${fileName}.json`);
};
export const getCurrentDate = () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
return `${day}.${month}.${year}`;
};

View File

@ -26,10 +26,16 @@ const PeopleSection = React.memo(() => {
/>
<PrivateRoute
exact
withManager
withManager
path={["/accounts"]}
component={HomeRedirectToFilter}
/>
<PrivateRoute
exact
withManager
path={["/accounts/changeOwner"]}
component={RedirectWithChangeOwnerDialog}
/>
<PrivateRoute
path={"/accounts/filter"}
withManager
@ -55,6 +61,20 @@ const HomeRedirectToFilter = (props) => {
return <Redirect to={`/accounts/filter?${urlFilter}`} />;
};
const RedirectWithChangeOwnerDialog = (props) => {
const filter = Filter.getDefault();
const urlFilter = filter.toUrlParams();
return (
<Redirect
to={{
pathname: "/accounts/filter",
search: `?${urlFilter}`,
state: { openChangeOwnerDialog: true },
}}
/>
);
};
const PeopleContent = (props) => {
const { loadBaseInfo, isLoading, setFirstLoad } = props;

View File

@ -44,6 +44,8 @@ const PureHome = ({
withPaging,
onClickBack,
setPortalTariff,
setChangeOwnerDialogVisible,
}) => {
const { location } = history;
const { pathname } = location;
@ -73,6 +75,9 @@ const PureHome = ({
setIsLoading(false);
setIsRefresh(false);
});
if (location?.state?.openChangeOwnerDialog)
setChangeOwnerDialogVisible(true);
}
}, [pathname, location, setSelectedNode]);
@ -140,24 +145,15 @@ export default inject(
const { settingsStore, currentTariffStatusStore } = auth;
const { setPortalTariff } = currentTariffStatusStore;
const { showCatalog, withPaging } = settingsStore;
const {
usersStore,
selectedGroupStore,
loadingStore,
viewAs,
} = peopleStore;
const { usersStore, selectedGroupStore, loadingStore, viewAs } =
peopleStore;
const { getUsersList } = usersStore;
const { selectedGroup } = selectedGroupStore;
const { setSelectedNode } = treeFoldersStore;
const { onClickBack } = filesActionsStore;
const {
isLoading,
setIsLoading,
setIsRefresh,
firstLoad,
setFirstLoad,
} = loadingStore;
const { isLoading, setIsLoading, setIsRefresh, firstLoad, setFirstLoad } =
loadingStore;
const { setChangeOwnerDialogVisible } = peopleStore.dialogStore;
return {
setPortalTariff,
isAdmin: auth.isAdmin,
@ -176,6 +172,7 @@ export default inject(
snackbarExist: auth.settingsStore.snackbarExist,
withPaging,
onClickBack,
setChangeOwnerDialogVisible,
};
}
)(observer(withRouter(Home)));

View File

@ -1,23 +1,95 @@
import React from "react";
import PropTypes from "prop-types";
import ErrorContainer from "@docspace/common/components/ErrorContainer";
import React, { useState } from "react";
import styled from "styled-components";
import { inject, observer } from "mobx-react";
import { I18nextProvider, useTranslation } from "react-i18next";
import ErrorContainer from "@docspace/common/components/ErrorContainer";
import Link from "@docspace/components/link";
import { ZendeskAPI } from "@docspace/common/components/Zendesk";
import { ReportDialog } from "SRC_DIR/components/dialogs";
import DocspaceLogo from "SRC_DIR/DocspaceLogo";
import i18n from "./i18n";
const Error520 = ({ match }) => {
const StyledWrapper = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 16px;
.logo {
margin-bottom: 28px;
}
.link {
margin-top: 24px;
}
`;
const Error520 = ({ errorLog, currentColorScheme, FirebaseHelper }) => {
const { t } = useTranslation(["Common"]);
const { error } = (match && match.params) || {};
const [reportDialogVisible, setReportDialogVisible] = useState(false);
const showDialog = () => {
setReportDialogVisible(true);
};
const closeDialog = () => {
setReportDialogVisible(false);
};
const onReloadClick = () => {
window.location.reload();
};
ZendeskAPI("webWidget", "show");
if (!FirebaseHelper.isEnabledDB)
return <ErrorContainer headerText={t("SomethingWentWrong")} />;
return (
<ErrorContainer headerText={t("SomethingWentWrong")} bodyText={error} />
<StyledWrapper>
<DocspaceLogo className="logo" />
<ErrorContainer
isPrimaryButton={false}
headerText={t("SomethingWentWrong")}
customizedBodyText={t("SomethingWentWrongDescription")}
buttonText={t("SendReport")}
onClickButton={showDialog}
/>
<Link
className="link"
type="action"
isHovered
fontWeight={600}
onClick={onReloadClick}
color={currentColorScheme?.main?.accent}
>
{t("ReloadPage")}
</Link>
<ReportDialog
visible={reportDialogVisible}
onClose={closeDialog}
error={errorLog}
/>
</StyledWrapper>
);
};
Error520.propTypes = {
match: PropTypes.object,
};
const Error520Wrapper = inject(({ auth }) => {
const { currentColorScheme, firebaseHelper } = auth.settingsStore;
export default () => (
return {
currentColorScheme,
FirebaseHelper: firebaseHelper,
};
})(observer(Error520));
export default (props) => (
<I18nextProvider i18n={i18n}>
<Error520 />
<Error520Wrapper {...props} />
</I18nextProvider>
);

View File

@ -132,7 +132,12 @@ const FilesSection = React.memo(({ withMainButton }) => {
exact
restricted
withManager
path={["/accounts", "/accounts/filter", "/accounts/create/:type"]}
path={[
"/accounts",
"/accounts/changeOwner",
"/accounts/filter",
"/accounts/create/:type",
]}
component={Accounts}
/>

View File

@ -90,11 +90,11 @@ const PersonalSettings = ({
hideAdminSettings={!showAdminSettings}
>
<Box className="settings-section">
{showTitle && (
{/* {showTitle && (
<Heading className="heading" level={2} size="xsmall">
{t("Common:Common")}
</Heading>
)}
)} */}
<ToggleButton
className="toggle-btn"
label={thumbnailsSizeLabel}
@ -124,6 +124,14 @@ const PersonalSettings = ({
isChecked={confirmDelete}
/>
)}
{!isVisitor && (
<ToggleButton
className="toggle-btn"
label={t("UpdateOrCreate")}
onChange={onChangeUpdateIfExist}
isChecked={updateIfExist}
/>
)}
</Box>
{/* <Box className="settings-section">
@ -154,7 +162,7 @@ const PersonalSettings = ({
/>
</Box> */}
{!isVisitor && (
{/* {!isVisitor && (
<Box className="settings-section">
<Heading className="heading" level={2} size="xsmall">
{t("StoringFileVersion")}
@ -176,7 +184,7 @@ const PersonalSettings = ({
/>
)}
</Box>
)}
)} */}
</StyledSettings>
);
};

View File

@ -63,25 +63,25 @@ const SectionBodyContent = ({ isErrorSettings, history, user }) => {
[setting, history]
);
const showAdminSettings = user.isAdmin || user.isOwner;
//const showAdminSettings = user.isAdmin || user.isOwner;
return isErrorSettings ? (
<Error520 />
) : (
<StyledContainer>
{!showAdminSettings ? (
<PersonalSettings
t={t}
showTitle={true}
showAdminSettings={showAdminSettings}
/>
) : (
<Submenu
data={data}
startSelect={setting === "common" ? commonSettings : adminSettings}
onSelect={onSelect}
/>
)}
{/* {!showAdminSettings ? ( */}
<PersonalSettings
t={t}
showTitle={true}
showAdminSettings={false} //showAdminSettings
/>
{/* ) : (
<Submenu
data={data}
startSelect={setting === "common" ? commonSettings : adminSettings}
onSelect={onSelect}
/>
)} */}
</StyledContainer>
);
};

View File

@ -261,6 +261,10 @@ const Wizard = (props) => {
}
};
const onClickRetry = () => {
window.location.href = "/";
};
if (!isWizardLoaded)
return <Loader className="pageLoader" type="rombs" size="40px" />;
@ -270,7 +274,7 @@ const Wizard = (props) => {
headerText={t("Common:SomethingWentWrong")}
bodyText={t("ErrorInitWizard")}
buttonText={t("ErrorInitWizardButton")}
buttonUrl="/"
onClickButton={onClickRetry}
/>
);

View File

@ -2163,6 +2163,8 @@ class FilesActionStore {
id = urlFilter.folder;
}
if (id === undefined) return;
setIsLoading(true);
fetchFiles(id, null, true, false).finally(() => setIsLoading(false));

View File

@ -5,13 +5,13 @@ import Error520 from "client/Error520";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
this.state = { error: null };
}
// eslint-disable-next-line no-unused-vars
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
return { error: error ?? "Unhandled exception" };
}
componentDidCatch(error, errorInfo) {
@ -21,9 +21,9 @@ class ErrorBoundary extends React.Component {
}
render() {
if (this.state.hasError) {
if (this.state.error) {
// You can render any custom fallback UI
return <Error520 />;
return <Error520 errorLog={this.state.error} />;
}
return this.props.children;

View File

@ -12,9 +12,10 @@ const ErrorContainer = (props) => {
headerText,
bodyText,
buttonText,
buttonUrl,
onClickButton,
children,
customizedBodyText,
isPrimaryButton,
...rest
} = props;
@ -354,16 +355,16 @@ const ErrorContainer = (props) => {
</Text>
)}
{buttonText && buttonUrl && (
{buttonText && onClickButton && (
<div id="button-container">
<Button
theme={rest?.theme}
id="button"
size="normal"
scale
primary
primary={isPrimaryButton}
label={buttonText}
onClick={() => (window.location.href = buttonUrl)}
onClick={onClickButton}
/>
</div>
)}
@ -372,11 +373,16 @@ const ErrorContainer = (props) => {
);
};
ErrorContainer.defaultProps = {
isPrimaryButton: true,
};
ErrorContainer.propTypes = {
headerText: PropTypes.string,
bodyText: PropTypes.string,
isPrimaryButton: PropTypes.bool,
buttonText: PropTypes.string,
buttonUrl: PropTypes.string,
onClickButton: PropTypes.func,
children: PropTypes.any,
};

View File

@ -16,6 +16,6 @@ storiesOf("Components| ErrorContainer", module)
"This page was removed, renamed or doesnt exist anymore."
)}
buttonText={text("buttonText", "Return to homepage")}
buttonUrl={text("buttonUrl", "/")}
onClickButton={() => console.log("click")}
/>
));

View File

@ -15,7 +15,6 @@ describe("<ErrorContainer />", () => {
headerText="Some error has happened"
bodyText="Try again later"
buttonText="Go back"
buttonUrl="/page"
/>
);
@ -23,7 +22,6 @@ describe("<ErrorContainer />", () => {
expect(wrapper.prop("headerText")).toEqual("Some error has happened");
expect(wrapper.prop("bodyText")).toEqual("Try again later");
expect(wrapper.prop("buttonText")).toEqual("Go back");
expect(wrapper.prop("buttonUrl")).toEqual("/page");
});
it("accepts id", () => {

View File

@ -10,7 +10,7 @@ const StyledErrorContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
margin: 0 16px;
padding-top: 36px;
border: 0;
box-sizing: border-box;
@ -44,16 +44,14 @@ const StyledErrorContainer = styled.div`
}
#button-container {
margin: 0 0 auto 0;
width: 320px;
margin-top: 24px;
}
#button {
display: inline-block;
margin: 0 0 36px 0;
}
#customized-text {
color: ${(props) => props.theme.errorContainer.bodyText};
}
@media screen and (max-width: 960px) {
body {
padding: 24px 24px 0 24px;
@ -62,10 +60,6 @@ const StyledErrorContainer = styled.div`
#container {
margin: 12px 0 48px 0;
}
#button {
margin: 0 0 24px 0;
}
}
@media screen and (max-width: 620px) {
@ -88,13 +82,11 @@ const StyledErrorContainer = styled.div`
}
#button-container {
align-self: stretch;
margin: auto 0 0 0;
width: 100%;
}
#button {
width: 100%;
margin: 0 0 18px 0;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/common",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -1,6 +1,7 @@
import firebase from "firebase/app";
import "firebase/remote-config";
import "firebase/storage";
import "firebase/database";
import CampaignsCloudPngUrl from "PUBLIC_DIR/images/campaigns.cloud.png";
import CampaignsDesktopPngUrl from "PUBLIC_DIR/images/campaigns.desktop.png";
@ -12,6 +13,7 @@ class FirebaseHelper {
remoteConfig = null;
firebaseConfig = null;
firebaseStorage = null;
firebaseDB = null;
constructor(settings) {
this.firebaseConfig = settings;
@ -28,6 +30,8 @@ class FirebaseHelper {
this.remoteConfig = firebase.remoteConfig();
this.firebaseDB = firebase.database();
this.remoteConfig.settings = {
fetchTimeoutMillis: 3600000,
minimumFetchIntervalMillis: 3600000,
@ -64,6 +68,10 @@ class FirebaseHelper {
);
}
get isEnabledDB() {
return this.isEnabled && this.config["databaseUrl"];
}
async checkMaintenance() {
if (!this.isEnabled) return Promise.reject("Not enabled");
@ -145,6 +153,16 @@ class FirebaseHelper {
const domain = this.config["authDomain"];
return `https://${domain}/locales/${lng}/CampaignPersonal${banner}.json`;
}
async sendCrashReport(report) {
try {
const reportListRef = this.firebaseDB.ref("reports");
const neReportRef = reportListRef.push();
neReportRef.set(report);
} catch (error) {
return Promise.reject(error);
}
}
}
export default FirebaseHelper;

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/components",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"scripts": {
"build": "echo 'skip it'",

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/editor",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"homepage": "/doceditor",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@docspace/login",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"homepage": "/login",
"scripts": {

View File

@ -46,6 +46,8 @@ internal class FileDao : AbstractDao, IFileDao<int>
private readonly IMapper _mapper;
private readonly ThumbnailSettings _thumbnailSettings;
private readonly IQuotaService _quotaService;
private readonly StorageFactory _storageFactory;
private readonly TenantQuotaController _tenantQuotaController;
public FileDao(
ILogger<FileDao> logger,
@ -73,7 +75,9 @@ internal class FileDao : AbstractDao, IFileDao<int>
Settings settings,
IMapper mapper,
ThumbnailSettings thumbnailSettings,
IQuotaService quotaService)
IQuotaService quotaService,
StorageFactory storageFactory,
TenantQuotaController tenantQuotaController)
: base(
dbContextManager,
userManager,
@ -102,6 +106,8 @@ internal class FileDao : AbstractDao, IFileDao<int>
_mapper = mapper;
_thumbnailSettings = thumbnailSettings;
_quotaService = quotaService;
_storageFactory = storageFactory;
_tenantQuotaController = tenantQuotaController;
}
public Task InvalidateCacheAsync(int fileId)
@ -559,7 +565,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
}
else
{
if(uploadSession != null)
if (uploadSession != null)
{
await _chunkedUploadSessionHolder.MoveAsync(uploadSession, GetUniqFilePath(file));
}
@ -795,7 +801,10 @@ internal class FileDao : AbstractDao, IFileDao<int>
if (deleteFolder)
{
await DeleteFolderAsync(fileId);
var tenantId = _tenantManager.GetCurrentTenant().Id;
_tenantQuotaController.Init(tenantId, ThumbnailTitle);
var store = _storageFactory.GetStorage(tenantId, FileConstant.StorageModule, _tenantQuotaController);
await store.DeleteDirectoryAsync(GetUniqFileDirectory(fileId));
}
if (toDeleteFile != null)
@ -919,7 +928,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
.OrderByDescending(r => r.Level)
.ToListAsync();
_factoryIndexer.Update(toUpdateFile, UpdateAction.Replace, w => w.Folders);
_ = _factoryIndexer.UpdateAsync(toUpdateFile, UpdateAction.Replace, w => w.Folders);
}
});
@ -1318,11 +1327,6 @@ internal class FileDao : AbstractDao, IFileDao<int>
}
}
private async Task DeleteFolderAsync(int fileId)
{
await _globalStore.GetStore().DeleteDirectoryAsync(GetUniqFileDirectory(fileId));
}
public Task<bool> IsExistOnStorageAsync(File<int> file)
{
return _globalStore.GetStore().IsFileAsync(string.Empty, GetUniqFilePath(file));

View File

@ -104,6 +104,9 @@
"EnterName": "Enter name",
"Error": "Error",
"ErrorInternalServer": "Internal server error. Try again later.",
"ErrorReport": "Error report",
"ErrorReportDescription": "Open the report below to see what data is included. Error reports do not contain any personal data of the users. To help our team better understand the problem, describe it in the free form using the comment field",
"ErrorReportSuccess": "Error report was successfully sent",
"Exabyte": "EB",
"FeedbackAndSupport": "Feedback & Support",
"FillFormButton": "Fill in the form",
@ -198,6 +201,7 @@
"RecoverDescribeYourProblemPlaceholder": "Describe your problem",
"RecoverTitle": "Access recovery",
"RegistrationEmail": "Your registration email address",
"ReloadPage": "Reload page",
"Rename": "Rename",
"RepeatInvitation": "Repeat invitation",
"RequiredField": "Required field",
@ -220,8 +224,9 @@
"SelectDOCXFormat": "Select .DOCX file",
"SelectFile": "Select file",
"SendButton": "Send",
"Sending": "Sending...",
"SendReport": "Send report",
"SendRequest": "Send request",
"Sending": "Sending...",
"Sessions": "Sessions",
"Settings": "Settings",
"SettingsDocSpace": "DocSpace Settings",
@ -237,6 +242,7 @@
"Size": "Size",
"SizeImageLarge": "The image size is too large, please select another image.",
"SomethingWentWrong": "Something went wrong.",
"SomethingWentWrongDescription": "Click Send report to automatically generate a report and help us fix the error. None of your personal data will be used in the report",
"SortBy": "Sort by",
"SpacesInLocalPart": "Local part can't contain spaces",
"Standard": "Standard",

View File

@ -176,7 +176,8 @@ public class SettingsController : BaseSettingsController
StorageBucket = _configuration["firebase:storageBucket"] ?? "",
MessagingSenderId = _configuration["firebase:messagingSenderId"] ?? "",
AppId = _configuration["firebase:appId"] ?? "",
MeasurementId = _configuration["firebase:measurementId"] ?? ""
MeasurementId = _configuration["firebase:measurementId"] ?? "",
DataBaseUrl = _configuration["firebase:databaseURL"] ?? ""
};
settings.HelpLink = _commonLinkUtility.GetHelpLink(_settingsManager, _additionalWhiteLabelSettingsHelper, true);

View File

@ -35,5 +35,5 @@ public class FirebaseDto
public string MessagingSenderId { get; set; }
public string AppId { get; set; }
public string MeasurementId { get; set; }
public string DataBaseUrl { get; set; }
}

View File

@ -29,6 +29,7 @@ using ASC.Core.Data;
using Microsoft.Net.Http.Headers;
using Constants = ASC.Core.Users.Constants;
using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
namespace ASC.Web.Core;
@ -52,6 +53,7 @@ public class CookiesManager
private readonly CoreBaseSettings _coreBaseSettings;
private readonly DbLoginEventsManager _dbLoginEventsManager;
private readonly MessageService _messageService;
private readonly SameSiteMode? _sameSiteMode;
public CookiesManager(
IHttpContextAccessor httpContextAccessor,
@ -61,7 +63,8 @@ public class CookiesManager
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
DbLoginEventsManager dbLoginEventsManager,
MessageService messageService)
MessageService messageService,
IConfiguration configuration)
{
_httpContextAccessor = httpContextAccessor;
_userManager = userManager;
@ -71,6 +74,11 @@ public class CookiesManager
_coreBaseSettings = coreBaseSettings;
_dbLoginEventsManager = dbLoginEventsManager;
_messageService = messageService;
if (Enum.TryParse<SameSiteMode>(configuration["web:samesite"], out var sameSiteMode))
{
_sameSiteMode = sameSiteMode;
}
}
public void SetCookies(CookiesType type, string value, bool session = false)
@ -89,10 +97,20 @@ public class CookiesManager
{
options.HttpOnly = true;
if (_sameSiteMode.HasValue && _sameSiteMode.Value != SameSiteMode.None)
{
options.SameSite = _sameSiteMode.Value;
}
var urlRewriter = _httpContextAccessor.HttpContext.Request.Url();
if (urlRewriter.Scheme == "https")
{
options.Secure = true;
if (_sameSiteMode.HasValue && _sameSiteMode.Value == SameSiteMode.None)
{
options.SameSite = _sameSiteMode.Value;
}
}
if (FromCors())

2187
yarn.lock

File diff suppressed because it is too large Load Diff