Skip to content

Commit

Permalink
feat(auth-admin): Delete email and phone from user profile (#16642)
Browse files Browse the repository at this point in the history
* Adds migration for inserting default scopes to existing clients

* Adds phone, address and email as default scopes when creating clients

* chore: nx format:write update dirty files

* pathc user admin profile to allow to delete email and phone from admin portal

* chore: nx format:write update dirty files

* remove stuff not supposed to be in this pr

* chore: nx format:write update dirty files

* fix onClick

* chore: nx format:write update dirty files

---------

Co-authored-by: andes-it <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 14, 2024
1 parent 85b4042 commit e1dd2d5
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 47 deletions.
32 changes: 32 additions & 0 deletions apps/services/user-profile/src/app/v2/user-profile.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ApiSecurity, ApiTags } from '@nestjs/swagger'
import {
BadRequestException,
Body,
Controller,
Get,
Headers,
Patch,
Query,
UseGuards,
} from '@nestjs/common'
Expand All @@ -19,6 +21,7 @@ import { UserProfileService } from './user-profile.service'
import { PaginatedUserProfileDto } from './dto/paginated-user-profile.dto'
import { ClientType } from '../types/ClientType'
import { ActorProfileDto } from './dto/actor-profile.dto'
import { PatchUserProfileDto } from './dto/patch-user-profile.dto'

const namespace = '@island.is/user-profile/v2/users'

Expand Down Expand Up @@ -132,4 +135,33 @@ export class UserProfileController {
fromNationalId,
})
}

