Skip to content

Commit

Permalink
Add client side preference verification
Browse files Browse the repository at this point in the history
Add support for hostname override
  • Loading branch information
CaramelFur committed Dec 25, 2022
1 parent 145ff69 commit dac4389
Show file tree
Hide file tree
Showing 25 changed files with 253 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class PreferenceDefaultsService {
private readonly sysDefaults: {
[key in SysPreference]: (() => PrefValueType) | PrefValueType;
} = {
[SysPreference.HostOverride]: '',

[SysPreference.JwtSecret]: () => {
const envSecret = this.jwtConfigService.getJwtSecret();
if (envSecret) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { InjectRepository } from '@nestjs/typeorm';
import {
DecodedSysPref,
PrefValueType,
PrefValueTypeStrings,
PrefValueTypeStrings
} from 'picsur-shared/dist/dto/preferences.dto';
import {
SysPreference,
SysPreferenceList,
SysPreferenceValidators,
SysPreferenceValueTypes,
SysPreferenceValueTypes
} from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm';
import {
ESysPreferenceBackend,
ESysPreferenceSchema,
ESysPreferenceSchema
} from '../../database/entities/sys-preference.entity';
import { MutexFallBack } from '../../util/mutex-fallback';
import { PreferenceCommonService } from './preference-common.service';
Expand Down Expand Up @@ -155,6 +155,9 @@ export class SysPreferenceDbService {
const valueValidated = SysPreferenceValidators[key as SysPreference].safeParse(
value,
);
if (!valueValidated.success) {
return Fail(FT.UsrValidation, undefined, valueValidated.error);
}

let verifySysPreference = new ESysPreferenceBackend();
verifySysPreference.key = validated.key;
Expand Down
20 changes: 8 additions & 12 deletions backend/src/collections/preference-db/usr-preference-db.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { InjectRepository } from '@nestjs/typeorm';
import {
DecodedUsrPref,
PrefValueType,
PrefValueTypeStrings,
PrefValueTypeStrings
} from 'picsur-shared/dist/dto/preferences.dto';
import {
UsrPreference,
UsrPreferenceList,
UsrPreferenceValidators,
UsrPreferenceValueTypes,
UsrPreferenceValueTypes
} from 'picsur-shared/dist/dto/usr-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm';
import {
EUsrPreferenceBackend,
EUsrPreferenceSchema,
EUsrPreferenceSchema
} from '../../database/entities/usr-preference.entity';
import { MutexFallBack } from '../../util/mutex-fallback';
import { PreferenceCommonService } from './preference-common.service';
Expand Down Expand Up @@ -193,15 +193,11 @@ export class UsrPreferenceDbService {
);
if (HasFailed(validated)) return validated;

if (!UsrPreferenceValidators[validated.key](validated.value))
throw Fail(
FT.UsrValidation,
undefined,
'Preference validator failed for ' +
validated.key +
' with value ' +
validated.value,
);
const valueValidated =
UsrPreferenceValidators[key as UsrPreference].safeParse(value);
if (!valueValidated.success) {
return Fail(FT.UsrValidation, undefined, valueValidated.error);
}

let verifySysPreference = new EUsrPreferenceBackend();
verifySysPreference.key = validated.key;
Expand Down
23 changes: 23 additions & 0 deletions backend/src/config/late/info.config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable, Logger } from '@nestjs/common';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { HasFailed } from 'picsur-shared/dist/types';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';

@Injectable()
export class InfoConfigService {
private readonly logger = new Logger(InfoConfigService.name);

constructor(private readonly prefService: SysPreferenceDbService) {}

public async getHostnameOverride(): Promise<string | undefined> {
const hostname = await this.prefService.getStringPreference(
SysPreference.HostOverride,
);
if (HasFailed(hostname)) {
this.logger.warn(hostname.print());
return undefined;
}

return hostname;
}
}
7 changes: 4 additions & 3 deletions backend/src/config/late/late-config.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PreferenceDbModule } from '../../collections/preference-db/preference-d
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';
import { EarlyConfigModule } from '../early/early-config.module';
import { EarlyJwtConfigService } from '../early/early-jwt.config.service';
import { InfoConfigService } from './info.config.service';
import { JwtConfigService } from './jwt.config.service';

// This module contains all configservices that depend on the syspref module
Expand All @@ -11,9 +12,9 @@ import { JwtConfigService } from './jwt.config.service';
// Otherwise we will create a circular depedency

@Module({
imports: [PreferenceDbModule, EarlyConfigModule],
providers: [JwtConfigService],
exports: [JwtConfigService, EarlyConfigModule],
imports: [EarlyConfigModule, PreferenceDbModule],
providers: [JwtConfigService, InfoConfigService],
exports: [EarlyConfigModule, JwtConfigService, InfoConfigService],
})
export class LateConfigModule implements OnModuleInit {
private readonly logger = new Logger(LateConfigModule.name);
Expand Down
15 changes: 9 additions & 6 deletions backend/src/routes/api/info/info.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { Controller, Get } from '@nestjs/common';
import {
AllFormatsResponse,
AllPermissionsResponse,
InfoResponse,
InfoResponse
} from 'picsur-shared/dist/dto/api/info.dto';
import {
FileType2Ext,
FileType2Mime,
SupportedAnimFileTypes,
SupportedImageFileTypes,
SupportedImageFileTypes
} from 'picsur-shared/dist/dto/mimes.dto';
import { TrackingState } from 'picsur-shared/dist/dto/tracking-state.enum';
import { FallbackIfFailed } from 'picsur-shared/dist/types';
import { HostConfigService } from '../../../config/early/host.config.service';
import { InfoConfigService } from '../../../config/late/info.config.service';
import { NoPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator';
import { UsageService } from '../../../managers/usage/usage.service';
Expand All @@ -23,21 +24,23 @@ import { PermissionsList } from '../../../models/constants/permissions.const';
export class InfoController {
constructor(
private readonly hostConfig: HostConfigService,
private readonly infoConfig: InfoConfigService,
private readonly usageService: UsageService,
) {}

@Get()
@Returns(InfoResponse)
async getInfo(): Promise<InfoResponse> {
const trackingID = FallbackIfFailed(
await this.usageService.getTrackingID(),
null,
) ?? undefined;
const trackingID =
FallbackIfFailed(await this.usageService.getTrackingID(), null) ??
undefined;
const hostOverride = await this.infoConfig.getHostnameOverride();

return {
demo: this.hostConfig.isDemo(),
production: this.hostConfig.isProduction(),
version: this.hostConfig.getVersion(),
host_override: hostOverride,
tracking: {
id: trackingID,
state: TrackingState.Detailed,
Expand Down
4 changes: 2 additions & 2 deletions backend/src/routes/api/info/info.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Module } from '@nestjs/common';
import { EarlyConfigModule } from '../../../config/early/early-config.module';
import { LateConfigModule } from '../../../config/late/late-config.module';
import { UsageManagerModule } from '../../../managers/usage/usage.module';
import { InfoController } from './info.controller';

@Module({
imports: [EarlyConfigModule, UsageManagerModule],
imports: [LateConfigModule, UsageManagerModule],
controllers: [InfoController],
})
export class InfoModule {}
26 changes: 15 additions & 11 deletions frontend/src/app/components/pref-option/pref-option.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<ng-container *ngIf="pref.type === 'string'">
<ng-container *ngIf="type === 'string'">
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<input
matInput
[value]="pref.value"
(change)="stringUpdateWrapper($event)"
autocorrect="off"
autocapitalize="none"
placeholder="Empty"
[formControl]="formControl"
/>
<!-- show tooltip on press -->
<button
Expand All @@ -28,20 +27,22 @@
help_outline
</mat-icon>
</button>
<mat-error *ngIf="formControl.invalid">
{{ getErrorMessage() }}
</mat-error>
</mat-form-field>
</div>
</ng-container>

<ng-container *ngIf="pref.type === 'number'">
<ng-container *ngIf="type === 'number'">
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<input
matInput
type="number"
[value]="pref.value"
(change)="numberUpdateWrapper($event)"
placeholder="Empty"
[formControl]="formControl"
/>
<button
mat-icon-button
Expand All @@ -60,18 +61,18 @@
help_outline
</mat-icon>
</button>
<mat-error *ngIf="formControl.invalid">
{{ getErrorMessage() }}
</mat-error>
</mat-form-field>
</div>
</ng-container>

<ng-container *ngIf="pref.type === 'boolean'">
<ng-container *ngIf="type === 'boolean'">
<div class="y-center">
<mat-form-field appearance="outline" color="accent">
<mat-label>{{ name }}</mat-label>
<mat-select
[value]="pref.value"
(valueChange)="booleanUpdateWrapper($event)"
>
<mat-select [formControl]="formControl" placeholder="Empty">
<mat-option [value]="false">No</mat-option>
<mat-option [value]="true">Yes</mat-option>
</mat-select>
Expand All @@ -91,6 +92,9 @@
help_outline
</mat-icon>
</button>
<mat-error *ngIf="formControl.invalid">
{{ getErrorMessage() }}
</mat-error>
</mat-form-field>
</div>
</ng-container>
78 changes: 39 additions & 39 deletions frontend/src/app/components/pref-option/pref-option.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl, FormControl, ValidationErrors } from '@angular/forms';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import {
DecodedPref,
PrefValueType
} from 'picsur-shared/dist/dto/preferences.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { Subject } from 'rxjs';
import { filter } from 'rxjs';
import { Required } from 'src/app/models/decorators/required.decorator';
import { Logger } from 'src/app/services/logger/logger.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { Throttle } from 'src/app/util/throttle';
import { ZodTypeAny } from 'zod';

@Component({
selector: 'pref-option',
Expand All @@ -19,61 +21,56 @@ import { Throttle } from 'src/app/util/throttle';
export class PrefOptionComponent implements OnInit {
private readonly logger = new Logger(PrefOptionComponent.name);

@Input() @Required pref: DecodedPref;
public formControl = new FormControl<any>(undefined, {
updateOn: 'blur',
validators: this.syncValidator.bind(this),
});

private pref: DecodedPref;
@Input('pref') set prefSet(pref: DecodedPref) {
this.pref = pref;
this.formControl.setValue(pref.value);
}
get type() {
return this.pref.type;
}

@Input('update') @Required updateFunction: (
key: string,
pref: PrefValueType,
) => AsyncFailable<any>;
@Input() @Required name: string = '';

@Input() @Required name: string = '';
@Input() helpText: string = '';

private updateSubject = new Subject<PrefValueType>();
@Input() validator?: ZodTypeAny = undefined;

constructor(private readonly errorService: ErrorService) {}

ngOnInit(): void {
this.subscribeUpdate();
}

get valString(): string {
if (this.pref.type !== 'string') {
throw new Error('Not a string preference');
}
return this.pref.value as string;
}

get valNumber(): number {
if (this.pref.type !== 'number') {
throw new Error('Not an int preference');
getErrorMessage() {
if (this.formControl.errors) {
const errors = this.formControl.errors;
if (errors['error']) {
return errors['error'];
}
return 'Invalid value';
}
return this.pref.value as number;
return '';
}

get valBool(): boolean {
if (this.pref.type !== 'boolean') {
throw new Error('Not a boolean preference');
}
return this.pref.value as boolean;
}
private syncValidator(control: AbstractControl): ValidationErrors | null {
if (!this.validator) return null;

update(value: any) {
this.updateSubject.next(value);
}
const result = this.validator.safeParse(control.value);

stringUpdateWrapper(e: Event) {
this.update((e.target as HTMLInputElement).value);
}

numberUpdateWrapper(e: Event) {
const value = (e.target as HTMLInputElement).valueAsNumber;
if (isNaN(value)) return;

this.update(value);
}
if (!result.success) {
return { error: result.error.issues[0]?.message ?? 'Invalid value' };
}

booleanUpdateWrapper(e: boolean) {
this.update(e);
return null;
}

private async updatePreference(value: PrefValueType) {
Expand All @@ -97,8 +94,11 @@ export class PrefOptionComponent implements OnInit {

@AutoUnsubscribe()
subscribeUpdate() {
return this.updateSubject
.pipe(Throttle(300))
return this.formControl.valueChanges
.pipe(
filter((value) => this.formControl.errors === null),
Throttle(300),
)
.subscribe(this.updatePreference.bind(this));
}
}
Loading

0 comments on commit dac4389

Please sign in to comment.