Skip to content

Commit

Permalink
Merge pull request #166 from Delemangi/admins
Browse files Browse the repository at this point in the history
Allow roles assigning
  • Loading branch information
Delemangi authored Jun 16, 2024
2 parents 0051cbc + b3777b7 commit 72efd65
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 1 deletion.
41 changes: 40 additions & 1 deletion backend/app/auth/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
from .dependencies import get_current_user
from .exceptions import AUTHENTICATION_2FA_EXCEPTION, CREDENTIALS_EXCEPTION
from .models import User as DbUser
from .schemas import Code2FA, RoleMetadata, Token, UpdateRole, User, UserMetadata
from .schemas import Code2FA, RoleMetadata, Token, UpdateRole, UpdateUser, User, UserMetadata
from .service import (
authenticate_user,
create_access_token,
create_user,
edit_role_quotas,
edit_user_role,
get_roles,
get_users,
oauth2_scheme,
remove_2fa_code,
remove_token,
Expand Down Expand Up @@ -163,3 +165,40 @@ async def edit_role(
)

return RequestStatus(message="Role quotas updated successfully")


@router.get("/users", response_model=list[UserMetadata])
async def users(
current_user: Annotated[DbUser, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_async_session)],
) -> list[UserMetadata]:
users = await get_users(current_user, session)
print("/users")

return [
UserMetadata(
username=str(user.username),
role=str(user.role.name),
files_quota=int(user.role.quota_files),
size_quota=int(user.role.quota_size),
is_2fa_enabled=(user.code_2fa is not None),
)
for user in users
]


@router.post("/users/edit")
async def edit_user(
update_input: UpdateUser,
current_user: Annotated[DbUser, Depends(get_current_user)],
session: Annotated[AsyncSession, Depends(get_async_session)],
) -> RequestStatus:
print("/users/edit")
await edit_user_role(
current_user,
update_input.username,
update_input.role_name,
session,
)

return RequestStatus(message="User role updated successfully")
5 changes: 5 additions & 0 deletions backend/app/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ class UpdateRole(BaseModel):
role_id: str
size: int
files: int


class UpdateUser(BaseModel):
username: str
role_name: str
28 changes: 28 additions & 0 deletions backend/app/auth/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,34 @@ async def edit_role_quotas(
await session.commit()


async def get_users(current_user: User, session: AsyncSession) -> list[User]:
if current_user.role.name != "admin":
raise NO_PERMISSION_EXCEPTION

users = await session.execute(select(User).options(joinedload(User.role)))

return list(users.scalars().all())


async def edit_user_role(
current_user: User, username: str, role_name: str, session: AsyncSession
) -> None:
print(username, role_name)

if current_user.role.name != "admin":
raise NO_PERMISSION_EXCEPTION

role_query = await session.execute(select(Role).where(Role.name == role_name))
role = role_query.scalar_one_or_none()

if role is None:
raise ValueError("Role not found")

await session.execute(update(User).where(User.username == username).values(role_id=role.id))
await session.commit()
print("done")


async def create_user(username: str, plain_password: str, session: AsyncSession) -> User:
password = pwd_context.hash(plain_password)

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/lib/components/admin/RoleRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
} from '@svelteuidev/core';
import { isAxiosError } from 'axios';
import { Update } from 'radix-icons-svelte';
import { clearSession } from '../../../auth/session';
import { editRole } from '../../../server/auth';
export let role: RoleMetadata;
Expand Down Expand Up @@ -63,6 +64,12 @@
return;
}
if (error.response?.status === 401) {
await clearSession();
window.location.href = '/auth/login';
return;
}
if (error.response?.status === 403) {
alert('You do not have permission to edit this role.');
return;
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/lib/components/admin/TitleUserRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script lang="ts">
import { Box, Flex, Text, createStyles, type DefaultTheme } from '@svelteuidev/core';
const useStyles = createStyles((theme: DefaultTheme) => {
return {
root: {
[`${theme.dark} &`]: {
bc: theme.fn.themeColor('dark', 5),
color: 'white'
},
backgroundColor: '$gray20',
textAlign: 'center',
padding: '$10',
borderRadius: '$md'
},
textStyle: {
flex: 1,
textAlign: 'center'
}
};
});
$: ({ classes, getStyles } = useStyles());
</script>

<Box class={getStyles()}>
<Flex align="center" justify="space-evenly" style="height: 100%;">
<Text size="md" weight="bold" transform="uppercase" class={classes.textStyle}>Name</Text>
<Text size="md" weight="bold" transform="uppercase" class={classes.textStyle}>Role</Text>
<Text size="md" weight="bold" transform="uppercase" class={classes.textStyle}>Actions</Text>
</Flex>
</Box>
136 changes: 136 additions & 0 deletions frontend/src/lib/components/admin/UserRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script lang="ts">
import type { RoleMetadata } from '$lib/types/RoleMetadata';
import type { UserMetadata } from '$lib/types/UserMetadata';
import {
ActionIcon,
Box,
Button,
Flex,
NativeSelect,
Overlay,
Text,
Title,
Tooltip,
createStyles,
type theme
} from '@svelteuidev/core';
import { isAxiosError } from 'axios';
import { StarFilled, Update } from 'radix-icons-svelte';
import { clearSession } from '../../../auth/session';
import { editUser } from '../../../server/auth';
export let user: UserMetadata;
export let roles: RoleMetadata[] = [];
let role: string;
const useStyles = createStyles((theme: theme) => {
return {
root: {
[`${theme.dark} &`]: {
bc: theme.fn.themeColor('dark', 5),
color: 'white'
},
backgroundColor: '$gray50',
textAlign: 'center',
padding: '$10',
borderRadius: '$md',
marginTop: '$3',
marginBottom: '$3'
},
flexOverlay: {
display: 'flex',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
direction: 'column',
backdropFilter: 'blur(5px)'
}
};
});
const saveUser = async () => {
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) {
window.location.href = '/auth/login';
return;
}
try {
await editUser(accessToken, user.username, role);
if (user.username === localStorage.getItem('username')) {
window.location.href = '/user/account';
return;
}
location.reload();
} catch (error) {
if (!isAxiosError(error)) {
alert('An unknown error occurred.');
return;
}
if (error.response?.status === 401) {
await clearSession();
window.location.href = '/auth/login';
return;
}
if (error.response?.status === 403) {
alert('You do not have permission to edit this user.');
return;
}
alert('An error occurred while editing the user.');
}
};
let overlayShown = false;
$: ({ classes, getStyles } = useStyles());
</script>

