1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

fix(web): fix Theme Custom CSS endpoint requiring the user to be logged in as the server admin (#4633)

* fix custom css requiring the user to be the admin and logged in

* move theme api to custom endpoint

* add e2e test
This commit is contained in:
Wingy 2023-10-25 15:13:05 -07:00 committed by GitHub
parent 237d1c1bf4
commit cb0e37e76e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 219 additions and 1 deletions

View File

@ -2958,6 +2958,19 @@ export interface ServerStatsResponseDto {
*/
'videos': number;
}
/**
*
* @export
* @interface ServerThemeDto
*/
export interface ServerThemeDto {
/**
*
* @type {SystemConfigThemeDto}
* @memberof ServerThemeDto
*/
'theme': SystemConfigThemeDto;
}
/**
*
* @export
@ -13193,6 +13206,35 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTheme: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/server-info/theme`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -13295,6 +13337,15 @@ export const ServerInfoApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getSupportedMediaTypes(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getTheme(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerThemeDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getTheme(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {*} [options] Override http request option.
@ -13362,6 +13413,14 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas
getSupportedMediaTypes(options?: AxiosRequestConfig): AxiosPromise<ServerMediaTypesResponseDto> {
return localVarFp.getSupportedMediaTypes(options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTheme(options?: AxiosRequestConfig): AxiosPromise<ServerThemeDto> {
return localVarFp.getTheme(options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
@ -13440,6 +13499,16 @@ export class ServerInfoApi extends BaseAPI {
return ServerInfoApiFp(this.configuration).getSupportedMediaTypes(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ServerInfoApi
*/
public getTheme(options?: AxiosRequestConfig) {
return ServerInfoApiFp(this.configuration).getTheme(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.

View File

@ -115,6 +115,7 @@ doc/ServerInfoResponseDto.md
doc/ServerMediaTypesResponseDto.md
doc/ServerPingResponse.md
doc/ServerStatsResponseDto.md
doc/ServerThemeDto.md
doc/ServerVersionResponseDto.md
doc/SharedLinkApi.md
doc/SharedLinkCreateDto.md
@ -285,6 +286,7 @@ lib/model/server_info_response_dto.dart
lib/model/server_media_types_response_dto.dart
lib/model/server_ping_response.dart
lib/model/server_stats_response_dto.dart
lib/model/server_theme_dto.dart
lib/model/server_version_response_dto.dart
lib/model/shared_link_create_dto.dart
lib/model/shared_link_edit_dto.dart
@ -438,6 +440,7 @@ test/server_info_response_dto_test.dart
test/server_media_types_response_dto_test.dart
test/server_ping_response_test.dart
test/server_stats_response_dto_test.dart
test/server_theme_dto_test.dart
test/server_version_response_dto_test.dart
test/shared_link_api_test.dart
test/shared_link_create_dto_test.dart

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/doc/ServerThemeDto.md generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4126,6 +4126,27 @@
]
}
},
"/server-info/theme": {
"get": {
"operationId": "getTheme",
"parameters": [],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ServerThemeDto"
}
}
},
"description": ""
}
},
"tags": [
"Server Info"
]
}
},
"/server-info/version": {
"get": {
"operationId": "getServerVersion",
@ -7812,6 +7833,17 @@
],
"type": "object"
},
"ServerThemeDto": {
"properties": {
"theme": {
"$ref": "#/components/schemas/SystemConfigThemeDto"
}
},
"required": [
"theme"
],
"type": "object"
},
"ServerVersionResponseDto": {
"properties": {
"major": {

View File

@ -1,5 +1,6 @@
import { FeatureFlags, IServerVersion } from '@app/domain';
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
import { SystemConfigThemeDto } from '../system-config/dto/system-config-theme.dto';
export class ServerPingResponse {
@ApiResponseProperty({ type: String, example: 'pong' })
@ -79,6 +80,10 @@ export class ServerMediaTypesResponseDto {
sidecar!: string[];
}
export class ServerThemeDto {
theme!: SystemConfigThemeDto;
}
export class ServerConfigDto {
oauthButtonText!: string;
loginPageMessage!: string;

View File

@ -70,6 +70,11 @@ export class ServerInfoService {
return this.configCore.getFeatures();
}
async getTheme() {
const { theme } = await this.configCore.getConfig();
return { theme };
}
async getConfig(): Promise<ServerConfigDto> {
const config = await this.configCore.getConfig();

View File

@ -298,4 +298,10 @@ describe(SystemConfigService.name, () => {
subscription.unsubscribe();
});
});
describe('getTheme', () => {
it('should return the default theme', async () => {
await expect(sut.getTheme()).resolves.toEqual(defaults.theme);
});
});
});

View File

@ -1,6 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { JobName } from '../job';
import { CommunicationEvent, ICommunicationRepository, IJobRepository, ISystemConfigRepository } from '../repositories';
import { SystemConfigThemeDto } from './dto/system-config-theme.dto';
import { SystemConfigDto, mapConfig } from './dto/system-config.dto';
import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto';
import {
@ -30,6 +31,11 @@ export class SystemConfigService {
return this.core.config$;
}
async getTheme(): Promise<SystemConfigThemeDto> {
const { theme } = await this.core.getConfig();
return theme;
}
async getConfig(): Promise<SystemConfigDto> {
const config = await this.core.getConfig();
return mapConfig(config);

View File

@ -6,6 +6,7 @@ import {
ServerMediaTypesResponseDto,
ServerPingResponse,
ServerStatsResponseDto,
ServerThemeDto,
ServerVersionResponseDto,
} from '@app/domain';
import { Controller, Get } from '@nestjs/common';
@ -43,6 +44,12 @@ export class ServerInfoController {
return this.service.getFeatures();
}
@PublicRoute()
@Get('theme')
getTheme(): Promise<ServerThemeDto> {
return this.service.getTheme();
}
@PublicRoute()
@Get('config')
getServerConfig(): Promise<ServerConfigDto> {

View File

@ -155,4 +155,16 @@ describe(`${ServerInfoController.name} (e2e)`, () => {
});
});
});
describe('GET /server-info/theme', () => {
it('should respond with the server theme', async () => {
const { status, body } = await request(server).get('/server-info/theme');
expect(status).toBe(200);
expect(body).toEqual({
theme: {
customCss: '',
},
});
});
});
});

