feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
- Profilseite mit MFA-Einrichtung, Passwort/E-Mail/Name aendern, Konto deaktivieren - Bearbeiten-Buttons fuer Bienenstaende/Voelker/Koeniginnen/Aufgaben-Datensaetze - Restliche englische Enum-Werte durch deutsche Labels ersetzt (SelectValue-Fix) - Sidebar-Eintraege fuer Ernten/Schleuderungen ergaenzt - Automatisierungen von 3 auf 6 Regeln erweitert (Schwarmstimmung, kritischer Gesundheitsstatus, Lagerbestand) - Responsive Grid-Layout fuer Volk-/Koeniginnen-/Honigchargen-/Bienenstand-Detailseiten - Etikettenvorlagen mit Platzhaltern inkl. QR-Code (verweist auf Herkunftsseite) und PDF-Druck - Entra-ID-SSO (Authorization Code + PKCE, kein Auto-Provisioning, Secrets nur ueber Environment-Variablen)
This commit is contained in:
1 parent
0a52b29c57
commit
8432313fa9
83 files changed
+2684
-289
No files matched your search
@@ -7,7 +7,6 @@ import {
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
@@ -19,49 +18,23 @@ import { LoginDto } from './dto/login.dto';
|
||||
import { AccessTokenDto } from './dto/access-token.dto';
|
||||
import { MfaVerifyDto } from './dto/mfa-verify.dto';
|
||||
import { AuthService, CurrentUserProfile } from './auth.service';
|
||||
import { parseDurationToMs } from './util/duration';
|
||||
import { SENSITIVE_ACTION_THROTTLE } from './util/throttle';
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
extractRefreshToken,
|
||||
setRefreshTokenCookie,
|
||||
} from './util/refresh-token-cookie';
|
||||
|
||||
interface MfaRequiredResponse {
|
||||
mfaRequired: true;
|
||||
mfaToken: string;
|
||||
}
|
||||
|
||||
const REFRESH_TOKEN_COOKIE = 'refreshToken';
|
||||
const REFRESH_TOKEN_COOKIE_PATH = '/api/v1/auth';
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
private setRefreshTokenCookie(res: Response, refreshToken: string): void {
|
||||
res.cookie(REFRESH_TOKEN_COOKIE, refreshToken, {
|
||||
httpOnly: true,
|
||||
// Explicit opt-out for local HTTP dev only; every real deployment must
|
||||
// set COOKIE_SECURE=true so the refresh-token cookie is never sent
|
||||
// over plaintext HTTP.
|
||||
secure: process.env.COOKIE_SECURE !== 'false',
|
||||
sameSite: 'lax',
|
||||
path: REFRESH_TOKEN_COOKIE_PATH,
|
||||
maxAge: parseDurationToMs(process.env.JWT_REFRESH_EXPIRES_IN ?? '7d'),
|
||||
});
|
||||
}
|
||||
|
||||
private clearRefreshTokenCookie(res: Response): void {
|
||||
res.clearCookie(REFRESH_TOKEN_COOKIE, { path: REFRESH_TOKEN_COOKIE_PATH });
|
||||
}
|
||||
|
||||
private extractRefreshToken(req: Request): string {
|
||||
const refreshToken = (req.cookies as Record<string, string> | undefined)?.[
|
||||
REFRESH_TOKEN_COOKIE
|
||||
];
|
||||
if (!refreshToken) {
|
||||
throw new UnauthorizedException('Missing refresh token');
|
||||
}
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Throttle(SENSITIVE_ACTION_THROTTLE)
|
||||
@Post('login')
|
||||
@@ -74,7 +47,7 @@ export class AuthController {
|
||||
if (result.mfaRequired) {
|
||||
return { mfaRequired: true, mfaToken: result.mfaToken };
|
||||
}
|
||||
this.setRefreshTokenCookie(res, result.refreshToken);
|
||||
setRefreshTokenCookie(res, result.refreshToken);
|
||||
return { accessToken: result.accessToken };
|
||||
}
|
||||
|
||||
@@ -88,7 +61,7 @@ export class AuthController {
|
||||
): Promise<AccessTokenDto> {
|
||||
const { accessToken, refreshToken } =
|
||||
await this.authService.verifyMfaAndLogin(dto.mfaToken, dto.code);
|
||||
this.setRefreshTokenCookie(res, refreshToken);
|
||||
setRefreshTokenCookie(res, refreshToken);
|
||||
return { accessToken };
|
||||
}
|
||||
|
||||
@@ -101,9 +74,9 @@ export class AuthController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<AccessTokenDto> {
|
||||
const { accessToken, refreshToken } = await this.authService.refresh(
|
||||
this.extractRefreshToken(req),
|
||||
extractRefreshToken(req),
|
||||
);
|
||||
this.setRefreshTokenCookie(res, refreshToken);
|
||||
setRefreshTokenCookie(res, refreshToken);
|
||||
return { accessToken };
|
||||
}
|
||||
|
||||
@@ -114,8 +87,8 @@ export class AuthController {
|
||||
@Req() req: Request,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<void> {
|
||||
await this.authService.logout(this.extractRefreshToken(req));
|
||||
this.clearRefreshTokenCookie(res);
|
||||
await this.authService.logout(extractRefreshToken(req));
|
||||
clearRefreshTokenCookie(res);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
|
||||
Reference in new issue
Block a user