add unlink functionality in profile section, when an email is unlinked that account is starting from scratch resulting in an empty dashboard.

This commit is contained in:
Bernaldo Mihasi 2023-05-23 17:15:11 +03:00
parent 5e764559cf
commit b2f2a79a5d
30 changed files with 818 additions and 7 deletions

View File

@ -0,0 +1,57 @@
package eu.eudat.controllers;
import eu.eudat.exceptions.emailconfirmation.HasConfirmedEmailException;
import eu.eudat.exceptions.emailconfirmation.TokenExpiredException;
import eu.eudat.logic.managers.UnlinkEmailConfirmationManager;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.security.Principal;
import eu.eudat.models.data.userinfo.UserUnlinkRequestModel;
import eu.eudat.types.ApiMessageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
@RestController
@CrossOrigin
@RequestMapping(value = "api/emailUnlinkConfirmation")
public class EmailUnlinkConfirmation {
private UnlinkEmailConfirmationManager unlinkEmailConfirmationManager;
@Autowired
public EmailUnlinkConfirmation(UnlinkEmailConfirmationManager unlinkEmailConfirmationManager){
this.unlinkEmailConfirmationManager = unlinkEmailConfirmationManager;
}
@Transactional
@RequestMapping(method = RequestMethod.GET, value = {"/{emailToken}"})
public @ResponseBody
ResponseEntity<ResponseItem> emailConfirmation(@PathVariable(value = "emailToken") String token) {
try {
this.unlinkEmailConfirmationManager.confirmEmail(token);
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.SUCCESS_MESSAGE));
} catch (TokenExpiredException | HasConfirmedEmailException ex) {
if (ex instanceof TokenExpiredException) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem().status(ApiMessageCode.NO_MESSAGE));
}
else {
return ResponseEntity.status(HttpStatus.FOUND).body(new ResponseItem().status(ApiMessageCode.WARN_MESSAGE));
}
}
}
@Transactional
@RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public @ResponseBody
ResponseEntity<ResponseItem> sendUnlinkConfirmationEmail(@RequestBody UserUnlinkRequestModel requestModel, Principal principal) {
try {
this.unlinkEmailConfirmationManager.sendConfirmationEmail(requestModel.getEmail(), principal, requestModel.getUserId(), requestModel.getProvider());
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.SUCCESS_MESSAGE));
} catch (Exception ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem().status(ApiMessageCode.NO_MESSAGE).message("Could not send unlink email."));
}
}
}

View File

