Skip to content
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

Move auth to ms2 #196

Merged
merged 23 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ad3735e
Use null instead of undefined
FelipeTrost Dec 11, 2023
8975a47
Changed ability and csrf setup
FelipeTrost Dec 11, 2023
58a6b25
Changed redis for lru-cache
FelipeTrost Dec 11, 2023
d9666ce
Fix: Admin role was being added twice
FelipeTrost Dec 13, 2023
a138d7e
Used .global for stores and moved init functions
FelipeTrost Dec 13, 2023
6874f88
Bug fix: Authcan redirected when loading
FelipeTrost Dec 13, 2023
b6c31dd
Ability filter generic
FelipeTrost Dec 13, 2023
2e4bab4
Bug fix: Ability filter recognize null values
FelipeTrost Dec 13, 2023
215f006
Unauthorized error class
FelipeTrost Dec 13, 2023
df81a65
Added permissions check and changed init
FelipeTrost Dec 13, 2023
e489f8c
Server actions for role-mappings and roles
FelipeTrost Dec 13, 2023
01155c7
Switched components to rsc and to server actions
FelipeTrost Dec 13, 2023
9a2dad5
loading page and error hanlers
FelipeTrost Dec 13, 2023
4433c05
Fixed permissions check
FelipeTrost Dec 15, 2023
4cb7867
Merge remote-tracking branch 'origin/main' into move-auth-to-ms2
FelipeTrost Dec 15, 2023
8bbfb1e
Ms2: Role and Role-Mapping migrations
FelipeTrost Dec 17, 2023
8617c0f
Fix: Dev admin should have admin role
FelipeTrost Dec 18, 2023
cd2b4bb
Remove debugger calls
FelipeTrost Dec 18, 2023
7dc213f
Update page when role changes
FelipeTrost Dec 18, 2023
f369c37
Removed console.log
FelipeTrost Dec 20, 2023
b551b07
Removed unneeded asyncs and cleaned imports
FelipeTrost Dec 20, 2023
e83d172
Implemented userError
FelipeTrost Dec 20, 2023
31c5301
Remove unnecessary dynamic
FelipeTrost Dec 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/management-system-v2/app/(dashboard)/iam/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import { Result } from 'antd';

const Error = () => <Result status="error" title="Something went wrong" />;

export default Error;
19 changes: 19 additions & 0 deletions src/management-system-v2/app/(dashboard)/iam/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Content from '@/components/content';
import { Space, Spin } from 'antd';
import { LoadingOutlined } from '@ant-design/icons';

const Loading = () => {
return (
<Content>
<Space
direction="vertical"
size="large"
style={{ display: 'flex', textAlign: 'center', marginTop: '2rem' }}
>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
</Space>
</Content>
);
};

export default Loading;
Original file line number Diff line number Diff line change
@@ -1,11 +1,70 @@
import Auth from '@/components/auth';
import RolePage from './role-page';
import Auth, { getCurrentUser } from '@/components/auth';
import Content from '@/components/content';
import { getRoleById } from '@/lib/data/legacy/iam/roles';
import { Button, Card, Result, Space, Tabs } from 'antd';
import { LeftOutlined } from '@ant-design/icons';
import Link from 'next/link';
import { ComponentProps } from 'react';
import RoleGeneralData from './roleGeneralData';
import RolePermissions from './rolePermissions';
import RoleMembers from './role-members';

type Items = ComponentProps<typeof Tabs>['items'];

const Page = async ({ params: { roleId } }: { params: { roleId: string } }) => {
const { ability } = await getCurrentUser();
const role = getRoleById(roleId, ability);

const items: Items = role
? [
{
key: 'generalData',
label: 'General Data',
children: <RoleGeneralData role={role} />,
},
{ key: 'permissions', label: 'Permissions', children: <RolePermissions role={role} /> },
{
key: 'members',
label: 'Manage Members',
children: <RoleMembers role={role} />,
},
]
: [];

if (!role)
return (
<Content>
<Result status="404" title="Role not found" />
</Content>
);

return (
<Content
title={
<Space>
<Link href="/iam/roles">
<Button icon={<LeftOutlined />} type="text">
Roles
</Button>
</Link>
{role?.name}
</Space>
}
>
<div style={{ maxWidth: '800px', margin: 'auto' }}>
<Card>
<Tabs items={items} />
</Card>
</div>
</Content>
);
};

