Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate identicons if no profile picture is set #150

Merged
merged 3 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions backend/src/main/java/org/cryptomator/hub/api/AuthorityDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,6 @@

abstract sealed class AuthorityDto permits UserDto, GroupDto {

private static final String DEFAULT_USER_IMAGE_URI = """
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0iYmxhY2siIGNsYXNzPSJ3LT\
UgaC01Ij4gICA8Y2lyY2xlIGN4PSIxMCIgY3k9IjEwIiByPSIxMCIgZmlsbD0id2hpdGUiIC8+ICAgPHBhdGggZD0iTTEwIDhhMyAzIDAgMTAwLTYgMyAzIDAgMDAwIDZ6TTMuND\
Y1IDE0LjQ5M2ExLjIzIDEuMjMgMCAwMC40MSAxLjQxMkE5Ljk1NyA5Ljk1NyAwIDAwMTAgMThjMi4zMSAwIDQuNDM4LS43ODQgNi4xMzEtMi4xLjQzLS4zMzMuNjA0LS45MDMuND\
A4LTEuNDFhNy4wMDIgNy4wMDIgMCAwMC0xMy4wNzQuMDAzeiIgLz4gPC9zdmc+
""";
private static final String DEFAULT_GROUP_IMAGE_URI = """
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0iYmxhY2siIGNsYXNzPSJ3LT\
UgaC01Ij4gICA8Y2lyY2xlIGN4PSIxMCIgY3k9IjEwIiByPSIxMCIgZmlsbD0id2hpdGUiIC8+ICAgPHBhdGggZD0iTTEwIDlhMyAzIDAgMTAwLTYgMyAzIDAgMDAwIDZ6TTYgOG\
EyIDIgMCAxMS00IDAgMiAyIDAgMDE0IDB6TTEuNDkgMTUuMzI2YS43OC43OCAwIDAxLS4zNTgtLjQ0MiAzIDMgMCAwMTQuMzA4LTMuNTE2IDYuNDg0IDYuNDg0IDAgMDAtMS45MD\
UgMy45NTljLS4wMjMuMjIyLS4wMTQuNDQyLjAyNS42NTRhNC45NyA0Ljk3IDAgMDEtMi4wNy0uNjU1ek0xNi40NCAxNS45OGE0Ljk3IDQuOTcgMCAwMDIuMDctLjY1NC43OC43OC\
AwIDAwLjM1Ny0uNDQyIDMgMyAwIDAwLTQuMzA4LTMuNTE3IDYuNDg0IDYuNDg0IDAgMDExLjkwNyAzLjk2IDIuMzIgMi4zMiAwIDAxLS4wMjYuNjU0ek0xOCA4YTIgMiAwIDExLT\
QgMCAyIDIgMCAwMTQgMHpNNS4zMDQgMTYuMTlhLjg0NC44NDQgMCAwMS0uMjc3LS43MSA1IDUgMCAwMTkuOTQ3IDAgLjg0My44NDMgMCAwMS0uMjc3LjcxQTYuOTc1IDYuOTc1ID\
AgMDExMCAxOGE2Ljk3NCA2Ljk3NCAwIDAxLTQuNjk2LTEuODF6IiAvPiA8L3N2Zz4=
""";

public enum Type {
USER, GROUP
}
Expand All @@ -42,10 +26,7 @@ protected AuthorityDto(String id, Type type, String name, String pictureUrl) {
this.id = id;
this.type = type;
this.name = name;
this.pictureUrl = Objects.requireNonNullElseGet(pictureUrl, () -> switch (type) {
case USER -> DEFAULT_USER_IMAGE_URI;
case GROUP -> DEFAULT_GROUP_IMAGE_URI;
});
this.pictureUrl = pictureUrl;
}

}
45 changes: 41 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@heroicons/vue": "^2.0.11",
"axios": "^0.27.2",
"file-saver": "^2.0.5",
"jdenticon": "^3.2.0",
"jszip": "^3.9.1",
"keycloak-js": "^18.0.0",
"miscreant": "^0.3.2",
Expand Down
85 changes: 74 additions & 11 deletions frontend/src/common/backend.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import AxiosStatic, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { JdenticonConfig, toSvg } from 'jdenticon';
import { base64 } from 'rfc4648';
import authPromise from './auth';
import { backendBaseURL } from './config';
import { VaultKeys } from './crypto';
Expand Down Expand Up @@ -56,35 +58,94 @@ enum AuthorityType {
}

abstract class AuthorityDto {
constructor(public id: string, public name: string, public type: AuthorityType, public pictureUrl: string) { }

private _pictureUrl?: string;

constructor(public id: string, public name: string, public type: AuthorityType, pictureUrl?: string) {
this._pictureUrl = pictureUrl;
}

public get pictureUrl() {
if (this._pictureUrl) {
return this._pictureUrl;
} else {
switch (this.type) {
case AuthorityType.User:
}
const svg = toSvg(this.id, 100, this.getIdenticonConfig());
const bytes = new TextEncoder().encode(svg);
return `data:image/svg+xml;base64,${base64.stringify(bytes)}`;
}
}

abstract getIdenticonConfig(): JdenticonConfig;

}

