proxy
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Param, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Header, Param, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { RequestUser } from '../common/request-user';
|
||||
@@ -12,6 +12,9 @@ export class AdminRegistrationsController {
|
||||
constructor(private readonly registrations: RegistrationService) {}
|
||||
|
||||
@Get()
|
||||
@Header('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate')
|
||||
@Header('Pragma', 'no-cache')
|
||||
@Header('Expires', '0')
|
||||
list() {
|
||||
return this.registrations.list();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Ber, BerWriter, Client } from 'ldapts';
|
||||
|
||||
interface LldapUserInput {
|
||||
username: string;
|
||||
@@ -70,6 +71,8 @@ export interface LldapAccountUser {
|
||||
|
||||
@Injectable()
|
||||
export class LldapService {
|
||||
private static readonly passwordModifyOid = '1.3.6.1.4.1.4203.1.11.1';
|
||||
|
||||
private cachedHeaders?: { expiresAt: number; headers: Record<string, string> };
|
||||
|
||||
constructor(private readonly config: ConfigService) {}
|
||||
@@ -84,11 +87,17 @@ export class LldapService {
|
||||
id: input.username,
|
||||
email: input.email,
|
||||
displayName: input.displayName,
|
||||
password: input.password,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await this.setPassword(input.username, input.password);
|
||||
} catch (error) {
|
||||
await this.deleteUser(input.username).catch(() => undefined);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const defaultGroup = this.config.get<string>('LLDAP_DEFAULT_GROUP');
|
||||
if (defaultGroup) {
|
||||
await this.addUserToGroup(input.username, defaultGroup);
|
||||
@@ -96,12 +105,18 @@ export class LldapService {
|
||||
}
|
||||
|
||||
async setPassword(username: string, password: string): Promise<void> {
|
||||
await this.graphql(
|
||||
`mutation SetPassword($userId: String!, $password: String!) {
|
||||
setPassword(userId: $userId, password: $password)
|
||||
}`,
|
||||
{ userId: username, password },
|
||||
);
|
||||
const client = new Client({ url: this.config.getOrThrow<string>('LLDAP_LDAP_URL') });
|
||||
try {
|
||||
await client.bind(this.adminDn(), this.config.getOrThrow<string>('LLDAP_ADMIN_PASSWORD'));
|
||||
await client.exop(
|
||||
LldapService.passwordModifyOid,
|
||||
this.passwordModifyRequestValue(this.userDn(username), password),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException(`LLDAP password change failed: ${this.errorMessage(error)}`);
|
||||
} finally {
|
||||
await client.unbind().catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(username: string, input: LldapUserUpdateInput): Promise<void> {
|
||||
@@ -438,4 +453,30 @@ export class LldapService {
|
||||
this.cachedHeaders = { headers, expiresAt: Date.now() + 5 * 60_000 };
|
||||
return headers;
|
||||
}
|
||||
|
||||
private passwordModifyRequestValue(userDn: string, newPassword: string): Buffer {
|
||||
const writer = new BerWriter();
|
||||
writer.startSequence(Ber.Sequence | Ber.Constructor);
|
||||
writer.writeString(userDn, 0x80);
|
||||
writer.writeString(newPassword, 0x82);
|
||||
writer.endSequence();
|
||||
return writer.buffer;
|
||||
}
|
||||
|
||||
private adminDn(): string {
|
||||
return this.userDn(this.config.getOrThrow<string>('LLDAP_ADMIN_USERNAME'));
|
||||
}
|
||||
|
||||
private userDn(username: string): string {
|
||||
const baseDn = this.config.getOrThrow<string>('LLDAP_BASE_DN');
|
||||
return `uid=${this.escapeDn(username)},ou=people,${baseDn}`;
|
||||
}
|
||||
|
||||
private escapeDn(value: string): string {
|
||||
return value.replace(/[\\,+"<>;=]/g, (char) => `\\${char}`);
|
||||
}
|
||||
|
||||
private errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : 'unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { IsEmail, IsString, Length, Matches } from 'class-validator';
|
||||
import { IsEmail, IsString, Length } from 'class-validator';
|
||||
|
||||
export class RegisterDto {
|
||||
@IsString()
|
||||
@Length(3, 64)
|
||||
@Matches(/^[a-zA-Z0-9._-]+$/)
|
||||
username!: string;
|
||||
|
||||
@IsEmail()
|
||||
email!: string;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { In, IsNull, Repository } from 'typeorm';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { assertPasswordPolicy } from '../common/password-policy';
|
||||
import { decryptSecret, encryptSecret, hashToken, randomToken } from '../common/token.util';
|
||||
@@ -26,23 +26,24 @@ export class RegistrationService {
|
||||
|
||||
async register(dto: RegisterDto, ipAddress?: string, userAgent?: string) {
|
||||
assertPasswordPolicy(dto.password);
|
||||
const email = dto.email.toLowerCase();
|
||||
|
||||
const existingLdapUser = await this.lldap.findUserByUsername(dto.username).catch(() => null);
|
||||
const existingLdapUser = await this.lldap.findUserByUsername(email).catch(() => null);
|
||||
if (existingLdapUser) {
|
||||
throw new ConflictException('Der Benutzername ist bereits vergeben.');
|
||||
throw new ConflictException('Diese E-Mail-Adresse ist bereits registriert.');
|
||||
}
|
||||
|
||||
const pending = await this.registrations.findOne({
|
||||
where: { username: dto.username, status: 'pending_email' },
|
||||
where: { username: email, status: 'pending_email' },
|
||||
});
|
||||
if (pending) {
|
||||
throw new ConflictException('Fuer diesen Benutzernamen existiert bereits eine offene Registrierung.');
|
||||
throw new ConflictException('Fuer diese E-Mail-Adresse existiert bereits eine offene Registrierung.');
|
||||
}
|
||||
|
||||
const registration = await this.registrations.save(
|
||||
this.registrations.create({
|
||||
username: dto.username,
|
||||
email: dto.email.toLowerCase(),
|
||||
username: email,
|
||||
email,
|
||||
displayName: dto.displayName,
|
||||
encryptedPassword: encryptSecret(dto.password, this.tokenSecret),
|
||||
status: 'pending_email',
|
||||
@@ -99,7 +100,10 @@ export class RegistrationService {
|
||||
}
|
||||
|
||||
async list() {
|
||||
return this.registrations.find({ order: { createdAt: 'DESC' } });
|
||||
return this.registrations.find({
|
||||
where: { status: In(['pending_email', 'pending_approval']) },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async approve(id: string, reviewer: string, ipAddress?: string, userAgent?: string) {
|
||||
|
||||
Reference in New Issue
Block a user