mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 22:18:12 +00:00
feat: ( passenger ) wire IAM global guard, org seeder, and backoffice auth
This commit is contained in:
165
apps/edr-passenger-api/src/seed/edr-passenger-org.seeder.ts
Normal file
165
apps/edr-passenger-api/src/seed/edr-passenger-org.seeder.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
Application,
|
||||
Organization,
|
||||
OrganizationConfiguration,
|
||||
Permission,
|
||||
Role,
|
||||
RolePermission,
|
||||
} from '@tria-plc/iamapi-common';
|
||||
import { DataSource, EntityManager, In } from 'typeorm';
|
||||
import { ERoleKey } from '@tria-plc/api-common/utils/enums/seed.enum';
|
||||
import {
|
||||
PASSENGER_PERMISSIONS,
|
||||
PASSENGER_PERMISSION_KEYS,
|
||||
} from './passenger-permissions.registry';
|
||||
import { EDR_PASSENGER_APPLICATION, EDR_PASSENGER_ROLES, type PassengerSeedRole } from './edr-passenger.seed';
|
||||
|
||||
const EDR_ORG_KEY = 'edr';
|
||||
const EDR_ORG_NAME = { am: 'EDR', en: 'EDR' };
|
||||
const SEED_FLAG = 'SEED_EDR_PASSENGER_ORG';
|
||||
|
||||
type SeedOrganization = { id: string; key: string };
|
||||
|
||||
@Injectable()
|
||||
export class EdrPassengerOrgSeeder {
|
||||
private readonly logger = new Logger(EdrPassengerOrgSeeder.name);
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async run() {
|
||||
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
|
||||
this.logger.log(`Skipping passenger org seed because ${SEED_FLAG} is not enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await this.ensureApplication(manager);
|
||||
await this.ensurePermissions(manager);
|
||||
const organization = await this.ensureOrganization(manager);
|
||||
await this.ensureOrganizationConfiguration(manager, organization.id);
|
||||
await this.ensureRoles(manager, EDR_PASSENGER_ROLES);
|
||||
await this.ensureRolePermissions(manager, EDR_PASSENGER_ROLES);
|
||||
await this.ensureSuperAdminPermissions(manager);
|
||||
});
|
||||
|
||||
this.logger.log(`Ensured EDR passenger organization seed for '${EDR_ORG_KEY}'`);
|
||||
}
|
||||
|
||||
private async ensureApplication(manager: EntityManager) {
|
||||
await manager.getRepository(Application).upsert(
|
||||
{
|
||||
id: EDR_PASSENGER_APPLICATION.id,
|
||||
key: EDR_PASSENGER_APPLICATION.key,
|
||||
name: EDR_PASSENGER_APPLICATION.name,
|
||||
},
|
||||
{ conflictPaths: { key: true } },
|
||||
);
|
||||
this.logger.log(`Ensured application '${EDR_PASSENGER_APPLICATION.key}'`);
|
||||
}
|
||||
|
||||
private async ensurePermissions(manager: EntityManager) {
|
||||
await manager.getRepository(Permission).upsert(
|
||||
PASSENGER_PERMISSIONS.map((p) => ({
|
||||
id: p.id,
|
||||
key: p.key,
|
||||
name: p.name,
|
||||
applicationId: EDR_PASSENGER_APPLICATION.id,
|
||||
})),
|
||||
{ conflictPaths: { key: true } },
|
||||
);
|
||||
this.logger.log(`Ensured ${PASSENGER_PERMISSIONS.length} passenger permissions`);
|
||||
}
|
||||
|
||||
private async ensureOrganization(manager: EntityManager): Promise<SeedOrganization> {
|
||||
const repo = manager.getRepository(Organization);
|
||||
let org = await repo.findOne({ where: { key: EDR_ORG_KEY }, select: { id: true, key: true } });
|
||||
|
||||
if (!org) {
|
||||
const result = await repo.insert({
|
||||
key: EDR_ORG_KEY,
|
||||
name: EDR_ORG_NAME,
|
||||
isGovernmentOrganization: true,
|
||||
});
|
||||
this.logger.log(`Seeded EDR passenger organization '${EDR_ORG_KEY}'`);
|
||||
return { id: result.identifiers[0]?.id as string, key: EDR_ORG_KEY };
|
||||
}
|
||||
|
||||
this.logger.log(`Ensured EDR passenger organization '${EDR_ORG_KEY}'`);
|
||||
return { id: org.id as string, key: EDR_ORG_KEY };
|
||||
}
|
||||
|
||||
private async ensureOrganizationConfiguration(manager: EntityManager, organizationId: string) {
|
||||
await manager.getRepository(OrganizationConfiguration).upsert(
|
||||
{ organizationId, canCreateBranchByItself: true, canStartReceivingRecord: true },
|
||||
{ conflictPaths: { organizationId: true } },
|
||||
);
|
||||
this.logger.log(`Ensured organization configuration for '${EDR_ORG_KEY}'`);
|
||||
}
|
||||
|
||||
private async ensureRoles(manager: EntityManager, seedRoles: PassengerSeedRole[]) {
|
||||
await manager.getRepository(Role).upsert(
|
||||
seedRoles.map(({ key, name }) => ({ key, name })),
|
||||
{ conflictPaths: { key: true } },
|
||||
);
|
||||
this.logger.log(`Ensured passenger roles: ${seedRoles.map((r) => r.key).join(', ')}`);
|
||||
}
|
||||
|
||||
private async ensureRolePermissions(manager: EntityManager, seedRoles: PassengerSeedRole[]) {
|
||||
const allPermissionKeys = [...new Set(seedRoles.flatMap((r) => r.permissionKeys))];
|
||||
if (!allPermissionKeys.length) return;
|
||||
|
||||
const roles = await manager.getRepository(Role).find({
|
||||
where: { key: In(seedRoles.map((r) => r.key)) },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
const permissions = await manager.getRepository(Permission).find({
|
||||
where: { key: In(allPermissionKeys) },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
const roleByKey = new Map(roles.map((r) => [r.key, r]));
|
||||
const permByKey = new Map(permissions.map((p) => [p.key, p]));
|
||||
|
||||
const links = seedRoles.flatMap((seedRole) => {
|
||||
const role = roleByKey.get(seedRole.key);
|
||||
if (!role) throw new Error(`missing_role:${seedRole.key}`);
|
||||
|
||||
return seedRole.permissionKeys.map((key) => {
|
||||
const perm = permByKey.get(key);
|
||||
if (!perm) throw new Error(`missing_permission:${key}`);
|
||||
return { roleId: role.id, permissionId: perm.id };
|
||||
});
|
||||
});
|
||||
|
||||
await manager.getRepository(RolePermission).upsert(links, {
|
||||
conflictPaths: { roleId: true, permissionId: true },
|
||||
});
|
||||
this.logger.log(`Ensured ${links.length} passenger role-permission links`);
|
||||
}
|
||||
|
||||
private async ensureSuperAdminPermissions(manager: EntityManager) {
|
||||
const role = await manager.getRepository(Role).findOne({
|
||||
where: { key: ERoleKey.SUPER_ADMIN },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
this.logger.warn(`Role ${ERoleKey.SUPER_ADMIN} not found; skipping super_admin permission links`);
|
||||
return;
|
||||
}
|
||||
|
||||
const permissions = await manager.getRepository(Permission).find({
|
||||
where: { key: In(PASSENGER_PERMISSION_KEYS) },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!permissions.length) return;
|
||||
|
||||
await manager.getRepository(RolePermission).upsert(
|
||||
permissions.map((p) => ({ roleId: role.id, permissionId: p.id })),
|
||||
{ conflictPaths: { roleId: true, permissionId: true } },
|
||||
);
|
||||
this.logger.log(`Ensured ${permissions.length} passenger permissions on super_admin`);
|
||||
}
|
||||
}
|
||||
47
apps/edr-passenger-api/src/seed/edr-passenger.seed.ts
Normal file
47
apps/edr-passenger-api/src/seed/edr-passenger.seed.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
PASSENGER_PERMISSIONS,
|
||||
PASSENGER_PERMISSION_KEYS,
|
||||
ROLE_PERMISSION_PRESETS,
|
||||
} from './passenger-permissions.registry';
|
||||
|
||||
export type PassengerSeedRole = {
|
||||
key: string;
|
||||
name: { en: string };
|
||||
permissionKeys: string[];
|
||||
};
|
||||
|
||||
export const EDR_PASSENGER_APPLICATION = {
|
||||
id: 'd2000001-0001-4000-8000-000000000001',
|
||||
key: 'edr_passenger_app',
|
||||
name: {
|
||||
am: 'EDR Passenger App',
|
||||
en: 'EDR Passenger App',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const EDR_PASSENGER_PERMISSIONS = [...PASSENGER_PERMISSIONS];
|
||||
|
||||
export { PASSENGER_PERMISSION_KEYS } from './passenger-permissions.registry';
|
||||
|
||||
export const EDR_PASSENGER_ROLES: PassengerSeedRole[] = [
|
||||
{
|
||||
key: 'edr_passenger_backoffice_admin',
|
||||
name: { en: 'EDR Passenger Backoffice Admin' },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.backofficeAdmin],
|
||||
},
|
||||
{
|
||||
key: 'edr_passenger_backoffice_staff',
|
||||
name: { en: 'EDR Passenger Backoffice Staff' },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.backofficeStaff],
|
||||
},
|
||||
{
|
||||
key: 'edr_passenger_agent',
|
||||
name: { en: 'EDR Passenger Agent' },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.agent],
|
||||
},
|
||||
{
|
||||
key: 'edr_passenger_finance',
|
||||
name: { en: 'EDR Passenger Finance' },
|
||||
permissionKeys: [...ROLE_PERMISSION_PRESETS.finance],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,121 @@
|
||||
const APP_KEY = 'edr_passenger_app';
|
||||
|
||||
export type PassengerPermissionSeed = {
|
||||
id: string;
|
||||
key: string;
|
||||
name: { am: string; en: string };
|
||||
applicationKey: string;
|
||||
};
|
||||
|
||||
const perm = (id: string, key: string, en: string): PassengerPermissionSeed => ({
|
||||
id,
|
||||
key,
|
||||
name: { am: en, en },
|
||||
applicationKey: APP_KEY,
|
||||
});
|
||||
|
||||
export const PASSENGER_PERMISSIONS: PassengerPermissionSeed[] = [
|
||||
perm('c1000001-0001-4000-8000-000000000001', 'edr_passenger_app:bookings:view', 'View bookings'),
|
||||
perm('c1000001-0001-4000-8000-000000000002', 'edr_passenger_app:bookings:manage', 'Manage bookings'),
|
||||
perm('c1000001-0001-4000-8000-000000000003', 'edr_passenger_app:bookings:cancel', 'Cancel bookings'),
|
||||
perm('c1000001-0001-4000-8000-000000000004', 'edr_passenger_app:passengers:view', 'View passengers'),
|
||||
perm('c1000001-0001-4000-8000-000000000005', 'edr_passenger_app:passengers:manage', 'Manage passengers'),
|
||||
perm('c1000001-0001-4000-8000-000000000006', 'edr_passenger_app:tickets:view', 'View tickets'),
|
||||
perm('c1000001-0001-4000-8000-000000000007', 'edr_passenger_app:tickets:manage', 'Manage tickets'),
|
||||
perm('c1000001-0001-4000-8000-000000000008', 'edr_passenger_app:payments:view_all', 'View all payments'),
|
||||
perm('c1000001-0001-4000-8000-000000000009', 'edr_passenger_app:payments:refund', 'Refund payments'),
|
||||
perm('c1000001-0001-4000-8000-00000000000a', 'edr_passenger_app:payments:manage_methods', 'Manage payment methods'),
|
||||
perm('c1000001-0001-4000-8000-00000000000b', 'edr_passenger_app:reports:view', 'View reports'),
|
||||
perm('c1000001-0001-4000-8000-00000000000c', 'edr_passenger_app:fraud:view', 'View fraud alerts'),
|
||||
perm('c1000001-0001-4000-8000-00000000000d', 'edr_passenger_app:fraud:manage', 'Manage fraud rules'),
|
||||
perm('c1000001-0001-4000-8000-00000000000e', 'edr_passenger_app:audit:view', 'View audit logs'),
|
||||
perm('c1000001-0001-4000-8000-00000000000f', 'edr_passenger_app:agents:view', 'View agents'),
|
||||
perm('c1000001-0001-4000-8000-000000000010', 'edr_passenger_app:agents:manage', 'Manage agents'),
|
||||
perm('c1000001-0001-4000-8000-000000000011', 'edr_passenger_app:currencies:manage', 'Manage currencies'),
|
||||
perm('c1000001-0001-4000-8000-000000000012', 'edr_passenger_app:notifications:send', 'Send notifications'),
|
||||
perm('c1000001-0001-4000-8000-000000000013', 'edr_passenger_app:dashboard:view', 'View dashboard'),
|
||||
perm('c1000001-0001-4000-8000-000000000014', 'edr_passenger_app:admin', 'Full admin access'),
|
||||
];
|
||||
|
||||
export const PASSENGER_PERMISSION_KEYS = PASSENGER_PERMISSIONS.map((p) => p.key);
|
||||
|
||||
export const PASSENGER_PERMS = {
|
||||
bookings: {
|
||||
view: 'edr_passenger_app:bookings:view',
|
||||
manage: 'edr_passenger_app:bookings:manage',
|
||||
cancel: 'edr_passenger_app:bookings:cancel',
|
||||
},
|
||||
passengers: {
|
||||
view: 'edr_passenger_app:passengers:view',
|
||||
manage: 'edr_passenger_app:passengers:manage',
|
||||
},
|
||||
tickets: {
|
||||
view: 'edr_passenger_app:tickets:view',
|
||||
manage: 'edr_passenger_app:tickets:manage',
|
||||
},
|
||||
payments: {
|
||||
viewAll: 'edr_passenger_app:payments:view_all',
|
||||
refund: 'edr_passenger_app:payments:refund',
|
||||
manageMethods: 'edr_passenger_app:payments:manage_methods',
|
||||
},
|
||||
reports: {
|
||||
view: 'edr_passenger_app:reports:view',
|
||||
},
|
||||
fraud: {
|
||||
view: 'edr_passenger_app:fraud:view',
|
||||
manage: 'edr_passenger_app:fraud:manage',
|
||||
},
|
||||
audit: {
|
||||
view: 'edr_passenger_app:audit:view',
|
||||
},
|
||||
agents: {
|
||||
view: 'edr_passenger_app:agents:view',
|
||||
manage: 'edr_passenger_app:agents:manage',
|
||||
},
|
||||
currencies: {
|
||||
manage: 'edr_passenger_app:currencies:manage',
|
||||
},
|
||||
notifications: {
|
||||
send: 'edr_passenger_app:notifications:send',
|
||||
},
|
||||
dashboard: {
|
||||
view: 'edr_passenger_app:dashboard:view',
|
||||
},
|
||||
admin: 'edr_passenger_app:admin',
|
||||
} as const;
|
||||
|
||||
export const ROLE_PERMISSION_PRESETS = {
|
||||
backofficeAdmin: [...PASSENGER_PERMISSION_KEYS],
|
||||
|
||||
backofficeStaff: [
|
||||
PASSENGER_PERMS.bookings.view,
|
||||
PASSENGER_PERMS.bookings.manage,
|
||||
PASSENGER_PERMS.bookings.cancel,
|
||||
PASSENGER_PERMS.passengers.view,
|
||||
PASSENGER_PERMS.passengers.manage,
|
||||
PASSENGER_PERMS.tickets.view,
|
||||
PASSENGER_PERMS.tickets.manage,
|
||||
PASSENGER_PERMS.payments.viewAll,
|
||||
PASSENGER_PERMS.reports.view,
|
||||
PASSENGER_PERMS.dashboard.view,
|
||||
PASSENGER_PERMS.notifications.send,
|
||||
PASSENGER_PERMS.agents.view,
|
||||
PASSENGER_PERMS.fraud.view,
|
||||
PASSENGER_PERMS.audit.view,
|
||||
],
|
||||
|
||||
agent: [
|
||||
PASSENGER_PERMS.bookings.view,
|
||||
PASSENGER_PERMS.bookings.manage,
|
||||
PASSENGER_PERMS.passengers.view,
|
||||
PASSENGER_PERMS.tickets.view,
|
||||
PASSENGER_PERMS.payments.refund,
|
||||
],
|
||||
|
||||
finance: [
|
||||
PASSENGER_PERMS.payments.viewAll,
|
||||
PASSENGER_PERMS.payments.refund,
|
||||
PASSENGER_PERMS.reports.view,
|
||||
PASSENGER_PERMS.dashboard.view,
|
||||
],
|
||||
} as const;
|
||||
106
apps/edr-passenger-api/src/seed/passenger-staff-users.seeder.ts
Normal file
106
apps/edr-passenger-api/src/seed/passenger-staff-users.seeder.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { hashPassword } from '@tria-plc/api-common/utils/argon';
|
||||
import { EUserStatus } from '@tria-plc/api-common/utils/enums/user.enum';
|
||||
import {
|
||||
Employee,
|
||||
Organization,
|
||||
Role,
|
||||
User,
|
||||
UserCredential,
|
||||
UserRole,
|
||||
} from '@tria-plc/iamapi-common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
const SEED_FLAG = 'SEED_PASSENGER_STAFF';
|
||||
const EDR_ORG_KEY = 'edr';
|
||||
|
||||
const STAFF_USERS = [
|
||||
{ email: 'passenger.admin@edr.local', username: 'passenger_admin', roleKey: 'edr_passenger_backoffice_admin' },
|
||||
{ email: 'passenger.staff@edr.local', username: 'passenger_staff', roleKey: 'edr_passenger_backoffice_staff' },
|
||||
{ email: 'passenger.agent@edr.local', username: 'passenger_agent', roleKey: 'edr_passenger_agent' },
|
||||
{ email: 'passenger.finance@edr.local', username: 'passenger_finance', roleKey: 'edr_passenger_finance' },
|
||||
] as const;
|
||||
|
||||
@Injectable()
|
||||
export class PassengerStaffUsersSeeder {
|
||||
private readonly logger = new Logger(PassengerStaffUsersSeeder.name);
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async run() {
|
||||
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
|
||||
this.logger.log(`Skipping passenger staff seed because ${SEED_FLAG} is not enabled`);
|
||||
return;
|
||||
}
|
||||
|
||||
const password = process.env.DEFAULT_PASSWORD?.trim() || '12345678';
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const organization = await manager.getRepository(Organization).findOne({
|
||||
where: { key: EDR_ORG_KEY },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
|
||||
if (!organization) throw new Error(`missing_organization:${EDR_ORG_KEY}`);
|
||||
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
for (const staff of STAFF_USERS) {
|
||||
const role = await manager.getRepository(Role).findOne({
|
||||
where: { key: staff.roleKey },
|
||||
select: { id: true, key: true },
|
||||
});
|
||||
if (!role) throw new Error(`missing_role:${staff.roleKey}`);
|
||||
|
||||
let user = await manager.getRepository(User).findOne({
|
||||
where: { email: staff.email },
|
||||
select: { id: true, email: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await manager.getRepository(User).save(
|
||||
manager.getRepository(User).create({
|
||||
email: staff.email,
|
||||
username: staff.username,
|
||||
name: { en: staff.username },
|
||||
isActive: true,
|
||||
hasSetPassword: true,
|
||||
status: EUserStatus.ACCEPTED,
|
||||
}),
|
||||
);
|
||||
this.logger.log(`Seeded passenger staff user ${staff.email}`);
|
||||
}
|
||||
|
||||
const credentialExists = await manager.getRepository(UserCredential).exists({
|
||||
where: { userId: user.id, isActive: true },
|
||||
});
|
||||
if (!credentialExists) {
|
||||
await manager.getRepository(UserCredential).insert({
|
||||
userId: user.id,
|
||||
password: hashedPassword,
|
||||
isActive: true,
|
||||
});
|
||||
}
|
||||
|
||||
await manager.getRepository(UserRole).upsert(
|
||||
{ userId: user.id, roleId: role.id, organizationId: organization.id },
|
||||
{ conflictPaths: { userId: true, roleId: true } },
|
||||
);
|
||||
|
||||
const employeeExists = await manager.getRepository(Employee).exists({
|
||||
where: { userId: user.id, organizationId: organization.id, isCurrent: true },
|
||||
});
|
||||
if (!employeeExists) {
|
||||
await manager.getRepository(Employee).insert({
|
||||
userId: user.id,
|
||||
organizationId: organization.id,
|
||||
isCurrent: true,
|
||||
name: { en: staff.username },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.log('Ensured passenger staff users');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user