mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-28 21:03:57 -05:00
refactor(frontend-config): return auth providers as array
This change removes the customAuthNames property and redefines the authProviders property of the frontend-config DTO. Instead of an map from auth providers to their enabled-state (boolean), there is now an array that just includes the configured auth providers while also having the identifier and providerName of custom auth providers. Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
dcfb00adc1
commit
20b0ded223
3 changed files with 229 additions and 234 deletions
|
@ -16,68 +16,67 @@ import { URL } from 'url';
|
||||||
|
|
||||||
import { ServerVersion } from '../monitoring/server-status.dto';
|
import { ServerVersion } from '../monitoring/server-status.dto';
|
||||||
|
|
||||||
export class AuthProviders {
|
export enum AuthProviderType {
|
||||||
/**
|
LOCAL = 'local',
|
||||||
* Is Facebook available as a auth provider?
|
LDAP = 'ldap',
|
||||||
*/
|
SAML = 'saml',
|
||||||
@IsBoolean()
|
OAUTH2 = 'oauth2',
|
||||||
facebook: boolean;
|
GITLAB = 'gitlab',
|
||||||
|
FACEBOOK = 'facebook',
|
||||||
/**
|
GITHUB = 'github',
|
||||||
* Is GitHub available as a auth provider?
|
TWITTER = 'twitter',
|
||||||
*/
|
DROPBOX = 'dropbox',
|
||||||
@IsBoolean()
|
GOOGLE = 'google',
|
||||||
github: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is Twitter available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
twitter: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is at least one GitLab server available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
gitlab: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is DropBox available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
dropbox: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is at least one LDAP server available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
ldap: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is Google available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
google: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is at least one SAML provider available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
saml: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is at least one OAuth2 provider available as a auth provider?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
oauth2: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is local auth available?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
local: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthProviderTypeWithCustomName =
|
||||||
|
| AuthProviderType.LDAP
|
||||||
|
| AuthProviderType.OAUTH2
|
||||||
|
| AuthProviderType.SAML
|
||||||
|
| AuthProviderType.GITLAB;
|
||||||
|
|
||||||
|
export type AuthProviderTypeWithoutCustomName =
|
||||||
|
| AuthProviderType.LOCAL
|
||||||
|
| AuthProviderType.FACEBOOK
|
||||||
|
| AuthProviderType.GITHUB
|
||||||
|
| AuthProviderType.TWITTER
|
||||||
|
| AuthProviderType.DROPBOX
|
||||||
|
| AuthProviderType.GOOGLE;
|
||||||
|
|
||||||
|
export class AuthProviderWithoutCustomNameDto {
|
||||||
|
/**
|
||||||
|
* The type of the auth provider.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
type: AuthProviderTypeWithoutCustomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthProviderWithCustomNameDto {
|
||||||
|
/**
|
||||||
|
* The type of the auth provider.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
type: AuthProviderTypeWithCustomName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier with which the auth provider can be called
|
||||||
|
* @example gitlab-fsorg
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
identifier: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name given to the auth provider
|
||||||
|
* @example GitLab fachschaften.org
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
providerName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthProviderDto =
|
||||||
|
| AuthProviderWithCustomNameDto
|
||||||
|
| AuthProviderWithoutCustomNameDto;
|
||||||
|
|
||||||
export class BrandingDto {
|
export class BrandingDto {
|
||||||
/**
|
/**
|
||||||
* The name to be displayed next to the HedgeDoc logo
|
* The name to be displayed next to the HedgeDoc logo
|
||||||
|
@ -96,52 +95,6 @@ export class BrandingDto {
|
||||||
logo?: URL;
|
logo?: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomAuthEntry {
|
|
||||||
/**
|
|
||||||
* The identifier with which the auth provider can be called
|
|
||||||
* @example gitlab
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
identifier: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name given to the auth provider
|
|
||||||
* @example GitLab
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
providerName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CustomAuthNamesDto {
|
|
||||||
/**
|
|
||||||
* All configured GitLab server
|
|
||||||
*/
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
gitlab: CustomAuthEntry[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All configured LDAP server
|
|
||||||
*/
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
ldap: CustomAuthEntry[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All configured OAuth2 provider
|
|
||||||
*/
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
oauth2: CustomAuthEntry[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All configured SAML provider
|
|
||||||
*/
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
saml: CustomAuthEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SpecialUrlsDto {
|
export class SpecialUrlsDto {
|
||||||
/**
|
/**
|
||||||
* A link to the privacy notice
|
* A link to the privacy notice
|
||||||
|
@ -200,10 +153,11 @@ export class FrontendConfigDto {
|
||||||
allowRegister: boolean;
|
allowRegister: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which auth providers are available?
|
* Which auth providers are enabled and how are they configured?
|
||||||
*/
|
*/
|
||||||
@ValidateNested()
|
@IsArray()
|
||||||
authProviders: AuthProviders;
|
@ValidateNested({ each: true })
|
||||||
|
authProviders: AuthProviderDto[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Individual branding information
|
* Individual branding information
|
||||||
|
@ -211,12 +165,6 @@ export class FrontendConfigDto {
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
branding: BrandingDto;
|
branding: BrandingDto;
|
||||||
|
|
||||||
/**
|
|
||||||
* The custom names of auth providers, which can be specified multiple times
|
|
||||||
*/
|
|
||||||
@ValidateNested()
|
|
||||||
customAuthNames: CustomAuthNamesDto;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is an image proxy enabled?
|
* Is an image proxy enabled?
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { GitlabScope, GitlabVersion } from '../config/gitlab.enum';
|
||||||
import { Loglevel } from '../config/loglevel.enum';
|
import { Loglevel } from '../config/loglevel.enum';
|
||||||
import { LoggerModule } from '../logger/logger.module';
|
import { LoggerModule } from '../logger/logger.module';
|
||||||
import { getServerVersionFromPackageJson } from '../utils/serverVersion';
|
import { getServerVersionFromPackageJson } from '../utils/serverVersion';
|
||||||
|
import { AuthProviderType } from './frontend-config.dto';
|
||||||
import { FrontendConfigService } from './frontend-config.service';
|
import { FrontendConfigService } from './frontend-config.service';
|
||||||
|
|
||||||
/* eslint-disable
|
/* eslint-disable
|
||||||
|
@ -250,85 +251,113 @@ describe('FrontendConfigService', () => {
|
||||||
expect(config.allowRegister).toEqual(
|
expect(config.allowRegister).toEqual(
|
||||||
enableRegister,
|
enableRegister,
|
||||||
);
|
);
|
||||||
expect(config.authProviders.dropbox).toEqual(
|
if (authConfig.dropbox.clientID) {
|
||||||
!!authConfig.dropbox.clientID,
|
expect(config.authProviders).toContainEqual({
|
||||||
);
|
type: AuthProviderType.DROPBOX,
|
||||||
expect(config.authProviders.facebook).toEqual(
|
});
|
||||||
!!authConfig.facebook.clientID,
|
}
|
||||||
);
|
if (authConfig.facebook.clientID) {
|
||||||
expect(config.authProviders.github).toEqual(
|
expect(config.authProviders).toContainEqual({
|
||||||
!!authConfig.github.clientID,
|
type: AuthProviderType.FACEBOOK,
|
||||||
);
|
});
|
||||||
expect(config.authProviders.google).toEqual(
|
}
|
||||||
!!authConfig.google.clientID,
|
if (authConfig.google.clientID) {
|
||||||
);
|
expect(config.authProviders).toContainEqual({
|
||||||
expect(config.authProviders.local).toEqual(
|
type: AuthProviderType.GOOGLE,
|
||||||
enableLogin,
|
});
|
||||||
);
|
}
|
||||||
expect(config.authProviders.twitter).toEqual(
|
if (authConfig.github.clientID) {
|
||||||
!!authConfig.twitter.consumerKey,
|
expect(config.authProviders).toContainEqual({
|
||||||
);
|
type: AuthProviderType.GITHUB,
|
||||||
expect(config.authProviders.gitlab).toEqual(
|
});
|
||||||
authConfig.gitlab.length !== 0,
|
}
|
||||||
);
|
if (authConfig.local.enableLogin) {
|
||||||
expect(config.authProviders.ldap).toEqual(
|
expect(config.authProviders).toContainEqual({
|
||||||
authConfig.ldap.length !== 0,
|
type: AuthProviderType.LOCAL,
|
||||||
);
|
});
|
||||||
expect(config.authProviders.saml).toEqual(
|
}
|
||||||
authConfig.saml.length !== 0,
|
if (authConfig.twitter.consumerKey) {
|
||||||
);
|
expect(config.authProviders).toContainEqual({
|
||||||
expect(config.authProviders.oauth2).toEqual(
|
type: AuthProviderType.TWITTER,
|
||||||
authConfig.oauth2.length !== 0,
|
});
|
||||||
);
|
}
|
||||||
|
expect(
|
||||||
|
config.authProviders.filter(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.GITLAB,
|
||||||
|
).length,
|
||||||
|
).toEqual(authConfig.gitlab.length);
|
||||||
|
expect(
|
||||||
|
config.authProviders.filter(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.LDAP,
|
||||||
|
).length,
|
||||||
|
).toEqual(authConfig.ldap.length);
|
||||||
|
expect(
|
||||||
|
config.authProviders.filter(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.SAML,
|
||||||
|
).length,
|
||||||
|
).toEqual(authConfig.saml.length);
|
||||||
|
expect(
|
||||||
|
config.authProviders.filter(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.OAUTH2,
|
||||||
|
).length,
|
||||||
|
).toEqual(authConfig.oauth2.length);
|
||||||
|
if (authConfig.gitlab.length > 0) {
|
||||||
|
expect(
|
||||||
|
config.authProviders.find(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.GITLAB,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
type: AuthProviderType.GITLAB,
|
||||||
|
providerName: authConfig.gitlab[0].providerName,
|
||||||
|
identifier: authConfig.gitlab[0].identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (authConfig.ldap.length > 0) {
|
||||||
|
expect(
|
||||||
|
config.authProviders.find(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.LDAP,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
type: AuthProviderType.LDAP,
|
||||||
|
providerName: authConfig.ldap[0].providerName,
|
||||||
|
identifier: authConfig.ldap[0].identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (authConfig.saml.length > 0) {
|
||||||
|
expect(
|
||||||
|
config.authProviders.find(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.SAML,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
type: AuthProviderType.SAML,
|
||||||
|
providerName: authConfig.saml[0].providerName,
|
||||||
|
identifier: authConfig.saml[0].identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (authConfig.oauth2.length > 0) {
|
||||||
|
expect(
|
||||||
|
config.authProviders.find(
|
||||||
|
(provider) =>
|
||||||
|
provider.type === AuthProviderType.OAUTH2,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
type: AuthProviderType.OAUTH2,
|
||||||
|
providerName: authConfig.oauth2[0].providerName,
|
||||||
|
identifier: authConfig.oauth2[0].identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
expect(config.allowAnonymous).toEqual(false);
|
expect(config.allowAnonymous).toEqual(false);
|
||||||
expect(config.branding.name).toEqual(customName);
|
expect(config.branding.name).toEqual(customName);
|
||||||
expect(config.branding.logo).toEqual(
|
expect(config.branding.logo).toEqual(
|
||||||
customLogo ? new URL(customLogo) : undefined,
|
customLogo ? new URL(customLogo) : undefined,
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
config.customAuthNames.gitlab.length,
|
|
||||||
).toEqual(authConfig.gitlab.length);
|
|
||||||
if (config.customAuthNames.gitlab.length === 1) {
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.gitlab[0].identifier,
|
|
||||||
).toEqual(authConfig.gitlab[0].identifier);
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.gitlab[0].providerName,
|
|
||||||
).toEqual(authConfig.gitlab[0].providerName);
|
|
||||||
}
|
|
||||||
expect(config.customAuthNames.ldap.length).toEqual(
|
|
||||||
authConfig.ldap.length,
|
|
||||||
);
|
|
||||||
if (config.customAuthNames.ldap.length === 1) {
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.ldap[0].identifier,
|
|
||||||
).toEqual(authConfig.ldap[0].identifier);
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.ldap[0].providerName,
|
|
||||||
).toEqual(authConfig.ldap[0].providerName);
|
|
||||||
}
|
|
||||||
expect(config.customAuthNames.saml.length).toEqual(
|
|
||||||
authConfig.saml.length,
|
|
||||||
);
|
|
||||||
if (config.customAuthNames.saml.length === 1) {
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.saml[0].identifier,
|
|
||||||
).toEqual(authConfig.saml[0].identifier);
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.saml[0].providerName,
|
|
||||||
).toEqual(authConfig.saml[0].providerName);
|
|
||||||
}
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.oauth2.length,
|
|
||||||
).toEqual(authConfig.oauth2.length);
|
|
||||||
if (config.customAuthNames.oauth2.length === 1) {
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.oauth2[0].identifier,
|
|
||||||
).toEqual(authConfig.oauth2[0].identifier);
|
|
||||||
expect(
|
|
||||||
config.customAuthNames.oauth2[0].providerName,
|
|
||||||
).toEqual(authConfig.oauth2[0].providerName);
|
|
||||||
}
|
|
||||||
expect(
|
expect(
|
||||||
config.iframeCommunication.editorOrigin,
|
config.iframeCommunication.editorOrigin,
|
||||||
).toEqual(new URL(appConfig.domain));
|
).toEqual(new URL(appConfig.domain));
|
||||||
|
|
|
@ -17,9 +17,9 @@ import externalServicesConfiguration, {
|
||||||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||||
import { getServerVersionFromPackageJson } from '../utils/serverVersion';
|
import { getServerVersionFromPackageJson } from '../utils/serverVersion';
|
||||||
import {
|
import {
|
||||||
AuthProviders,
|
AuthProviderDto,
|
||||||
|
AuthProviderType,
|
||||||
BrandingDto,
|
BrandingDto,
|
||||||
CustomAuthNamesDto,
|
|
||||||
FrontendConfigDto,
|
FrontendConfigDto,
|
||||||
IframeCommunicationDto,
|
IframeCommunicationDto,
|
||||||
SpecialUrlsDto,
|
SpecialUrlsDto,
|
||||||
|
@ -48,7 +48,6 @@ export class FrontendConfigService {
|
||||||
allowRegister: this.authConfig.local.enableRegister,
|
allowRegister: this.authConfig.local.enableRegister,
|
||||||
authProviders: this.getAuthProviders(),
|
authProviders: this.getAuthProviders(),
|
||||||
branding: this.getBranding(),
|
branding: this.getBranding(),
|
||||||
customAuthNames: this.getCustomAuthNames(),
|
|
||||||
iframeCommunication: this.getIframeCommunication(),
|
iframeCommunication: this.getIframeCommunication(),
|
||||||
maxDocumentLength: this.appConfig.maxDocumentLength,
|
maxDocumentLength: this.appConfig.maxDocumentLength,
|
||||||
plantUmlServer: this.externalServicesConfig.plantUmlServer
|
plantUmlServer: this.externalServicesConfig.plantUmlServer
|
||||||
|
@ -60,48 +59,67 @@ export class FrontendConfigService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAuthProviders(): AuthProviders {
|
private getAuthProviders(): AuthProviderDto[] {
|
||||||
return {
|
const providers: AuthProviderDto[] = [];
|
||||||
dropbox: !!this.authConfig.dropbox.clientID,
|
if (this.authConfig.local.enableLogin) {
|
||||||
facebook: !!this.authConfig.facebook.clientID,
|
providers.push({
|
||||||
github: !!this.authConfig.github.clientID,
|
type: AuthProviderType.LOCAL,
|
||||||
gitlab: this.authConfig.gitlab.length !== 0,
|
});
|
||||||
google: !!this.authConfig.google.clientID,
|
}
|
||||||
local: this.authConfig.local.enableLogin,
|
if (this.authConfig.dropbox.clientID) {
|
||||||
ldap: this.authConfig.ldap.length !== 0,
|
providers.push({
|
||||||
oauth2: this.authConfig.oauth2.length !== 0,
|
type: AuthProviderType.DROPBOX,
|
||||||
saml: this.authConfig.saml.length !== 0,
|
});
|
||||||
twitter: !!this.authConfig.twitter.consumerKey,
|
}
|
||||||
};
|
if (this.authConfig.facebook.clientID) {
|
||||||
}
|
providers.push({
|
||||||
|
type: AuthProviderType.FACEBOOK,
|
||||||
private getCustomAuthNames(): CustomAuthNamesDto {
|
});
|
||||||
return {
|
}
|
||||||
gitlab: this.authConfig.gitlab.map((entry) => {
|
if (this.authConfig.github.clientID) {
|
||||||
return {
|
providers.push({
|
||||||
identifier: entry.identifier,
|
type: AuthProviderType.GITHUB,
|
||||||
providerName: entry.providerName,
|
});
|
||||||
};
|
}
|
||||||
}),
|
if (this.authConfig.google.clientID) {
|
||||||
ldap: this.authConfig.ldap.map((entry) => {
|
providers.push({
|
||||||
return {
|
type: AuthProviderType.GOOGLE,
|
||||||
identifier: entry.identifier,
|
});
|
||||||
providerName: entry.providerName,
|
}
|
||||||
};
|
if (this.authConfig.twitter.consumerKey) {
|
||||||
}),
|
providers.push({
|
||||||
oauth2: this.authConfig.oauth2.map((entry) => {
|
type: AuthProviderType.TWITTER,
|
||||||
return {
|
});
|
||||||
identifier: entry.identifier,
|
}
|
||||||
providerName: entry.providerName,
|
this.authConfig.gitlab.forEach((gitLabEntry) => {
|
||||||
};
|
providers.push({
|
||||||
}),
|
type: AuthProviderType.GITLAB,
|
||||||
saml: this.authConfig.saml.map((entry) => {
|
providerName: gitLabEntry.providerName,
|
||||||
return {
|
identifier: gitLabEntry.identifier,
|
||||||
identifier: entry.identifier,
|
});
|
||||||
providerName: entry.providerName,
|
});
|
||||||
};
|
this.authConfig.ldap.forEach((ldapEntry) => {
|
||||||
}),
|
providers.push({
|
||||||
};
|
type: AuthProviderType.LDAP,
|
||||||
|
providerName: ldapEntry.providerName,
|
||||||
|
identifier: ldapEntry.identifier,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.authConfig.oauth2.forEach((oauth2Entry) => {
|
||||||
|
providers.push({
|
||||||
|
type: AuthProviderType.OAUTH2,
|
||||||
|
providerName: oauth2Entry.providerName,
|
||||||
|
identifier: oauth2Entry.identifier,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.authConfig.saml.forEach((samlEntry) => {
|
||||||
|
providers.push({
|
||||||
|
type: AuthProviderType.SAML,
|
||||||
|
providerName: samlEntry.providerName,
|
||||||
|
identifier: samlEntry.identifier,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBranding(): BrandingDto {
|
private getBranding(): BrandingDto {
|
||||||
|
|
Loading…
Reference in a new issue