fix(auth): keep secondary positions in permission checks

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.
This commit is contained in:
Nathnael
2026-08-25 12:00:43 +00:00
parent 0a85dbe60d
commit 01ea05f013
12 changed files with 265 additions and 67 deletions

View File

@@ -1,5 +1,5 @@
import { applyDecorators, UseGuards } from '@nestjs/common'; import { applyDecorators, UseGuards } from '@nestjs/common';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from './freight-jwt.guard';
import { import {
FreightPermissionGuard, FreightPermissionGuard,
@@ -11,7 +11,7 @@ import { FREIGHT_PERMS } from '../seed/freight-permissions.registry';
export const BookingStaff = (permission: string | string[]) => export const BookingStaff = (permission: string | string[]) =>
applyDecorators( applyDecorators(
UseGuards( UseGuards(
JwtGuard, FreightJwtGuard,
FreightPermissionGuard( FreightPermissionGuard(
Array.isArray(permission) ? permission : [permission], Array.isArray(permission) ? permission : [permission],
), ),
@@ -26,11 +26,11 @@ export const BookingStaff = (permission: string | string[]) =>
* BookingStaff(<view key>) or MixedAudience(); kept for routes not yet swept. * BookingStaff(<view key>) or MixedAudience(); kept for routes not yet swept.
*/ */
export const StaffReference = () => export const StaffReference = () =>
applyDecorators(UseGuards(JwtGuard, FreightPermissionGuard([]))); applyDecorators(UseGuards(FreightJwtGuard, FreightPermissionGuard([])));
/** Portal routes: customer accounts only; ownership scoping stays in services. */ /** Portal routes: customer accounts only; ownership scoping stays in services. */
export const PortalCustomer = () => export const PortalCustomer = () =>
applyDecorators(UseGuards(JwtGuard, PortalCustomerGuard)); applyDecorators(UseGuards(FreightJwtGuard, PortalCustomerGuard));
/** /**
* Routes both audiences call (sign, shared document reads, handover): staff * Routes both audiences call (sign, shared document reads, handover): staff
@@ -40,7 +40,7 @@ export const PortalCustomer = () =>
export const MixedAudience = (permission: string | string[]) => export const MixedAudience = (permission: string | string[]) =>
applyDecorators( applyDecorators(
UseGuards( UseGuards(
JwtGuard, FreightJwtGuard,
MixedAudienceGuard( MixedAudienceGuard(
Array.isArray(permission) ? permission : [permission], Array.isArray(permission) ? permission : [permission],
), ),

View File

@@ -0,0 +1,101 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { InjectDataSource } from '@nestjs/typeorm';
import { JwtGuard as IamJwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { DataSource } from 'typeorm';
/** One position as the login snapshot stores it (`iam.sessions.userInfo`). */
type SnapshotPosition = { id?: string; [key: string]: unknown };
type SessionUserInfo = {
employee?: { id?: string; positions?: SnapshotPosition[] }[];
};
/**
* Like the IAM JwtGuard, but keeps the caller's SECONDARY positions.
*
* IAM models an employee as holding many positions, and the login snapshot in
* `iam.sessions.userInfo` carries all of them. `JwtGuard.parseToken` then
* collapses that to a single `employee.position` — whichever the request
* headers select, else `positions[0]` — and drops the rest. Non-delegate
* secondary positions vanish entirely, so staff holding two posts resolve to
* only one post's permissions and every check on the other one rejects them.
*
* This re-attaches the full list as `employee.positions`. `employee.position`
* is left exactly as the parent set it, so everything reading the single
* position today (audit log, delegation deadline) is unaffected; only the
* permission utils, which prefer the array, see the difference.
*/
@Injectable()
export class FreightJwtGuard extends IamJwtGuard implements CanActivate {
// ponytail: unbounded-until-TTL map, cleared wholesale when it gets big.
// Sessions are few and the value is small; swap for an LRU if that changes.
private static readonly CACHE_TTL_MS = 30_000;
private static readonly CACHE_MAX_ENTRIES = 5_000;
private readonly cache = new Map<
string,
{ positions: SnapshotPosition[]; expiresAt: number }
>();
constructor(
reflector: Reflector,
@InjectDataSource() private readonly ds: DataSource,
) {
super(reflector, ds);
}
async canActivate(context: ExecutionContext): Promise<boolean> {
if (!(await super.canActivate(context))) return false;
const user = context.switchToHttp().getRequest().user as
| TCurrentUser
| undefined;
const employee = user?.employee;
if (!employee || !user?.sessionId) return true;
const positions = await this.positionsForSession(
user.sessionId,
employee.id,
);
// Never blank out what the parent resolved: an unreadable session or a
// snapshot without positions must degrade to the single-position
// behaviour, not to no positions at all.
if (positions.length) {
(employee as { positions?: SnapshotPosition[] }).positions = positions;
}
return true;
}
/** Every position the login snapshot holds for this employee. */
private async positionsForSession(
sessionId: string,
employeeId: string | undefined,
): Promise<SnapshotPosition[]> {
const now = Date.now();
const hit = this.cache.get(sessionId);
if (hit && hit.expiresAt > now) return hit.positions;
let positions: SnapshotPosition[] = [];
try {
const rows: { userInfo: SessionUserInfo | null }[] = await this.ds.query(
`SELECT "userInfo" FROM iam.sessions WHERE id = $1`,
[sessionId],
);
const employees = rows[0]?.userInfo?.employee ?? [];
const match =
employees.find((e) => e?.id && e.id === employeeId) ?? employees[0];
positions = match?.positions ?? [];
} catch {
return []; // iam unreachable — caller keeps the parent's single position
}
if (this.cache.size >= FreightJwtGuard.CACHE_MAX_ENTRIES)
this.cache.clear();
this.cache.set(sessionId, {
positions,
expiresAt: now + FreightJwtGuard.CACHE_TTL_MS,
});
return positions;
}
}

View File

@@ -2,6 +2,7 @@ import {
assertCanApproveContractStep, assertCanApproveContractStep,
canEditContractStep, canEditContractStep,
collectPermissionKeys, collectPermissionKeys,
collectPositionTypeKeys,
hasFreightPermission, hasFreightPermission,
setPositionTypePermissionResolver, setPositionTypePermissionResolver,
} from './freight-permission.util'; } from './freight-permission.util';
@@ -121,3 +122,66 @@ describe('collectPermissionKeys — position-type grants', () => {
expect(hasFreightPermission(direct, CLEARANCE)).toBe(true); expect(hasFreightPermission(direct, CLEARANCE)).toBe(true);
}); });
}); });
/**
* IAM lets an employee hold several positions, but the vendored `JwtGuard`
* collapses `employee.positions[]` down to a single `employee.position` and
* drops the rest — so staff on two posts resolved to one post's permissions
* and every check on the other rejected them. `FreightJwtGuard` restores the
* full list as `employee.positions`; these cover the union that depends on it.
*/
describe('multiple positions', () => {
// Shaped like the real two-post employee: GL chief AND GL director.
const twoPost = {
employee: {
// What the vendored guard leaves behind — one of the two, arbitrarily.
position: {
positionType: { key: 'djibouti-gl-chief' },
permissions: [{ key: FREIGHT_PERMS.contracts.view }],
},
// What FreightJwtGuard puts back.
positions: [
{
positionType: { key: 'djibouti-gl-chief' },
permissions: [{ key: FREIGHT_PERMS.contracts.view }],
},
{
positionType: { key: 'djibouti-gl-director' },
permissions: [{ key: FREIGHT_PERMS.bookings.view }],
},
],
},
};
it('unions permissions across every position', () => {
const keys = collectPermissionKeys(twoPost);
expect(keys).toContain(FREIGHT_PERMS.contracts.view);
expect(keys).toContain(FREIGHT_PERMS.bookings.view);
});
it('grants the secondary positions permission, not just the first', () => {
expect(hasFreightPermission(twoPost, FREIGHT_PERMS.bookings.view)).toBe(true);
});
it('answers to both position types', () => {
expect(collectPositionTypeKeys(twoPost)).toEqual(
expect.arrayContaining(['djibouti-gl-chief', 'djibouti-gl-director']),
);
});
it('does not double-count the position the guard also left singular', () => {
const keys = collectPermissionKeys(twoPost);
expect(keys.filter((k) => k === FREIGHT_PERMS.contracts.view)).toHaveLength(1);
});
it('still resolves the single position when the array is absent', () => {
// A request that skipped FreightJwtGuard must degrade to the old behaviour,
// not to no permissions at all.
const onePost = {
employee: {
position: { permissions: [{ key: FREIGHT_PERMS.contracts.view }] },
},
};
expect(hasFreightPermission(onePost, FREIGHT_PERMS.contracts.view)).toBe(true);
});
});