View File

@ -2958,6 +2958,19 @@ export interface ServerStatsResponseDto {
*/
'videos': number;
}
/**
*
* @export
* @interface ServerThemeDto
*/
export interface ServerThemeDto {
/**
*
* @type {SystemConfigThemeDto}
* @memberof ServerThemeDto
*/
'theme': SystemConfigThemeDto;
}
/**
*
* @export
@ -13193,6 +13206,35 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTheme: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/server-info/theme`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@ -13295,6 +13337,15 @@ export const ServerInfoApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getSupportedMediaTypes(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getTheme(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerThemeDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getTheme(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {*} [options] Override http request option.
@ -13362,6 +13413,14 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas
getSupportedMediaTypes(options?: AxiosRequestConfig): AxiosPromise<ServerMediaTypesResponseDto> {
return localVarFp.getSupportedMediaTypes(options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTheme(options?: AxiosRequestConfig): AxiosPromise<ServerThemeDto> {
return localVarFp.getTheme(options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
@ -13440,6 +13499,16 @@ export class ServerInfoApi extends BaseAPI {
return ServerInfoApiFp(this.configuration).getSupportedMediaTypes(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ServerInfoApi
*/
public getTheme(options?: AxiosRequestConfig) {
return ServerInfoApiFp(this.configuration).getTheme(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.

View File

@ -1,6 +1,10 @@
import { RequestHandler, text } from '@sveltejs/kit';
export const GET = (async ({ locals: { api } }) => {
const { customCss } = await api.systemConfigApi.getConfig().then((res) => res.data.theme);
const {
data: {
theme: { customCss },
},
} = await api.serverInfoApi.getTheme();
return text(customCss, {
headers: {
'Content-Type': 'text/css',