mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
fix(settings): Also sanitize fediverse and twitter handle in the frontend
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
34bb825f21
commit
44742d617f
8 changed files with 79 additions and 53 deletions
|
|
@ -21,30 +21,40 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<AccountPropertySection v-bind.sync="fediverse"
|
||||
<AccountPropertySection v-bind.sync="value"
|
||||
:readable="readable"
|
||||
:on-validate="onValidate"
|
||||
:placeholder="t('settings', 'Your handle')" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import type { AccountProperties } from '../../constants/AccountPropertyConstants.js'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { ref } from 'vue'
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
|
||||
|
||||
import AccountPropertySection from './shared/AccountPropertySection.vue'
|
||||
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
|
||||
const { fediverse } = loadState<AccountProperties>('settings', 'personalInfoParameters', {})
|
||||
|
||||
const { fediverse } = loadState('settings', 'personalInfoParameters', {})
|
||||
const value = ref({ ...fediverse })
|
||||
const readable = NAME_READABLE_ENUM[fediverse.name]
|
||||
|
||||
export default {
|
||||
name: 'FediverseSection',
|
||||
/**
|
||||
* Validate a fediverse handle
|
||||
* @param text The potential fediverse handle
|
||||
*/
|
||||
function onValidate(text: string): boolean {
|
||||
const result = text.match(/^@?([^@/]+)@([^@/]+)$/)
|
||||
if (result === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
components: {
|
||||
AccountPropertySection,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
fediverse: { ...fediverse, readable: NAME_READABLE_ENUM[fediverse.name] },
|
||||
}
|
||||
},
|
||||
try {
|
||||
return URL.parse(`https://${result[2]}/`) !== null
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -21,30 +21,31 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<AccountPropertySection v-bind.sync="twitter"
|
||||
<AccountPropertySection v-bind.sync="value"
|
||||
:readable="readable"
|
||||
:on-validate="onValidate"
|
||||
:placeholder="t('settings', 'Your X (formerly Twitter) handle')" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
<script setup lang="ts">
|
||||
import type { AccountProperties } from '../../constants/AccountPropertyConstants.js'
|
||||
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { ref } from 'vue'
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.ts'
|
||||
import AccountPropertySection from './shared/AccountPropertySection.vue'
|
||||
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
|
||||
const { twitter } = loadState<AccountProperties>('settings', 'personalInfoParameters', {})
|
||||
|
||||
const { twitter } = loadState('settings', 'personalInfoParameters', {})
|
||||
const value = ref({ ...twitter })
|
||||
const readable = NAME_READABLE_ENUM[twitter.name]
|
||||
|
||||
export default {
|
||||
name: 'TwitterSection',
|
||||
|
||||
components: {
|
||||
AccountPropertySection,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
twitter: { ...twitter, readable: NAME_READABLE_ENUM[twitter.name] },
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Validate that the text might be a twitter handle
|
||||
* @param text The potential twitter handle
|
||||
*/
|
||||
function onValidate(text: string): boolean {
|
||||
return text.match(/^@?([a-zA-Z0-9_]{2,15})$/) !== null
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -121,12 +121,12 @@ export const ACCOUNT_SETTING_PROPERTY_READABLE_ENUM = Object.freeze({
|
|||
})
|
||||
|
||||
/** Enum of scopes */
|
||||
export const SCOPE_ENUM = Object.freeze({
|
||||
PRIVATE: 'v2-private',
|
||||
LOCAL: 'v2-local',
|
||||
FEDERATED: 'v2-federated',
|
||||
PUBLISHED: 'v2-published',
|
||||
})
|
||||
export enum SCOPE_ENUM {
|
||||
PRIVATE = 'v2-private',
|
||||
LOCAL = 'v2-local',
|
||||
FEDERATED = 'v2-federated',
|
||||
PUBLISHED = 'v2-published',
|
||||
}
|
||||
|
||||
/** Enum of readable account properties to supported scopes */
|
||||
export const PROPERTY_READABLE_SUPPORTED_SCOPES_ENUM = Object.freeze({
|
||||
|
|
@ -197,11 +197,11 @@ export const SCOPE_PROPERTY_ENUM = Object.freeze({
|
|||
export const DEFAULT_ADDITIONAL_EMAIL_SCOPE = SCOPE_ENUM.LOCAL
|
||||
|
||||
/** Enum of verification constants, according to IAccountManager */
|
||||
export const VERIFICATION_ENUM = Object.freeze({
|
||||
NOT_VERIFIED: 0,
|
||||
VERIFICATION_IN_PROGRESS: 1,
|
||||
VERIFIED: 2,
|
||||
})
|
||||
export enum VERIFICATION_ENUM {
|
||||
NOT_VERIFIED = 0,
|
||||
VERIFICATION_IN_PROGRESS = 1,
|
||||
VERIFIED = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Email validation regex
|
||||
|
|
@ -210,3 +210,12 @@ export const VERIFICATION_ENUM = Object.freeze({
|
|||
*/
|
||||
// eslint-disable-next-line no-control-regex
|
||||
export const VALIDATE_EMAIL_REGEX = /^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/i
|
||||
|
||||
export interface IAccountProperty {
|
||||
name: string
|
||||
value: string
|
||||
scope: SCOPE_ENUM
|
||||
verified: VERIFICATION_ENUM
|
||||
}
|
||||
|
||||
export type AccountProperties = Record<(typeof ACCOUNT_PROPERTY_ENUM)[keyof (typeof ACCOUNT_PROPERTY_ENUM)], IAccountProperty>
|
||||
|
|
@ -26,7 +26,7 @@ import { generateOcsUrl } from '@nextcloud/router'
|
|||
import { confirmPassword } from '@nextcloud/password-confirmation'
|
||||
import '@nextcloud/password-confirmation/dist/style.css'
|
||||
|
||||
import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.js'
|
||||
import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.ts'
|
||||
|
||||
/**
|
||||
* Save the primary email of the user
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import { generateOcsUrl } from '@nextcloud/router'
|
|||
import { confirmPassword } from '@nextcloud/password-confirmation'
|
||||
import '@nextcloud/password-confirmation/dist/style.css'
|
||||
|
||||
import { SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.js'
|
||||
import { SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.ts'
|
||||
|
||||
/**
|
||||
* Save the primary account property value for the user
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
* TODO add nice validation errors for Profile page settings modal
|
||||
*/
|
||||
|
||||
import { VALIDATE_EMAIL_REGEX } from '../constants/AccountPropertyConstants.js'
|
||||
import { VALIDATE_EMAIL_REGEX } from '../constants/AccountPropertyConstants.ts'
|
||||
|
||||
/**
|
||||
* Validate the email input
|
||||
|
|
|
|||
|
|
@ -608,7 +608,6 @@ Feature: provisioning
|
|||
| user_status |
|
||||
| viewer |
|
||||
| workflowengine |
|
||||
| webhook_listeners |
|
||||
| weather_status |
|
||||
| files_external |
|
||||
| oauth2 |
|
||||
|
|
|
|||
|
|
@ -115,18 +115,26 @@ const checkSettingsVisibility = (property: string, defaultVisibility: Visibility
|
|||
}) */
|
||||
}
|
||||
|
||||
const genericProperties = ['Location', 'X (formerly Twitter)', 'Fediverse']
|
||||
const genericProperties = [
|
||||
['Location', 'Berlin'],
|
||||
['X (formerly Twitter)', 'nextclouders'],
|
||||
['Fediverse', 'nextcloud@mastodon.xyz'],
|
||||
]
|
||||
const nonfederatedProperties = ['Organisation', 'Role', 'Headline', 'About']
|
||||
|
||||
describe('Settings: Change personal information', { testIsolation: true }, () => {
|
||||
|
||||
before(() => {
|
||||
// make sure the fediverse check does not do http requests
|
||||
cy.runOccCommand('config:system:set has_internet_connection --value false')
|
||||
// ensure we can set locale and language
|
||||
cy.runOccCommand('config:system:delete force_language')
|
||||
cy.runOccCommand('config:system:delete force_locale')
|
||||
})
|
||||
|
||||
after(() => {
|
||||
cy.runOccCommand('config:system:delete has_internet_connection')
|
||||
|
||||
cy.runOccCommand('config:system:set force_language --value en')
|
||||
cy.runOccCommand('config:system:set force_locale --value en_US')
|
||||
})
|
||||
|
|
@ -350,22 +358,21 @@ describe('Settings: Change personal information', { testIsolation: true }, () =>
|
|||
})
|
||||
|
||||
// Check generic properties that allow any visibility and any value
|
||||
genericProperties.forEach((property) => {
|
||||
genericProperties.forEach(([property, value]) => {
|
||||
it(`Can set ${property} and change its visibility`, () => {
|
||||
const uniqueValue = `${property.toUpperCase()} ${property.toLowerCase()}`
|
||||
cy.contains('label', property).scrollIntoView()
|
||||
inputForLabel(property).type(uniqueValue)
|
||||
inputForLabel(property).type(value)
|
||||
handlePasswordConfirmation(user.password)
|
||||
|
||||
cy.wait('@submitSetting')
|
||||
cy.reload()
|
||||
inputForLabel(property).should('have.value', uniqueValue)
|
||||
inputForLabel(property).should('have.value', value)
|
||||
|
||||
checkSettingsVisibility(property)
|
||||
|
||||
// check it is visible on the profile
|
||||
cy.visit(`/u/${user.userId}`)
|
||||
cy.contains(uniqueValue).should('be.visible')
|
||||
cy.contains(value).should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue