@openmdm/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +105 -3
- package/dist/index.js +1553 -41
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +9 -0
- package/dist/schema.js +259 -0
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +591 -1
- package/dist/types.js +21 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/audit.ts +317 -0
- package/src/authorization.ts +418 -0
- package/src/dashboard.ts +327 -0
- package/src/index.ts +222 -0
- package/src/plugin-storage.ts +128 -0
- package/src/queue.ts +161 -0
- package/src/schedule.ts +325 -0
- package/src/schema.ts +277 -0
- package/src/tenant.ts +237 -0
- package/src/types.ts +708 -0
package/src/schema.ts
CHANGED
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
* - mdm_rollbacks: Rollback operation history and status
|
|
19
19
|
* - mdm_webhook_endpoints: Outbound webhook configuration
|
|
20
20
|
* - mdm_webhook_deliveries: Webhook delivery history
|
|
21
|
+
* - mdm_tenants: Multi-tenant organization isolation
|
|
22
|
+
* - mdm_roles: RBAC role definitions
|
|
23
|
+
* - mdm_users: User accounts for authorization
|
|
24
|
+
* - mdm_user_roles: User-role mapping
|
|
25
|
+
* - mdm_audit_logs: Compliance and audit trail
|
|
26
|
+
* - mdm_scheduled_tasks: Scheduled task definitions
|
|
27
|
+
* - mdm_task_executions: Task execution history
|
|
28
|
+
* - mdm_message_queue: Persistent push message queue
|
|
29
|
+
* - mdm_plugin_storage: Plugin state persistence
|
|
21
30
|
*/
|
|
22
31
|
|
|
23
32
|
// ============================================
|
|
@@ -456,6 +465,274 @@ export const mdmSchema: SchemaDefinition = {
|
|
|
456
465
|
{ columns: ['created_at'] },
|
|
457
466
|
],
|
|
458
467
|
},
|
|
468
|
+
|
|
469
|
+
// ----------------------------------------
|
|
470
|
+
// Tenants Table (Multi-tenancy)
|
|
471
|
+
// ----------------------------------------
|
|
472
|
+
mdm_tenants: {
|
|
473
|
+
columns: {
|
|
474
|
+
id: { type: 'string', primaryKey: true },
|
|
475
|
+
name: { type: 'string' },
|
|
476
|
+
slug: { type: 'string', unique: true },
|
|
477
|
+
status: {
|
|
478
|
+
type: 'enum',
|
|
479
|
+
enumValues: ['active', 'suspended', 'pending'],
|
|
480
|
+
default: 'pending',
|
|
481
|
+
},
|
|
482
|
+
settings: { type: 'json', nullable: true },
|
|
483
|
+
metadata: { type: 'json', nullable: true },
|
|
484
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
485
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
486
|
+
},
|
|
487
|
+
indexes: [
|
|
488
|
+
{ columns: ['slug'], unique: true },
|
|
489
|
+
{ columns: ['status'] },
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
// ----------------------------------------
|
|
494
|
+
// Roles Table (RBAC)
|
|
495
|
+
// ----------------------------------------
|
|
496
|
+
mdm_roles: {
|
|
497
|
+
columns: {
|
|
498
|
+
id: { type: 'string', primaryKey: true },
|
|
499
|
+
tenant_id: {
|
|
500
|
+
type: 'string',
|
|
501
|
+
nullable: true,
|
|
502
|
+
references: { table: 'mdm_tenants', column: 'id', onDelete: 'cascade' },
|
|
503
|
+
},
|
|
504
|
+
name: { type: 'string' },
|
|
505
|
+
description: { type: 'text', nullable: true },
|
|
506
|
+
permissions: { type: 'json' },
|
|
507
|
+
is_system: { type: 'boolean', default: false },
|
|
508
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
509
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
510
|
+
},
|
|
511
|
+
indexes: [
|
|
512
|
+
{ columns: ['tenant_id'] },
|
|
513
|
+
{ columns: ['name'] },
|
|
514
|
+
{ columns: ['tenant_id', 'name'], unique: true },
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// ----------------------------------------
|
|
519
|
+
// Users Table (RBAC)
|
|
520
|
+
// ----------------------------------------
|
|
521
|
+
mdm_users: {
|
|
522
|
+
columns: {
|
|
523
|
+
id: { type: 'string', primaryKey: true },
|
|
524
|
+
tenant_id: {
|
|
525
|
+
type: 'string',
|
|
526
|
+
nullable: true,
|
|
527
|
+
references: { table: 'mdm_tenants', column: 'id', onDelete: 'cascade' },
|
|
528
|
+
},
|
|
529
|
+
email: { type: 'string' },
|
|
530
|
+
name: { type: 'string', nullable: true },
|
|
531
|
+
status: {
|
|
532
|
+
type: 'enum',
|
|
533
|
+
enumValues: ['active', 'inactive', 'pending'],
|
|
534
|
+
default: 'pending',
|
|
535
|
+
},
|
|
536
|
+
metadata: { type: 'json', nullable: true },
|
|
537
|
+
last_login_at: { type: 'datetime', nullable: true },
|
|
538
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
539
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
540
|
+
},
|
|
541
|
+
indexes: [
|
|
542
|
+
{ columns: ['tenant_id'] },
|
|
543
|
+
{ columns: ['email'] },
|
|
544
|
+
{ columns: ['tenant_id', 'email'], unique: true },
|
|
545
|
+
{ columns: ['status'] },
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
// ----------------------------------------
|
|
550
|
+
// User Roles (Many-to-Many)
|
|
551
|
+
// ----------------------------------------
|
|
552
|
+
mdm_user_roles: {
|
|
553
|
+
columns: {
|
|
554
|
+
user_id: {
|
|
555
|
+
type: 'string',
|
|
556
|
+
references: { table: 'mdm_users', column: 'id', onDelete: 'cascade' },
|
|
557
|
+
},
|
|
558
|
+
role_id: {
|
|
559
|
+
type: 'string',
|
|
560
|
+
references: { table: 'mdm_roles', column: 'id', onDelete: 'cascade' },
|
|
561
|
+
},
|
|
562
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
563
|
+
},
|
|
564
|
+
indexes: [
|
|
565
|
+
{ columns: ['user_id', 'role_id'], unique: true },
|
|
566
|
+
{ columns: ['user_id'] },
|
|
567
|
+
{ columns: ['role_id'] },
|
|
568
|
+
],
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
// ----------------------------------------
|
|
572
|
+
// Audit Logs Table
|
|
573
|
+
// ----------------------------------------
|
|
574
|
+
mdm_audit_logs: {
|
|
575
|
+
columns: {
|
|
576
|
+
id: { type: 'string', primaryKey: true },
|
|
577
|
+
tenant_id: {
|
|
578
|
+
type: 'string',
|
|
579
|
+
nullable: true,
|
|
580
|
+
references: { table: 'mdm_tenants', column: 'id', onDelete: 'cascade' },
|
|
581
|
+
},
|
|
582
|
+
user_id: {
|
|
583
|
+
type: 'string',
|
|
584
|
+
nullable: true,
|
|
585
|
+
references: { table: 'mdm_users', column: 'id', onDelete: 'set null' },
|
|
586
|
+
},
|
|
587
|
+
action: { type: 'string' },
|
|
588
|
+
resource: { type: 'string' },
|
|
589
|
+
resource_id: { type: 'string', nullable: true },
|
|
590
|
+
details: { type: 'json', nullable: true },
|
|
591
|
+
ip_address: { type: 'string', nullable: true },
|
|
592
|
+
user_agent: { type: 'text', nullable: true },
|
|
593
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
594
|
+
},
|
|
595
|
+
indexes: [
|
|
596
|
+
{ columns: ['tenant_id'] },
|
|
597
|
+
{ columns: ['user_id'] },
|
|
598
|
+
{ columns: ['action'] },
|
|
599
|
+
{ columns: ['resource'] },
|
|
600
|
+
{ columns: ['resource', 'resource_id'] },
|
|
601
|
+
{ columns: ['created_at'] },
|
|
602
|
+
],
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
// ----------------------------------------
|
|
606
|
+
// Scheduled Tasks Table
|
|
607
|
+
// ----------------------------------------
|
|
608
|
+
mdm_scheduled_tasks: {
|
|
609
|
+
columns: {
|
|
610
|
+
id: { type: 'string', primaryKey: true },
|
|
611
|
+
tenant_id: {
|
|
612
|
+
type: 'string',
|
|
613
|
+
nullable: true,
|
|
614
|
+
references: { table: 'mdm_tenants', column: 'id', onDelete: 'cascade' },
|
|
615
|
+
},
|
|
616
|
+
name: { type: 'string' },
|
|
617
|
+
description: { type: 'text', nullable: true },
|
|
618
|
+
task_type: {
|
|
619
|
+
type: 'enum',
|
|
620
|
+
enumValues: ['command', 'policy_update', 'app_install', 'maintenance', 'custom'],
|
|
621
|
+
},
|
|
622
|
+
schedule: { type: 'json' },
|
|
623
|
+
target: { type: 'json', nullable: true },
|
|
624
|
+
payload: { type: 'json', nullable: true },
|
|
625
|
+
status: {
|
|
626
|
+
type: 'enum',
|
|
627
|
+
enumValues: ['active', 'paused', 'completed', 'failed'],
|
|
628
|
+
default: 'active',
|
|
629
|
+
},
|
|
630
|
+
next_run_at: { type: 'datetime', nullable: true },
|
|
631
|
+
last_run_at: { type: 'datetime', nullable: true },
|
|
632
|
+
max_retries: { type: 'integer', default: 3 },
|
|
633
|
+
retry_count: { type: 'integer', default: 0 },
|
|
634
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
635
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
636
|
+
},
|
|
637
|
+
indexes: [
|
|
638
|
+
{ columns: ['tenant_id'] },
|
|
639
|
+
{ columns: ['task_type'] },
|
|
640
|
+
{ columns: ['status'] },
|
|
641
|
+
{ columns: ['next_run_at'] },
|
|
642
|
+
],
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
// ----------------------------------------
|
|
646
|
+
// Task Executions Table
|
|
647
|
+
// ----------------------------------------
|
|
648
|
+
mdm_task_executions: {
|
|
649
|
+
columns: {
|
|
650
|
+
id: { type: 'string', primaryKey: true },
|
|
651
|
+
task_id: {
|
|
652
|
+
type: 'string',
|
|
653
|
+
references: { table: 'mdm_scheduled_tasks', column: 'id', onDelete: 'cascade' },
|
|
654
|
+
},
|
|
655
|
+
status: {
|
|
656
|
+
type: 'enum',
|
|
657
|
+
enumValues: ['running', 'completed', 'failed'],
|
|
658
|
+
default: 'running',
|
|
659
|
+
},
|
|
660
|
+
started_at: { type: 'datetime', default: 'now' },
|
|
661
|
+
completed_at: { type: 'datetime', nullable: true },
|
|
662
|
+
devices_processed: { type: 'integer', default: 0 },
|
|
663
|
+
devices_succeeded: { type: 'integer', default: 0 },
|
|
664
|
+
devices_failed: { type: 'integer', default: 0 },
|
|
665
|
+
error: { type: 'text', nullable: true },
|
|
666
|
+
details: { type: 'json', nullable: true },
|
|
667
|
+
},
|
|
668
|
+
indexes: [
|
|
669
|
+
{ columns: ['task_id'] },
|
|
670
|
+
{ columns: ['status'] },
|
|
671
|
+
{ columns: ['started_at'] },
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
|
|
675
|
+
// ----------------------------------------
|
|
676
|
+
// Message Queue Table
|
|
677
|
+
// ----------------------------------------
|
|
678
|
+
mdm_message_queue: {
|
|
679
|
+
columns: {
|
|
680
|
+
id: { type: 'string', primaryKey: true },
|
|
681
|
+
tenant_id: {
|
|
682
|
+
type: 'string',
|
|
683
|
+
nullable: true,
|
|
684
|
+
references: { table: 'mdm_tenants', column: 'id', onDelete: 'cascade' },
|
|
685
|
+
},
|
|
686
|
+
device_id: {
|
|
687
|
+
type: 'string',
|
|
688
|
+
references: { table: 'mdm_devices', column: 'id', onDelete: 'cascade' },
|
|
689
|
+
},
|
|
690
|
+
message_type: { type: 'string' },
|
|
691
|
+
payload: { type: 'json' },
|
|
692
|
+
priority: {
|
|
693
|
+
type: 'enum',
|
|
694
|
+
enumValues: ['high', 'normal', 'low'],
|
|
695
|
+
default: 'normal',
|
|
696
|
+
},
|
|
697
|
+
status: {
|
|
698
|
+
type: 'enum',
|
|
699
|
+
enumValues: ['pending', 'processing', 'delivered', 'failed', 'expired'],
|
|
700
|
+
default: 'pending',
|
|
701
|
+
},
|
|
702
|
+
attempts: { type: 'integer', default: 0 },
|
|
703
|
+
max_attempts: { type: 'integer', default: 3 },
|
|
704
|
+
last_attempt_at: { type: 'datetime', nullable: true },
|
|
705
|
+
last_error: { type: 'text', nullable: true },
|
|
706
|
+
expires_at: { type: 'datetime', nullable: true },
|
|
707
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
708
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
709
|
+
},
|
|
710
|
+
indexes: [
|
|
711
|
+
{ columns: ['tenant_id'] },
|
|
712
|
+
{ columns: ['device_id'] },
|
|
713
|
+
{ columns: ['status'] },
|
|
714
|
+
{ columns: ['priority'] },
|
|
715
|
+
{ columns: ['expires_at'] },
|
|
716
|
+
{ columns: ['device_id', 'status', 'priority'] },
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
// ----------------------------------------
|
|
721
|
+
// Plugin Storage Table
|
|
722
|
+
// ----------------------------------------
|
|
723
|
+
mdm_plugin_storage: {
|
|
724
|
+
columns: {
|
|
725
|
+
plugin_name: { type: 'string' },
|
|
726
|
+
key: { type: 'string' },
|
|
727
|
+
value: { type: 'json' },
|
|
728
|
+
created_at: { type: 'datetime', default: 'now' },
|
|
729
|
+
updated_at: { type: 'datetime', default: 'now' },
|
|
730
|
+
},
|
|
731
|
+
indexes: [
|
|
732
|
+
{ columns: ['plugin_name', 'key'], unique: true },
|
|
733
|
+
{ columns: ['plugin_name'] },
|
|
734
|
+
],
|
|
735
|
+
},
|
|
459
736
|
},
|
|
460
737
|
};
|
|
461
738
|
|
package/src/tenant.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenMDM Tenant Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides multi-tenancy support for the MDM system.
|
|
5
|
+
* Enables organization isolation, tenant management, and resource quotas.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Tenant,
|
|
10
|
+
TenantManager,
|
|
11
|
+
TenantFilter,
|
|
12
|
+
TenantListResult,
|
|
13
|
+
TenantStats,
|
|
14
|
+
CreateTenantInput,
|
|
15
|
+
UpdateTenantInput,
|
|
16
|
+
DatabaseAdapter,
|
|
17
|
+
} from './types';
|
|
18
|
+
import { TenantNotFoundError, ValidationError } from './types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a unique ID for entities
|
|
22
|
+
*/
|
|
23
|
+
function generateId(): string {
|
|
24
|
+
return crypto.randomUUID();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate tenant slug format
|
|
29
|
+
*/
|
|
30
|
+
function validateSlug(slug: string): boolean {
|
|
31
|
+
// Slug must be lowercase alphanumeric with hyphens, 3-50 chars
|
|
32
|
+
const slugRegex = /^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/;
|
|
33
|
+
return slugRegex.test(slug);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a TenantManager instance
|
|
38
|
+
*/
|
|
39
|
+
export function createTenantManager(db: DatabaseAdapter): TenantManager {
|
|
40
|
+
return {
|
|
41
|
+
async get(id: string): Promise<Tenant | null> {
|
|
42
|
+
if (!db.findTenant) {
|
|
43
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
44
|
+
}
|
|
45
|
+
return db.findTenant(id);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async getBySlug(slug: string): Promise<Tenant | null> {
|
|
49
|
+
if (!db.findTenantBySlug) {
|
|
50
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
51
|
+
}
|
|
52
|
+
return db.findTenantBySlug(slug);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async list(filter?: TenantFilter): Promise<TenantListResult> {
|
|
56
|
+
if (!db.listTenants) {
|
|
57
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
58
|
+
}
|
|
59
|
+
return db.listTenants(filter);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async create(data: CreateTenantInput): Promise<Tenant> {
|
|
63
|
+
if (!db.createTenant || !db.findTenantBySlug) {
|
|
64
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Validate slug format
|
|
68
|
+
if (!validateSlug(data.slug)) {
|
|
69
|
+
throw new ValidationError(
|
|
70
|
+
'Invalid slug format. Must be 3-50 lowercase alphanumeric characters with hyphens.',
|
|
71
|
+
{ slug: data.slug }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check for duplicate slug
|
|
76
|
+
const existing = await db.findTenantBySlug(data.slug);
|
|
77
|
+
if (existing) {
|
|
78
|
+
throw new ValidationError(`Tenant with slug '${data.slug}' already exists`, {
|
|
79
|
+
slug: data.slug,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return db.createTenant({
|
|
84
|
+
...data,
|
|
85
|
+
slug: data.slug.toLowerCase(),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async update(id: string, data: UpdateTenantInput): Promise<Tenant> {
|
|
90
|
+
if (!db.updateTenant || !db.findTenant || !db.findTenantBySlug) {
|
|
91
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const tenant = await db.findTenant(id);
|
|
95
|
+
if (!tenant) {
|
|
96
|
+
throw new TenantNotFoundError(id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate new slug if provided
|
|
100
|
+
if (data.slug) {
|
|
101
|
+
if (!validateSlug(data.slug)) {
|
|
102
|
+
throw new ValidationError(
|
|
103
|
+
'Invalid slug format. Must be 3-50 lowercase alphanumeric characters with hyphens.',
|
|
104
|
+
{ slug: data.slug }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for duplicate slug
|
|
109
|
+
const existing = await db.findTenantBySlug(data.slug);
|
|
110
|
+
if (existing && existing.id !== id) {
|
|
111
|
+
throw new ValidationError(`Tenant with slug '${data.slug}' already exists`, {
|
|
112
|
+
slug: data.slug,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
data.slug = data.slug.toLowerCase();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return db.updateTenant(id, data);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async delete(id: string, cascade: boolean = false): Promise<void> {
|
|
123
|
+
if (!db.deleteTenant || !db.findTenant) {
|
|
124
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const tenant = await db.findTenant(id);
|
|
128
|
+
if (!tenant) {
|
|
129
|
+
throw new TenantNotFoundError(id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If cascade is true, the database adapter should handle
|
|
133
|
+
// deletion of all related resources (devices, policies, etc.)
|
|
134
|
+
// This is typically done via ON DELETE CASCADE in the schema
|
|
135
|
+
|
|
136
|
+
await db.deleteTenant(id);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async getStats(tenantId: string): Promise<TenantStats> {
|
|
140
|
+
if (!db.getTenantStats || !db.findTenant) {
|
|
141
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const tenant = await db.findTenant(tenantId);
|
|
145
|
+
if (!tenant) {
|
|
146
|
+
throw new TenantNotFoundError(tenantId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return db.getTenantStats(tenantId);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async activate(id: string): Promise<Tenant> {
|
|
153
|
+
if (!db.updateTenant || !db.findTenant) {
|
|
154
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tenant = await db.findTenant(id);
|
|
158
|
+
if (!tenant) {
|
|
159
|
+
throw new TenantNotFoundError(id);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (tenant.status === 'active') {
|
|
163
|
+
return tenant;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return db.updateTenant(id, { status: 'active' });
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
async deactivate(id: string): Promise<Tenant> {
|
|
170
|
+
if (!db.updateTenant || !db.findTenant) {
|
|
171
|
+
throw new Error('Database adapter does not support tenant operations');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tenant = await db.findTenant(id);
|
|
175
|
+
if (!tenant) {
|
|
176
|
+
throw new TenantNotFoundError(id);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (tenant.status === 'suspended') {
|
|
180
|
+
return tenant;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return db.updateTenant(id, { status: 'suspended' });
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Default system roles that can be used across tenants
|
|
190
|
+
*/
|
|
191
|
+
export const DEFAULT_SYSTEM_ROLES = {
|
|
192
|
+
SUPER_ADMIN: {
|
|
193
|
+
name: 'Super Admin',
|
|
194
|
+
description: 'Full system access across all tenants',
|
|
195
|
+
permissions: [{ action: '*' as const, resource: '*' as const }],
|
|
196
|
+
isSystem: true,
|
|
197
|
+
},
|
|
198
|
+
TENANT_ADMIN: {
|
|
199
|
+
name: 'Tenant Admin',
|
|
200
|
+
description: 'Full access within the tenant',
|
|
201
|
+
permissions: [
|
|
202
|
+
{ action: 'manage' as const, resource: 'devices' as const },
|
|
203
|
+
{ action: 'manage' as const, resource: 'policies' as const },
|
|
204
|
+
{ action: 'manage' as const, resource: 'applications' as const },
|
|
205
|
+
{ action: 'manage' as const, resource: 'commands' as const },
|
|
206
|
+
{ action: 'manage' as const, resource: 'groups' as const },
|
|
207
|
+
{ action: 'manage' as const, resource: 'users' as const },
|
|
208
|
+
{ action: 'read' as const, resource: 'audit' as const },
|
|
209
|
+
],
|
|
210
|
+
isSystem: true,
|
|
211
|
+
},
|
|
212
|
+
DEVICE_MANAGER: {
|
|
213
|
+
name: 'Device Manager',
|
|
214
|
+
description: 'Manage devices and send commands',
|
|
215
|
+
permissions: [
|
|
216
|
+
{ action: 'manage' as const, resource: 'devices' as const },
|
|
217
|
+
{ action: 'manage' as const, resource: 'commands' as const },
|
|
218
|
+
{ action: 'read' as const, resource: 'policies' as const },
|
|
219
|
+
{ action: 'read' as const, resource: 'groups' as const },
|
|
220
|
+
],
|
|
221
|
+
isSystem: true,
|
|
222
|
+
},
|
|
223
|
+
VIEWER: {
|
|
224
|
+
name: 'Viewer',
|
|
225
|
+
description: 'Read-only access to all resources',
|
|
226
|
+
permissions: [
|
|
227
|
+
{ action: 'read' as const, resource: 'devices' as const },
|
|
228
|
+
{ action: 'read' as const, resource: 'policies' as const },
|
|
229
|
+
{ action: 'read' as const, resource: 'applications' as const },
|
|
230
|
+
{ action: 'read' as const, resource: 'commands' as const },
|
|
231
|
+
{ action: 'read' as const, resource: 'groups' as const },
|
|
232
|
+
],
|
|
233
|
+
isSystem: true,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export type SystemRoleName = keyof typeof DEFAULT_SYSTEM_ROLES;
|