-
Notifications
You must be signed in to change notification settings - Fork 0
Permissions Demo #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Permissions Demo #17
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.js | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| # local env files | ||
| .env*.local | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # permissions-demo | ||
|
|
||
| ## Overview | ||
|
|
||
| This demo showcases **custom** for **permissions** (on-demand) in **react**. | ||
|
|
||
| ## Path | ||
|
|
||
| ``` | ||
| apps/react/permissions/on-demand/custom/permissions-demo/ | ||
| ``` | ||
|
|
||
| ## Package Name | ||
|
|
||
| `@apps/react-permissions-on-demand-custom-permissions-demo` | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| ``` | ||
| permissions-demo/ | ||
| ├── app/ | ||
| │ ├── layout.tsx # Root layout | ||
| │ └── page.tsx # Main page | ||
| ├── components/ | ||
| │ ├── header/ # Header components (Velt notifications, etc.) | ||
| │ │ └── header.tsx | ||
| │ ├── sidebar/ # Sidebar components | ||
| │ │ └── sidebar.tsx | ||
| │ └── document/ # Main document/canvas logic | ||
| │ └── document-canvas.tsx | ||
| ├── hooks/ # Custom React hooks | ||
| ├── lib/ # Utility functions | ||
| │ └── utils.ts | ||
| ├── public/ # Static assets | ||
| ├── styles/ # Global styles | ||
| │ └── globals.css | ||
| ├── .npmrc # pnpm config to prevent Tailwind v4 hoisting | ||
| ├── next.config.js | ||
| ├── tailwind.config.js | ||
| ├── tsconfig.json | ||
| ├── components.json # shadcn/ui configuration | ||
| └── package.json | ||
| ``` | ||
|
|
||
| ## Getting Started | ||
|
|
||
| ### Install Dependencies | ||
|
|
||
| From the monorepo root: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| ### Run Development Server | ||
|
|
||
| ```bash | ||
| cd apps/react/permissions/on-demand/custom/permissions-demo | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| Or from the root: | ||
|
|
||
| ```bash | ||
| pnpm --filter @apps/react-permissions-on-demand-custom-permissions-demo dev | ||
| ``` | ||
|
|
||
| ### Build for Production | ||
|
|
||
| ```bash | ||
| pnpm --filter @apps/react-permissions-on-demand-custom-permissions-demo build | ||
| ``` | ||
|
|
||
| ## Structure | ||
|
|
||
| - **Framework**: react | ||
| - **Feature**: permissions | ||
| - **Document**: on-demand | ||
| - **Library**: custom | ||
| - **Demo**: permissions-demo | ||
|
|
||
| ## Component Organization | ||
|
|
||
| - **`components/header/`** - Contains Velt components like notifications, presence indicators, header buttons | ||
| - **`components/sidebar/`** - Contains sidebar-related components | ||
| - **`components/document/`** - Contains the main application logic and custom integration | ||
| - **`hooks/`** - Custom React hooks for state management and side effects | ||
| - **`lib/`** - Utility functions and helpers | ||
|
|
||
| ## Important Configuration | ||
|
|
||
| ### .npmrc File | ||
| This demo includes a `.npmrc` file that prevents pnpm from hoisting Tailwind CSS v4 from other workspace packages. This is necessary because: | ||
| - This demo uses Tailwind CSS v3.4.x with traditional PostCSS configuration | ||
| - Other apps in the monorepo may use Tailwind CSS v4 | ||
| - Without the `.npmrc`, pnpm would hoist v4 and cause PostCSS errors | ||
|
|
||
| **Do not delete the `.npmrc` file** - it ensures the correct Tailwind version is used. | ||
|
|
||
| ## Next Steps | ||
|
|
||
| 1. Add your custom implementation in `components/document/` | ||
| 2. Add Velt collaboration features in `components/header/` | ||
| 3. Update this README with specific usage instructions | ||
| 4. Add the demo to `master-sample-app` if it should be showcased | ||
| 5. Update deployment configs (Vercel, GitHub Actions) if needed | ||
|
|
||
| ## Learn More | ||
|
|
||
| - [Monorepo Structure Guide](../../../../../README_MONOREPO.md) | ||
| - [Structure Documentation](../../../../../docs/structure.md) | ||
| - [Velt Documentation](https://docs.velt.dev) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import { | ||
| computeEffectiveAccess, | ||
| loadPermissionSettings, | ||
| loadSelectedUser, | ||
| PermissionSettings, | ||
| UserRole, | ||
| NodeType, | ||
| } from '@/lib/permissions-data'; | ||
|
|
||
| // Velt Permission Query types | ||
| interface PermissionResource { | ||
| type: NodeType | 'context'; | ||
| id: string; | ||
| source: string; | ||
| organizationId: string; | ||
| context?: Record<string, string | number>; | ||
| } | ||
|
|
||
| interface PermissionQueryRequest { | ||
| userId: string; | ||
| resource: PermissionResource; | ||
| } | ||
|
|
||
| interface PermissionQueryBody { | ||
| data: { | ||
| requests: PermissionQueryRequest[]; | ||
| }; | ||
| } | ||
|
|
||
| // Velt Permission Result types | ||
| interface PermissionResultItem { | ||
| userId: string; | ||
| resourceId: string; | ||
| type: string; | ||
| organizationId: string; | ||
| hasAccess: boolean; | ||
| accessRole?: 'viewer' | 'editor'; | ||
| expiresAt?: number; | ||
| } | ||
|
|
||
| interface PermissionResultResponse { | ||
| data: PermissionResultItem[]; | ||
| success: boolean; | ||
| statusCode: number; | ||
| message?: string; | ||
| } | ||
|
|
||
| // Map Velt resource IDs to our internal IDs | ||
| function mapResourceId(resourceId: string, resourceType: string): string { | ||
| // Our demo uses IDs like 'org-a', 'folder-a', 'doc-a' | ||
| // Velt might send different IDs, so we need to map them | ||
|
|
||
| // If it's already our format, return as-is | ||
| if (resourceId.startsWith('org-') || resourceId.startsWith('folder-') || resourceId.startsWith('doc-')) { | ||
| return resourceId; | ||
| } | ||
|
|
||
| // For the demo, we'll use the organization ID 'org-a' for organization type | ||
| if (resourceType === 'organization') { | ||
| return 'org-a'; | ||
| } | ||
|
|
||
| return resourceId; | ||
| } | ||
|
|
||
| export async function POST(request: NextRequest): Promise<NextResponse<PermissionResultResponse>> { | ||
| try { | ||
| const body: PermissionQueryBody = await request.json(); | ||
| const { requests } = body.data; | ||
|
|
||
| // Load current permission settings and user from the request headers or defaults | ||
| // In a real app, these would come from your database | ||
| // For this demo, we read from localStorage via cookies/headers or use defaults | ||
|
|
||
| let permissionSettings: PermissionSettings; | ||
| let selectedUser: UserRole; | ||
|
|
||
| // Try to get settings from custom headers (for demo purposes) | ||
| const settingsHeader = request.headers.get('x-demo-permission-settings'); | ||
| const userHeader = request.headers.get('x-demo-selected-user'); | ||
|
|
||
| if (settingsHeader) { | ||
| try { | ||
| permissionSettings = JSON.parse(settingsHeader); | ||
| } catch { | ||
| permissionSettings = loadPermissionSettings(); | ||
| } | ||
| } else { | ||
| permissionSettings = loadPermissionSettings(); | ||
| } | ||
|
|
||
| if (userHeader && ['Intern', 'Owner', 'Custom'].includes(userHeader)) { | ||
| selectedUser = userHeader as UserRole; | ||
| } else { | ||
| selectedUser = loadSelectedUser(); | ||
| } | ||
|
|
||
| // Process each permission request | ||
| const permissions: PermissionResultItem[] = []; | ||
|
|
||
| for (const req of requests) { | ||
| const { userId, resource } = req; | ||
| const { type, id, organizationId } = resource; | ||
|
|
||
| // Handle context-based requests (return access based on org membership) | ||
| if (type === 'context') { | ||
| const orgAccess = computeEffectiveAccess('org-a', selectedUser, permissionSettings); | ||
| permissions.push({ | ||
| userId, | ||
| resourceId: id, | ||
| type: 'context', | ||
| organizationId, | ||
| hasAccess: orgAccess.hasAccess, | ||
| }); | ||
| continue; | ||
| } | ||
|
|
||
| // Map the resource ID to our internal ID | ||
| const internalId = mapResourceId(id, type); | ||
|
|
||
| // Compute access for this resource | ||
| const access = computeEffectiveAccess(internalId, selectedUser, permissionSettings); | ||
|
|
||
| const result: PermissionResultItem = { | ||
| userId, | ||
| resourceId: id, | ||
| type, | ||
| organizationId, | ||
| hasAccess: access.hasAccess, | ||
| }; | ||
|
|
||
| // Add accessRole for documents | ||
| if (type === 'document' && access.hasAccess) { | ||
| result.accessRole = access.accessRole; | ||
| // Set expiration 10 minutes from now for demo | ||
| result.expiresAt = Date.now() + 10 * 60 * 1000; | ||
| } | ||
|
|
||
| permissions.push(result); | ||
| } | ||
|
|
||
| // Return response in Velt's expected format | ||
| const response: PermissionResultResponse = { | ||
| data: permissions, | ||
| success: true, | ||
| statusCode: 200, | ||
| message: 'Permissions validated successfully', | ||
| }; | ||
|
|
||
| return NextResponse.json(response, { status: 200 }); | ||
| } catch (error) { | ||
| console.error('Permission check error:', error); | ||
|
|
||
| return NextResponse.json( | ||
| { | ||
| data: [], | ||
| success: false, | ||
| statusCode: 500, | ||
| message: 'Internal server error', | ||
| }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Handle OPTIONS for CORS | ||
| export async function OPTIONS(): Promise<NextResponse> { | ||
| return new NextResponse(null, { | ||
| status: 200, | ||
| headers: { | ||
| 'Access-Control-Allow-Origin': '*', | ||
| 'Access-Control-Allow-Methods': 'POST, OPTIONS', | ||
| 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-demo-permission-settings, x-demo-selected-user', | ||
| }, | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
|
|
||
| // [Velt] Replace with your own API key and auth token from https://console.velt.dev | ||
| const NEXT_PUBLIC_VELT_API_KEY = "YOUR_VELT_API_KEY"; | ||
| const VELT_AUTH_TOKEN = "YOUR_VELT_AUTH_TOKEN"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Placeholder API credentials prevent demo from workingThe |
||
|
|
||
| export async function POST(req: NextRequest) { | ||
| try { | ||
| const { userId, organizationId, email, isAdmin } = await req.json(); | ||
| if (!userId || !organizationId) { | ||
| return NextResponse.json({ error: 'Missing userId or organizationId' }, { status: 400 }); | ||
| } | ||
| if (!VELT_AUTH_TOKEN) { | ||
| return NextResponse.json({ error: 'Server configuration error: missing VELT_AUTH_TOKEN' }, { status: 500 }); | ||
| } | ||
| const body = { | ||
| data: { | ||
| userId, | ||
| userProperties: { | ||
| organizationId, | ||
| ...(typeof isAdmin === 'boolean' ? { isAdmin } : {}), | ||
| ...(email ? { email } : {}), | ||
| }, | ||
| }, | ||
| }; | ||
| const res = await fetch('https://api.velt.dev/v2/auth/token/get', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', 'x-velt-api-key': NEXT_PUBLIC_VELT_API_KEY, 'x-velt-auth-token': VELT_AUTH_TOKEN }, | ||
| body: JSON.stringify(body), | ||
| }); | ||
| const json = await res.json(); | ||
| const token = json?.result?.data?.token; | ||
| if (!res.ok || !token) { | ||
| return NextResponse.json({ error: json?.error?.message || 'Failed to generate token' }, { status: 500 }); | ||
| } | ||
| return NextResponse.json({ token }); | ||
| } catch { | ||
| return NextResponse.json({ error: 'Internal error' }, { status: 500 }); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Permission API ignores client-side settings changes
The permission API route calls
loadPermissionSettings()andloadSelectedUser()as fallbacks, but these functions requirelocalStoragewhich doesn't exist on the server. They will always return default values. The API expects settings via custom headers (x-demo-permission-settings,x-demo-selected-user), but no code in the codebase sends these headers. This means permission changes made in the UI are never reflected in actual permission checks - the demo UI appears to work but the underlying permission system always uses defaults.