@opensaas/stack-core 0.3.0 → 0.5.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +152 -0
- package/dist/access/engine.d.ts +1 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +38 -0
- package/dist/access/engine.js.map +1 -1
- package/dist/config/index.d.ts +5 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +3 -0
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +159 -73
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +19 -6
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +88 -72
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/fields/index.d.ts +65 -9
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +89 -8
- package/dist/fields/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/handler.js +1 -0
- package/dist/mcp/handler.js.map +1 -1
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +4 -2
- package/dist/validation/schema.js.map +1 -1
- package/package.json +7 -7
- package/src/access/engine.ts +48 -3
- package/src/config/index.ts +8 -13
- package/src/config/plugin-engine.ts +6 -3
- package/src/config/types.ts +208 -109
- package/src/context/index.ts +14 -7
- package/src/context/nested-operations.ts +83 -71
- package/src/fields/index.ts +124 -20
- package/src/index.ts +9 -0
- package/src/mcp/handler.ts +2 -1
- package/src/validation/schema.ts +4 -2
- package/tests/field-types.test.ts +6 -5
- package/tests/sudo.test.ts +230 -1
- package/tsconfig.tsbuildinfo +1 -1
package/tests/sudo.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
2
|
import { getContext } from '../src/context/index.js'
|
|
3
3
|
import { config, list } from '../src/config/index.js'
|
|
4
|
-
import { text, integer } from '../src/fields/index.js'
|
|
4
|
+
import { text, integer, relationship } from '../src/fields/index.js'
|
|
5
5
|
import type { PrismaClient } from '@prisma/client'
|
|
6
6
|
|
|
7
7
|
describe('Sudo Context', () => {
|
|
@@ -402,4 +402,233 @@ describe('Sudo Context', () => {
|
|
|
402
402
|
expect(sudoResult).toHaveLength(1)
|
|
403
403
|
})
|
|
404
404
|
})
|
|
405
|
+
|
|
406
|
+
describe('Nested Operations with Sudo Mode', () => {
|
|
407
|
+
// Mock Prisma client with User and Post models for nested operations
|
|
408
|
+
const mockPrismaWithRelations = {
|
|
409
|
+
user: {
|
|
410
|
+
findUnique: vi.fn(),
|
|
411
|
+
findMany: vi.fn(),
|
|
412
|
+
create: vi.fn(),
|
|
413
|
+
update: vi.fn(),
|
|
414
|
+
},
|
|
415
|
+
post: {
|
|
416
|
+
findUnique: vi.fn(),
|
|
417
|
+
findMany: vi.fn(),
|
|
418
|
+
create: vi.fn(),
|
|
419
|
+
update: vi.fn(),
|
|
420
|
+
},
|
|
421
|
+
} as unknown as PrismaClient
|
|
422
|
+
|
|
423
|
+
const nestedTestConfig = config({
|
|
424
|
+
db: {
|
|
425
|
+
provider: 'sqlite',
|
|
426
|
+
},
|
|
427
|
+
lists: {
|
|
428
|
+
User: list({
|
|
429
|
+
fields: {
|
|
430
|
+
email: text({ validation: { isRequired: true } }),
|
|
431
|
+
name: text(),
|
|
432
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
433
|
+
},
|
|
434
|
+
access: {
|
|
435
|
+
operation: {
|
|
436
|
+
create: async () => false, // Block all creates
|
|
437
|
+
update: async () => false, // Block all updates
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
}),
|
|
441
|
+
Post: list({
|
|
442
|
+
fields: {
|
|
443
|
+
title: text({ validation: { isRequired: true } }),
|
|
444
|
+
content: text(),
|
|
445
|
+
author: relationship({ ref: 'User.posts' }),
|
|
446
|
+
},
|
|
447
|
+
access: {
|
|
448
|
+
operation: {
|
|
449
|
+
create: async () => true,
|
|
450
|
+
update: async () => true,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
beforeEach(() => {
|
|
458
|
+
vi.clearAllMocks()
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('should allow nested create in sudo mode when access control would deny', async () => {
|
|
462
|
+
const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
|
|
463
|
+
const sudoContext = context.sudo()
|
|
464
|
+
|
|
465
|
+
// Mock successful creation
|
|
466
|
+
mockPrismaWithRelations.post.create.mockResolvedValue({
|
|
467
|
+
id: '1',
|
|
468
|
+
title: 'Test Post',
|
|
469
|
+
content: 'Test Content',
|
|
470
|
+
authorId: 'user-1',
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
// In sudo mode, nested create should succeed despite User access control blocking creates
|
|
474
|
+
const result = await sudoContext.db.post.create({
|
|
475
|
+
data: {
|
|
476
|
+
title: 'Test Post',
|
|
477
|
+
content: 'Test Content',
|
|
478
|
+
author: {
|
|
479
|
+
create: {
|
|
480
|
+
email: 'test@example.com',
|
|
481
|
+
name: 'Test User',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
expect(result).toBeDefined()
|
|
488
|
+
expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it('should allow nested connect in sudo mode when access control would deny', async () => {
|
|
492
|
+
const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
|
|
493
|
+
const sudoContext = context.sudo()
|
|
494
|
+
|
|
495
|
+
// Mock existing user
|
|
496
|
+
mockPrismaWithRelations.user.findUnique.mockResolvedValue({
|
|
497
|
+
id: 'user-1',
|
|
498
|
+
email: 'existing@example.com',
|
|
499
|
+
name: 'Existing User',
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
// Mock successful creation
|
|
503
|
+
mockPrismaWithRelations.post.create.mockResolvedValue({
|
|
504
|
+
id: '1',
|
|
505
|
+
title: 'Test Post',
|
|
506
|
+
content: 'Test Content',
|
|
507
|
+
authorId: 'user-1',
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
// In sudo mode, nested connect should succeed despite User access control blocking updates
|
|
511
|
+
const result = await sudoContext.db.post.create({
|
|
512
|
+
data: {
|
|
513
|
+
title: 'Test Post',
|
|
514
|
+
content: 'Test Content',
|
|
515
|
+
author: {
|
|
516
|
+
connect: { id: 'user-1' },
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
expect(result).toBeDefined()
|
|
522
|
+
expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
it('should allow nested update in sudo mode when access control would deny', async () => {
|
|
526
|
+
const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
|
|
527
|
+
const sudoContext = context.sudo()
|
|
528
|
+
|
|
529
|
+
// Mock existing post (needed for access control check in main operation)
|
|
530
|
+
mockPrismaWithRelations.post.findUnique.mockResolvedValue({
|
|
531
|
+
id: '1',
|
|
532
|
+
title: 'Original Post',
|
|
533
|
+
content: 'Original Content',
|
|
534
|
+
authorId: 'user-1',
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
// Mock existing user (for nested update)
|
|
538
|
+
mockPrismaWithRelations.user.findUnique.mockResolvedValue({
|
|
539
|
+
id: 'user-1',
|
|
540
|
+
email: 'existing@example.com',
|
|
541
|
+
name: 'Existing User',
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
// Mock successful update
|
|
545
|
+
mockPrismaWithRelations.post.update.mockResolvedValue({
|
|
546
|
+
id: '1',
|
|
547
|
+
title: 'Updated Post',
|
|
548
|
+
content: 'Updated Content',
|
|
549
|
+
authorId: 'user-1',
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
// In sudo mode, nested update should succeed despite User access control blocking updates
|
|
553
|
+
const result = await sudoContext.db.post.update({
|
|
554
|
+
where: { id: '1' },
|
|
555
|
+
data: {
|
|
556
|
+
title: 'Updated Post',
|
|
557
|
+
author: {
|
|
558
|
+
update: {
|
|
559
|
+
where: { id: 'user-1' },
|
|
560
|
+
data: { name: 'Updated Name' },
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
expect(result).toBeDefined()
|
|
567
|
+
expect(mockPrismaWithRelations.post.update).toHaveBeenCalled()
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it('should allow nested connectOrCreate in sudo mode when access control would deny', async () => {
|
|
571
|
+
const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
|
|
572
|
+
const sudoContext = context.sudo()
|
|
573
|
+
|
|
574
|
+
// Mock existing user check (will find existing user)
|
|
575
|
+
mockPrismaWithRelations.user.findUnique.mockResolvedValue({
|
|
576
|
+
id: 'user-1',
|
|
577
|
+
email: 'existing@example.com',
|
|
578
|
+
name: 'Existing User',
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// Mock successful creation
|
|
582
|
+
mockPrismaWithRelations.post.create.mockResolvedValue({
|
|
583
|
+
id: '1',
|
|
584
|
+
title: 'Test Post',
|
|
585
|
+
content: 'Test Content',
|
|
586
|
+
authorId: 'user-1',
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
// In sudo mode, nested connectOrCreate should succeed despite access control
|
|
590
|
+
const result = await sudoContext.db.post.create({
|
|
591
|
+
data: {
|
|
592
|
+
title: 'Test Post',
|
|
593
|
+
content: 'Test Content',
|
|
594
|
+
author: {
|
|
595
|
+
connectOrCreate: {
|
|
596
|
+
where: { id: 'user-1' },
|
|
597
|
+
create: {
|
|
598
|
+
email: 'new@example.com',
|
|
599
|
+
name: 'New User',
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
expect(result).toBeDefined()
|
|
607
|
+
expect(mockPrismaWithRelations.post.create).toHaveBeenCalled()
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
it('should still enforce access control in nested operations without sudo mode', async () => {
|
|
611
|
+
const context = getContext(nestedTestConfig, mockPrismaWithRelations, null)
|
|
612
|
+
|
|
613
|
+
// Mock existing user
|
|
614
|
+
mockPrismaWithRelations.user.findUnique.mockResolvedValue({
|
|
615
|
+
id: 'user-1',
|
|
616
|
+
email: 'existing@example.com',
|
|
617
|
+
name: 'Existing User',
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
// Without sudo, nested connect should fail due to User update access control
|
|
621
|
+
await expect(
|
|
622
|
+
context.db.post.create({
|
|
623
|
+
data: {
|
|
624
|
+
title: 'Test Post',
|
|
625
|
+
content: 'Test Content',
|
|
626
|
+
author: {
|
|
627
|
+
connect: { id: 'user-1' },
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
}),
|
|
631
|
+
).rejects.toThrow('Access denied')
|
|
632
|
+
})
|
|
633
|
+
})
|
|
405
634
|
})
|