export class UserDto extends AuthorityDto {
constructor(public id: string, public name: string, public type: AuthorityType, public pictureUrl: string, public email: string, public devices: DeviceDto[]) {
constructor(public id: string, public name: string, public type: AuthorityType, public email: string, public devices: DeviceDto[], pictureUrl?: string) {
super(id, name, type, pictureUrl);
}

getIdenticonConfig(): JdenticonConfig {
return {
hues: [6, 28, 48, 121, 283],
saturation: {
color: 0.59,
},
lightness: {
color: [0.32, 0.49],
grayscale: [0.32, 0.49]
},
backColor: '#F7F7F7',
padding: 0
};
}

static typeOf(obj: any): obj is UserDto {
const userDto = obj as UserDto;
return typeof userDto.id === 'string'
&& typeof userDto.name === 'string'
&& typeof userDto.pictureUrl === 'string'
&& (typeof userDto.pictureUrl === 'string' || userDto.pictureUrl === null)
&& userDto.type === AuthorityType.User;
}

static copy(obj: UserDto): UserDto {
return new UserDto(obj.id, obj.name, obj.type, obj.email, obj.devices, obj.pictureUrl);
}
}

export class GroupDto extends AuthorityDto {
constructor(public id: string, public name: string, public type: AuthorityType, public pictureUrl: string) {
constructor(public id: string, public name: string, public type: AuthorityType, pictureUrl: string) {
super(id, name, type, pictureUrl);
}

getIdenticonConfig(): JdenticonConfig {
return {
hues: [190],
saturation: {
color: 0.59
},
lightness: {
color: [0.81, 0.97],
grayscale: [0.81, 0.97]
},
backColor: '#005E71',
padding: 0
};
}

static typeOf(obj: any): obj is GroupDto {
const groupDto = obj as GroupDto;
return typeof groupDto.id === 'string'
&& typeof groupDto.name === 'string'
&& typeof groupDto.pictureUrl === 'string'
&& (typeof groupDto.pictureUrl === 'string' || groupDto.pictureUrl === null)
&& groupDto.type === AuthorityType.Group;
}

static copy(obj: GroupDto): GroupDto {
return new GroupDto(obj.id, obj.name, obj.type, obj.pictureUrl);
}
}

export type BillingDto = {
Expand Down Expand Up @@ -128,9 +189,9 @@ class VaultService {
return axiosAuth.get<(UserDto | GroupDto)[]>(`/vaults/${vaultId}/members`, { headers: { 'Cryptomator-Vault-Admin-Authorization': vaultAdminAuthorizationJWT } }).then(response => {
return response.data.map(authority => {
if (UserDto.typeOf(authority)) {
return new UserDto(authority.id, authority.name, authority.type, authority.pictureUrl, authority.email, authority.devices);
return UserDto.copy(authority);
} else if (GroupDto.typeOf(authority)) {
return new GroupDto(authority.id, authority.name, authority.type, authority.pictureUrl);
return GroupDto.copy(authority);
} else {
throw new Error('Provided data is not of type UserDTO or GroupDTO');
}
Expand Down Expand Up @@ -200,11 +261,13 @@ class UserService {
return axiosAuth.put('/users/me');
}
public async me(withDevices: boolean = false, withAccessibleVaults: boolean = false): Promise<UserDto> {
return axiosAuth.get<UserDto>(`/users/me?withDevices=${withDevices}&withAccessibleVaults=${withAccessibleVaults}`).then(response => response.data);
return axiosAuth.get<UserDto>(`/users/me?withDevices=${withDevices}&withAccessibleVaults=${withAccessibleVaults}`).then(response => UserDto.copy(response.data));
}

public async listAll(): Promise<UserDto[]> {
return axiosAuth.get<UserDto[]>('/users/').then(response => response.data);
return axiosAuth.get<UserDto[]>('/users/').then(response => {
return response.data.map(dto => UserDto.copy(dto));
});
}

}
Expand All @@ -214,9 +277,9 @@ class AuthorityService {
return axiosAuth.get<(UserDto | GroupDto)[]>(`/authorities/search?query=${query}`).then(response => {
return response.data.map(authority => {
if (UserDto.typeOf(authority)) {
return new UserDto(authority.id, authority.name, authority.type, authority.pictureUrl, authority.email, authority.devices);
return UserDto.copy(authority);
} else if (GroupDto.typeOf(authority)) {
return new GroupDto(authority.id, authority.name, authority.type, authority.pictureUrl);
return GroupDto.copy(authority);
} else {
throw new Error('Provided data is not of type UserDTO or GroupDTO');
}
Expand Down