View File

@@ -17,6 +17,15 @@ type MeLikeUser = {
permissions?: PermissionLike[]; permissions?: PermissionLike[];
positionType?: PositionTypeLike | null; positionType?: PositionTypeLike | null;
}; };
/**
* Every position the employee holds, restored by `FreightJwtGuard`
* from the login snapshot. The IAM guard only ever sets the singular
* `position` above; without this, a second post's grants are invisible.
*/
positions?: {
permissions?: PermissionLike[];
positionType?: PositionTypeLike | null;
}[];
delegatedPositions?: { permissions?: PermissionLike[] }[]; delegatedPositions?: { permissions?: PermissionLike[] }[];
} }
| { | {
@@ -98,10 +107,15 @@ export function collectPermissionKeys(user: MeLikeUser | null | undefined): stri
return [...keys]; return [...keys];
} }
for (const p of employee.position?.permissions ?? []) { // `position` is whichever single post the IAM guard selected; `positions` is
if (p.key) keys.add(p.key); // the full set FreightJwtGuard restores. Walk both — the array is absent on
// a session the guard could not re-read, and the two overlap harmlessly.
for (const pos of [employee.position, ...(employee.positions ?? [])]) {
for (const p of pos?.permissions ?? []) {
if (p.key) keys.add(p.key);
}
addTypePermissions(pos?.positionType);
} }
addTypePermissions(employee.position?.positionType);
for (const delegated of employee.delegatedPositions ?? []) { for (const delegated of employee.delegatedPositions ?? []) {
for (const p of delegated.permissions ?? []) { for (const p of delegated.permissions ?? []) {
if (p.key) keys.add(p.key); if (p.key) keys.add(p.key);
@@ -158,8 +172,10 @@ export function collectPositionTypeKeys(
return [...keys]; return [...keys];
} }
if (employee.position?.positionType?.key) { // Both shapes, same reason as collectPermissionKeys: an employee holding two
keys.add(employee.position.positionType.key); // posts answers to both their position types.
for (const pos of [employee.position, ...(employee.positions ?? [])]) {
if (pos?.positionType?.key) keys.add(pos.positionType.key);
} }
return [...keys]; return [...keys];
} }

View File

@@ -1,5 +1,5 @@
import { applyDecorators, UseGuards } from '@nestjs/common'; import { applyDecorators, UseGuards } from '@nestjs/common';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from './freight-jwt.guard';
import { FreightPermissionGuard } from './freight-permission.guard'; import { FreightPermissionGuard } from './freight-permission.guard';
import { import {
@@ -10,7 +10,7 @@ import {
export const RuleEngineView = (slug: RuleEngineResourceSlug) => export const RuleEngineView = (slug: RuleEngineResourceSlug) =>
applyDecorators( applyDecorators(
UseGuards(JwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.view(slug)])), UseGuards(FreightJwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.view(slug)])),
); );
// Granular CRUD replaces the retired coarse RuleEngineManage. Each write // Granular CRUD replaces the retired coarse RuleEngineManage. Each write
@@ -18,17 +18,17 @@ export const RuleEngineView = (slug: RuleEngineResourceSlug) =>
// update on PATCH / reorder / move-order, delete on DELETE. // update on PATCH / reorder / move-order, delete on DELETE.
export const RuleEngineCreate = (slug: RuleEngineResourceSlug) => export const RuleEngineCreate = (slug: RuleEngineResourceSlug) =>
applyDecorators( applyDecorators(
UseGuards(JwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.create(slug)])), UseGuards(FreightJwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.create(slug)])),
); );
export const RuleEngineUpdate = (slug: RuleEngineResourceSlug) => export const RuleEngineUpdate = (slug: RuleEngineResourceSlug) =>
applyDecorators( applyDecorators(
UseGuards(JwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.update(slug)])), UseGuards(FreightJwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.update(slug)])),
); );
export const RuleEngineDelete = (slug: RuleEngineResourceSlug) => export const RuleEngineDelete = (slug: RuleEngineResourceSlug) =>
applyDecorators( applyDecorators(
UseGuards(JwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.delete(slug)])), UseGuards(FreightJwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.delete(slug)])),
); );
/** /**
@@ -38,5 +38,5 @@ export const RuleEngineDelete = (slug: RuleEngineResourceSlug) =>
*/ */
export const RuleEngineApprove = (slug: RuleEngineApprovableSlug) => export const RuleEngineApprove = (slug: RuleEngineApprovableSlug) =>
applyDecorators( applyDecorators(
UseGuards(JwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.approve(slug)])), UseGuards(FreightJwtGuard, FreightPermissionGuard([FREIGHT_PERMS.ruleEngine.approve(slug)])),
); );

