Skip to content

Commit 8d5587e

Browse files
DOMA-2807 fixed bugs after testing
1 parent cdd3880 commit 8d5587e

25 files changed

+441
-264
lines changed
Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
1+
import styled from '@emotion/styled'
12
import React, { CSSProperties, useMemo } from 'react'
2-
import xss from 'xss'
3+
import { sanitizeXss } from '@condo/domains/common/utils/xss'
4+
import { colors } from '@condo/domains/common/constants/style'
35

4-
type HtmlContentProps = {
6+
export type HtmlContentProps = {
57
html: string
68
style?: CSSProperties
79
className?: string
10+
css?
811
}
912

10-
export const HtmlContent: React.FC<HtmlContentProps> = ({ html, style, className }) => {
13+
const StyledDiv = styled.div`
14+
overflow: hidden;
15+
word-break: break-word;
16+
17+
p {
18+
margin: 0;
19+
}
20+
21+
a {
22+
color: ${colors.black};
23+
text-decoration: underline;
24+
}
25+
`
26+
27+
export const HtmlContent = React.forwardRef<HTMLDivElement, HtmlContentProps>(({ html, style, className }, ref) => {
1128
const htmlContent = useMemo(() => ({
12-
__html: xss(html),
29+
__html: sanitizeXss(html),
1330
}), [html])
1431

1532
return (
16-
<div
33+
<StyledDiv
34+
ref={ref}
1735
className={className}
1836
dangerouslySetInnerHTML={htmlContent}
1937
style={style}
2038
/>
2139
)
22-
}
40+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const _xss = require('xss')
2+
3+
const whiteList = _xss.getDefaultWhiteList()
4+
whiteList.p.push('style')
5+
whiteList.span.push('style')
6+
whiteList.li.push('style')
7+
whiteList.a.push('style')
8+
9+
const xss = new _xss.FilterXSS({
10+
whiteList,
11+
css: {
12+
whiteList: {
13+
'text-align': true,
14+
'background-color': true,
15+
},
16+
},
17+
})
18+
19+
const sanitizeXss = (html) => xss.process(html)
20+
21+
module.exports = { sanitizeXss }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const { sanitizeXss } = require('@condo/domains/common/utils/xss')
2+
3+
const ALLOWED_STYLE_ATTR = 'style="text-align:center; background-color:red;"'
4+
const getValidHtmlTagsWithStyles = style => `<li ${style}><p ${style}><a ${style}>lorem</a> <span ${style}>ipsum</span></p>`
5+
6+
describe('xss', () => {
7+
it('allow text-align and background-color style attribute values to p, span, li, a, tags', () => {
8+
const html = getValidHtmlTagsWithStyles(ALLOWED_STYLE_ATTR)
9+
10+
expect(sanitizeXss(html)).toEqual(html)
11+
})
12+
13+
it('not allow other style attribute values to p, span, li, a, tags', () => {
14+
const html = getValidHtmlTagsWithStyles('style="width: expression(alert(\'XSS\'));"')
15+
const expectedResult = '<li style><p style><a style>lorem</a> <span style>ipsum</span></p>'
16+
17+
expect(sanitizeXss(html)).toEqual(expectedResult)
18+
})
19+
20+
it('not allow style attribute to other tags', () => {
21+
const html = '<div style="background-image: url(javascript:alert(\'XSS\'))">123</div>'
22+
const expectedResult = '<div>123</div>'
23+
24+
expect(sanitizeXss(html)).toEqual(expectedResult)
25+
})
26+
27+
it('escape script tag', () => {
28+
const html = '<script> console.log(\'123\') </script>'
29+
const expectedResult = '&lt;script&gt; console.log(\'123\') &lt;/script&gt;'
30+
31+
expect(sanitizeXss(html)).toEqual(expectedResult)
32+
})
33+
})

apps/condo/domains/property/components/UnitInfo.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,12 @@ export const UnitInfo: IUnitInfo = (props) => {
9191
}, [form, setSelectedUnitName, setSelectedUnitType, updateSectionAndFloor])
9292

9393
const colSpan = isSmall ? 24 : 20
94+
const inputColSpan = isSmall ? 8 : 5
9495

9596
return (
9697
<Col span={colSpan}>
9798
<Row gutter={UNIT_FIELDS_GUTTER}>
98-
<Col span={5} data-cy={'unit-name-input-item'}>
99+
<Col span={inputColSpan} data-cy={'unit-name-input-item'}>
99100
<TicketFormItem name={'unitName'} label={FlatNumberLabel}>
100101
<UnitNameInput
101102
property={property}
@@ -105,12 +106,12 @@ export const UnitInfo: IUnitInfo = (props) => {
105106
/>
106107
</TicketFormItem>
107108
</Col>
108-
<Col span={5}>
109+
<Col span={inputColSpan}>
109110
<TicketFormItem name={'sectionName'} label={SectionNameLabel}>
110111
<Input disabled/>
111112
</TicketFormItem>
112113
</Col>
113-
<Col span={5}>
114+
<Col span={inputColSpan}>
114115
<TicketFormItem name={'floorName'} label={FloorNameLabel}>
115116
<Input disabled/>
116117
</TicketFormItem>

apps/condo/domains/property/schema/Property.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ const get = require('lodash/get')
66
const isEmpty = require('lodash/isEmpty')
77
const Ajv = require('ajv')
88
const dayjs = require('dayjs')
9-
const { Text, Select, Virtual, Integer, CalendarDay, Decimal } = require('@keystonejs/fields')
109

10+
const { Text, Select, Virtual, Integer, CalendarDay, Decimal } = require('@keystonejs/fields')
1111
const { Json } = require('@core/keystone/fields')
1212
const { GQLListSchema } = require('@core/keystone/schema')
1313
const { historical, versioned, uuided, tracked, softDeleted } = require('@core/keystone/plugins')
14+
const { Checkbox } = require('@keystonejs/fields')
1415

1516
const { compareStrI } = require('@condo/domains/common/utils/string.utils')
1617
const { SENDER_FIELD, DV_FIELD, ADDRESS_META_FIELD } = require('@condo/domains/common/schema/fields')
@@ -22,21 +23,17 @@ const {
2223
JSON_EXPECT_OBJECT_ERROR,
2324
UNIQUE_ALREADY_EXISTS_ERROR,
2425
} = require('@condo/domains/common/constants/errors')
25-
2626
const { ORGANIZATION_OWNED_FIELD } = require('@condo/domains/organization/schema/fields')
27-
27+
const { PROPERTY_MAP_JSON_FIELDS } = require('@condo/domains/property/gql')
2828
const access = require('@condo/domains/property/access/Property')
2929
const MapSchemaJSON = require('@condo/domains/property/components/panels/Builder/MapJsonSchema.json')
30-
const { Checkbox } = require('@keystonejs/fields')
31-
3230
const { manageResidentToPropertyAndOrganizationConnections } = require('@condo/domains/resident/tasks')
3331
const { manageTicketPropertyAddressChange } = require('@condo/domains/ticket/tasks')
3432

35-
3633
const { PROPERTY_MAP_GRAPHQL_TYPES, GET_TICKET_INWORK_COUNT_BY_PROPERTY_ID_QUERY, GET_TICKET_CLOSED_COUNT_BY_PROPERTY_ID_QUERY } = require('../gql')
3734
const { Property: PropertyAPI } = require('../utils/serverSchema')
3835
const { normalizePropertyMap } = require('../utils/serverSchema/helpers')
39-
const { PROPERTY_MAP_JSON_FIELDS } = require('@condo/domains/property/gql')
36+
const { softDeleteTicketHintPropertiesByProperty } = require('../../ticket/utils/serverSchema/resolveHelpers')
4037

4138
const ajv = new Ajv()
4239
const jsonMapValidator = ajv.compile(MapSchemaJSON)
@@ -327,7 +324,7 @@ const Property = new GQLListSchema('Property', {
327324
}
328325
return resolvedData
329326
},
330-
afterChange: async ({ operation, existingItem, updatedItem }) => {
327+
afterChange: async ({ context, operation, existingItem, updatedItem }) => {
331328
const isSoftDeleteOperation = operation === 'update' && !existingItem.deletedAt && Boolean(updatedItem.deletedAt)
332329
const isCreatedProperty = operation === 'create' && Boolean(updatedItem.address)
333330
const isRestoredProperty = operation === 'update' && Boolean(existingItem.deletedAt) && !updatedItem.deletedAt
@@ -347,6 +344,10 @@ const Property = new GQLListSchema('Property', {
347344

348345
// Reconnect residents (if any) to oldest non-deleted property with address = affectedAddress
349346
await manageResidentToPropertyAndOrganizationConnections.delay(affectedAddress)
347+
348+
if (isSoftDeleteOperation) {
349+
await softDeleteTicketHintPropertiesByProperty(context, updatedItem)
350+
}
350351
}
351352
},
352353
},

apps/condo/domains/ticket/components/BaseTicketForm/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export const BaseTicketForm: React.FC<ITicketFormProps> = (props) => {
235235
const NoPropertiesMessage = intl.formatMessage({ id: 'pages.condo.ticket.alert.NoProperties' })
236236
const CanReadByResidentMessage = intl.formatMessage({ id: 'pages.condo.ticket.field.CanReadByResident' })
237237

238-
const { isSmall } = useLayoutContext()
238+
const { isSmall, breakpoints } = useLayoutContext()
239239

240240
const router = useRouter()
241241

@@ -395,6 +395,8 @@ export const BaseTicketForm: React.FC<ITicketFormProps> = (props) => {
395395
setSelectedPropertyId(null)
396396
}, [])
397397

398+
const propertyInfoColSpan = isSmall ? 24 : 17
399+
398400
return (
399401
<>
400402
<FormWithAction
@@ -420,7 +422,7 @@ export const BaseTicketForm: React.FC<ITicketFormProps> = (props) => {
420422
<Row gutter={BIG_VERTICAL_GUTTER}>
421423
<Col span={24}>
422424
<Row gutter={BIG_HORIZONTAL_GUTTER} justify={'space-between'}>
423-
<Col span={17}>
425+
<Col span={propertyInfoColSpan}>
424426
<Row gutter={BIG_VERTICAL_GUTTER}>
425427
<Col span={24}>
426428
<Row gutter={SMALL_VERTICAL_GUTTER}>
@@ -453,8 +455,8 @@ export const BaseTicketForm: React.FC<ITicketFormProps> = (props) => {
453455
</Row>
454456
</Col>
455457
{
456-
isSmall && (
457-
<Col span={12}>
458+
selectedPropertyId && !breakpoints.xl && (
459+
<Col span={24}>
458460
<TicketPropertyHintCard
459461
propertyId={selectedPropertyId}
460462
hintContentStyle={TICKET_PROPERTY_HINT_STYLES}
@@ -471,7 +473,7 @@ export const BaseTicketForm: React.FC<ITicketFormProps> = (props) => {
471473
</Row>
472474
</Col>
473475
{
474-
!isSmall && (
476+
selectedPropertyId && breakpoints.xl && (
475477
<Col span={6}>
476478
<TicketPropertyHintCard
477479
propertyId={selectedPropertyId}

apps/condo/domains/ticket/components/TicketPropertyHint/BaseTicketPropertyHintForm.tsx

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Alert, Col, Form, Input, Row, Typography } from 'antd'
22
import { Gutter } from 'antd/es/grid/row'
33
import { get, isEmpty } from 'lodash'
44
import getConfig from 'next/config'
5+
import { useRouter } from 'next/router'
6+
import qs from 'qs'
57
import { Rule } from 'rc-field-form/lib/interface'
68
import React, { CSSProperties, useCallback, useMemo, useState } from 'react'
79
import { Editor } from '@tinymce/tinymce-react'
@@ -35,22 +37,36 @@ const LAYOUT = {
3537
const HINT_LINK_STYLES: CSSProperties = { color: colors.black, textDecoration: 'underline' }
3638
const TEXT_STYLES: CSSProperties = { margin: 0 }
3739

38-
const TicketPropertyHintAlert = () => {
40+
type TicketPropertyHintAlertProps = {
41+
hintFilters?: string
42+
}
43+
44+
const TicketPropertyHintAlert: React.FC<TicketPropertyHintAlertProps> = ({ hintFilters }) => {
3945
const intl = useIntl()
4046
const AlertMessage = intl.formatMessage({ id: 'pages.condo.settings.hint.alert.title' })
4147
const AlertContent = intl.formatMessage({ id: 'pages.condo.settings.hint.alert.content' })
4248
const ShowHintsMessage = intl.formatMessage({ id: 'pages.condo.settings.hint.alert.showHints' })
4349

50+
const router = useRouter()
51+
const queryFilters = useMemo(() => hintFilters ? { filters: hintFilters } : {}, [hintFilters])
52+
const query = useMemo(() => qs.stringify(
53+
{
54+
tab: 'hint',
55+
...queryFilters,
56+
},
57+
{ arrayFormat: 'comma', skipNulls: true, addQueryPrefix: true },
58+
), [queryFilters])
59+
const linkHref = useMemo(() => '/settings' + query, [query])
60+
const showHintsMessageHandler = useCallback(() => router.push(linkHref), [linkHref, router])
61+
4462
const AlertDescription = useMemo(() => (
4563
<>
4664
<Typography.Paragraph style={TEXT_STYLES}>{AlertContent}</Typography.Paragraph>
47-
<a href={'/settings?tab=hint'} target={'_blank'} rel="noreferrer">
48-
<Typography.Link style={HINT_LINK_STYLES}>
49-
{ShowHintsMessage}
50-
</Typography.Link>
51-
</a>
65+
<Typography.Link style={HINT_LINK_STYLES} onClick={showHintsMessageHandler}>
66+
{ShowHintsMessage}
67+
</Typography.Link>
5268
</>
53-
), [AlertContent, ShowHintsMessage])
69+
), [AlertContent, ShowHintsMessage, showHintsMessageHandler])
5470

5571
return (
5672
<Alert
@@ -82,17 +98,29 @@ const {
8298

8399
const { TinyMceApiKey } = publicRuntimeConfig
84100

85-
export const BaseTicketPropertyHintForm = ({ children, action, organizationId, initialValues, mode }) => {
101+
type BaseTicketPropertyHintFormProps = {
102+
children
103+
action
104+
organizationId: string
105+
initialValues
106+
mode: string
107+
hintFilters?: string
108+
}
109+
110+
export const BaseTicketPropertyHintForm: React.FC<BaseTicketPropertyHintFormProps> = ({ hintFilters, children, action, organizationId, initialValues, mode }) => {
86111
const intl = useIntl()
87112
const NameMessage = intl.formatMessage({ id: 'pages.condo.property.section.form.name' })
88113
const HintMessage = intl.formatMessage({ id: 'Hint' })
89114
const BuildingsMessage = intl.formatMessage({ id: 'pages.condo.property.index.TableField.Buildings' })
90115

91-
const editor_init_values = useMemo(() => ({
116+
const router = useRouter()
117+
118+
const editorInitValues = useMemo(() => ({
92119
link_title: false,
93120
contextmenu: '',
94121
menubar: false,
95122
elementpath: false,
123+
content_style: 'p {margin: 0}',
96124
plugins: 'link autolink lists',
97125
toolbar: 'undo redo | ' +
98126
'link | bold italic backcolor | alignleft aligncenter ' +
@@ -181,6 +209,8 @@ export const BaseTicketPropertyHintForm = ({ children, action, organizationId, i
181209
}
182210
}
183211
}
212+
213+
await router.push('/settings?tab=hint')
184214
}, [action, createTicketPropertyHintPropertyAction, initialPropertyIds, initialValues, organizationId, organizationTicketPropertyHintProperties, softDeleteTicketPropertyHintPropertyAction])
185215

186216
if (organizationTicketPropertyHintPropertiesLoading) {
@@ -194,7 +224,7 @@ export const BaseTicketPropertyHintForm = ({ children, action, organizationId, i
194224
{
195225
mode === 'create' && !isEmpty(propertiesWithTicketPropertyHint) && (
196226
<Col span={24}>
197-
<TicketPropertyHintAlert />
227+
<TicketPropertyHintAlert hintFilters={hintFilters} />
198228
</Col>
199229
)
200230
}
@@ -262,7 +292,7 @@ export const BaseTicketPropertyHintForm = ({ children, action, organizationId, i
262292
value={editorValue}
263293
onEditorChange={(newValue) => handleEditorChange(newValue, form)}
264294
initialValue={initialContent}
265-
init={editor_init_values}
295+
init={editorInitValues}
266296
/>
267297
</Col>
268298
</Row>

apps/condo/domains/ticket/components/TicketPropertyHint/CreateTicketPropertyHintForm.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { get } from 'lodash'
2-
import { useRouter } from 'next/router'
32
import React, { useMemo } from 'react'
43

54
import { useOrganization } from '@core/next/organization'
@@ -17,11 +16,7 @@ export const CreateTicketPropertyHintForm = () => {
1716
const SaveLabel = intl.formatMessage({ id: 'Save' })
1817

1918
const { organization } = useOrganization()
20-
21-
const router = useRouter()
22-
const action = TicketPropertyHint.useCreate({ organization: organization.id }, () => {
23-
router.push('/settings?tab=hint')
24-
})
19+
const action = TicketPropertyHint.useCreate({ organization: organization.id }, () => Promise.resolve())
2520

2621
const organizationId = useMemo(() => get(organization, 'id'), [organization])
2722

0 commit comments

Comments
 (0)