@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-bypass-layers
|
|
3
|
+
description: |
|
|
4
|
+
Multi-layer security architecture for API bypass (superadmin/developer).
|
|
5
|
+
Covers authentication layers, authorization, team context, RLS policies, and three-layer bypass validation.
|
|
6
|
+
Use this skill when implementing admin bypass features or validating security architecture.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# API Bypass Layers Skill
|
|
12
|
+
|
|
13
|
+
Security architecture for superadmin and developer bypass access in the API layer.
|
|
14
|
+
|
|
15
|
+
## File References
|
|
16
|
+
|
|
17
|
+
| File | Purpose |
|
|
18
|
+
|------|---------|
|
|
19
|
+
| `packages/core/src/lib/api/auth/dual-auth.ts` | App-level bypass (canBypassTeamContext) |
|
|
20
|
+
| `packages/core/src/lib/api/entity/generic-handler.ts` | CRUD handler (validateTeamContextWithBypass) |
|
|
21
|
+
| `packages/core/migrations/001_better_auth_and_functions.sql` | get_auth_user_id() function |
|
|
22
|
+
| `packages/core/migrations/010_teams_functions_triggers.sql` | can_bypass_rls(), get_user_team_ids(), RLS policies |
|
|
23
|
+
| `packages/core/migrations/090_sample_data.sql` | System Admin Team (team-nextspark-001) |
|
|
24
|
+
|
|
25
|
+
## Architecture Overview
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
29
|
+
│ LAYER 1: AUTHENTICATION │
|
|
30
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
31
|
+
│ API Key Auth │ Session Auth │
|
|
32
|
+
│ ├─ x-api-key header │ ├─ Browser cookies │
|
|
33
|
+
│ ├─ Constant-time delay │ ├─ Better Auth framework │
|
|
34
|
+
│ ├─ Key expiration check │ └─ Full access (scopes: ['all']) │
|
|
35
|
+
│ ├─ Failed attempt lockout │ │
|
|
36
|
+
│ └─ Scoped permissions │ │
|
|
37
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
38
|
+
↓
|
|
39
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ LAYER 2: AUTHORIZATION │
|
|
41
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
42
|
+
│ Admin Bypass Check (Three-Layer Validation) │
|
|
43
|
+
│ ├─ LAYER 1: Role check (superadmin OR developer) │
|
|
44
|
+
│ ├─ LAYER 2: Header confirmation (x-admin-bypass) │
|
|
45
|
+
│ └─ LAYER 3: System Admin Team membership (team-nextspark-001) │
|
|
46
|
+
│ │
|
|
47
|
+
│ Permission Check │
|
|
48
|
+
│ ├─ Entity-level permissions (entity.action format) │
|
|
49
|
+
│ ├─ Team role hierarchy (owner > admin > member > viewer) │
|
|
50
|
+
│ └─ Scope validation for API keys │
|
|
51
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
52
|
+
↓
|
|
53
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ LAYER 3: TEAM CONTEXT │
|
|
55
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
56
|
+
│ Normal Mode (isBypass = false) │
|
|
57
|
+
│ ├─ x-team-id header REQUIRED │
|
|
58
|
+
│ ├─ Validate user is team member │
|
|
59
|
+
│ ├─ Filter by teamId AND userId │
|
|
60
|
+
│ └─ Reject if not member (403 TEAM_ACCESS_DENIED) │
|
|
61
|
+
│ │
|
|
62
|
+
│ Bypass Mode (isBypass = true) │
|
|
63
|
+
│ ├─ x-team-id header OPTIONAL │
|
|
64
|
+
│ ├─ Skip membership validation │
|
|
65
|
+
│ ├─ Skip userId filter │
|
|
66
|
+
│ └─ Cross-team or specific team access │
|
|
67
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
68
|
+
↓
|
|
69
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ LAYER 4: DATABASE RLS │
|
|
71
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
72
|
+
│ PostgreSQL Row Level Security (Final Defense) │
|
|
73
|
+
│ ├─ can_bypass_rls() - superadmin ALWAYS, developer IF member │
|
|
74
|
+
│ ├─ get_auth_user_id() - reads from app.user_id GUC │
|
|
75
|
+
│ ├─ get_user_team_ids() - array of user's teams │
|
|
76
|
+
│ └─ Policies: SELECT/INSERT/UPDATE/DELETE per table │
|
|
77
|
+
│ │
|
|
78
|
+
│ Example Policy: │
|
|
79
|
+
│ can_bypass_rls() OR teamId = ANY(get_user_team_ids()) │
|
|
80
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## When to Use This Skill
|
|
84
|
+
|
|
85
|
+
- Understanding the multi-layer security model
|
|
86
|
+
- Implementing admin bypass for new features
|
|
87
|
+
- Debugging authorization issues
|
|
88
|
+
- Validating RLS policies work correctly
|
|
89
|
+
- Adding new bypass-protected endpoints
|
|
90
|
+
- Testing cross-team access scenarios
|
|
91
|
+
|
|
92
|
+
## Constants
|
|
93
|
+
|
|
94
|
+
**File:** `packages/core/src/lib/api/auth/dual-auth.ts:13-31`
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// System Admin Team - Members can bypass team context validation
|
|
98
|
+
export const SYSTEM_ADMIN_TEAM_ID = 'team-nextspark-001'
|
|
99
|
+
|
|
100
|
+
// Header required to confirm cross-team access intention
|
|
101
|
+
export const ADMIN_BYPASS_HEADER = 'x-admin-bypass'
|
|
102
|
+
export const ADMIN_BYPASS_VALUE = 'confirm-cross-team-access'
|
|
103
|
+
|
|
104
|
+
// Roles that can potentially bypass team context
|
|
105
|
+
const ELEVATED_ROLES = ['superadmin', 'developer'] as const
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## App-Level Bypass: canBypassTeamContext()
|
|
109
|
+
|
|
110
|
+
**File:** `packages/core/src/lib/api/auth/dual-auth.ts:205-226`
|
|
111
|
+
|
|
112
|
+
Three-layer validation for admin bypass at the application level:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
export async function canBypassTeamContext(
|
|
116
|
+
authResult: DualAuthResult,
|
|
117
|
+
request: NextRequest
|
|
118
|
+
): Promise<boolean> {
|
|
119
|
+
// LAYER 1: Must have elevated role
|
|
120
|
+
if (!authResult.success || !authResult.user) return false
|
|
121
|
+
const hasElevatedRole = ELEVATED_ROLES.includes(
|
|
122
|
+
authResult.user.role as typeof ELEVATED_ROLES[number]
|
|
123
|
+
)
|
|
124
|
+
if (!hasElevatedRole) return false
|
|
125
|
+
|
|
126
|
+
// LAYER 2: Must include confirmation header
|
|
127
|
+
const bypassHeader = request.headers.get(ADMIN_BYPASS_HEADER)
|
|
128
|
+
if (bypassHeader !== ADMIN_BYPASS_VALUE) return false
|
|
129
|
+
|
|
130
|
+
// LAYER 3: Must be member of System Admin Team
|
|
131
|
+
const isMember = await checkSystemAdminMembership(authResult.user.id)
|
|
132
|
+
if (!isMember) {
|
|
133
|
+
console.log('[dual-auth] User has elevated role but is not member of System Admin Team')
|
|
134
|
+
}
|
|
135
|
+
return isMember
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Three-Layer Validation Table
|
|
140
|
+
|
|
141
|
+
| Layer | Check | Requirement |
|
|
142
|
+
|-------|-------|-------------|
|
|
143
|
+
| 1 | Role | `user.role` is `superadmin` OR `developer` |
|
|
144
|
+
| 2 | Header | `x-admin-bypass: confirm-cross-team-access` |
|
|
145
|
+
| 3 | Membership | User belongs to `team-nextspark-001` |
|
|
146
|
+
|
|
147
|
+
## Team Context Validation: validateTeamContextWithBypass()
|
|
148
|
+
|
|
149
|
+
**File:** `packages/core/src/lib/api/entity/generic-handler.ts:267-309`
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
async function validateTeamContextWithBypass(
|
|
153
|
+
request: NextRequest,
|
|
154
|
+
authResult: DualAuthResult,
|
|
155
|
+
userId: string
|
|
156
|
+
): Promise<{ valid: true; teamId: string | null; isBypass: boolean } | { valid: false; error: NextResponse }> {
|
|
157
|
+
const teamId = getTeamIdFromRequest(request)
|
|
158
|
+
|
|
159
|
+
// Check if user can bypass team validation
|
|
160
|
+
const canBypass = await canBypassTeamContext(authResult, request)
|
|
161
|
+
|
|
162
|
+
if (canBypass) {
|
|
163
|
+
// Admin bypass: teamId is optional
|
|
164
|
+
// - If provided: filter by that team (no membership check)
|
|
165
|
+
// - If not provided: cross-team access (all teams)
|
|
166
|
+
// isBypass = true means skip userId filter too (see all records)
|
|
167
|
+
console.log('[GenericHandler] Admin bypass active:', { userId, teamId: teamId || 'cross-team' })
|
|
168
|
+
return { valid: true, teamId, isBypass: true }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Normal flow: require teamId and membership
|
|
172
|
+
if (!teamId) {
|
|
173
|
+
const response = createApiError(
|
|
174
|
+
'Team context required. Include x-team-id header.',
|
|
175
|
+
400,
|
|
176
|
+
undefined,
|
|
177
|
+
'TEAM_CONTEXT_REQUIRED'
|
|
178
|
+
)
|
|
179
|
+
return { valid: false, error: await addCorsHeaders(response) }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const isMember = await validateTeamMembership(userId, teamId)
|
|
183
|
+
if (!isMember) {
|
|
184
|
+
const response = createApiError(
|
|
185
|
+
'Access denied: You are not a member of this team',
|
|
186
|
+
403,
|
|
187
|
+
undefined,
|
|
188
|
+
'TEAM_ACCESS_DENIED'
|
|
189
|
+
)
|
|
190
|
+
return { valid: false, error: await addCorsHeaders(response) }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return { valid: true, teamId, isBypass: false }
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## isBypass Flag Usage
|
|
198
|
+
|
|
199
|
+
**File:** `packages/core/src/lib/api/entity/generic-handler.ts`
|
|
200
|
+
|
|
201
|
+
The `isBypass` flag controls userId filtering in queries:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// Line ~509, 538, 563, 1138
|
|
205
|
+
if (userId && !entityConfig.access?.shared && !skipUserFilter && !isBypass) {
|
|
206
|
+
// Apply userId filter - only see own records
|
|
207
|
+
}
|
|
208
|
+
// When isBypass = true, this filter is skipped, allowing cross-user access
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Database-Level Bypass: can_bypass_rls()
|
|
212
|
+
|
|
213
|
+
**File:** `packages/core/migrations/010_teams_functions_triggers.sql:115-147`
|
|
214
|
+
|
|
215
|
+
**CRITICAL DIFFERENCE from App-Level:**
|
|
216
|
+
- **Superadmin:** ALWAYS bypasses RLS (no team membership check)
|
|
217
|
+
- **Developer:** Only bypasses IF member of System Admin Team
|
|
218
|
+
|
|
219
|
+
```sql
|
|
220
|
+
CREATE OR REPLACE FUNCTION public.can_bypass_rls()
|
|
221
|
+
RETURNS BOOLEAN AS $$
|
|
222
|
+
DECLARE
|
|
223
|
+
current_user_id TEXT;
|
|
224
|
+
user_role TEXT;
|
|
225
|
+
is_system_admin_member BOOLEAN;
|
|
226
|
+
BEGIN
|
|
227
|
+
current_user_id := public.get_auth_user_id();
|
|
228
|
+
|
|
229
|
+
-- Get user role
|
|
230
|
+
SELECT role INTO user_role
|
|
231
|
+
FROM public."users"
|
|
232
|
+
WHERE id = current_user_id;
|
|
233
|
+
|
|
234
|
+
-- Superadmin ALWAYS bypasses (no team membership check)
|
|
235
|
+
IF user_role = 'superadmin' THEN
|
|
236
|
+
RETURN TRUE;
|
|
237
|
+
END IF;
|
|
238
|
+
|
|
239
|
+
-- Developer can bypass ONLY if member of System Admin Team
|
|
240
|
+
IF user_role = 'developer' THEN
|
|
241
|
+
SELECT EXISTS(
|
|
242
|
+
SELECT 1 FROM public."team_members"
|
|
243
|
+
WHERE "userId" = current_user_id
|
|
244
|
+
AND "teamId" = 'team-nextspark-001'
|
|
245
|
+
) INTO is_system_admin_member;
|
|
246
|
+
|
|
247
|
+
RETURN is_system_admin_member;
|
|
248
|
+
END IF;
|
|
249
|
+
|
|
250
|
+
RETURN FALSE;
|
|
251
|
+
END;
|
|
252
|
+
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## App vs DB Bypass Logic Comparison
|
|
256
|
+
|
|
257
|
+
| Role | App-Level (canBypassTeamContext) | DB-Level (can_bypass_rls) |
|
|
258
|
+
|------|----------------------------------|---------------------------|
|
|
259
|
+
| superadmin | Needs header + team membership | ALWAYS bypasses |
|
|
260
|
+
| developer | Needs header + team membership | Only with team membership |
|
|
261
|
+
| member | Cannot bypass | Cannot bypass |
|
|
262
|
+
|
|
263
|
+
**Why the difference?**
|
|
264
|
+
- App-level: Requires explicit intent (header) to prevent accidents
|
|
265
|
+
- DB-level: Final defense, superadmin always trusted at data layer
|
|
266
|
+
|
|
267
|
+
## RLS Helper Functions
|
|
268
|
+
|
|
269
|
+
### get_auth_user_id()
|
|
270
|
+
|
|
271
|
+
**File:** `packages/core/migrations/001_better_auth_and_functions.sql:10-24`
|
|
272
|
+
|
|
273
|
+
```sql
|
|
274
|
+
CREATE OR REPLACE FUNCTION public.get_auth_user_id()
|
|
275
|
+
RETURNS TEXT
|
|
276
|
+
LANGUAGE plpgsql
|
|
277
|
+
SECURITY DEFINER
|
|
278
|
+
SET search_path = public
|
|
279
|
+
AS $$
|
|
280
|
+
DECLARE v TEXT;
|
|
281
|
+
BEGIN
|
|
282
|
+
v := current_setting('app.user_id', true);
|
|
283
|
+
IF v IS NULL OR v = '' THEN
|
|
284
|
+
RETURN NULL;
|
|
285
|
+
END IF;
|
|
286
|
+
RETURN v;
|
|
287
|
+
END;
|
|
288
|
+
$$;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### get_user_team_ids()
|
|
292
|
+
|
|
293
|
+
**File:** `packages/core/migrations/010_teams_functions_triggers.sql:98-109`
|
|
294
|
+
|
|
295
|
+
```sql
|
|
296
|
+
CREATE OR REPLACE FUNCTION public.get_user_team_ids()
|
|
297
|
+
RETURNS TEXT[] AS $$
|
|
298
|
+
DECLARE
|
|
299
|
+
user_teams TEXT[];
|
|
300
|
+
BEGIN
|
|
301
|
+
SELECT ARRAY_AGG(tm."teamId") INTO user_teams
|
|
302
|
+
FROM public."team_members" tm
|
|
303
|
+
WHERE tm."userId" = public.get_auth_user_id();
|
|
304
|
+
|
|
305
|
+
RETURN COALESCE(user_teams, ARRAY[]::TEXT[]);
|
|
306
|
+
END;
|
|
307
|
+
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## RLS Policy Pattern
|
|
311
|
+
|
|
312
|
+
**File:** `packages/core/migrations/010_teams_functions_triggers.sql:164-208`
|
|
313
|
+
|
|
314
|
+
```sql
|
|
315
|
+
-- SELECT policy
|
|
316
|
+
CREATE POLICY "teams_select_policy" ON public."teams"
|
|
317
|
+
FOR SELECT TO authenticated
|
|
318
|
+
USING (
|
|
319
|
+
public.is_superadmin() -- Alias for can_bypass_rls()
|
|
320
|
+
OR
|
|
321
|
+
id IN (
|
|
322
|
+
SELECT "teamId" FROM public."team_members"
|
|
323
|
+
WHERE "userId" = public.get_auth_user_id()
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
-- UPDATE policy
|
|
328
|
+
CREATE POLICY "teams_update_policy" ON public."teams"
|
|
329
|
+
FOR UPDATE TO authenticated
|
|
330
|
+
USING (
|
|
331
|
+
public.is_superadmin()
|
|
332
|
+
OR
|
|
333
|
+
"ownerId" = public.get_auth_user_id()
|
|
334
|
+
)
|
|
335
|
+
WITH CHECK (
|
|
336
|
+
public.is_superadmin()
|
|
337
|
+
OR
|
|
338
|
+
"ownerId" = public.get_auth_user_id()
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
-- DELETE policy
|
|
342
|
+
CREATE POLICY "teams_delete_policy" ON public."teams"
|
|
343
|
+
FOR DELETE TO authenticated
|
|
344
|
+
USING (
|
|
345
|
+
public.is_superadmin()
|
|
346
|
+
OR
|
|
347
|
+
"ownerId" = public.get_auth_user_id()
|
|
348
|
+
);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Error Codes
|
|
352
|
+
|
|
353
|
+
| Code | HTTP Status | Trigger |
|
|
354
|
+
|------|-------------|---------|
|
|
355
|
+
| `TEAM_CONTEXT_REQUIRED` | 400 | Missing `x-team-id` header (non-bypass mode) |
|
|
356
|
+
| `TEAM_ACCESS_DENIED` | 403 | User not member of specified team |
|
|
357
|
+
| `AUTHENTICATION_FAILED` | 401 | Invalid credentials |
|
|
358
|
+
|
|
359
|
+
## Normal Mode vs Bypass Mode
|
|
360
|
+
|
|
361
|
+
### Normal Mode (Default)
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// Headers required
|
|
365
|
+
const headers = {
|
|
366
|
+
'x-team-id': 'team-xxx' // REQUIRED
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Behavior
|
|
370
|
+
// - teamId is REQUIRED
|
|
371
|
+
// - Validate user is team member
|
|
372
|
+
// - Filter results by teamId AND userId
|
|
373
|
+
// - Return 403 TEAM_ACCESS_DENIED if not member
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Bypass Mode (Admin)
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Headers required
|
|
380
|
+
const headers = {
|
|
381
|
+
'x-admin-bypass': 'confirm-cross-team-access', // REQUIRED
|
|
382
|
+
'x-team-id': 'team-xxx' // OPTIONAL (for filtering)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Behavior
|
|
386
|
+
// - teamId is OPTIONAL
|
|
387
|
+
// - Skip membership validation
|
|
388
|
+
// - Skip userId filter (isBypass = true)
|
|
389
|
+
// - Access all teams or specific team
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Request Flow Diagram
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
396
|
+
│ API REQUEST │
|
|
397
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
398
|
+
│ Headers: │
|
|
399
|
+
│ ├─ Authorization: Bearer <api-key> OR Cookie: session=... │
|
|
400
|
+
│ ├─ x-team-id: team-xxx (required in normal mode) │
|
|
401
|
+
│ └─ x-admin-bypass: confirm-... (enables bypass mode) │
|
|
402
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
403
|
+
│
|
|
404
|
+
▼
|
|
405
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
406
|
+
│ BACKEND SECURITY LAYERS │
|
|
407
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
408
|
+
│ │
|
|
409
|
+
│ LAYER 1: Authentication (dual-auth.ts:71-89) │
|
|
410
|
+
│ └─ authenticateRequest() → DualAuthResult │
|
|
411
|
+
│ │
|
|
412
|
+
│ LAYER 2: Authorization (dual-auth.ts:205-226) │
|
|
413
|
+
│ └─ canBypassTeamContext() → role + header + team check │
|
|
414
|
+
│ │
|
|
415
|
+
│ LAYER 3: Team Context (generic-handler.ts:267-309) │
|
|
416
|
+
│ └─ validateTeamContextWithBypass() → { teamId, isBypass } │
|
|
417
|
+
│ │
|
|
418
|
+
│ LAYER 4: Query Building (generic-handler.ts:509+) │
|
|
419
|
+
│ └─ Add teamId/userId filters based on isBypass flag │
|
|
420
|
+
│ │
|
|
421
|
+
│ LAYER 5: Database RLS (010_teams_functions_triggers.sql:115-147) │
|
|
422
|
+
│ └─ can_bypass_rls() OR teamId = ANY(get_user_team_ids()) │
|
|
423
|
+
│ │
|
|
424
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
425
|
+
│
|
|
426
|
+
▼
|
|
427
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
428
|
+
│ FILTERED RESPONSE │
|
|
429
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
430
|
+
│ { success: true, data: [...filtered results...], info: { total: N } } │
|
|
431
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Testing Bypass Mode
|
|
435
|
+
|
|
436
|
+
### Cypress API Test
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
describe('Admin Bypass', () => {
|
|
440
|
+
const SUPERADMIN_KEY = Cypress.env('SUPERADMIN_API_KEY')
|
|
441
|
+
|
|
442
|
+
it('should allow cross-team access with bypass', () => {
|
|
443
|
+
cy.request({
|
|
444
|
+
method: 'GET',
|
|
445
|
+
url: '/api/v1/products',
|
|
446
|
+
headers: {
|
|
447
|
+
'Authorization': `Bearer ${SUPERADMIN_KEY}`,
|
|
448
|
+
'x-admin-bypass': 'confirm-cross-team-access'
|
|
449
|
+
// No x-team-id = cross-team mode
|
|
450
|
+
}
|
|
451
|
+
}).then((response) => {
|
|
452
|
+
expect(response.status).to.eq(200)
|
|
453
|
+
// Returns data from ALL teams
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should filter to specific team with bypass', () => {
|
|
458
|
+
cy.request({
|
|
459
|
+
method: 'GET',
|
|
460
|
+
url: '/api/v1/products',
|
|
461
|
+
headers: {
|
|
462
|
+
'Authorization': `Bearer ${SUPERADMIN_KEY}`,
|
|
463
|
+
'x-admin-bypass': 'confirm-cross-team-access',
|
|
464
|
+
'x-team-id': 'team-tmt-002' // Filter to specific team
|
|
465
|
+
}
|
|
466
|
+
}).then((response) => {
|
|
467
|
+
expect(response.status).to.eq(200)
|
|
468
|
+
// Returns data only from team-tmt-002
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('should reject bypass without header', () => {
|
|
473
|
+
cy.request({
|
|
474
|
+
method: 'GET',
|
|
475
|
+
url: '/api/v1/products',
|
|
476
|
+
headers: {
|
|
477
|
+
'Authorization': `Bearer ${SUPERADMIN_KEY}`
|
|
478
|
+
// Missing x-admin-bypass and x-team-id
|
|
479
|
+
},
|
|
480
|
+
failOnStatusCode: false
|
|
481
|
+
}).then((response) => {
|
|
482
|
+
expect(response.status).to.eq(400)
|
|
483
|
+
expect(response.body.code).to.eq('TEAM_CONTEXT_REQUIRED')
|
|
484
|
+
})
|
|
485
|
+
})
|
|
486
|
+
})
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Security Strengths
|
|
490
|
+
|
|
491
|
+
| Feature | Implementation | Benefit |
|
|
492
|
+
|---------|----------------|---------|
|
|
493
|
+
| **Dual Authentication** | API Key + Session | Flexibility for different use cases |
|
|
494
|
+
| **Three-Layer App Bypass** | Role + Header + Team | Defense in depth, prevents accidents |
|
|
495
|
+
| **RLS Final Defense** | PostgreSQL policies | Data isolation even if app bypassed |
|
|
496
|
+
| **Team Isolation** | App + DB checks | Multi-tenant security |
|
|
497
|
+
| **System Admin Team** | Hardcoded ID | Prevents privilege escalation |
|
|
498
|
+
| **isBypass Flag** | Skip userId filter | Cross-user visibility for admins |
|
|
499
|
+
|
|
500
|
+
## Anti-Patterns
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// NEVER: Allow bypass based on role alone at app level
|
|
504
|
+
if (user.role === 'superadmin') {
|
|
505
|
+
return true // WRONG! Missing header and team checks at app level
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// CORRECT: Use the three-layer function
|
|
509
|
+
const isBypass = await canBypassTeamContext(authResult, request)
|
|
510
|
+
|
|
511
|
+
// NEVER: Trust client-provided bypass status
|
|
512
|
+
const isBypass = request.body.isBypass // User can fake this!
|
|
513
|
+
|
|
514
|
+
// CORRECT: Validate bypass on server
|
|
515
|
+
const isBypass = await canBypassTeamContext(authResult, request)
|
|
516
|
+
|
|
517
|
+
// NEVER: Skip userId filter without checking bypass
|
|
518
|
+
if (user.role === 'superadmin') {
|
|
519
|
+
skipUserFilter = true // WRONG! Should use isBypass from validation
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// CORRECT: Use the isBypass flag from validateTeamContextWithBypass
|
|
523
|
+
const { teamId, isBypass } = await validateTeamContextWithBypass(request, authResult, userId)
|
|
524
|
+
if (!isBypass) {
|
|
525
|
+
// Apply userId filter
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Checklist
|
|
530
|
+
|
|
531
|
+
Before finalizing bypass implementation:
|
|
532
|
+
|
|
533
|
+
- [ ] App-level uses three-layer validation (role + header + team)
|
|
534
|
+
- [ ] `x-admin-bypass` header value is exactly `confirm-cross-team-access`
|
|
535
|
+
- [ ] System Admin Team ID is `team-nextspark-001` (hardcoded)
|
|
536
|
+
- [ ] RLS policies use `can_bypass_rls()` function
|
|
537
|
+
- [ ] Superadmin always bypasses RLS (no team check at DB level)
|
|
538
|
+
- [ ] Developer requires System Admin Team membership for RLS bypass
|
|
539
|
+
- [ ] `isBypass` flag controls userId filter in queries
|
|
540
|
+
- [ ] Normal mode requires `x-team-id` header
|
|
541
|
+
- [ ] Bypass mode makes `x-team-id` optional
|
|
542
|
+
- [ ] Error codes: `TEAM_CONTEXT_REQUIRED` (400), `TEAM_ACCESS_DENIED` (403)
|
|
543
|
+
|
|
544
|
+
## Related Skills
|
|
545
|
+
|
|
546
|
+
- `better-auth` - Authentication patterns and session management
|
|
547
|
+
- `permissions-system` - RBAC + Features + Quotas permission model
|
|
548
|
+
- `database-migrations` - PostgreSQL RLS policies
|
|
549
|
+
- `nextjs-api-development` - API route patterns with dual auth
|
|
550
|
+
- `cypress-api` - API testing with bypass scenarios
|