diff --git a/src/components/EmailSection/EmailSection.vue b/src/components/EmailSection/EmailSection.vue index 0495863..e756380 100644 --- a/src/components/EmailSection/EmailSection.vue +++ b/src/components/EmailSection/EmailSection.vue @@ -1,195 +1,202 @@ - - - - - + + + + + diff --git a/src/service/PersonalInfo/EmailService.js b/src/service/PersonalInfo/EmailService.js index c758e51..82eb8a0 100644 --- a/src/service/PersonalInfo/EmailService.js +++ b/src/service/PersonalInfo/EmailService.js @@ -1,151 +1,153 @@ -import axios from '@nextcloud/axios' -import { getCurrentUser } from '@nextcloud/auth' -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' - -/** - * Save the primary email of the user - * - * @param {string} email the primary email - * @return {object} - */ -export const savePrimaryEmail = async (email) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}', { userId }) - - await confirmPassword() - - const res = await axios.put(url, { - key: ACCOUNT_PROPERTY_ENUM.EMAIL, - value: email, - }) - - return res.data -} - -/** - * Save an additional email of the user - * - * Will be appended to the user's additional emails* - * - * @param {string} email the additional email - * @return {object} - */ -export const saveAdditionalEmail = async (email) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}', { userId }) - - await confirmPassword() - - const res = await axios.put(url, { - key: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION, - value: email, - }) - - return res.data -} - -/** - * Save the notification email of the user - * - * @param {string} email the notification email - * @return {object} - */ -export const saveNotificationEmail = async (email) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}', { userId }) - - await confirmPassword() - - const res = await axios.put(url, { - key: ACCOUNT_PROPERTY_ENUM.NOTIFICATION_EMAIL, - value: email, - }) - - return res.data -} - -/** - * Remove an additional email of the user - * - * @param {string} email the additional email - * @return {object} - */ -export const removeAdditionalEmail = async (email) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}/{collection}', { userId, collection: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION }) - - await confirmPassword() - - const res = await axios.put(url, { - key: email, - value: '', - }) - - return res.data -} - -/** - * Update an additional email of the user - * - * @param {string} prevEmail the additional email to be updated - * @param {string} newEmail the new additional email - * @return {object} - */ -export const updateAdditionalEmail = async (prevEmail, newEmail) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}/{collection}', { userId, collection: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION }) - - await confirmPassword() - - const res = await axios.put(url, { - key: prevEmail, - value: newEmail, - }) - - return res.data -} - -/** - * Save the federation scope for the primary email of the user - * - * @param {string} scope the federation scope - * @return {object} - */ -export const savePrimaryEmailScope = async (scope) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}', { userId }) - - await confirmPassword() - - const res = await axios.put(url, { - key: `${ACCOUNT_PROPERTY_ENUM.EMAIL}${SCOPE_SUFFIX}`, - value: scope, - }) - - return res.data -} - -/** - * Save the federation scope for the additional email of the user - * - * @param {string} email the additional email - * @param {string} scope the federation scope - * @return {object} - */ -export const saveAdditionalEmailScope = async (email, scope) => { - const userId = getCurrentUser().uid - const url = generateOcsUrl('cloud/users/{userId}/{collectionScope}', { userId, collectionScope: `${ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION}${SCOPE_SUFFIX}` }) - - await confirmPassword() - - const res = await axios.put(url, { - key: email, - value: scope, - }) - - return res.data -} +import axios from '@nextcloud/axios' +import { getCurrentUser } from '@nextcloud/auth' +import { generateOcsUrl } from '@nextcloud/router' +import { confirmPassword } from '@nextcloud/password-confirmation' +import '@nextcloud/password-confirmation/dist/style.css' +import { convertEmailDomainToASCII } from '../../utils/email.js' + +import { ACCOUNT_PROPERTY_ENUM, SCOPE_SUFFIX } from '../../constants/AccountPropertyConstants.js' + +/** + * Save the primary email of the user + * + * @param {string} email the primary email + * @return {object} + */ +export const savePrimaryEmail = async (email) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: ACCOUNT_PROPERTY_ENUM.EMAIL, + value: convertEmailDomainToASCII(email), + }) + + return res.data +} + +/** + * Save an additional email of the user + * + * Will be appended to the user's additional emails* + * + * @param {string} email the additional email + * @return {object} + */ +export const saveAdditionalEmail = async (email) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION, + value: convertEmailDomainToASCII(email), + }) + + return res.data +} + +/** + * Save the notification email of the user + * + * @param {string} email the notification email + * @return {object} + */ +export const saveNotificationEmail = async (email) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: ACCOUNT_PROPERTY_ENUM.NOTIFICATION_EMAIL, + value: convertEmailDomainToASCII(email), + }) + + return res.data +} + +/** + * Remove an additional email of the user + * + * @param {string} email the additional email + * @return {object} + */ +export const removeAdditionalEmail = async (email) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}/{collection}', { userId, collection: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION }) + + await confirmPassword() + + const res = await axios.put(url, { + key: convertEmailDomainToASCII(email), + value: '', + }) + + return res.data +} + +/** + * Update an additional email of the user + * + * @param {string} prevEmail the additional email to be updated + * @param {string} newEmail the new additional email + * @return {object} + */ +export const updateAdditionalEmail = async (prevEmail, newEmail) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}/{collection}', { userId, collection: ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION }) + + await confirmPassword() + + const res = await axios.put(url, { + key: convertEmailDomainToASCII(prevEmail), + value: convertEmailDomainToASCII(newEmail), + }) + + return res.data + +} + +/** + * Save the federation scope for the primary email of the user + * + * @param {string} scope the federation scope + * @return {object} + */ +export const savePrimaryEmailScope = async (scope) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}', { userId }) + + await confirmPassword() + + const res = await axios.put(url, { + key: `${ACCOUNT_PROPERTY_ENUM.EMAIL}${SCOPE_SUFFIX}`, + value: scope, + }) + + return res.data +} + +/** + * Save the federation scope for the additional email of the user + * + * @param {string} email the additional email + * @param {string} scope the federation scope + * @return {object} + */ +export const saveAdditionalEmailScope = async (email, scope) => { + const userId = getCurrentUser().uid + const url = generateOcsUrl('cloud/users/{userId}/{collectionScope}', { userId, collectionScope: `${ACCOUNT_PROPERTY_ENUM.EMAIL_COLLECTION}${SCOPE_SUFFIX}` }) + + await confirmPassword() + + const res = await axios.put(url, { + key: convertEmailDomainToASCII(email), + value: scope, + }) + + return res.data +} diff --git a/src/utils/email.js b/src/utils/email.js new file mode 100644 index 0000000..a73f041 --- /dev/null +++ b/src/utils/email.js @@ -0,0 +1,55 @@ +import punycode from 'punycode' + +/** + * Convert the domain part of an email address to ASCII (punycode) if needed. + * Returns the original email if conversion fails or input is malformed. + * + * @param {string} email + * @return {string} + */ +export function convertEmailDomainToASCII(email) { + if (typeof email !== 'string') { + return email + } + const parts = email.split('@') + if (parts.length !== 2) { + return email + } + const [local, domain] = parts + if (/[^\x00-\x7F]/.test(domain)) { + try { + const ascii = punycode.toASCII(domain) + return `${local}@${ascii}` + } catch (e) { + return email + } + } + return email +} + +/** + * Convert punycode domain back to Unicode for display purposes. + * If the domain is not punycoded, returns the original email. + * + * @param {string} email + * @return {string} + */ +export function convertEmailDomainToUnicode(email) { + if (typeof email !== 'string') { + return email + } + const parts = email.split('@') + if (parts.length !== 2) { + return email + } + const [local, domain] = parts + if (/\bxn--/i.test(domain)) { + try { + const unicode = punycode.toUnicode(domain) + return `${local}@${unicode}` + } catch (e) { + return email + } + } + return email +}