Skip to content

Commit 23f7cca

Browse files
xkostka2xkureck
authored andcommitted
feat(user-profile): compress new anti-phishing images
* when uploading anti-phishing image, in case it is bigger than 100x100 pixels, shrink image down to 100x100 and if needed try even smaller sizes up to 50x50 until the desired size is reached * maximal size of saved image changed from 5kB to 6kB
1 parent eecabc9 commit 23f7cca

File tree

8 files changed

+81
-64
lines changed

8 files changed

+81
-64
lines changed

apps/user-profile/src/app/components/dialogs/add-auth-img-dialog/add-auth-img-dialog.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
alert_type="error"
1212
>{{'DIALOGS.ADD_AUTH_IMG.IMG_TOO_LONG' | customTranslate | translate}}</perun-web-apps-alert
1313
>
14-
<div *ngIf="newImage" class="center-content">
15-
<img [src]="newImage" alt="" class="img-size" />
14+
<div *ngIf="newImage && !imgTooLong" class="center-content">
15+
<img [src]="newImage" alt="" />
1616
</div>
1717
<button (click)="generateImg()" class="m-1" color="accent" mat-flat-button>
1818
{{'DIALOGS.ADD_AUTH_IMG.GENERATE' | customTranslate | translate}}

apps/user-profile/src/app/components/dialogs/add-auth-img-dialog/add-auth-img-dialog.component.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
.img-size {
2-
width: 100px;
3-
height: 100px;
4-
}
5-
61
.center-content {
72
display: flex;
83
justify-content: center;

apps/user-profile/src/app/components/dialogs/add-auth-img-dialog/add-auth-img-dialog.component.ts

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface AddAuthImgDialogData {
1010

1111
const MAX_SIZE = 100;
1212
const MIN_SIZE = 50;
13-
const MAX_LENGTH = 5120;
13+
const MAX_LENGTH = 6144;
1414

1515
@Component({
1616
selector: 'perun-web-apps-add-auth-img-dialog',
@@ -21,8 +21,8 @@ export class AddAuthImgDialogComponent implements OnInit {
2121
theme: string;
2222
newImage = '';
2323
attribute: Attribute;
24-
radioBtn: string;
2524
imgTooLong: boolean;
25+
imageType: string;
2626

2727
constructor(
2828
private dialogRef: MatDialogRef<AddAuthImgDialogComponent>,
@@ -35,11 +35,10 @@ export class AddAuthImgDialogComponent implements OnInit {
3535
this.theme = this.data.theme;
3636
this.attribute = this.data.attribute;
3737
this.newImage = this.attribute.value as unknown as string;
38-
this.imageType = null;
3938
}
4039

41-
handleInputChange(e: Event | DragEvent): void {
42-
const file = (e as DragEvent).dataTransfer.files[0] ?? (e.target as HTMLInputElement)?.files[0];
40+
handleInputChange(e: InputEvent): void {
41+
const file = e.dataTransfer?.files[0] ?? (e.target as HTMLInputElement)?.files[0];
4342
const pattern = /image-*/;
4443
const reader = new FileReader();
4544
if (!file.type.match(pattern)) {
@@ -51,38 +50,51 @@ export class AddAuthImgDialogComponent implements OnInit {
5150
reader.readAsDataURL(file);
5251
}
5352

54-
_handleReaderLoaded(e) {
55-
const reader = e.target;
56-
53+
_handleReaderLoaded(event: ProgressEvent): void {
54+
const reader: FileReader = event.target as FileReader;
5755
let size = MAX_SIZE;
58-
let result = null;
59-
do {
60-
const img = document.createElement("img");
61-
img.src = reader.result;
62-
const canvas = document.createElement("canvas");
63-
let ctx = canvas.getContext("2d");
64-
ctx.drawImage(img, 0, 0);
65-
const width = img.width;
66-
const height = img.height;
67-
if (width > height) {
68-
if (width > MAX_SIZE) {
69-
height *= MAX_SIZE / width;
70-
width = MAX_SIZE;
71-
}
72-
} else if (height > MAX_SIZE) {
73-
width *= MAX_SIZE / height;
74-
height = MAX_SIZE;
75-
}
76-
canvas.width = width;
77-
canvas.height = height;
78-
ctx = canvas.getContext("2d");
79-
ctx.drawImage(img, 0, 0, width, height);
80-
result = canvas.toDataURL(this.imageType);
81-
size -= 10;
82-
} while(size >= MIN_SIZE && result.length >= MAX_LENGTH);
83-
84-
this.imgTooLong = result.length >= MAX_LENGTH;
85-
this.newImage = result;
56+
const compressRecursive = (): void => {
57+
compressImage(reader.result as string)
58+
.then((compressed) => {
59+
if (size > MIN_SIZE && compressed.length > MAX_LENGTH) {
60+
size -= 10;
61+
compressRecursive();
62+
} else {
63+
this.newImage = compressed;
64+
this.imgTooLong = this.newImage.length >= MAX_LENGTH;
65+
}
66+
})
67+
.catch((error) => console.error(error));
68+
};
69+
70+
const compressImage = (src: string): Promise<string> =>
71+
new Promise((res, rej) => {
72+
const img = new Image();
73+
img.src = src;
74+
img.onload = (): void => {
75+
const elem = document.createElement('canvas');
76+
let width = img.width;
77+
let height = img.height;
78+
if (width > height) {
79+
if (width > size) {
80+
height *= size / width;
81+
width = size;
82+
}
83+
} else if (height > size) {
84+
width *= size / height;
85+
height = size;
86+
}
87+
elem.width = width;
88+
elem.height = height;
89+
const ctx = elem.getContext('2d');
90+
ctx.drawImage(img, 0, 0, width, height);
91+
const data = ctx.canvas.toDataURL();
92+
res(data);
93+
};
94+
img.onerror = (error): void => rej(error);
95+
});
96+
97+
compressRecursive();
8698
}
8799

88100
onAdd(): void {

apps/user-profile/src/app/pages/settings-page/settings-authorization/settings-authentication.component.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
<h1 class="page-subtitle">{{'AUTHENTICATION.TITLE' | customTranslate | translate}}</h1>
44
<p>{{'AUTHENTICATION.ANTI_PHISHING_INFO' | customTranslate | translate}}</p>
55
<div *ngIf="imageSrc && imageSrc.length">
6-
<img [src]="imageSrc" alt="" class="img-size" />
6+
<img [src]="imageSrc" alt="" />
77
</div>
8-
<button (click)="onAddImg()" class="m-1 action-button" color="accent" mat-flat-button>
8+
<button (click)="onAddImg()" class="mr-2 mt-1 action-button" color="accent" mat-flat-button>
99
{{'AUTHENTICATION.NEW_IMG' | customTranslate | translate}}
1010
</button>
1111
<button
1212
(click)="onDeleteImg()"
13-
class="m-1"
1413
color="warn"
1514
[disabled]="!imgAtt || !imgAtt.value"
1615
mat-flat-button>
Original file line numberDiff line numberDiff line change
@@ -1,4 +0,0 @@
1-
.img-size {
2-
width: 100px;
3-
height: 100px;
4-
}

apps/user-profile/src/app/pages/settings-page/settings-authorization/settings-authentication.component.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MatDialog } from '@angular/material/dialog';
33
import { getDefaultDialogConfig } from '@perun-web-apps/perun/utils';
44
import { AddAuthImgDialogComponent } from '../../../components/dialogs/add-auth-img-dialog/add-auth-img-dialog.component';
55
import { Attribute, AttributesManagerService } from '@perun-web-apps/perun/openapi';
6-
import { AuthService, StoreService } from '@perun-web-apps/perun/services';
6+
import { AuthService, NotificatorService, StoreService } from '@perun-web-apps/perun/services';
77
import { RemoveStringValueDialogComponent } from '../../../components/dialogs/remove-string-value-dialog/remove-string-value-dialog.component';
88
import { TranslateService } from '@ngx-translate/core';
99
import { OAuthService } from 'angular-oauth2-oidc';
@@ -27,21 +27,30 @@ export class SettingsAuthenticationComponent implements OnInit, AfterViewInit {
2727
mfaApiUrl = '';
2828
loadingMfa = false;
2929
loadingImg = false;
30+
saveImgSuccess = '';
31+
removeImgSuccess = '';
3032

3133
constructor(
3234
private dialog: MatDialog,
3335
private attributesManagerService: AttributesManagerService,
3436
private store: StoreService,
3537
private translate: TranslateService,
3638
private oauthService: OAuthService,
37-
private authService: AuthService
39+
private authService: AuthService,
40+
private notificatorService: NotificatorService
3841
) {
3942
translate
4043
.get('AUTHENTICATION.DELETE_IMG_DIALOG_TITLE')
4144
.subscribe((res: string) => (this.removeDialogTitle = res));
4245
translate
4346
.get('AUTHENTICATION.DELETE_IMG_DIALOG_DESC')
4447
.subscribe((res: string) => (this.removeDialogDescription = res));
48+
translate
49+
.get('AUTHENTICATION.SAVE_IMG_SUCCESS')
50+
.subscribe((res: string) => (this.saveImgSuccess = res));
51+
translate
52+
.get('AUTHENTICATION.REMOVE_IMG_SUCCESS')
53+
.subscribe((res: string) => (this.removeImgSuccess = res));
4554
}
4655

4756
ngAfterViewInit(): void {
@@ -94,6 +103,7 @@ export class SettingsAuthenticationComponent implements OnInit, AfterViewInit {
94103

95104
dialogRef.afterClosed().subscribe((result) => {
96105
if (result) {
106+
this.notificatorService.showSuccess(this.saveImgSuccess);
97107
this.loadImage();
98108
}
99109
});
@@ -136,8 +146,9 @@ export class SettingsAuthenticationComponent implements OnInit, AfterViewInit {
136146

137147
const dialogRef = this.dialog.open(RemoveStringValueDialogComponent, config);
138148

139-
dialogRef.afterClosed().subscribe((sshAdded) => {
140-
if (sshAdded) {
149+
dialogRef.afterClosed().subscribe((imgRemoved) => {
150+
if (imgRemoved) {
151+
this.notificatorService.showSuccess(this.removeImgSuccess);
141152
this.loadImage();
142153
}
143154
});

apps/user-profile/src/assets/i18n/cs.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@
165165
"NEW_IMG": "Nový obrázek",
166166
"DELETE_IMG": "Vymazat obrázek",
167167
"ANTI_PHISHING_INFO": "Tento bezpečnostní obrázek se vám ukáže před tím, než zadáte heslo, ujistíte se tak, že se nepřihlašujete na podvrženou stránku",
168-
"DELETE_IMG_DIALOG_TITLE": "Vymazat proti-phishingový obrázek",
168+
"DELETE_IMG_DIALOG_TITLE": "Vymazat bezpečnostní obrázek",
169169
"DELETE_IMG_DIALOG_DESC": "Váš bezpečnostní obrázek bude odstraněn a nebude použit během autentizace.",
170-
"MFA_INFO": "Spravovat moje prostředky vícefázového ověření (MFA)"
170+
"MFA_INFO": "Spravovat moje prostředky vícefázového ověření (MFA)",
171+
"SAVE_IMG_SUCCESS": "Bezpečnostní obrázek byl změněn.",
172+
"REMOVE_IMG_SUCCESS": "Bezpečnostní obrázek byl odebrán."
171173
},
172174
"LOCAL_ACCOUNT": {
173175
"TITLE": "Lokální účet",
@@ -230,13 +232,13 @@
230232
"CLOSE": "Zrušit"
231233
},
232234
"ADD_AUTH_IMG": {
233-
"TITLE": "Změna proti-phishingového obrázku",
234-
"INFO": "Prosím, zvolte obrázek s velikostí maximálně 5kB.",
235+
"TITLE": "Změna bezpečnostního obrázku",
236+
"INFO": "Prosím, zvolte obrázek.",
235237
"GENERATE": "Generovat obrázek",
236238
"UPLOAD_IMG_LABEL": "Nahrát obrázek z vašeho počítače",
237239
"CANCEL": "Zrušit",
238240
"ADD": "Uložit obrázek",
239-
"IMG_TOO_LONG": "Velikost obrázku je větší než 5kB",
241+
"IMG_TOO_LONG": "Zvolený obrázek se nepodařilo zmenšit na cílovou velikost. Prosím zvolte jiný obrázek.",
240242
"DELAY_INFO": "Změna bezpečnostního obrázku se projeví většinou do 5 minut, v ojedinělých případech může propagace změny trvat několik hodin."
241243
}
242244
},

apps/user-profile/src/assets/i18n/en.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@
165165
"NEW_IMG": "New image",
166166
"DELETE_IMG": "Delete image",
167167
"ANTI_PHISHING_INFO": "You will be shown this security image before you enter your password so you will know that you are visiting the right site",
168-
"DELETE_IMG_DIALOG_TITLE": "Delete anti-phishing image",
168+
"DELETE_IMG_DIALOG_TITLE": "Delete security image",
169169
"DELETE_IMG_DIALOG_DESC": "Your security image will be deleted and will not be used during authentication process.",
170-
"MFA_INFO": "Manage my MFA tokens"
170+
"MFA_INFO": "Manage my MFA tokens",
171+
"SAVE_IMG_SUCCESS": "Security image has been saved.",
172+
"REMOVE_IMG_SUCCESS": "Security image has been removed."
171173
},
172174
"LOCAL_ACCOUNT": {
173175
"TITLE": "Local account",
@@ -230,13 +232,13 @@
230232
"CLOSE": "Cancel"
231233
},
232234
"ADD_AUTH_IMG": {
233-
"TITLE": "Change anti-phishing image",
234-
"INFO": "Please, choose an image with size max 5kB",
235+
"TITLE": "Change security image",
236+
"INFO": "Please, choose an image.",
235237
"GENERATE": "Generate image",
236238
"UPLOAD_IMG_LABEL": "Upload an image from your computer",
237239
"CANCEL": "Cancel",
238240
"ADD": "Save image",
239-
"IMG_TOO_LONG": "The image size is more than 5kB.",
241+
"IMG_TOO_LONG": "Your image could not be resized to fit. Please try a different one.",
240242
"DELAY_INFO": "New security image usually becomes effective in 5 minutes, in rare cases it might take a couple of hours."
241243
}
242244
},

0 commit comments

Comments
 (0)