From 66c7b7abff9081f69fac4e4b1e34d94627e7f0c1 Mon Sep 17 00:00:00 2001 From: Mikko Riippi Date: Thu, 8 Aug 2024 10:15:01 +0300 Subject: [PATCH] feat: password change HP-2289 (#371) * feat: password change --- src/auth/__tests__/authService.test.ts | 8 ++ src/auth/authService.ts | 18 ++++ src/common/test/myProfileMocking.ts | 2 + src/graphql/generatedTypes.ts | 13 ++- src/graphql/typings.ts | 1 + src/i18n/en.json | 33 ++++--- src/i18n/fi.json | 27 ++--- src/i18n/sv.json | 21 ++-- .../AuthenticationProviderInformation.tsx | 44 ++++++++- ...AuthenticationProviderInformation.test.tsx | 24 +++++ ...nticationProviderInformation.test.tsx.snap | 99 ++++++++++++++++++- .../authenticationProviderUtil.ts | 7 ++ src/profile/graphql/MyProfileQuery.ts | 1 + 13 files changed, 254 insertions(+), 44 deletions(-) diff --git a/src/auth/__tests__/authService.test.ts b/src/auth/__tests__/authService.test.ts index 41244e293..c8b256757 100644 --- a/src/auth/__tests__/authService.test.ts +++ b/src/auth/__tests__/authService.test.ts @@ -535,4 +535,12 @@ describe('authService', () => { }); }); }); + + describe('changePassword', () => { + it('should resolve if signinRedirect is successful', async () => { + vi.spyOn(userManager, 'signinRedirect').mockResolvedValueOnce(); + + await expect(authService.changePassword()).resolves.toBeUndefined(); + }); + }); }); diff --git a/src/auth/authService.ts b/src/auth/authService.ts index 953f1175f..9658b9fca 100644 --- a/src/auth/authService.ts +++ b/src/auth/authService.ts @@ -258,6 +258,24 @@ export class AuthService { sessionStorage.removeItem(API_TOKEN); this.userSessionValidityPoller.stop(); } + + public async changePassword(): Promise { + let success = true; + await this.userManager + .signinRedirect({ + ui_locales: i18n.language, + extraQueryParams: { + kc_action: 'UPDATE_PASSWORD', + }, + }) + .catch(error => { + success = false; + if (error.message !== 'Network Error') { + Sentry.captureException(error); + } + }); + return success ? Promise.resolve() : Promise.reject(); + } } export default new AuthService(); diff --git a/src/common/test/myProfileMocking.ts b/src/common/test/myProfileMocking.ts index 22259d321..1250ae8a8 100644 --- a/src/common/test/myProfileMocking.ts +++ b/src/common/test/myProfileMocking.ts @@ -21,6 +21,7 @@ import { Addresses, Phones, InsertableNode, + LoginMethodType, } from '../../graphql/typings'; import { AdditionalInformationSource, @@ -185,6 +186,7 @@ export const getMyProfile = (): ProfileRoot => ({ __typename: 'PhoneNodeConnection', }, verifiedPersonalInformation: null, + loginMethods: [LoginMethodType.PASSWORD], __typename: 'ProfileNode', }, }); diff --git a/src/graphql/generatedTypes.ts b/src/graphql/generatedTypes.ts index 7d135ff95..ca6cebd2d 100644 --- a/src/graphql/generatedTypes.ts +++ b/src/graphql/generatedTypes.ts @@ -206,6 +206,15 @@ export enum Language { SWEDISH = 'SWEDISH' } +export enum LoginMethodType { + /** One-time password */ + OTP = 'OTP', + /** Salasana */ + PASSWORD = 'PASSWORD', + /** Suomi.fi */ + SUOMI_FI = 'SUOMI_FI' +} + export enum PhoneType { /** Home phone */ HOME = 'HOME', @@ -524,12 +533,12 @@ export type MyProfileQueryEmailsFragment = { readonly __typename: 'EmailNodeConn export type MyProfileQueryPhonesFragment = { readonly __typename: 'PhoneNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'PhoneNodeEdge', readonly node: { readonly __typename: 'PhoneNode', readonly primary: boolean, readonly id: string, readonly phone: string, readonly phoneType: PhoneType | null } | null } | null> }; -export type MyProfileQueryFragment = { readonly __typename: 'ProfileNode', readonly id: string, readonly firstName: string, readonly lastName: string, readonly nickname: string, readonly language: Language | null, readonly primaryAddress: { readonly __typename: 'AddressNode', readonly id: string, readonly primary: boolean, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null, readonly addresses: { readonly __typename: 'AddressNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'AddressNodeEdge', readonly node: { readonly __typename: 'AddressNode', readonly primary: boolean, readonly id: string, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null } | null> } | null, readonly primaryEmail: { readonly __typename: 'EmailNode', readonly id: string, readonly email: string, readonly primary: boolean, readonly emailType: EmailType | null } | null, readonly emails: { readonly __typename: 'EmailNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'EmailNodeEdge', readonly node: { readonly __typename: 'EmailNode', readonly primary: boolean, readonly id: string, readonly email: string, readonly emailType: EmailType | null } | null } | null> } | null, readonly primaryPhone: { readonly __typename: 'PhoneNode', readonly id: string, readonly phone: string, readonly primary: boolean, readonly phoneType: PhoneType | null } | null, readonly phones: { readonly __typename: 'PhoneNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'PhoneNodeEdge', readonly node: { readonly __typename: 'PhoneNode', readonly primary: boolean, readonly id: string, readonly phone: string, readonly phoneType: PhoneType | null } | null } | null> } | null, readonly verifiedPersonalInformation: { readonly __typename: 'VerifiedPersonalInformationNode', readonly firstName: string, readonly lastName: string, readonly givenName: string, readonly nationalIdentificationNumber: string, readonly municipalityOfResidence: string, readonly municipalityOfResidenceNumber: string, readonly permanentAddress: { readonly __typename: 'VerifiedPersonalInformationAddressNode', readonly streetAddress: string, readonly postalCode: string, readonly postOffice: string } | null, readonly permanentForeignAddress: { readonly __typename: 'VerifiedPersonalInformationForeignAddressNode', readonly streetAddress: string, readonly additionalAddress: string, readonly countryCode: string } | null } | null }; +export type MyProfileQueryFragment = { readonly __typename: 'ProfileNode', readonly id: string, readonly firstName: string, readonly lastName: string, readonly nickname: string, readonly language: Language | null, readonly loginMethods: ReadonlyArray | null, readonly primaryAddress: { readonly __typename: 'AddressNode', readonly id: string, readonly primary: boolean, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null, readonly addresses: { readonly __typename: 'AddressNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'AddressNodeEdge', readonly node: { readonly __typename: 'AddressNode', readonly primary: boolean, readonly id: string, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null } | null> } | null, readonly primaryEmail: { readonly __typename: 'EmailNode', readonly id: string, readonly email: string, readonly primary: boolean, readonly emailType: EmailType | null } | null, readonly emails: { readonly __typename: 'EmailNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'EmailNodeEdge', readonly node: { readonly __typename: 'EmailNode', readonly primary: boolean, readonly id: string, readonly email: string, readonly emailType: EmailType | null } | null } | null> } | null, readonly primaryPhone: { readonly __typename: 'PhoneNode', readonly id: string, readonly phone: string, readonly primary: boolean, readonly phoneType: PhoneType | null } | null, readonly phones: { readonly __typename: 'PhoneNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'PhoneNodeEdge', readonly node: { readonly __typename: 'PhoneNode', readonly primary: boolean, readonly id: string, readonly phone: string, readonly phoneType: PhoneType | null } | null } | null> } | null, readonly verifiedPersonalInformation: { readonly __typename: 'VerifiedPersonalInformationNode', readonly firstName: string, readonly lastName: string, readonly givenName: string, readonly nationalIdentificationNumber: string, readonly municipalityOfResidence: string, readonly municipalityOfResidenceNumber: string, readonly permanentAddress: { readonly __typename: 'VerifiedPersonalInformationAddressNode', readonly streetAddress: string, readonly postalCode: string, readonly postOffice: string } | null, readonly permanentForeignAddress: { readonly __typename: 'VerifiedPersonalInformationForeignAddressNode', readonly streetAddress: string, readonly additionalAddress: string, readonly countryCode: string } | null } | null }; export type MyProfileQueryVariables = Exact<{ [key: string]: never; }>; -export type MyProfileQuery = { readonly myProfile: { readonly __typename: 'ProfileNode', readonly id: string, readonly firstName: string, readonly lastName: string, readonly nickname: string, readonly language: Language | null, readonly primaryAddress: { readonly __typename: 'AddressNode', readonly id: string, readonly primary: boolean, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null, readonly addresses: { readonly __typename: 'AddressNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'AddressNodeEdge', readonly node: { readonly __typename: 'AddressNode', readonly primary: boolean, readonly id: string, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null } | null> } | null, readonly primaryEmail: { readonly __typename: 'EmailNode', readonly id: string, readonly email: string, readonly primary: boolean, readonly emailType: EmailType | null } | null, readonly emails: { readonly __typename: 'EmailNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'EmailNodeEdge', readonly node: { readonly __typename: 'EmailNode', readonly primary: boolean, readonly id: string, readonly email: string, readonly emailType: EmailType | null } | null } | null> } | null, readonly primaryPhone: { readonly __typename: 'PhoneNode', readonly id: string, readonly phone: string, readonly primary: boolean, readonly phoneType: PhoneType | null } | null, readonly phones: { readonly __typename: 'PhoneNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'PhoneNodeEdge', readonly node: { readonly __typename: 'PhoneNode', readonly primary: boolean, readonly id: string, readonly phone: string, readonly phoneType: PhoneType | null } | null } | null> } | null, readonly verifiedPersonalInformation: { readonly __typename: 'VerifiedPersonalInformationNode', readonly firstName: string, readonly lastName: string, readonly givenName: string, readonly nationalIdentificationNumber: string, readonly municipalityOfResidence: string, readonly municipalityOfResidenceNumber: string, readonly permanentAddress: { readonly __typename: 'VerifiedPersonalInformationAddressNode', readonly streetAddress: string, readonly postalCode: string, readonly postOffice: string } | null, readonly permanentForeignAddress: { readonly __typename: 'VerifiedPersonalInformationForeignAddressNode', readonly streetAddress: string, readonly additionalAddress: string, readonly countryCode: string } | null } | null } | null }; +export type MyProfileQuery = { readonly myProfile: { readonly __typename: 'ProfileNode', readonly id: string, readonly firstName: string, readonly lastName: string, readonly nickname: string, readonly language: Language | null, readonly loginMethods: ReadonlyArray | null, readonly primaryAddress: { readonly __typename: 'AddressNode', readonly id: string, readonly primary: boolean, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null, readonly addresses: { readonly __typename: 'AddressNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'AddressNodeEdge', readonly node: { readonly __typename: 'AddressNode', readonly primary: boolean, readonly id: string, readonly address: string, readonly postalCode: string, readonly city: string, readonly countryCode: string, readonly addressType: AddressType | null } | null } | null> } | null, readonly primaryEmail: { readonly __typename: 'EmailNode', readonly id: string, readonly email: string, readonly primary: boolean, readonly emailType: EmailType | null } | null, readonly emails: { readonly __typename: 'EmailNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'EmailNodeEdge', readonly node: { readonly __typename: 'EmailNode', readonly primary: boolean, readonly id: string, readonly email: string, readonly emailType: EmailType | null } | null } | null> } | null, readonly primaryPhone: { readonly __typename: 'PhoneNode', readonly id: string, readonly phone: string, readonly primary: boolean, readonly phoneType: PhoneType | null } | null, readonly phones: { readonly __typename: 'PhoneNodeConnection', readonly edges: ReadonlyArray<{ readonly __typename: 'PhoneNodeEdge', readonly node: { readonly __typename: 'PhoneNode', readonly primary: boolean, readonly id: string, readonly phone: string, readonly phoneType: PhoneType | null } | null } | null> } | null, readonly verifiedPersonalInformation: { readonly __typename: 'VerifiedPersonalInformationNode', readonly firstName: string, readonly lastName: string, readonly givenName: string, readonly nationalIdentificationNumber: string, readonly municipalityOfResidence: string, readonly municipalityOfResidenceNumber: string, readonly permanentAddress: { readonly __typename: 'VerifiedPersonalInformationAddressNode', readonly streetAddress: string, readonly postalCode: string, readonly postOffice: string } | null, readonly permanentForeignAddress: { readonly __typename: 'VerifiedPersonalInformationForeignAddressNode', readonly streetAddress: string, readonly additionalAddress: string, readonly countryCode: string } | null } | null } | null }; export type NameQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/src/graphql/typings.ts b/src/graphql/typings.ts index c7bc6af4b..e1098d83a 100644 --- a/src/graphql/typings.ts +++ b/src/graphql/typings.ts @@ -93,6 +93,7 @@ export { AddressType, Language, TranslationLanguage, + LoginMethodType, } from './generatedTypes'; export type AnyObject = Record; diff --git a/src/i18n/en.json b/src/i18n/en.json index b652490dc..d23fce8b4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -42,8 +42,8 @@ "title": "Do you want to delete your information?", "loadingServices": "Searching for services connected to the profile", "deleteSuccessful": "The Helsinki profile and all the data we hold on you have been deleted successfully!", - "deleteFailed": "The deletion failed for some reason. Please try again after a while!", - "deleteInfoforLightAuthentication": "You have services linked to your profile for which you have used strong identification. Please log out and log in again using Suomi.fi-authentication to delete your data." + "deleteFailed": "The deletion failed for some reason. Please try again after a while.", + "deleteInfoforLightAuthentication": "You have services linked to your profile for which you have used strong identification. Please log out and log in again using Suomi.fi authentication to delete your data." }, "deleteProfileModal": { "delete": "Delete my information", @@ -62,7 +62,7 @@ "button": "Download my information (.json)", "panelText": "Here you can download a file containing all the data related to you. The file contains all the data about you stored in the Helsinki profile and information about all the associated e-services.", "panelTitle": "Do you want to download your information?", - "extrapanelTextforLightAuthentication": "You have services linked to your profile for which you have used strong identification. Please log out and log back in with Suomi.fi-authentication to download your data." + "extrapanelTextforLightAuthentication": "You have services linked to your profile for which you have used strong identification. Please log out and log back in with Suomi.fi authentication to download your data." }, "expandingPanel": { "closeButtonText": "Close", @@ -101,9 +101,9 @@ }, "loading": "Loading...", "login": { - "description": "A Helsinki profile will be created for you when you log in to City of Helsinki services. Through your profile, you can view and manage your data and how it is used in different services.", + "description": "A Helsinki profile will be created for you when you log in to City of Helsinki services. Through your profile, you can manage your data and view how it is used in different services.", "login": "Log in", - "title": "Your profile information in one address!" + "title": "Your profile information at one address!" }, "nav": { "information": "Information", @@ -188,11 +188,14 @@ "ariaShowOptions": "Show options", "ariaSelectedOption": "{{value}} is selected", "ariaNoSelectedItemForLabel": "{{label}} is not selected", + "changePassword": "Change password", "deleteProfile": "Delete your profile", - "description": "The data stored in the Helsinki profile is used in the City of Helsinki’s e-services. You can find more detailed information on the {{linkToServicesText}} page.", + "description": "The data stored in the Helsinki profile is used in the City of Helsinki’s e-services. You can find more detailed information on the {{linkToServicesText}} page.", "downloadData": "Download your data", "email": "Email", + "loginAndAuthentication": "Login and authentication", "name": "Name", + "password": "Password", "personalData": "Personal data", "phone": "Phone number", "title": "My information", @@ -200,7 +203,7 @@ "authenticationMethod": "Authentication method", "permanentAddress": "Permanent address", "permanentForeignAddress": "Permanent foreign address", - "verifiedDataInformation": "You can only see the official information when you have logged in using strong authentication via the Suomi.fi service. The information has been retrieved from The National Population Information System and cannot be edited via your Helsinki profile. You can check the data stored about you in the Population Information System in the suomi.fi -web service.", + "verifiedDataInformation": "You can only see the official information when you have logged in using strong authentication via the Suomi.fi service. The information has been retrieved from The National Population Information System and cannot be edited via your Helsinki profile. You can check the data stored about you in the Population Information System in the Suomi.fi web service.", "verifiedDataInformationLink": "https://www.suomi.fi/", "verifiedData": "Verified data", "verifiedBasicData": "Official information", @@ -239,7 +242,7 @@ "connectionRemovalVerificationButtonText": "Delete data", "connectionRemovalError": "For some reason, the deletion was not successful. Please, try again later!", "contactServiceToDelete": "If you still want to delete the data, please contact the service directly and then retry deleting the data.", - "explanationforLightAuthentication": "You can only delete data from this service if you are strongly identified. Please log out and log in again using Suomi.fi-identification to delete your data." + "explanationforLightAuthentication": "You can only delete data from this service if you are strongly identified. Please log out and log in again using Suomi.fi identification to delete your data." }, "skipToContent": "Skip to content", "validation": { @@ -253,12 +256,12 @@ "requiredFieldIndication": "Fields marked with an * are compulsory." }, "identityProvider": { - "helsinkiAccount": "Helsinki Profile user ID", - "github": "GitHub", - "google": "Google", - "facebook": "Facebook", - "yletunnus": "Yle", - "tunnistusSuomifi": "Suomi.fi" + "helsinkiAccount": "You are authenticated with Helsinki ID.", + "github": "You are authenticated with GitHub account.", + "google": "You are authenticated with Google account.", + "facebook": "You are authenticated with Facebook account.", + "yletunnus": "You are authenticated with Yle ID.", + "tunnistusSuomifi": "You are authenticated with Suomi.fi e-Identification." }, "opensInNewWindow": "{{title}}. Opens in a new window.", "openInNewTabAriaLabel": "Opens in a new tab.", @@ -285,7 +288,7 @@ "consentStorageDescription": "Cookie stores consent for using analytics cookies." }, "changeProfilePassword ": { - "explanationForStrongAuthentication": "You can only change your password if you are strongly identified. Log out and log in again using Suomi.fi-authentication to change your password." + "explanationForStrongAuthentication": "You can only change your password if you are strongly identified. Log out and log in again using Suomi.fi authentication to change your password." }, "userGuide": "Helsinki profile guide" } \ No newline at end of file diff --git a/src/i18n/fi.json b/src/i18n/fi.json index c8c310652..7e133cbde 100644 --- a/src/i18n/fi.json +++ b/src/i18n/fi.json @@ -42,7 +42,7 @@ "title": "Haluatko poistaa omat tietosi?", "loadingServices": "Haetaan profiiliin yhdistettyjä palveluita", "deleteSuccessful": "Helsinki-profiili ja kaikki sinusta tallentamamme tiedot on poistettu onnistuneesti!", - "deleteFailed": "Jostain syystä poisto ei onnistunut. Kokeile hetken kuluttua uudelleen!", + "deleteFailed": "Jostain syystä poisto ei onnistunut. Kokeile hetken kuluttua uudelleen.", "deleteInfoforLightAuthentication": "Profiilissasi on yhdistettynä palveluita joihin olet käyttänyt vahvaa tunnistautumista. Kirjaudu ulos ja takaisin sisään Suomi.fi-tunnistautuen poistaaksesi tiedot." }, "deleteProfileModal": { @@ -177,9 +177,9 @@ }, "profileInformation": { "address": "Osoite", - "addressDescription": "Nämä tiedot olet itse lisännyt Helsinki-profiili -palveluun tai muuhun Helsingin kaupungin asiointipalveluun. Tätä osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi -tunnistautumista hyödyntäen. Voit halutessasi poistaa tiedot Helsinki-profiilista, jolloin ne poistuvat myös muista asiointipalveluista. Älä poista tietoja, jos sinulla on asiointi kesken.", - "addressDescriptionNoWeakAddress": "Et ole lisännyt toista osoitetta. Toista osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi -tunnistautumista hyödyntäen.", - "addressDescriptionNoAddress": "Et ole lisännyt osoitetta. Tätä osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi -tunnistautumista hyödyntäen.", + "addressDescription": "Nämä tiedot olet itse lisännyt Helsinki-profiili-palveluun tai muuhun Helsingin kaupungin asiointipalveluun. Tätä osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi-tunnistautumista hyödyntäen. Voit halutessasi poistaa tiedot Helsinki-profiilista, jolloin ne poistuvat myös muista asiointipalveluista. Älä poista tietoja, jos sinulla on asiointi kesken.", + "addressDescriptionNoWeakAddress": "Et ole lisännyt toista osoitetta. Toista osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi-tunnistautumista hyödyntäen.", + "addressDescriptionNoAddress": "Et ole lisännyt osoitetta. Tätä osoitetietoa käytämme silloin, kun olet tunnistautunut kevyesti, eli jotain muuta kuin Suomi.fi-tunnistautumista hyödyntäen.", "addressTitleWhenHasVerifiedData": "Muu osoite", "ariaEditAddress": "Muokkaa osoitetta {{value}}", "ariaEditPhone": "Muokkaa puhelinnumeroa {{value}}", @@ -188,11 +188,14 @@ "ariaShowOptions": "Näytä vaihtoehdot", "ariaSelectedOption": "{{value}} on valittu", "ariaNoSelectedItemForLabel": "{{label}} ei ole valittuna", + "changePassword": "Vaihda salasana", "deleteProfile": "Poista omat tietosi", - "description": "Helsinki-profiiliin tallennettuja tietoja käytetään Helsingin kaupungin asiointipalveluissa. Tarkemmat tiedot löydät {{linkToServicesText}} sivulta.", + "description": "Helsinki-profiiliin tallennettuja tietoja käytetään Helsingin kaupungin asiointipalveluissa. Tarkemmat tiedot löydät {{linkToServicesText}} -sivulta.", "downloadData": "Lataa omat tietosi (.json)", "email": "Sähköpostiosoite", + "loginAndAuthentication": "Kirjautuminen ja tunnistautuminen", "name": "Nimi", + "password": "Salasana", "personalData": "Henkilötiedot", "phone": "Puhelinnumero", "title": "Omat tiedot", @@ -200,7 +203,7 @@ "authenticationMethod": "Tunnistautumistapa", "permanentAddress": "Vakinainen osoite", "permanentForeignAddress": "Vakinainen ulkomainen osoite", - "verifiedDataInformation": "Viralliset tiedot näet vain, kun olet vahvasti tunnistautunut Suomi.fi -kirjautumisen avulla. Tiedot on haettu virallisesta väestötietojärjestelmästä, eikä niitä voi muokata Helsinki-profiilin kautta. Voit tutustua väestötietojärjestelmään tallennettuihin tietoihisi suomi.fi -verkkopalvelussa.", + "verifiedDataInformation": "Viralliset tiedot näet vain, kun olet vahvasti tunnistautunut Suomi.fi-kirjautumisen avulla. Tiedot on haettu virallisesta väestötietojärjestelmästä, eikä niitä voi muokata Helsinki-profiilin kautta. Voit tutustua väestötietojärjestelmään tallennettuihin tietoihisi Suomi.fi-verkkopalvelussa.", "verifiedDataInformationLink": "https://www.suomi.fi/", "verifiedData": "Vahvistettu tieto", "verifiedBasicData": "Viralliset tiedot", @@ -253,12 +256,12 @@ "requiredFieldIndication": "Tähdellä (*) merkityt kentät ovat pakollisia." }, "identityProvider": { - "helsinkiAccount": "Helsinki-profiilin käyttäjätunnus", - "github": "GitHub", - "google": "Google", - "facebook": "Facebook", - "yletunnus": "Yle", - "tunnistusSuomifi": "Suomi.fi" + "helsinkiAccount": "Olet tunnistautuneena Helsinki-tunnuksella.", + "github": "Olet tunnistautuneena GitHub-tilillä.", + "google": "Olet tunnistautuneena Google-tilillä.", + "facebook": "Olet tunnistautuneena Facebook-tilillä.", + "yletunnus": "Olet tunnistautuneena Yle Tunnuksella.", + "tunnistusSuomifi": "Olet tunnistautuneena Suomi.fi-tunnistuksella." }, "opensInNewWindow": "{{title}}. Avautuu uudessa ikkunassa.", "openInNewTabAriaLabel": "Avautuu uudessa välilehdessä.", diff --git a/src/i18n/sv.json b/src/i18n/sv.json index d96106886..c9cb240be 100644 --- a/src/i18n/sv.json +++ b/src/i18n/sv.json @@ -42,7 +42,7 @@ "title": "Vill du radera din information?", "loadingServices": "Söker efter tjänster som är kopplade till profilen", "deleteSuccessful": "Helsingforsprofilen och alla uppgifter som vi har sparat om dig har tagits bort på ett lyckat sätt.", - "deleteFailed": "Av någon anledning lyckades borttagningen inte. Försök på nytt om en stund!", + "deleteFailed": "Av någon anledning lyckades borttagningen inte. Försök på nytt om en stund.", "deleteInfoforLightAuthentication": "Du har tjänster kopplade till din Helsingforsprofil för vilka du har använt stark autentisering. Logga ut och logga in igen med Suomi.fi-autentisering för att radera dina uppgifter." }, "deleteProfileModal": { @@ -188,11 +188,14 @@ "ariaShowOptions": "Visa alternativ", "ariaSelectedOption": "{{value}} har valts", "ariaNoSelectedItemForLabel": "{{label}} har inte valts", + "changePassword": "Uppdatera lösenord", "deleteProfile": "Radera profilen", - "description": "Uppgifterna som sparats i Helsingforsprofilen används i Helsingfors stads e-tjänster. Närmare information hittar du på {{linkToServicesText}} sidan.", + "description": "Uppgifterna som sparats i Helsingforsprofilen används i Helsingfors stads e-tjänster. Närmare information hittar du på {{linkToServicesText}} sidan.", "downloadData": "Ladda ner min information", "email": "Epost", + "loginAndAuthentication": "Inloggning och autentisering", "name": "Namn", + "password": "Lösenord", "personalData": "Personuppgifter", "phone": "Telefonnummer", "title": "Min information", @@ -200,7 +203,7 @@ "authenticationMethod": "Autentiseringsmetod", "permanentAddress": "Stadigvarande adress", "permanentForeignAddress": "Stadigvarande utländsk adress", - "verifiedDataInformation": "Du ser de officiella uppgifterna endast när du loggat in med stark identifiering, det vill säga Suomi-fi-identifieringen. Uppgifterna har hämtats från det officiella befolkningsdatasystemet och kan inte redigeras via Helsingforsprofilen. Du kan se uppgifterna om dig i befolkningsdatasystemet i webbtjänsten suomi.fi.", + "verifiedDataInformation": "Du ser de officiella uppgifterna endast när du loggat in med stark identifiering, det vill säga Suomi-fi-identifieringen. Uppgifterna har hämtats från det officiella befolkningsdatasystemet och kan inte redigeras via Helsingforsprofilen. Du kan se uppgifterna om dig i befolkningsdatasystemet i webbtjänsten Suomi.fi.", "verifiedDataInformationLink": "https://www.suomi.fi/", "verifiedData": "Verifierad uppgift", "verifiedBasicData": "Officiella uppgifter", @@ -253,12 +256,12 @@ "requiredFieldIndication": "Fälten markerade med en asterisk (*) är obligatoriska." }, "identityProvider": { - "helsinkiAccount": "Användarnamnet för Helsingforsprofilen", - "github": "GitHub", - "google": "Google", - "facebook": "Facebook", - "yletunnus": "Yle", - "tunnistusSuomifi": "Suomi.fi" + "helsinkiAccount": "Du är autentiserad med Helsingfors-konto.", + "github": "Du är autentiserad med GitHub-konto.", + "google": "Du är autentiserad med Google-konto.", + "facebook": "Du är autentiserad med Facebook-konto.", + "yletunnus": "Du är autentiserad med Yle Konto.", + "tunnistusSuomifi": "Du är autentiserad med Suomi.fi-identifikation." }, "opensInNewWindow": "{{title}}. Öppnas i ett nytt fönster.", "openInNewTabAriaLabel": "Öppnas i en ny flik.", diff --git a/src/profile/components/profileInformation/AuthenticationProviderInformation.tsx b/src/profile/components/profileInformation/AuthenticationProviderInformation.tsx index 61fece591..a05e3565f 100644 --- a/src/profile/components/profileInformation/AuthenticationProviderInformation.tsx +++ b/src/profile/components/profileInformation/AuthenticationProviderInformation.tsx @@ -1,15 +1,23 @@ -import React from 'react'; +import React, { Fragment, useContext } from 'react'; import { useTranslation } from 'react-i18next'; +import { Button, IconLinkExternal } from 'hds-react'; +import classNames from 'classnames'; +import authService from '../../../auth/authService'; import useProfile from '../../../auth/useProfile'; -import { getAmrStatic } from './authenticationProviderUtil'; +import { getAmrStatic, hasPasswordLogin } from './authenticationProviderUtil'; import ProfileSection from '../../../common/profileSection/ProfileSection'; import commonFormStyles from '../../../common/cssHelpers/form.module.css'; +import { ProfileContext } from '../../context/ProfileContext'; function AuthenticationProviderInformation(): React.ReactElement | null { const { t } = useTranslation(); const { profile } = useProfile(); + const { data } = useContext(ProfileContext); + + const hasPassword = hasPasswordLogin(data); + const amr = getAmrStatic(profile); if (!amr) { @@ -21,10 +29,38 @@ function AuthenticationProviderInformation(): React.ReactElement | null { return (
-
-

{t('profileInformation.authenticationMethod')}

+
+

{t('profileInformation.loginAndAuthentication')}

{authenticationMethodReferenceName}
+ + {hasPassword && ( + +
+
+
+

+ {t('profileInformation.password')} +

+
+
+ +
+
+
+ )}
); diff --git a/src/profile/components/profileInformation/__tests__/AuthenticationProviderInformation.test.tsx b/src/profile/components/profileInformation/__tests__/AuthenticationProviderInformation.test.tsx index 19566d345..485aab208 100644 --- a/src/profile/components/profileInformation/__tests__/AuthenticationProviderInformation.test.tsx +++ b/src/profile/components/profileInformation/__tests__/AuthenticationProviderInformation.test.tsx @@ -49,4 +49,28 @@ describe(' ', () => { expect(container).toMatchSnapshot(); }); }); + + describe('renders correctly according to Login method', () => { + it('should render change password button when password is used', () => { + vi.spyOn( + authenticationProviderUtil, + 'hasPasswordLogin' + ).mockImplementation(() => true); + + const { container, getByTestId } = getWrapper(); + expect(getByTestId('change-password-button')).toBeVisible(); + expect(container).toMatchSnapshot(); + }); + + it('should not render change password button when password is not used', () => { + vi.spyOn( + authenticationProviderUtil, + 'hasPasswordLogin' + ).mockImplementation(() => false); + + const { container, queryByTestId } = getWrapper(); + expect(queryByTestId('change-password-button')).not.toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + }); }); diff --git a/src/profile/components/profileInformation/__tests__/__snapshots__/AuthenticationProviderInformation.test.tsx.snap b/src/profile/components/profileInformation/__tests__/__snapshots__/AuthenticationProviderInformation.test.tsx.snap index dd9c93c2d..5cad9b436 100644 --- a/src/profile/components/profileInformation/__tests__/__snapshots__/AuthenticationProviderInformation.test.tsx.snap +++ b/src/profile/components/profileInformation/__tests__/__snapshots__/AuthenticationProviderInformation.test.tsx.snap @@ -1,5 +1,100 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[` > renders correctly according to Login method > should not render change password button when password is not used 1`] = ` +
+
+
+
+

+ profileInformation.loginAndAuthentication +

+ + identityProvider.tunnistusSuomifi + +
+
+
+
+`; + +exports[` > renders correctly according to Login method > should render change password button when password is used 1`] = ` +
+
+
+
+

+ profileInformation.loginAndAuthentication +

+ + identityProvider.tunnistusSuomifi + +
+
+
+
+

+ profileInformation.password +

+
+
+ +
+
+
+
+
+`; + exports[` > renders correctly when AMR is helsinkiAccountAMR > should render helsinki account information as expected based on config 1`] = `
> renders correctly when AMR is class="editor-description-container" >

- profileInformation.authenticationMethod + profileInformation.loginAndAuthentication

identityProvider.helsinkiAccount @@ -35,7 +130,7 @@ exports[` > renders correctly when AMR is class="editor-description-container" >