@ -0,0 +1,106 @@
package eu.eudat.logic.managers;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.data.entities.Credential;
import eu.eudat.data.entities.EmailConfirmation;
import eu.eudat.data.entities.UserInfo;
import eu.eudat.data.entities.UserToken;
import eu.eudat.exceptions.emailconfirmation.HasConfirmedEmailException;
import eu.eudat.exceptions.emailconfirmation.TokenExpiredException;
import eu.eudat.logic.builders.entity.UserTokenBuilder;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.models.data.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class UnlinkEmailConfirmationManager {
private static Logger logger = LoggerFactory.getLogger(UnlinkEmailConfirmationManager.class);
private ApiContext apiContext;
private DatabaseRepository databaseRepository;
@Autowired
public UnlinkEmailConfirmationManager(ApiContext apiContext) {
this.apiContext = apiContext;
this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
}
@Transactional
public void confirmEmail(String token) throws TokenExpiredException, HasConfirmedEmailException {
EmailConfirmation loginConfirmationEmail = apiContext.getOperationsContext()
.getDatabaseRepository().getLoginConfirmationEmailDao().asQueryable()
.where((builder, root) -> builder.equal(root.get("token"), UUID.fromString(token))).getSingle();
if (loginConfirmationEmail.getExpiresAt().compareTo(new Date()) < 0)
throw new TokenExpiredException("Token has expired.");
if(loginConfirmationEmail.getIsConfirmed())
throw new HasConfirmedEmailException("Email is already confirmed.");
// UserInfo userAskingForUnlink = databaseRepository.getUserInfoDao().asQueryable()
// .where((builder, root) -> builder.equal(root.get("id"), loginConfirmationEmail.getUserId())).getSingle();
try {
Map<String, Object> map = new ObjectMapper().readValue(loginConfirmationEmail.getData(), new TypeReference<Map<String, Object>>() {});
String emailTobeUnlinked = (String) map.get("email");
Integer provider = Integer.valueOf((String) map.get("provider"));
unlinkUser(emailTobeUnlinked, provider);
loginConfirmationEmail.setIsConfirmed(true);
databaseRepository.getLoginConfirmationEmailDao().createOrUpdate(loginConfirmationEmail);
}
catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Transactional
private void unlinkUser(String emailTobeUnlinked, Integer provider){
Credential credential = databaseRepository.getCredentialDao().asQueryable()
.where((builder, root) -> builder.and(builder.equal(root.get("email"), emailTobeUnlinked), builder.equal(root.get("provider"), provider))).getSingle();
if(credential != null) {
UserInfo userTobeUnlinked = databaseRepository.getUserInfoDao().asQueryable()
.where((builder, root) -> builder.and(builder.equal(root.get("userStatus"), 1), builder.equal(root.get("name"), credential.getPublicValue()))).getSingle();
userTobeUnlinked.setEmail(emailTobeUnlinked);
userTobeUnlinked.setUserStatus((short) 0);
databaseRepository.getUserInfoDao().createOrUpdate(userTobeUnlinked);
credential.setUserInfo(userTobeUnlinked);
databaseRepository.getCredentialDao().createOrUpdate(credential);
UserToken userToken = this.apiContext.getOperationsContext().getBuilderFactory().getBuilder(UserTokenBuilder.class)
.token(UUID.randomUUID()).user(userTobeUnlinked)
.expiresAt(Timestamp.valueOf(LocalDateTime.now().plusDays(10))).issuedAt(new Date())
.build();
apiContext.getOperationsContext().getDatabaseRepository().getUserTokenDao().createOrUpdate(userToken);
}
}
public void sendConfirmationEmail(String email, Principal principal, UUID userId, Integer provider) {
UserInfo user = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(principal.getId());
if (user.getEmail() != null && !user.getEmail().equals(email)) {
apiContext.getUtilitiesService().getConfirmationEmailService().createUnlinkConfirmationEmail(
databaseRepository.getLoginConfirmationEmailDao(),
apiContext.getUtilitiesService().getMailService(),
email,
userId,
principal,
provider
);
}
}
}

View File

@ -12,7 +12,11 @@ public interface ConfirmationEmailService {
public void createMergeConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService, String email, UUID userId, Principal principal, Integer provider);
public void createUnlinkConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService, String email, UUID userId, Principal principal, Integer provider);
public CompletableFuture sentConfirmationEmail(EmailConfirmation confirmationEmail, MailService mailService);
public CompletableFuture sentMergeConfirmationEmail(EmailConfirmation confirmationEmail, MailService mailService, String userName);
public CompletableFuture sentUnlinkConfirmationEmail(EmailConfirmation confirmationEmail, MailService mailService);
}

View File

