mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
IAM lets an employee hold several positions, but the vendored JwtGuard collapses employee.positions[] down to a single employee.position and drops the rest. Non-delegate secondary positions vanished entirely, so staff on two posts resolved to one post's permissions and every check on the other rejected them. FreightJwtGuard re-attaches the full list from the same session snapshot the parent guard already read, so nothing extra is fetched per request beyond a cached session lookup. employee.position is left untouched, keeping audit logging and delegation unaffected. collectPermissionKeys and collectPositionTypeKeys now union across every position, and /me returns them all. Verified against a real two-position user (djibouti-gl-director + djibouti-gl-chief) on the local dev database: /me positions 1 -> 2 /me permissionKeys 17 -> 28 GET /api/interchange-documents 403 -> 200 GET /api/trains 403 -> 200 11 permissions recovered, none lost. Six single-position users return byte-identical payloads before and after.
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { Body, Controller, Patch, Post, UseGuards } from "@nestjs/common";
|
|
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
|
|
import { CurrentUser } from "@tria-plc/api-common/modules/auth/decorators/current-user.decorator";
|
|
import { FreightJwtGuard } from "../../common/freight-jwt.guard";
|
|
import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type";
|
|
|
|
import { AccountService } from "./account.service";
|
|
import {
|
|
SendContactOtpDto,
|
|
UpdateAccountNameDto,
|
|
UpdateContactDto,
|
|
} from "./dto/account.dto";
|
|
|
|
/**
|
|
* The caller's own account record. Everything here is scoped to the JWT's user
|
|
* id — there is no `:id` parameter to tamper with, so these routes need no
|
|
* permission key beyond being authenticated.
|
|
*/
|
|
@ApiTags("auth")
|
|
@Controller("me")
|
|
@ApiBearerAuth()
|
|
@UseGuards(FreightJwtGuard)
|
|
export class AccountController {
|
|
constructor(private readonly accountService: AccountService) {}
|
|
|
|
@Post("contact/otp")
|
|
@ApiOperation({
|
|
summary: "Send a verification code to a new email/phone before changing it",
|
|
description:
|
|
"The code goes to the NEW value supplied here, proving the caller controls " +
|
|
"it. Returns the target masked — an unverified caller never gets it back in full.",
|
|
})
|
|
sendContactOtp(
|
|
@CurrentUser() user: TCurrentUser,
|
|
@Body() dto: SendContactOtpDto,
|
|
): Promise<{ sentTo: string }> {
|
|
return this.accountService.sendContactOtp(user.id, dto);
|
|
}
|
|
|
|
@Patch("contact")
|
|
@ApiOperation({
|
|
summary: "Change the account's email or phone, gated by a verification code",
|
|
description:
|
|
"Verifies the code and writes the new value in one call, so the API never " +
|
|
"has to take a client's word that verification happened.",
|
|
})
|
|
updateContact(
|
|
@CurrentUser() user: TCurrentUser,
|
|
@Body() dto: UpdateContactDto,
|
|
): Promise<{ success: true; value: string }> {
|
|
return this.accountService.updateContact(user.id, dto);
|
|
}
|
|
|
|
@Patch("name")
|
|
@ApiOperation({ summary: "Change the account's display name" })
|
|
updateName(
|
|
@CurrentUser() user: TCurrentUser,
|
|
@Body() dto: UpdateAccountNameDto,
|
|
): Promise<{ success: true }> {
|
|
return this.accountService.updateName(user.id, dto);
|
|
}
|
|
}
|