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

✨ User management #2499

Merged
merged 10 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
This file is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.14.3]
## [0.15.0]

### Added

- Add option to disable registration to admin panel [#2499](https://github.com/ViewTube/viewtube/pull/2499)
- Add option to require login everywhere to admin panel [#2499](https://github.com/ViewTube/viewtube/pull/2499)
- Allow users to be added via the admin panel [#2499](https://github.com/ViewTube/viewtube/pull/2499)

### Fixed

- Use loadeddata event to fix video player aspect ratio (Thanks @themisir) [#2505](https://github.com/ViewTube/viewtube/pull/2505)
- Use value of apiUrl, fixes account deletion issue (Thanks @alvanrahimli) [#2515](https://github.com/ViewTube/viewtube/pull/2515)
- Fix logout not working [#2499](https://github.com/ViewTube/viewtube/pull/2499)

## [0.14.2]

Expand Down Expand Up @@ -64,11 +71,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Reworked authentication with new devices interface in profile [#2186](https://github.com/ViewTube/viewtube/pull/2186)
- Add support for socks proxies [#2269](https://github.com/ViewTube/viewtube/pull/2269)

### Fixed

- Make client cookie logic more robust [#2259](https://github.com/ViewTube/viewtube/pull/2259)
- Upgrade packages and cleanup unused [#2261](https://github.com/ViewTube/viewtube/pull/2261)
- Fix history and profile page [#2282](https://github.com/ViewTube/viewtube/pull/2282)

## [0.12.2]

Expand Down Expand Up @@ -374,8 +383,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial release

[unreleased]: https://github.com/viewtube/viewtube/compare/v0.14.3...development
[0.14.3]: https://github.com/viewtube/viewtube/compare/v0.14.2...v0.14.3
[unreleased]: https://github.com/viewtube/viewtube/compare/v0.15.0...development
[0.14.3]: https://github.com/viewtube/viewtube/compare/v0.14.2...v0.15.0
[0.14.2]: https://github.com/viewtube/viewtube/compare/v0.14.1...v0.14.2
[0.14.1]: https://github.com/viewtube/viewtube/compare/v0.14.0...v0.14.1
[0.14.0]: https://github.com/viewtube/viewtube/compare/v0.13.1...v0.14.0
Expand Down
88 changes: 88 additions & 0 deletions client/components/admin/CreateUser.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { useMessagesStore } from '@/store/messages';

const username = ref('');
const password = ref('');

const { createUser } = useCreateUser();
const messagesStore = useMessagesStore();

const createUserAdmin = async () => {
if (username.value && password.value) {
const createdUser = await createUser({
username: username.value,
password: password.value
})
.then(
res => res,
reason => {
throw reason;
}
)
.catch(err => {
messagesStore.createMessage({
type: 'error',
title: 'Error creating user',
message: err?.data?.message ?? 'Unknown error'
});
});

username.value = '';
password.value = '';

if (createdUser) {
messagesStore.createMessage({
type: 'info',
title: 'User created',
message: `User ${createdUser.username} created`
});
}
}
};
</script>

<template>
<SectionSubtitle title="Create new user" />

<form id="create-user" class="create-user-form" method="post" @submit.prevent="createUserAdmin">
<FormInput id="username" v-model="username" type="username" label="Username" />
<FormInput id="password" v-model="password" type="password" label="Password" />
<FormSubmitButton :label="'Create user'" />
</form>
</template>

<style lang="scss" scoped>
.create-user-form {
display: flex;
flex-direction: row;

@media screen and (max-width: $mobile-width) {
flex-direction: column;
}

:deep(.submit-button) {
width: auto;
padding: 5px 15px;
margin: 16px 0;
}

:deep(.form-input > .input) {
margin-left: 0;
margin-right: 10px;
width: calc(100% - 10px);

@media screen and (max-width: $mobile-width) {
margin-right: 0;
width: 100%;
}
}

:deep(.form-input > .input-label) {
left: 14px;
}

:deep(.form-input > .form-input-icon) {
right: 25px;
}
}
</style>
74 changes: 74 additions & 0 deletions client/components/admin/UserManagement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { useMessagesStore } from '~/store/messages';

const { apiUrl } = useApiUrl();
const { vtFetch } = useVtFetch();
const messagesStore = useMessagesStore();

const { data, refresh } = useGetServerSettings();

const onRegistrationEnabledChange = async (value: boolean) => {
await vtFetch(`${apiUrl.value}admin/server-settings`, {
method: 'POST',
body: {
registrationEnabled: value
}
});
await refresh();
await nextTick();
messagesStore.createMessage({
type: 'info',
title: 'Server settings updated',
message: `Public registration ${
value ? 'enabled' : 'disabled'
}. Restart the server for the changes to take effect.`
});
};

const onRequireLoginEverywhereChange = async (value: boolean) => {
await vtFetch(`${apiUrl.value}admin/server-settings`, {
method: 'POST',
body: {
requireLoginEverywhere: value
}
});
await refresh();
await nextTick();
messagesStore.createMessage({
type: 'info',
title: 'Server settings updated',
message: `Require login everywhere ${
value ? 'enabled' : 'disabled'
}. Restart the server for the changes to take effect.`
});
};
</script>

<template>
<div v-if="data" class="user-management">
<ButtonsSwitchButton
label="Enable public registration (restart required)"
:value="data.registrationEnabled"
small-label="Anyone can create an account"
small-label-negative="Accounts can only be created by the admin"
@valuechange="onRegistrationEnabledChange"
/>
<ButtonsSwitchButton
label="Require login everywhere (restart required)"
:value="data.requireLoginEverywhere"
small-label="Users must be logged in to access the site"
small-label-negative="Users can access the site without being logged in"
@valuechange="onRequireLoginEverywhereChange"
/>
</div>
<Spinner v-if="!data" />
</template>

<style lang="scss" scoped>
.user-management {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 15px 0;
}
</style>
14 changes: 14 additions & 0 deletions client/components/admin/Users.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts"></script>

<template>
<div class="admin-users">
<SectionSubtitle title="Options" />
<AdminUserManagement />
<AdminCreateUser />
</div>
</template>

<style lang="scss" scoped>
.admin-users {
}
</style>
26 changes: 21 additions & 5 deletions client/components/buttons/SwitchButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@
</div>
<div v-if="label" class="label-container">
<label :for="`switch-button-${randomId}`" class="label">{{ label }}</label>
<label v-if="smallLabel" :for="`switch-button-${randomId}`" class="small-label">{{
smallLabel
}}</label>

<label
v-if="smallLabel && smallLabelNegative && !value"
:for="`switch-button-${randomId}`"
class="small-label"
>
{{ smallLabelNegative }}
</label>
<label v-else-if="smallLabel" :for="`switch-button-${randomId}`" class="small-label">
{{ smallLabel }}
</label>
</div>
</div>
</template>
Expand All @@ -30,10 +38,16 @@
name: 'SwitchButton',
props: {
value: Boolean,
label: String,

Check warning on line 41 in client/components/buttons/SwitchButton.vue

View workflow job for this annotation

GitHub Actions / lint:client

Prop 'label' requires default value to be set
smallLabel: {
type: String,
required: false
required: false,
default: null
},
smallLabelNegative: {
type: String,
required: false,
default: null
},
disabled: Boolean,
right: {
Expand All @@ -43,7 +57,7 @@
},
setup(_, { emit }) {
const onChange = (e: any): void => {
emit('valuechange', e.target.checked);

Check warning on line 60 in client/components/buttons/SwitchButton.vue

View workflow job for this annotation

GitHub Actions / lint:client

The "valuechange" event has been triggered but not declared on `emits` option
};

const ID = (_length = 13) => {
Expand Down Expand Up @@ -176,7 +190,9 @@
background-color: var(--theme-color);
display: block;
position: absolute;
transition: background-color 300ms $intro-easing, left 300ms $overshoot-easing;
transition:
background-color 300ms $intro-easing,
left 300ms $overshoot-easing;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion client/components/form/LoginForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<InformationHint class="hint">Usernames are case sensitive</InformationHint>
<Spinner />
<form id="login" method="post" @submit.prevent="login">
<FormInput :id="'username'" v-model="username" :label="'username'" :type="'username'" />
<FormInput :id="'username'" v-model="username" :label="'username'" :type="'username'" autofocus />
<FormInput :id="'password'" v-model="password" :label="'password'" :type="'password'" />
<SubmitButton :label="'Sign in'" />
</form>
Expand All @@ -29,7 +29,7 @@
InformationHint
},
props: {
complete: Function

Check warning on line 32 in client/components/form/LoginForm.vue

View workflow job for this annotation

GitHub Actions / lint:client

Prop 'complete' requires default value to be set
},
setup(props) {
const route = useRoute();
Expand Down
2 changes: 1 addition & 1 deletion client/components/form/RegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ captchaStore.getCaptcha();
<InformationHint class="hint">Usernames are case sensitive</InformationHint>
<Spinner />
<form id="register" ref="registerForm" method="post" @submit.prevent="register">
<FormInput :id="'username'" v-model="username" :label="'username'" :type="'username'" />
<FormInput :id="'username'" v-model="username" :label="'username'" :type="'username'" autofocus />
<FormInput :id="'password'" v-model="password" :label="'password'" :type="'password'" />
<FormInput
:id="'repeat-password'"
Expand Down
13 changes: 5 additions & 8 deletions client/components/header/UserMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
>Sign in</a
>
<a
v-show="!userAuthenticated"
v-show="!userAuthenticated && registrationEnabled"
id="register"
v-tippy="'Sign up'"
:href="`/register${currentPageRef('register')}`"
Expand Down Expand Up @@ -100,7 +100,7 @@
<div class="menu-btn-content"><VTIcon name="mdi:account-circle" />Sign in</div>
</a>
<a
v-show="!userAuthenticated"
v-show="!userAuthenticated && registrationEnabled"
id="register-btn"
v-tippy="'Sign up'"
:href="`/register${currentPageRef('register')}`"
Expand Down Expand Up @@ -170,6 +170,7 @@ export default defineComponent({

const { apiUrl } = useApiUrl();
const router = useRouter();
const { registrationEnabled } = useRegistrationEnabled();

const accountMenuVisible = ref(false);
const settingsOpen = ref(false);
Expand Down Expand Up @@ -248,10 +249,6 @@ export default defineComponent({
router.push(`/register${currentPageRef('register')}`);
closeAllPopups();
};
const logout = (): void => {
userStore.logout();
closeAllPopups();
};

const onEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
Expand Down Expand Up @@ -290,13 +287,13 @@ export default defineComponent({
openSubscriptions,
getProfileImageUrl,
login,
logout,
register,
loginOpen,
registerOpen,
onLoginClick,
onRegisterClick,
userStore
userStore,
registrationEnabled
};
}
});
Expand Down
28 changes: 25 additions & 3 deletions client/composables/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ export const useGetAdminInfo = () => {
const { apiUrl } = useApiUrl();
const { vtFetch } = useVtFetch();

return useLazyAsyncData<ApiDto<'InfoDto'>>(
'admin-info',
() => vtFetch(`${apiUrl.value}admin/info`),
return useLazyAsyncData('admin-info', () => vtFetch(`${apiUrl.value}admin/info`), {
server: false
});
};

export const useGetServerSettings = () => {
const { apiUrl } = useApiUrl();
const { vtFetch } = useVtFetch();

return useLazyAsyncData(
'server-settings',
() => vtFetch<ApiDto<'ServerSettingsDto'>>(`${apiUrl.value}admin/server-settings`),
{
server: false
}
Expand All @@ -36,3 +45,16 @@ export const useGetBlockedVideos = () => {
}
);
};

export const useCreateUser = () => {
const { apiUrl } = useApiUrl();
const { vtFetch } = useVtFetch();

const createUser = async (data: { username: string; password: string }) => {
return vtFetch<ApiDto<'UserprofileDto'>>(`${apiUrl.value}admin/users`, {
method: 'POST',
body: data
});
};
return { createUser };
};
6 changes: 6 additions & 0 deletions client/composables/registrationEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const useRegistrationEnabled = () => {
const config = useRuntimeConfig();
return {
registrationEnabled: config.public.registrationEnabled
};
};
2 changes: 1 addition & 1 deletion client/composables/vtFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const useVtFetch = () => {
requestOptions.headers = { ...requestOptions.headers, cookie: cookieHeader };
}

if (process.server && !options?.external && global.nestApp) {
if (process.server && !options?.external && global?.nestApp) {
const response = await global.nestApp.inject({
method: (requestOptions.method ?? 'GET') as HTTPMethods,
url: request.toString(),
Expand Down
Loading
Loading