@Patch('/.national-id')
@Documentation({
description: 'Update user profile for given nationalId.',
request: {
header: {
'X-Param-National-Id': {
required: true,
description: 'National id of the user to update',
},
},
},
response: { status: 200, type: UserProfileDto },
})
@Audit<UserProfileDto>({
resources: (profile) => profile.nationalId,
})
@Scopes(AdminPortalScope.serviceDesk)
patchUserProfile(
@Headers('X-Param-National-Id') nationalId: string,
@Body() userProfile: PatchUserProfileDto,
): Promise<UserProfileDto> {
return this.userProfileService.patch(
{
nationalId,
},
userProfile,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,19 @@ export class UserProfileService {
}

async patch(
user: User,
user: {
nationalId: string
audkenniSimNumber?: string
},
userProfile: PatchUserProfileDto,
): Promise<UserProfileDto> {
const { nationalId, audkenniSimNumber } = user
const isEmailDefined = isDefined(userProfile.email)
const isMobilePhoneNumberDefined = isDefined(userProfile.mobilePhoneNumber)

const audkenniSimSameAsMobilePhoneNumber =
audkenniSimNumber &&
isMobilePhoneNumberDefined &&
this.checkAudkenniSameAsMobilePhoneNumber(
audkenniSimNumber,
userProfile.mobilePhoneNumber,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class UserProfileServiceV1 {
return userProfileResponse
}

async updateUserProfile(
async updateMeUserProfile(
input: UpdateUserProfileInput,
user: User,
): Promise<UserProfile> {
Expand Down
17 changes: 15 additions & 2 deletions libs/api/domains/user-profile/src/lib/V2/userProfile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export class UserProfileServiceV2 {
}

async createUserProfile(input: UpdateUserProfileInput, user: User) {
return this.updateUserProfile(input, user)
return this.updateMeUserProfile(input, user)
}

async updateUserProfile(
async updateMeUserProfile(
input: UpdateUserProfileInput,
user: User,
): Promise<UserProfile> {
Expand Down Expand Up @@ -171,4 +171,17 @@ export class UserProfileServiceV2 {
xParamNationalId: nationalId,
})
}

async updateUserProfile(
input: UpdateUserProfileInput,
user: User,
nationalId: string,
): Promise<AdminUserProfile> {
return this.v2UserProfileApiWithAuth(
user,
).userProfileControllerPatchUserProfile({
xParamNationalId: nationalId,
patchUserProfileDto: input,
})
}
}
29 changes: 27 additions & 2 deletions libs/api/domains/user-profile/src/lib/adminUserProfile.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'
import { UseGuards } from '@nestjs/common'
import {
Args,
Mutation,
Parent,
Query,
ResolveField,
Resolver,
} from '@nestjs/graphql'
import { BadRequestException, UseGuards } from '@nestjs/common'

import type { User } from '@island.is/auth-nest-tools'
import { CurrentUser, IdsUserGuard } from '@island.is/auth-nest-tools'
Expand All @@ -8,6 +15,8 @@ import { IdentityClientService } from '@island.is/clients/identity'
import { UserProfileService } from './userProfile.service'
import { PaginatedUserProfileResponse } from './dto/paginated-user-profile.response'
import { AdminUserProfile } from './adminUserProfile.model'
import { UserProfile } from './userProfile.model'
import { UpdateUserProfileInput } from './dto/updateUserProfileInput'

@UseGuards(IdsUserGuard)
@Resolver(() => AdminUserProfile)
Expand Down Expand Up @@ -39,6 +48,22 @@ export class AdminUserProfileResolver {
return this.userUserProfileService.getUserProfiles(user, query)
}

@Mutation(() => AdminUserProfile, {
nullable: false,
name: 'UserProfileAdminUpdateProfile',
})
async updateUserProfile(
@Args('nationalId') nationalId: string,
@Args('input') input: UpdateUserProfileInput,
@CurrentUser() user: User,
): Promise<AdminUserProfile> {
return this.userUserProfileService.updateUserProfile(
nationalId,
input,
user,
)
}

@ResolveField('fullName', () => String, { nullable: true })
async getFullName(@Parent() adminUserProfile: AdminUserProfile) {
const identity = await this.identityService.getIdentity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IsEmail,
IsEnum,
IsBoolean,
ValidateIf,
} from 'class-validator'
import { Locale } from '../types/locales.enum'
import { DataStatus } from '../types/dataStatus.enum'
Expand All @@ -23,6 +24,7 @@ export class UpdateUserProfileInput {

@Field(() => String, { nullable: true })
@IsOptional()
@ValidateIf((e) => e.email !== '')
@IsEmail()
email?: string

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class UserProfileResolver {
@Args('input') input: UpdateUserProfileInput,
@CurrentUser() user: User,
): Promise<UserProfile | null> {
return this.userProfileService.updateUserProfile(input, user)
return this.userProfileService.updateMeUserProfile(input, user)
}

@Mutation(() => UserProfile, { nullable: true })
Expand Down
14 changes: 11 additions & 3 deletions libs/api/domains/user-profile/src/lib/userProfile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,28 @@ export class UserProfileService {
return service.createUserProfile(input, user)
}

async updateUserProfile(
async updateMeUserProfile(
input: UpdateUserProfileInput,
user: User,
): Promise<UserProfile> {
const service = await this.getService(user)

return service.updateUserProfile(input, user)
return service.updateMeUserProfile(input, user)
}

async updateUserProfile(
nationalId: string,
input: UpdateUserProfileInput,
user: User,
): Promise<UserProfile> {
return this.userProfileServiceV2.updateUserProfile(input, user, nationalId)
}

async deleteIslykillValue(
input: DeleteIslykillValueInput,
user: User,
): Promise<DeleteIslykillSettings> {
const { nationalId } = await this.userProfileServiceV2.updateUserProfile(
const { nationalId } = await this.userProfileServiceV2.updateMeUserProfile(
{
...(input.email && { email: '' }),
...(input.mobilePhoneNumber && { mobilePhoneNumber: '' }),
Expand Down
12 changes: 12 additions & 0 deletions libs/portals/admin/service-desk/src/lib/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const m = defineMessages({
id: 'admin-portal.service-desk:email',
defaultMessage: 'Netfang',
},
delete: {
id: 'admin-portal.service-desk:delete-email',
defaultMessage: 'Eyða',
},
phone: {
id: 'admin-portal.service-desk:phone',
defaultMessage: 'Símanúmer',
Expand Down Expand Up @@ -139,4 +143,12 @@ export const m = defineMessages({
id: 'admin-portal.service-desk:no-users-found',
defaultMessage: 'Engir notendur fundust',
},
noEmail: {
id: 'admin-portal.service-desk:no-email',
defaultMessage: 'Ekkert netfang skráð',
},
noMobilePhone: {
id: 'admin-portal.service-desk:no-mobile-phone',
defaultMessage: 'Ekkert símanúmer skráð',
},
})
51 changes: 37 additions & 14 deletions libs/portals/admin/service-desk/src/screens/Companies/Companies.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { useEffect, useState } from 'react'
import { Form, useActionData, useNavigate } from 'react-router-dom'
import { useEffect, useRef, useState } from 'react'
import {
Form,
useActionData,
useNavigate,
useSearchParams,
useSubmit,
} from 'react-router-dom'
import * as kennitala from 'kennitala'

import { useLocale } from '@island.is/localization'
Expand All @@ -20,35 +26,42 @@ import { ServiceDeskPaths } from '../../lib/paths'
import * as styles from './Companies.css'

const Companies = () => {
const [searchParams, setSearchParams] = useSearchParams()
const searchQuery = searchParams?.get('q')

const [focused, setFocused] = useState(false)
const [searchInput, setSearchInput] = useState('')
const [prevSearchInput, setPrevSearchInput] = useState('')
const [searchInput, setSearchInput] = useState(() => searchQuery || '')
const submit = useSubmit()

const actionData = useActionData() as GetCompaniesResult
const { formatMessage } = useLocale()
const navigate = useNavigate()
const { isSubmitting, isLoading } = useSubmitting()
const companies = actionData?.data?.data
const formRef = useRef(null)

const onFocus = () => setFocused(true)
const onBlur = () => setFocused(false)

useEffect(() => {
if (actionData) {
setPrevSearchInput(searchInput)
}

if (actionData?.globalError) {
toast.error(formatMessage(m.errorDefault))
}
}, [actionData])

useEffect(() => {
if (searchInput) {
submit(formRef.current)
}
}, [])

return (
<>
<IntroHeader
title={formatMessage(m.procures)}
intro={formatMessage(m.procuresDescription)}
/>
<Form method="post">
<Form method="post" ref={formRef}>
<Box display={['inline', 'inline', 'flex']} className={styles.search}>
<AsyncSearchInput
hasFocus={focused}
Expand All @@ -65,20 +78,30 @@ const Companies = () => {
}}
buttonProps={{
type: 'submit',
disabled: searchInput.length === 0,
disabled: !searchInput,
onClick: (e) => {
e.preventDefault()
if (searchInput) {
setSearchParams((params) => {
params.set('q', searchInput)
return params
})
submit(formRef.current)
}
},
}}
/>
</Box>
</Form>
<Box marginTop={4}>
<Box className={styles.relative}>
<Stack space={3}>
{companies?.length === 0 ? (
{companies?.length === 0 && !!searchQuery ? (
<Card
title={
kennitala.isValid(prevSearchInput)
? formatNationalId(prevSearchInput)
: prevSearchInput
kennitala.isValid(searchQuery)
? formatNationalId(searchQuery)
: searchQuery
}
description={formatMessage(m.noContent)}
bgGrey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { m } from '../../lib/messages'
import { ServiceDeskPaths } from '../../lib/paths'
import { CompanyRelationshipResult } from './Procurers.loader'
import { Card } from '../../components/Card'
import React from 'react'

const Procurers = () => {
const { formatMessage } = useLocale()
Expand All @@ -18,7 +19,11 @@ const Procurers = () => {

return (
<Stack space="containerGutter">
<BackButton onClick={() => navigate(ServiceDeskPaths.Companies)} />
<BackButton
onClick={() => {
navigate(-1)
}}
/>{' '}
<div>
<IntroHeader title={company.name} intro={formattedNationalId} />
<Box marginTop={[3, 3, 6]}>
Expand Down
19 changes: 19 additions & 0 deletions libs/portals/admin/service-desk/src/screens/User/User.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,22 @@ query GetUserProfileByNationalId($nationalId: String!) {
locale
}
}

mutation UpdateUserProfile(
$nationalId: String!
$input: UpdateUserProfileInput!
) {
UserProfileAdminUpdateProfile(nationalId: $nationalId, input: $input) {
nationalId
email
emailVerified
mobilePhoneNumber
mobilePhoneNumberVerified
lastNudge
nextNudge
fullName
documentNotifications
emailNotifications
locale
}
}
Loading

0 comments on commit e1dd2d5

Please sign in to comment.