View File

@@ -1,7 +1,7 @@
import { Body, Controller, Patch, Post, UseGuards } from "@nestjs/common"; import { Body, Controller, Patch, Post, UseGuards } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { CurrentUser } from "@tria-plc/api-common/modules/auth/decorators/current-user.decorator"; import { CurrentUser } from "@tria-plc/api-common/modules/auth/decorators/current-user.decorator";
import { JwtGuard } from "@tria-plc/api-common/modules/auth/services/jwt.guard"; import { FreightJwtGuard } from "../../common/freight-jwt.guard";
import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type"; import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type";
import { AccountService } from "./account.service"; import { AccountService } from "./account.service";
@@ -19,7 +19,7 @@ import {
@ApiTags("auth") @ApiTags("auth")
@Controller("me") @Controller("me")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
export class AccountController { export class AccountController {
constructor(private readonly accountService: AccountService) {} constructor(private readonly accountService: AccountService) {}

View File

@@ -1,7 +1,7 @@
import { Controller, Get, UseGuards } from '@nestjs/common'; import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CurrentUser } from '@tria-plc/api-common/modules/auth/decorators/current-user.decorator'; import { CurrentUser } from '@tria-plc/api-common/modules/auth/decorators/current-user.decorator';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from '../../common/freight-jwt.guard';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type'; import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { FreightMeService } from './freight-me.service'; import { FreightMeService } from './freight-me.service';
@@ -13,7 +13,7 @@ export class FreightMeController {
constructor(private readonly freightMeService: FreightMeService) {} constructor(private readonly freightMeService: FreightMeService) {}
@Get() @Get()
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
@ApiOperation({ @ApiOperation({
summary: 'Current user with flat permissionKeys for backoffice gating', summary: 'Current user with flat permissionKeys for backoffice gating',
}) })

View File

@@ -9,6 +9,9 @@ import {
} from '../../common/freight-permission.util'; } from '../../common/freight-permission.util';
import { PERMISSIONS_CATALOG } from '../../seed/freight-permissions.registry'; import { PERMISSIONS_CATALOG } from '../../seed/freight-permissions.registry';
/** One position as the session snapshot carries it. */
type TokenPosition = NonNullable<TCurrentUser['employee']>['position'];
@Injectable() @Injectable()
export class FreightMeService { export class FreightMeService {
constructor(@InjectDataSource() private readonly dataSource: DataSource) {} constructor(@InjectDataSource() private readonly dataSource: DataSource) {}
@@ -65,49 +68,63 @@ export class FreightMeService {
} }
async getEnrichedProfile(user: TCurrentUser) { async getEnrichedProfile(user: TCurrentUser) {
const positionId = user.employee?.position?.id; const employeeRecord = user.employee as
const [positionType, positionTypePermissionKeys] = await Promise.all([ | (typeof user.employee & { positions?: TokenPosition[] })
this.lookupPositionType(positionId), | undefined;
this.lookupPositionTypePermissions(positionId),
]);
// Merge the type-level grants into the position's own permission list so // `FreightJwtGuard` restores every position the login snapshot holds; the
// BOTH consumers see them: `collectPermissionKeys` below, and the // stock IAM guard only ever leaves the single `position`. Fall back to it
// backoffice's `getPermissionKeys`, which walks this same nested array. // so a request that somehow skipped our guard still resolves one post
const positionPermissions = [ // rather than none.
...(user.employee?.position?.permissions ?? []), const rawPositions: TokenPosition[] = employeeRecord?.positions?.length
]; ? employeeRecord.positions
const seenPermissionKeys = new Set( : employeeRecord?.position
positionPermissions.map((p) => p?.key).filter(Boolean), ? [employeeRecord.position]
: [];
const enrichedPositions = await Promise.all(
rawPositions.map(async (position) => {
const [positionType, positionTypePermissionKeys] = await Promise.all([
this.lookupPositionType(position.id),
this.lookupPositionTypePermissions(position.id),
]);
// Merge the type-level grants into this position's own permission list
// so BOTH consumers see them: `collectPermissionKeys` below, and the
// backoffice's `getPermissionKeys`, which walks this nested array.
const permissions = [...(position.permissions ?? [])];
const seen = new Set(permissions.map((p) => p?.key).filter(Boolean));
for (const key of positionTypePermissionKeys) {
if (!seen.has(key)) {
seen.add(key);
permissions.push({ key } as (typeof permissions)[number]);
}
}
return {
positionTypePermissionKeys,
position: {
id: position.id,
key: position.key,
employeePositionId: position.employeePositionId,
name: position.name,
isDelegate: position.isDelegate,
parentPositionId: position.parentPositionId,
permissions,
positionType,
},
};
}),
); );
for (const key of positionTypePermissionKeys) {
if (!seenPermissionKeys.has(key)) {
seenPermissionKeys.add(key);
positionPermissions.push({ key } as (typeof positionPermissions)[number]);
}
}
const employee = user.employee const employee = employeeRecord
? [ ? [
{ {
id: user.employee.id, id: employeeRecord.id,
organizationId: user.employee.organizationId, organizationId: employeeRecord.organizationId,
unitId: user.employee.unitId, unitId: employeeRecord.unitId,
name: user.employee.name, name: employeeRecord.name,
positions: user.employee.position positions: enrichedPositions.map((p) => p.position),
? [
{
id: user.employee.position.id,
key: user.employee.position.key,
employeePositionId: user.employee.position.employeePositionId,
name: user.employee.position.name,
isDelegate: user.employee.position.isDelegate,
parentPositionId: user.employee.position.parentPositionId,
permissions: positionPermissions,
positionType,
},
]
: [],
}, },
] ]
: []; : [];
@@ -118,7 +135,7 @@ export class FreightMeService {
const permissionKeys = [ const permissionKeys = [
...new Set([ ...new Set([
...collectPermissionKeys(user), ...collectPermissionKeys(user),
...positionTypePermissionKeys, ...enrichedPositions.flatMap((p) => p.positionTypePermissionKeys),
]), ]),
]; ];

View File

@@ -1,7 +1,7 @@
import { Controller, Get, Post, UseGuards } from '@nestjs/common'; import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CurrentUser } from '@tria-plc/api-common/modules/auth/decorators/current-user.decorator'; import { CurrentUser } from '@tria-plc/api-common/modules/auth/decorators/current-user.decorator';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from '../../common/freight-jwt.guard';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type'; import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { ChatSync } from '../../common/booking-guards'; import { ChatSync } from '../../common/booking-guards';
@@ -18,7 +18,7 @@ export class ChatController {
) {} ) {}
@Get('sso') @Get('sso')
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
@ApiOperation({ summary: 'One-click sign-in link into EDR internal chat' }) @ApiOperation({ summary: 'One-click sign-in link into EDR internal chat' })
getSso(@CurrentUser() user: TCurrentUser) { getSso(@CurrentUser() user: TCurrentUser) {
return this.sso.getSsoUrl(user); return this.sso.getSsoUrl(user);

View File

@@ -3,7 +3,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { InjectDataSource } from '@nestjs/typeorm'; import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { CurrentUser } from '@edr/api-common'; import { CurrentUser } from '@edr/api-common';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from '../../common/freight-jwt.guard';
import type { Response } from 'express'; import type { Response } from 'express';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type'; import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
@@ -57,7 +57,7 @@ const toCatalogEntry = (dataset: ExportDataset): ExportCatalogEntry => ({
@ApiTags('Exports') @ApiTags('Exports')
@ApiBearerAuth() @ApiBearerAuth()
@Controller('exports') @Controller('exports')
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
export class ExportsController { export class ExportsController {
constructor( constructor(
private readonly runner: ExportRunnerService, private readonly runner: ExportRunnerService,

View File

@@ -10,7 +10,7 @@ import {
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger"; import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { JwtGuard } from "@tria-plc/api-common/modules/auth/services/jwt.guard"; import { FreightJwtGuard } from "../../common/freight-jwt.guard";
import { import {
AuthUserPayload, AuthUserPayload,
@@ -21,7 +21,7 @@ import { NotificationInboxService } from "./notification-inbox.service";
@ApiTags("notifications") @ApiTags("notifications")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
@Controller("notifications") @Controller("notifications")
export class NotificationInboxController { export class NotificationInboxController {
constructor(private readonly service: NotificationInboxService) {} constructor(private readonly service: NotificationInboxService) {}

View File

@@ -17,7 +17,7 @@ import {
} from '@nestjs/swagger'; } from '@nestjs/swagger';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type'; import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator'; import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard'; import { FreightJwtGuard } from '../../common/freight-jwt.guard';
import { OptionalJwtGuard } from './optional-jwt.guard'; import { OptionalJwtGuard } from './optional-jwt.guard';
import { import {
CompleteVerificationResultDto, CompleteVerificationResultDto,
@@ -91,7 +91,7 @@ export class VerifaydaController {
} }
@Get('status') @Get('status')
@UseGuards(JwtGuard) @UseGuards(FreightJwtGuard)
@ApiBearerAuth('JWT-auth') @ApiBearerAuth('JWT-auth')
@ApiOperation({ @ApiOperation({
summary: "Get the current user's Fayda verification status", summary: "Get the current user's Fayda verification status",