export default Auth(
{
action: ['view', 'manage'],
resource: 'Role',
fallbackRedirect: '/',
},
RolePage,
Page,
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,42 @@

import { FC, useMemo, useState } from 'react';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import {
ApiData,
useDeleteAsset,
useGetAsset,
useInvalidateAsset,
usePostAsset,
} from '@/lib/fetch-data';
import { ApiData, useGetAsset } from '@/lib/fetch-data';
import UserList, { UserListProps } from '@/components/user-list';
import { Button, Modal, Tooltip } from 'antd';
import ConfirmationButton from '@/components/confirmation-button';
import { addRoleMappings, deleteRoleMappings } from '@/lib/data/role-mappings';
import { useRouter } from 'next/navigation';

type Role = ApiData<'/roles', 'get'>[number];
type Users = ApiData<'/users', 'get'>;

const AddUserModal: FC<{ role: Role; open: boolean; close: () => void }> = ({
role,
open,
close,
}) => {
const { data: users, isLoading: isLoadingUsers } = useGetAsset('/users', {});
const invalidateRole = useInvalidateAsset('/roles/{id}', { params: { path: { id: role.id } } });
const { mutateAsync, isLoading: isLoadingMutation } = usePostAsset('/role-mappings', {
onSuccess: invalidateRole,
});
const [loading, setLoading] = useState(false);
const { data: users, refetch: refetchUsers, isLoading: usersLoading } = useGetAsset('/users', {});
const router = useRouter();

type AddUserParams = Parameters<NonNullable<UserListProps['selectedRowActions']>>;
const addUsers = async (users: AddUserParams[2], clearIds?: AddUserParams[1]) => {
if (clearIds) clearIds();
await mutateAsync({
body: users.map((user) => ({
setLoading(true);
await addRoleMappings(
users.map((user) => ({
userId: user.id,
roleId: role.id,
email: user.email.value,
lastName: user.lastName.value,
firstName: user.firstName.value,
username: user.username.value,
})),
parseAs: 'text',
});
);
setLoading(false);
refetchUsers();
router.refresh();
};

const usersNotInRole = useMemo(() => {
Expand All @@ -60,7 +58,7 @@ const AddUserModal: FC<{ role: Role; open: boolean; close: () => void }> = ({
>
<UserList
users={usersNotInRole}
loading={isLoadingUsers || isLoadingMutation}
loading={loading || usersLoading}
columns={(clearSelected, hoveredId, selectedRowKeys) => [
{
dataIndex: 'id',
Expand Down Expand Up @@ -88,34 +86,33 @@ const AddUserModal: FC<{ role: Role; open: boolean; close: () => void }> = ({
);
};

const RoleMembers: FC<{ role: Role; isLoadingRole?: boolean }> = ({ role, isLoadingRole }) => {
const RoleMembers: FC<{ role: Role }> = ({ role }) => {
const [addUserModalOpen, setAddUserModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const router = useRouter();

const refetchRole = useInvalidateAsset('/roles/{id}', { params: { path: { id: role.id } } });
const { mutateAsync: deleteUser, isLoading: isLoadingDelete } = useDeleteAsset(
'/role-mappings/users/{userId}/roles/{roleId}',
{ onSuccess: refetchRole },
);

async function deleteMembers(userIds: string[], clearIds?: () => void) {
if (clearIds) clearIds();
async function deleteMembers(userIds: string[], clearIds: () => void) {
clearIds();
setLoading(true);

await Promise.allSettled(
userIds.map((userId) =>
deleteUser({
parseAs: 'text',
params: { path: { roleId: role.id, userId: userId } },
}),
),
await deleteRoleMappings(
userIds.map((userId) => ({
roleId: role.id,
userId: userId,
})),
);

setLoading(false);
router.refresh();
}

return (
<>
<AddUserModal role={role} open={addUserModalOpen} close={() => setAddUserModalOpen(false)} />

<UserList
users={role.members.map((member) => ({ ...member, id: member.userId }))}
loading={isLoadingDelete || isLoadingRole}
loading={loading}
columns={(clearSelected, hoveredId, selectedRowKeys) => [
{
dataIndex: 'id',
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
'use client';

import { toCaslResource } from '@/lib/ability/caslAbility';
import { useGetAsset, usePutAsset } from '@/lib/fetch-data';
import { Alert, App, Button, DatePicker, Form, Input, Spin } from 'antd';
import { ApiData } from '@/lib/fetch-data';
import { Alert, App, Button, DatePicker, Form, Input } from 'antd';
import { FC, useEffect, useState } from 'react';
import dayjs from 'dayjs';
import germanLocale from 'antd/es/date-picker/locale/de_DE';
import { useAbilityStore } from '@/lib/abilityStore';
import { updateRole } from '@/lib/data/roles';
import { useRouter } from 'next/navigation';

const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => {
type Role = ApiData<'/roles/{id}', 'get'>;

const RoleGeneralData: FC<{ role: Role }> = ({ role: _role }) => {
const { message } = App.useApp();
const ability = useAbilityStore((store) => store.ability);
const [form] = Form.useForm();

const { data, isLoading, error } = useGetAsset('/roles/{id}', {
params: { path: { id: roleId } },
});

const { mutateAsync: updateRole, isLoading: putLoading } = usePutAsset('/roles/{id}', {
onError: () => message.open({ type: 'error', content: 'Something went wrong' }),
});
const router = useRouter();

const [submittable, setSubmittable] = useState(false);
const values = Form.useWatch('name', form);
Expand All @@ -35,9 +32,7 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => {
);
}, [form, values]);

if (isLoading || error || !data) return <Spin />;

const role = toCaslResource('Role', data);
const role = toCaslResource('Role', _role);

async function submitChanges(values: Record<string, any>) {
if (typeof values.expirationDayJs === 'object') {
Expand All @@ -46,15 +41,13 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => {
}

try {
await updateRole({
params: { path: { id: roleId } },
body: values,
});

// success message has to go here, or else the mutation will stop loading when the message
// disappears
const result = await updateRole(role.id, values);
if (result && 'error' in result) throw new Error();
router.refresh();
message.open({ type: 'success', content: 'Role updated' });
} catch (e) {}
} catch (_) {
message.open({ type: 'error', content: 'Something went wrong' });
}
}

return (
Expand Down Expand Up @@ -93,7 +86,7 @@ const RoleGeneralData: FC<{ roleId: string }> = ({ roleId }) => {
</Form.Item>

<Form.Item>
<Button type="primary" htmlType="submit" loading={putLoading} disabled={!submittable}>
<Button type="primary" htmlType="submit" disabled={!submittable}>
Update Role
</Button>
</Form.Item>
Expand Down
Loading