<Box class={getStyles()}>
<Flex align="center" justify="space-evenly" style="height: 100%;">
<Text size="sm" css={{ flex: 1, textAlign: 'center' }}>
{user.username}
</Text>
<Text size="sm" css={{ flex: 1, textAlign: 'center' }}>
{user.role.toUpperCase()}
</Text>
<Flex justify="center" gap="xs" css={{ flex: 1 }}>
<Tooltip openDelay={10} label="Edit">
<ActionIcon variant="filled" color="blue" on:click={() => (overlayShown = true)}>
<Update size={20} />
</ActionIcon>
</Tooltip>
</Flex>
</Flex>
</Box>

{#if overlayShown}
<Overlay opacity={1} color="#000" zIndex={5} center class={classes.flexOverlay}>
<Box class={getStyles()}>
<Flex direction="column" align="space-evenly" gap="l" justify="center">
<Title order={3}>Edit User</Title>
<NativeSelect
placeholder="Role..."
label="Role"
description="Pick a role"
required
data={roles.map((role) => {
return { value: role.name, label: role.name.toUpperCase() };
})}
bind:value={role}
icon={StarFilled}
/>
<Flex gap="lg" justify="space-between">
<Button variant="filled" on:click={saveUser} disabled={!role}>Submit</Button>

<Button variant="light" on:click={() => (overlayShown = false)}>Close</Button>
</Flex>
</Flex>
</Box>
</Overlay>
{/if}
36 changes: 36 additions & 0 deletions frontend/src/routes/admin/users/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts">
import TitleUserRow from '$lib/components/admin/TitleUserRow.svelte';
import UserRow from '$lib/components/admin/UserRow.svelte';
import type { RoleMetadata } from '$lib/types/RoleMetadata';
import type { UserMetadata } from '$lib/types/UserMetadata';
import { Title } from '@svelteuidev/core';
import { onMount } from 'svelte';
import { getRoles, getUsers } from '../../../server/auth';
let users: UserMetadata[] = [];
let roles: RoleMetadata[] = [];
onMount(async () => {
const accessToken = localStorage.getItem('accessToken');
if (!accessToken) {
window.location.href = '/auth/login';
return;
}
users = await getUsers(accessToken);
roles = await getRoles(accessToken);
console.log('new roles', roles);
});
</script>

<div
style="display: flex; flex-direction: column; justify-content: center; align-items: center; margin-bottom: 2rem;"
>
<Title order={1}>Users</Title>
</div>

<TitleUserRow />
{#each users as user}
<UserRow {user} {roles} />
{/each}
8 changes: 8 additions & 0 deletions frontend/src/routes/user/account/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { isAxiosError } from 'axios';
import { LockClosed, LockOpen2 } from 'radix-icons-svelte';
import { onMount } from 'svelte';
import { clearSession } from '../../../auth/session';
import { getUserMetadata } from '../../../server/auth';
import { getFilesForSpecifiedUser } from '../../../server/files';
import { getPermanentToken } from '../../../server/sharex';
Expand Down Expand Up @@ -34,6 +35,12 @@
return;
}
if (error.response?.status === 401) {
await clearSession();
window.location.href = '/auth/login';
return;
}
alert('An error occurred while fetching the user data.');
}
});
Expand Down Expand Up @@ -111,6 +118,7 @@
<br />
<Title>Admin</Title>

<Anchor href="/admin/users">Manage Users</Anchor>
<Anchor href="/admin/roles">Manage Roles</Anchor>
{/if}
</Flex>
27 changes: 27 additions & 0 deletions frontend/src/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,30 @@ export const editRole = async (

return result.data;
};

export const getUsers = async (token: string) => {
const result = await axios.get<UserMetadata[]>(`${BASE_URL}/auth/users`, {
headers: {
authorization: `Bearer ${token}`
}
});

return result.data;
};

export const editUser = async (token: string, username: string, role_name: string) => {
const result = await axios.post(
`${BASE_URL}/auth/users/edit`,
{
username,
role_name
},
{
headers: {
authorization: `Bearer ${token}`
}
}
);

return result.data;
};

0 comments on commit 72efd65

Please sign in to comment.