Skip to content

Commit

Permalink
feat: display overriden permissions warning
Browse files Browse the repository at this point in the history
  • Loading branch information
fgmadeira committed Feb 27, 2024
1 parent 5790397 commit 6b26993
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 20 deletions.
4 changes: 4 additions & 0 deletions ethereal-nexus-dashboard/src/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { userLoginSchema } from '@/data/users/dto';
import { getUserByEmail } from '@/data/users/actions';
import { getMembersByUser } from '@/data/member/actions';
import { NextAuthConfig } from 'next-auth';
import { Permissions } from '@/data/users/permission-utils';

type Role = "viewer" | "user" | "admin"

Expand All @@ -14,6 +15,9 @@ declare module "next-auth" {

interface Session {
user?: User;
permissions: {
[key: string]: Permissions | undefined
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ import { upsertApiKey } from '@/data/users/actions';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ApiKey, ApiKeyPermissions, apiKeyPermissionsSchema, NewApiKey, newApiKeySchema } from '@/data/users/dto';
import { ApiKey, ApiKeyPermissions, NewApiKey, newApiKeySchema } from '@/data/users/dto';
import { useSession } from 'next-auth/react';
import { notFound } from 'next/navigation';
import { ShieldBan } from 'lucide-react';
import { isPermissionsHigher, Permissions } from '@/data/users/permission-utils';

type ProjectLabels = {
id: string,
name: string,
}

type ApiKeyDialogProps = {
apyKey?: ApiKey;
apyKey?: Omit<ApiKey, 'member_permissions'>;
availableProjects: ProjectLabels[];
onComplete?: () => void
}
Expand Down Expand Up @@ -129,6 +130,7 @@ export function ApiKeyForm({ apyKey, availableProjects, onComplete }: ApiKeyDial
control={form.control}
name={'permissions'}
render={({ field }) => {
const restricted = isPermissionsHigher(field.value?.[item.id], session?.permissions[item.id])
return (
<FormItem
key={item.id}
Expand All @@ -137,7 +139,9 @@ export function ApiKeyForm({ apyKey, availableProjects, onComplete }: ApiKeyDial
<FormLabel className="font-normal">
{item.name}
</FormLabel>
<FormControl>
<div className="flex gap-2">
{restricted ? <span className="flex items-center gap-1 text-muted-foreground text-sm" ><ShieldBan className="h-4 w-4" color="red" />Restricted</span> : null}
<FormControl>
<Select
value={field.value?.[item.id]}
defaultValue={session?.permissions[item.id] ?? 'read'}
Expand All @@ -154,12 +158,15 @@ export function ApiKeyForm({ apyKey, availableProjects, onComplete }: ApiKeyDial
<SelectContent>
<SelectGroup>
<SelectItem value="none">No access</SelectItem>
<SelectItem value="read">Can read</SelectItem>
<SelectItem value="write">Can edit</SelectItem>
<SelectItem value="read" disabled={session?.permissions[item.id] === 'none'}>Can read</SelectItem>
<SelectItem value="write" disabled={session?.permissions[item.id] !== 'write'}>Can edit</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>

</div>
<FormMessage />
</FormItem>
)
}}
Expand Down
19 changes: 5 additions & 14 deletions ethereal-nexus-dashboard/src/data/users/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import {
userSchema
} from '@/data/users/dto';
import { z } from 'zod';
import { and, eq, getTableColumns, isNotNull, ne, sql } from 'drizzle-orm';
import { and, eq, getTableColumns, isNotNull, sql } from 'drizzle-orm';
import { ActionResponse } from '@/data/action';
import { actionError, actionSuccess, actionZodError } from '@/data/utils';
import { members } from '@/data/member/schema';
import { ap } from 'types-ramda';
import { lowestPermission } from '@/data/users/permission-utils';

export async function insertUser(user: z.infer<typeof newUserSchema>): ActionResponse<z.infer<typeof userPublicSchema>> {
const safeUser = newUserSchema.safeParse(user);
Expand Down Expand Up @@ -121,7 +121,7 @@ export async function getUserByEmail(unsafeEmail: string): ActionResponse<z.infe
}
}

export async function getApiKeyById(apiKey: string): ActionResponse<ApiKey> {
export async function getApiKeyById(apiKey: string): ActionResponse<Omit<ApiKey, 'member_permissions'>> {
const input = apiKeySchema.pick({id: true}).safeParse({ id: apiKey });
if (!input.success) {
return actionZodError('The api key is not valid.', input.error);
Expand All @@ -133,7 +133,7 @@ export async function getApiKeyById(apiKey: string): ActionResponse<ApiKey> {
where: eq(apiKeys.id, id),
});

const safe = apiKeySchema.safeParse(result);
const safe = apiKeySchema.omit({member_permissions: true}).safeParse(result);
if (!safe.success) {
return actionZodError(
'There\'s an issue with the api key record.',
Expand All @@ -147,20 +147,11 @@ export async function getApiKeyById(apiKey: string): ActionResponse<ApiKey> {
}
}

const permissions = ["write", "read", "none"] as const
type Permissions = typeof permissions[number]
function comparePermissions(left: Permissions, right: Permissions) {
const leftIndex = permissions.indexOf(left);
const rightIndex = permissions.indexOf(right);

return permissions[Math.max(leftIndex, rightIndex)];
}

const apiKeyValidPermissions = apiKeySchema.transform(val => {
let permissions = val.permissions;
if(val.permissions && val.member_permissions) {
permissions = Object.keys(val.permissions).reduce((acc, resource) => {
acc[resource] = comparePermissions(val.permissions?.[resource]!, val.member_permissions?.[resource]!)
acc[resource] = lowestPermission(val.permissions?.[resource]!, val.member_permissions?.[resource]!)
return acc
}, {})
}
Expand Down
1 change: 1 addition & 0 deletions ethereal-nexus-dashboard/src/data/users/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const transformId = val => ({
key: '************' + val.key.substr(val.id.length - 13)
});
export const apiKeyPublicSchema = apiKeySchema
.omit({member_permissions: true})
.transform(transformId);
export type PublicApiKey = z.infer<typeof apiKeyPublicSchema>

Expand Down
19 changes: 19 additions & 0 deletions ethereal-nexus-dashboard/src/data/users/permission-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const permissions = ['none', 'read', 'write'] as const;
export type Permissions = typeof permissions[number]

export function lowestPermission(left: Permissions, right: Permissions) {
const leftIndex = permissions.indexOf(left);
const rightIndex = permissions.indexOf(right);

return permissions[Math.min(leftIndex, rightIndex)];
}

export function isPermissionsHigher(left: Permissions | undefined, right: Permissions | undefined) {
if (!left || !right) {
return false;
}
const leftIndex = permissions.indexOf(left);
const rightIndex = permissions.indexOf(right);

return leftIndex > rightIndex;
}

0 comments on commit 6b26993

Please sign in to comment.