@ -1,6 +1,7 @@
package eu.eudat.logic.services.utilities;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.data.dao.entities.EmailConfirmationDao;
import eu.eudat.data.entities.EmailConfirmation;
@ -84,6 +85,30 @@ public class ConfirmationEmailServiceImpl implements ConfirmationEmailService {
});
}
@Override
public CompletableFuture sentUnlinkConfirmationEmail(EmailConfirmation confirmationEmail, MailService mailService) {
String email = null;
try {
Map<String, Object> map = new ObjectMapper().readValue(confirmationEmail.getData(), new TypeReference<Map<String, Object>>() {});
email = (String) map.get("email");
}
catch (JsonProcessingException e){
logger.error(e.getMessage(), e);
}
String finalEmail = email;
return CompletableFuture.runAsync(() -> {
SimpleMail mail = new SimpleMail();
mail.setSubject(environment.getProperty("conf_email.subject"));
mail.setContent(createUnlinkContent(confirmationEmail.getToken(), mailService, finalEmail));
mail.setTo(confirmationEmail.getEmail());
try {
mailService.sendSimpleMail(mail);
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
});
}
private String createMergeContent(UUID confirmationToken, MailService mailService, String userName) {
String content = mailService.getMailTemplateContent(this.environment.getProperty("email.merge"));
content = content.replace("{userName}", userName);
@ -94,6 +119,16 @@ public class ConfirmationEmailServiceImpl implements ConfirmationEmailService {
return content;
}
private String createUnlinkContent(UUID confirmationToken, MailService mailService, String email) {
String content = mailService.getMailTemplateContent(this.environment.getProperty("email.unlink"));
content = content.replace("{confirmationToken}", confirmationToken.toString());
content = content.replace("{expiration_time}", secondsToTime(Integer.parseInt(this.environment.getProperty("conf_email.expiration_time_seconds"))));
content = content.replace("{host}", this.environment.getProperty("dmp.domain"));
content = content.replace("{email}", email);
return content;
}
private String secondsToTime(int seconds) {
int sec = seconds % 60;
int hour = seconds / 60;
@ -128,4 +163,30 @@ public class ConfirmationEmailServiceImpl implements ConfirmationEmailService {
sentMergeConfirmationEmail(confirmationEmail, mailService, principal.getName());
}
@Override
public void createUnlinkConfirmationEmail(EmailConfirmationDao loginConfirmationEmailDao, MailService mailService,
String email, UUID userId, Principal principal, Integer provider) {
EmailConfirmation confirmationEmail = new EmailConfirmation();
confirmationEmail.setEmail(principal.getEmail());
confirmationEmail.setExpiresAt(Date
.from(new Date()
.toInstant()
.plusSeconds(Long.parseLong(this.environment.getProperty("conf_email.expiration_time_seconds")))
)
);
confirmationEmail.setUserId(userId);
try {
Map<String, Object> map = new HashMap<>();
map.put("email", email);
map.put("provider", provider.toString());
confirmationEmail.setData(new ObjectMapper().writeValueAsString(map));
} catch (JsonProcessingException e) {
logger.error(e.getMessage(), e);
}
confirmationEmail.setIsConfirmed(false);
confirmationEmail.setToken(UUID.randomUUID());
confirmationEmail = loginConfirmationEmailDao.createOrUpdate(confirmationEmail);
sentUnlinkConfirmationEmail(confirmationEmail, mailService);
}
}

View File

@ -0,0 +1,30 @@
package eu.eudat.models.data.userinfo;
import java.util.UUID;
public class UserUnlinkRequestModel {
private UUID userId;
private String email;
private Integer provider;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getProvider() {
return provider;
}
public void setProvider(Integer provider) {
this.provider = provider;
}
}

View File

@ -32,6 +32,7 @@ configuration.configurable_login_providers=configurableLoginProviders.json
email.invite=classpath:templates/email/email.html
email.confirmation=classpath:templates/email/emailConfirmation.html
email.merge=classpath:templates/email/emailMergeConfirmation.html
email.unlink=classpath:templates/email/emailUnlinkConfirmation.html
#############FACEBOOK LOGIN CONFIGURATIONS#########
facebook.login.clientId=

View File

@ -31,6 +31,7 @@ configuration.configurable_login_providers=configurableLoginProviders.json
email.invite=classpath:templates/email/email.html
email.confirmation=classpath:templates/email/emailConfirmation.html
email.merge=classpath:templates/email/emailMergeConfirmation.html
email.unlink=classpath:templates/email/emailUnlinkConfirmation.html
email.dataset.template=classpath:templates/email/emailAdmin.html
####################INVITATION MAIL CONFIGURATIONS##############

View File

@ -58,6 +58,7 @@ configuration.configurable_login_providers=configurableLoginProviders.json
email.invite=file:templates/email/email.html
email.confirmation=file:templates/email/emailConfirmation.html
email.merge=file:templates/email/emailMergeConfirmation.html
email.unlink=classpath:templates/email/emailUnlinkConfirmation.html
email.dataset.template=file:templates/email/emailAdmin.html
#############LOGIN CONFIGURATIONS#########

View File

@ -0,0 +1,304 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Simple Transactional Email</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<img src="classpath:images\OpenDMP.png" alt="OpenDMP" width="100" height="81">
<h2>You have made a request to unlink your email account in ARGOS.</h2>
<p>Please confirm that you want to unlink your {email} account.
<br/>The link will expire in {expiration_time}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{host}/unlink/confirmation/{confirmationToken}" target="_blank">Confirm Unlink Request</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -51,6 +51,7 @@ import { AboutService } from './services/about/about.service';
import { FaqService } from './services/faq/faq.service';
import { GlossaryService } from './services/glossary/glossary.service';
import { TermsOfServiceService } from './services/terms-of-service/terms-of-service.service';
import { UnlinkAccountEmailConfirmationService } from './services/unlink-account-email-confirmation/unlink-account-email-confirmation.service';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -122,6 +123,7 @@ export class CoreServiceModule {
TermsOfServiceService,
CurrencyService,
MergeEmailConfirmationService,
UnlinkAccountEmailConfirmationService,
ConfigurationService,
{
provide: APP_INITIALIZER,

View File

@ -0,0 +1,5 @@
export class UnlinkAccountRequestModel {
userId: String;
email: String;
provider: number;
}

View File

@ -0,0 +1,23 @@
import { HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { BaseHttpService } from "../http/base-http.service";
import { ConfigurationService } from "../configuration/configuration.service";
import { UnlinkAccountRequestModel } from "@app/core/model/unlink-account/unlink-account";
@Injectable()
export class UnlinkAccountEmailConfirmationService {
private actioUrl: string;
private headers: HttpHeaders;
constructor(private http: BaseHttpService, private configurationService: ConfigurationService) {
this.actioUrl = configurationService.server + 'emailUnlinkConfirmation/';
}
public emailConfirmation(token: string) {
return this.http.get<String>(this.actioUrl + token, { headers: this.headers });
}
public sendConfirmationEmail(unlinkRequest: UnlinkAccountRequestModel) {
return this.http.post<String>(this.actioUrl, unlinkRequest, { headers: this.headers });
}
}

View File

@ -18,6 +18,7 @@ import { MergeLoginService } from './utilities/merge-login.service';
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
import { SamlLoginService } from '@app/core/services/saml-login.service';
import { SamlResponseLoginComponent } from './saml/saml-login-response/saml-login-response.component';
import { UnlinkEmailConfirmation } from './unlink-email-confirmation/unlink-email-confirmation.component';
@NgModule({
imports: [
@ -37,6 +38,7 @@ import { SamlResponseLoginComponent } from './saml/saml-login-response/saml-logi
ConfigurableLoginComponent,
ZenodoLoginComponent,
MergeEmailConfirmation,
UnlinkEmailConfirmation,
SamlResponseLoginComponent
],
exports: [

View File

@ -12,6 +12,7 @@ import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
import { Oauth2DialogComponent } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.component';
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
import { SamlResponseLoginComponent } from './saml/saml-login-response/saml-login-response.component';
import { UnlinkEmailConfirmation } from './unlink-email-confirmation/unlink-email-confirmation.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
@ -21,6 +22,7 @@ const routes: Routes = [
{ path: 'external/b2access', component: Oauth2DialogComponent },
{ path: 'confirmation/:token', component: EmailConfirmation },
{ path: 'merge/confirmation/:token', component: MergeEmailConfirmation },
{ path: 'unlink/confirmation/:token', component: UnlinkEmailConfirmation },
{ path: 'confirmation', component: EmailConfirmation },
{ path: 'openaire', component: Oauth2DialogComponent},
{ path: 'configurable/:id', component: ConfigurableLoginComponent},

View File

@ -0,0 +1,54 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UnlinkAccountEmailConfirmationService } from '@app/core/services/unlink-account-email-confirmation/unlink-account-email-confirmation.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BaseComponent } from '@common/base/base.component';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from "rxjs/operators";
@Component({
selector: 'app-unlink-email-confirmation-component',
templateUrl: './unlink-email-confirmation.component.html'
})
export class UnlinkEmailConfirmation extends BaseComponent implements OnInit {
constructor(
private emailConfirmationService: UnlinkAccountEmailConfirmationService,
private route: ActivatedRoute,
private router: Router,
private language: TranslateService,
private uiNotificationService: UiNotificationService
) { super(); }
ngOnInit(): void {
this.route.params
.pipe(takeUntil(this._destroyed))
.subscribe(params => {
const token = params['token']
if (token != null) {
this.emailConfirmationService.emailConfirmation(token)
.pipe(takeUntil(this._destroyed))
.subscribe(
result => this.onCallbackEmailConfirmationSuccess(),
error => this.onCallbackError(error)
)
}
});
}
onCallbackEmailConfirmationSuccess() {
this.router.navigate(['home']);
}
onCallbackError(error: any) {
if (error.status === 302) {
this.uiNotificationService.snackBarNotification(this.language.instant('EMAIL-CONFIRMATION.EMAIL-FOUND'), SnackBarNotificationLevel.Warning);
this.router.navigate(['home']);
}
else {
this.uiNotificationService.snackBarNotification(this.language.instant('EMAIL-CONFIRMATION.EXPIRED-EMAIL'), SnackBarNotificationLevel.Error);
this.router.navigate(['login']);
}
}
}

View File

@ -45,7 +45,7 @@
</div>
<div class="col-auto vertical-line">
<div *ngIf="userCredentials">
<div *ngFor="let userCredential of userCredentials">
<div *ngFor="let userCredential of userCredentials; index as i">
<div class="row user-credential">
<!-- <div class="col-auto pl-4 ml-2 pr-0 d-flex align-items-center"><mat-icon class="check-icon mat-icon">check</mat-icon></div> -->
<div class="col-auto mail-text pr-0">{{userCredential.email}}</div>
@ -58,8 +58,10 @@
<span *ngIf="userCredential.provider === authProviderEnum.OpenAire" class="openaireIcon"></span>
<span *ngIf="userCredential.provider === authProviderEnum.Configurable" class="configurableIcon"></span>
<span *ngIf="userCredential.provider === authProviderEnum.Zenodo" class="zenodoIcon"></span>
<!-- <div class="col-auto"></div>
<div class="col-auto d-flex align-items-center" (click)="removeAccount()"><mat-icon class="clear-icon mat-icon">clear</mat-icon></div> -->
<!-- <div class="col-auto"></div> -->
<div *ngIf="i != 0" class="col-auto d-flex align-items-center unlink-mail" (click)="removeAccount(userCredential)">
<mat-icon [matTooltip]="'USER-PROFILE.ACTIONS.UNLINK-ACCOUNT' | translate" matTooltipPosition="right">link_off</mat-icon>
</div>
</div>
</div>
</div>

View File

@ -195,6 +195,21 @@
border-radius: 50%;
}
.unlink-mail {
line-height: 2rem;
cursor: pointer;
mat-icon {
color: var(--primary-color);
display: inline-flex;
vertical-align: middle;
}
}
.unlink-mail:hover {
background-color: #f4f8f9;
}
.add-new-btn {
margin-top: 2rem;
}

View File

@ -30,8 +30,11 @@ import { MergeEmailConfirmationService } from '@app/core/services/merge-email-co
import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { PopupNotificationDialogComponent } from "@app/library/notification/popup/popup-notification.component";
import { UnlinkAccountRequestModel } from '@app/core/model/unlink-account/unlink-account';
import { UnlinkAccountEmailConfirmationService } from '@app/core/services/unlink-account-email-confirmation/unlink-account-email-confirmation.service';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
@Component({
selector: 'app-user-profile',
@ -82,6 +85,7 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
private dialog: MatDialog,
public enumUtils: EnumUtils,
private mergeEmailConfirmation: MergeEmailConfirmationService,
private unlinkAccountEmailConfirmation: UnlinkAccountEmailConfirmationService,
private httpClient: HttpClient,
private matomoService: MatomoService
) {
@ -123,8 +127,12 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
this.userService.getEmails(userId).pipe(takeUntil(this._destroyed))
.subscribe(result => {
this.userCredentials = result;
});
this.user.subscribe(x => {
const mainEmail = result.filter(el => el.email === x.email)
const otherEmails = result.filter(el => el.email !== x.email)
this.userCredentials = [...mainEmail, ...otherEmails];
}
)});
});
}
@ -292,8 +300,40 @@ export class UserProfileComponent extends BaseComponent implements OnInit, OnDes
.subscribe(() => this.router.navigate(['/reload']).then(() => this.router.navigate(['/profile'])));
}
public removeAccount() {
public removeAccount(userCredential :UserCredentialModel) {
this.dialog.open(ConfirmationDialogComponent, {
data:{
message: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.MESSAGE', {'accountToBeUnlinked': userCredential.email}),
confirmButton: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.CONFIRM'),
cancelButton: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT-DIALOG.CANCEL')
},
maxWidth:'35em'
})
.afterClosed()
.subscribe(confirm=>{
if(confirm){
const unlinkAccountModel: UnlinkAccountRequestModel = {
userId: this.currentUserId,
email: userCredential.email,
provider: userCredential.provider
};
this.unlinkAccountEmailConfirmation.sendConfirmationEmail(unlinkAccountModel).pipe(takeUntil(this._destroyed)).subscribe(
result => {
this.dialog.open(PopupNotificationDialogComponent, {
data: {
title: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT.TITLE'),
message: this.language.instant('USER-PROFILE.UNLINK-ACCOUNT.MESSAGE', {'accountToBeUnlinked': userCredential.email})
}, maxWidth: '35em'
});
},
error => { this.onCallbackError(error); }
);
}
});
}
onCallbackError(errorResponse: HttpErrorResponse) {
this.uiNotificationService.snackBarNotification(errorResponse.message, SnackBarNotificationLevel.Warning);
}
public addAccount() {

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Einstellungen",
"TIMEZONE": "Zeitzone",
@ -1738,6 +1747,7 @@
"SAVE": "Save",
"LINK-NEW": "Link new",
"LINK-NEW-ACCOUNT": "Link new account",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Add",
"CANCEL": "Cancel"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be no longer connected to your current ARGOS profile."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Settings",
"TIMEZONE": "Time Zone",
@ -1738,6 +1747,7 @@
"SAVE": "Save",
"LINK-NEW": "Link new",
"LINK-NEW-ACCOUNT": "Link new account",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Add",
"CANCEL": "Cancel"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Configuración",
"TIMEZONE": "Zona horaria",
@ -1738,6 +1747,7 @@
"SAVE": "Grabar",
"LINK-NEW": "Nuevo enlace",
"LINK-NEW-ACCOUNT": "Enlazar nueva cuenta",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Añadir",
"CANCEL": "Cancelar"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Ρυθμίσεις",
"TIMEZONE": "Ζώνη Ώρας",
@ -1738,6 +1747,7 @@
"SAVE": "Save",
"LINK-NEW": "Link new",
"LINK-NEW-ACCOUNT": "Link new account",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Add",
"CANCEL": "Cancel"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Potvrdi povezani korisnički račun",
"MESSAGE": "Potvrdu za ovu radnju poslali smo Vam putem elektroničke pošte. Kada potvrdite, korisnički računi biti će povezani. Posljednji račun elektroničke pošte koji povežete biti će onaj koji sadrži sve podatke o Planu upravljanja podacima i aktivnostima u {{ APP_NAME }}u."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Postavke",
"TIMEZONE": "Vremenska zona",
@ -1738,6 +1747,7 @@
"SAVE": "Spremi",
"LINK-NEW": "Poveži novo",
"LINK-NEW-ACCOUNT": "Poveži novi korisnički račun",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Unesi",
"CANCEL": "Poništi"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Zweryfikuj połączone konto",
"MESSAGE": "Wysłano wiadomość e-mail w celu weryfikacji tej akcji. Po zaakceptowaniu będzie można zobaczyć połączone konta. Ostatnie połączone konto e-mail będzie tym, które zawiera wszystkie twoje rejestry DMP i aktywność w {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Ustawienia",
"TIMEZONE": "Strefa czasowa",
@ -1738,6 +1747,7 @@
"SAVE": "Zapisz",
"LINK-NEW": "Połącz nowy",
"LINK-NEW-ACCOUNT": "Połącz nowe konto",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Dodaj",
"CANCEL": "Anuluj"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Definições",
"TIMEZONE": "Fuso horário",
@ -1738,6 +1747,7 @@
"SAVE": "Guardar",
"LINK-NEW": "Ligar a nova(o)",
"LINK-NEW-ACCOUNT": "Ligar a nova conta",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Adicionar",
"CANCEL": "Cancelar"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Nastavenia",
"TIMEZONE": "Časové pásmo",
@ -1738,6 +1747,7 @@
"SAVE": "Save",
"LINK-NEW": "Link new",
"LINK-NEW-ACCOUNT": "Link new account",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Add",
"CANCEL": "Cancel"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Podešavanja",
"TIMEZONE": "Vremenska zona",
@ -1738,6 +1747,7 @@
"SAVE": "Save",
"LINK-NEW": "Link new",
"LINK-NEW-ACCOUNT": "Link new account",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Add",
"CANCEL": "Cancel"
}

View File

@ -1691,6 +1691,15 @@
"TITLE": "Verify linked account",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, you will be able to see your accounts merged. The last email account that you merge will be the one containing all of your DMP records and activity in {{ APP_NAME }}."
},
"UNLINK-ACCOUNT": {
"TITLE": "Verify account to be unlinked",
"MESSAGE": "An email to verify this action has been sent to you. Once accepted, {{accountToBeUnlinked}} will be unlinked."
},
"UNLINK-ACCOUNT-DIALOG": {
"MESSAGE": "By clicking \"Confirm\", you accept the transfer of your ARGOS activity performed from this account to your main ARGOS account, which is the one that is listed first. By logging in again with the unlinked account, you will be able to start your ARGOS experience from scratch, in an empty dashboard.",
"CONFIRM": "Confirm",
"CANCEL": "Cancel"
},
"SETTINGS": {
"TITLE": "Ayarlar",
"TIMEZONE": "Zaman Dilimi",
@ -1738,6 +1747,7 @@
"SAVE": "Kaydet",
"LINK-NEW": "Yeni bağlantı",
"LINK-NEW-ACCOUNT": "Yeni hesabı bağla",
"UNLINK-ACCOUNT": "Unlink",
"ADD": "Ekle",
"CANCEL": "İptal"
}