b9d87be360
- Tabelle CustomModule mit Zeilenschutz (tenant_isolation_policy), Migration 20260929120000 - API /custom-modules: Lesen für jeden Angemeldeten, Schreiben nur Administrator, nur https ohne Zugangsdaten - Seitenleiste zeigt eigene Module unter ihrer Kategorie, Rahmen-Seite mit Sandbox und „In neuem Tab öffnen“ - MODULE_CATEGORIES als gemeinsame Liste, Zugriffsklassifikation nachgemessen fortgeschrieben Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
import { PartialType } from '@nestjs/mapped-types';
|
|
import { MODULE_CATEGORIES } from '@tessera/shared';
|
|
import { Transform } from 'class-transformer';
|
|
import {
|
|
IsIn,
|
|
IsNotEmpty,
|
|
IsString,
|
|
MaxLength,
|
|
Validate,
|
|
ValidatorConstraint,
|
|
type ValidatorConstraintInterface,
|
|
} from 'class-validator';
|
|
|
|
/**
|
|
* Adresse eines eigenen Moduls (T-9WC-03, T-9WC-06): gueltig nur, wenn der
|
|
* URL-Parser sie annimmt, das Schema `https:` ist, ein Rechnername da ist und
|
|
* weder Benutzername noch Kennwort in der Adresse stehen — sonst saehe jeder
|
|
* Benutzer die Zugangsdaten. `javascript:`, `data:`, `http:` und `ftp:` fallen
|
|
* damit heraus.
|
|
*/
|
|
@ValidatorConstraint({ name: 'nurHttpsOhneZugangsdaten', async: false })
|
|
class NurHttpsOhneZugangsdatenConstraint implements ValidatorConstraintInterface {
|
|
validate(value: unknown): boolean {
|
|
if (typeof value !== 'string') return false;
|
|
let parsed: URL;
|
|
try {
|
|
parsed = new URL(value);
|
|
} catch {
|
|
return false;
|
|
}
|
|
return (
|
|
parsed.protocol === 'https:' &&
|
|
parsed.hostname !== '' &&
|
|
parsed.username === '' &&
|
|
parsed.password === ''
|
|
);
|
|
}
|
|
|
|
defaultMessage(): string {
|
|
return 'Nur https-Adressen ohne Zugangsdaten sind erlaubt.';
|
|
}
|
|
}
|
|
|
|
const trimString = ({ value }: { value: unknown }) =>
|
|
typeof value === 'string' ? value.trim() : value;
|
|
|
|
/** DTO fuer das Anlegen eines eigenen Moduls. */
|
|
export class CreateCustomModuleDto {
|
|
@Transform(trimString)
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@MaxLength(100)
|
|
name!: string;
|
|
|
|
@Transform(trimString)
|
|
@IsString()
|
|
@MaxLength(2048)
|
|
@Validate(NurHttpsOhneZugangsdatenConstraint)
|
|
url!: string;
|
|
|
|
@IsIn([...MODULE_CATEGORIES])
|
|
category!: (typeof MODULE_CATEGORIES)[number];
|
|
}
|
|
|
|
/** Teil-Update: jedes gesetzte Feld wird genauso geprueft wie beim Anlegen. */
|
|
export class UpdateCustomModuleDto extends PartialType(CreateCustomModuleDto) {}
|