- profileInformation.authenticationMethod + profileInformation.loginAndAuthentication

identityProvider.tunnistusSuomifi diff --git a/src/profile/components/profileInformation/authenticationProviderUtil.ts b/src/profile/components/profileInformation/authenticationProviderUtil.ts index f2547365a..70b34c0c1 100644 --- a/src/profile/components/profileInformation/authenticationProviderUtil.ts +++ b/src/profile/components/profileInformation/authenticationProviderUtil.ts @@ -4,6 +4,7 @@ import { tunnistusSuomifiAMR, } from '../../../auth/useProfile'; import config from '../../../config'; +import { LoginMethodType, ProfileRoot } from '../../../graphql/typings'; function getAmrFromProfileData(profile: Profile | null): string | undefined { return profile && Array.isArray(profile.amr) ? profile.amr[0] : ''; @@ -43,3 +44,9 @@ export function hasTunnistusSuomiFiAmr(profile: Profile | null): boolean { export function hasHelsinkiAccountAMR(profile: Profile | null): boolean { return getAmrFromProfileData(profile) === config.helsinkiAccountAMR; } + +export function hasPasswordLogin(data: ProfileRoot | undefined): boolean { + return ( + data?.myProfile?.loginMethods?.includes(LoginMethodType.PASSWORD) ?? false + ); +} diff --git a/src/profile/graphql/MyProfileQuery.ts b/src/profile/graphql/MyProfileQuery.ts index 6599d144c..0dbed68e1 100644 --- a/src/profile/graphql/MyProfileQuery.ts +++ b/src/profile/graphql/MyProfileQuery.ts @@ -119,6 +119,7 @@ export const MY_PROFILE = gql` lastName nickname language + loginMethods primaryAddress { ...MyProfileQueryPrimaryAddress }