Support roles with rights and add moderator role
This commit is contained in:
parent
e02573ad67
commit
954605a804
|
@ -8,15 +8,14 @@ import { FriendsRoutes } from './friends'
|
||||||
import { RequestSchedulersRoutes } from './request-schedulers'
|
import { RequestSchedulersRoutes } from './request-schedulers'
|
||||||
import { UsersRoutes } from './users'
|
import { UsersRoutes } from './users'
|
||||||
import { VideoAbusesRoutes } from './video-abuses'
|
import { VideoAbusesRoutes } from './video-abuses'
|
||||||
import { AdminGuard } from './admin-guard.service'
|
|
||||||
import { VideoBlacklistRoutes } from './video-blacklist'
|
import { VideoBlacklistRoutes } from './video-blacklist'
|
||||||
|
|
||||||
const adminRoutes: Routes = [
|
const adminRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: AdminComponent,
|
component: AdminComponent,
|
||||||
canActivate: [ MetaGuard, AdminGuard ],
|
canActivate: [ MetaGuard ],
|
||||||
canActivateChild: [ MetaGuard, AdminGuard ],
|
canActivateChild: [ MetaGuard ],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponen
|
||||||
import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
|
import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
|
||||||
import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
|
import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-blacklist'
|
||||||
import { SharedModule } from '../shared'
|
import { SharedModule } from '../shared'
|
||||||
import { AdminGuard } from './admin-guard.service'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -45,8 +44,7 @@ import { AdminGuard } from './admin-guard.service'
|
||||||
providers: [
|
providers: [
|
||||||
FriendService,
|
FriendService,
|
||||||
RequestSchedulersService,
|
RequestSchedulersService,
|
||||||
UserService,
|
UserService
|
||||||
AdminGuard
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminModule { }
|
export class AdminModule { }
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
import { UserRightGuard } from '../../core'
|
||||||
import { FriendsComponent } from './friends.component'
|
import { FriendsComponent } from './friends.component'
|
||||||
import { FriendAddComponent } from './friend-add'
|
import { FriendAddComponent } from './friend-add'
|
||||||
import { FriendListComponent } from './friend-list'
|
import { FriendListComponent } from './friend-list'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
|
|
||||||
export const FriendsRoutes: Routes = [
|
export const FriendsRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'friends',
|
path: 'friends',
|
||||||
component: FriendsComponent,
|
component: FriendsComponent,
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_PODS
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
import { UserRightGuard } from '../../core'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
import { RequestSchedulersComponent } from './request-schedulers.component'
|
import { RequestSchedulersComponent } from './request-schedulers.component'
|
||||||
import { RequestSchedulersStatsComponent } from './request-schedulers-stats'
|
import { RequestSchedulersStatsComponent } from './request-schedulers-stats'
|
||||||
|
|
||||||
|
@ -7,6 +9,10 @@ export const RequestSchedulersRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'requests',
|
path: 'requests',
|
||||||
component: RequestSchedulersComponent,
|
component: RequestSchedulersComponent,
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_REQUEST_SCHEDULERS
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -9,10 +9,11 @@ import {
|
||||||
USER_USERNAME,
|
USER_USERNAME,
|
||||||
USER_EMAIL,
|
USER_EMAIL,
|
||||||
USER_PASSWORD,
|
USER_PASSWORD,
|
||||||
USER_VIDEO_QUOTA
|
USER_VIDEO_QUOTA,
|
||||||
|
USER_ROLE
|
||||||
} from '../../../shared'
|
} from '../../../shared'
|
||||||
import { ServerService } from '../../../core'
|
import { ServerService } from '../../../core'
|
||||||
import { UserCreate } from '../../../../../../shared'
|
import { UserCreate, UserRole } from '../../../../../../shared'
|
||||||
import { UserEdit } from './user-edit'
|
import { UserEdit } from './user-edit'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -28,12 +29,14 @@ export class UserAddComponent extends UserEdit implements OnInit {
|
||||||
'username': '',
|
'username': '',
|
||||||
'email': '',
|
'email': '',
|
||||||
'password': '',
|
'password': '',
|
||||||
|
'role': '',
|
||||||
'videoQuota': ''
|
'videoQuota': ''
|
||||||
}
|
}
|
||||||
validationMessages = {
|
validationMessages = {
|
||||||
'username': USER_USERNAME.MESSAGES,
|
'username': USER_USERNAME.MESSAGES,
|
||||||
'email': USER_EMAIL.MESSAGES,
|
'email': USER_EMAIL.MESSAGES,
|
||||||
'password': USER_PASSWORD.MESSAGES,
|
'password': USER_PASSWORD.MESSAGES,
|
||||||
|
'role': USER_ROLE.MESSAGES,
|
||||||
'videoQuota': USER_VIDEO_QUOTA.MESSAGES
|
'videoQuota': USER_VIDEO_QUOTA.MESSAGES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +55,7 @@ export class UserAddComponent extends UserEdit implements OnInit {
|
||||||
username: [ '', USER_USERNAME.VALIDATORS ],
|
username: [ '', USER_USERNAME.VALIDATORS ],
|
||||||
email: [ '', USER_EMAIL.VALIDATORS ],
|
email: [ '', USER_EMAIL.VALIDATORS ],
|
||||||
password: [ '', USER_PASSWORD.VALIDATORS ],
|
password: [ '', USER_PASSWORD.VALIDATORS ],
|
||||||
|
role: [ UserRole.USER, USER_ROLE.VALIDATORS ],
|
||||||
videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
|
videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="role">Role</label>
|
||||||
|
<select class="form-control" id="role" formControlName="role">
|
||||||
|
<option *ngFor="let role of roles" [value]="role.value">
|
||||||
|
{{ role.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div *ngIf="formErrors.role" class="alert alert-danger">
|
||||||
|
{{ formErrors.role }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="videoQuota">Video quota</label>
|
<label for="videoQuota">Video quota</label>
|
||||||
<select class="form-control" id="videoQuota" formControlName="videoQuota">
|
<select class="form-control" id="videoQuota" formControlName="videoQuota">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ServerService } from '../../../core'
|
import { ServerService } from '../../../core'
|
||||||
import { FormReactive } from '../../../shared'
|
import { FormReactive } from '../../../shared'
|
||||||
import { VideoResolution } from '../../../../../../shared/models/videos/video-resolution.enum'
|
import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared'
|
||||||
|
|
||||||
export abstract class UserEdit extends FormReactive {
|
export abstract class UserEdit extends FormReactive {
|
||||||
videoQuotaOptions = [
|
videoQuotaOptions = [
|
||||||
|
@ -14,6 +14,8 @@ export abstract class UserEdit extends FormReactive {
|
||||||
{ value: 50 * 1024 * 1024 * 1024, label: '50GB' }
|
{ value: 50 * 1024 * 1024 * 1024, label: '50GB' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key, label: USER_ROLE_LABELS[key] }))
|
||||||
|
|
||||||
protected abstract serverService: ServerService
|
protected abstract serverService: ServerService
|
||||||
abstract isCreation (): boolean
|
abstract isCreation (): boolean
|
||||||
abstract getFormButtonTitle (): string
|
abstract getFormButtonTitle (): string
|
||||||
|
|
|
@ -6,11 +6,15 @@ import { Subscription } from 'rxjs/Subscription'
|
||||||
import { NotificationsService } from 'angular2-notifications'
|
import { NotificationsService } from 'angular2-notifications'
|
||||||
|
|
||||||
import { UserService } from '../shared'
|
import { UserService } from '../shared'
|
||||||
import { USER_EMAIL, USER_VIDEO_QUOTA } from '../../../shared'
|
import {
|
||||||
|
USER_EMAIL,
|
||||||
|
USER_VIDEO_QUOTA,
|
||||||
|
USER_ROLE,
|
||||||
|
User
|
||||||
|
} from '../../../shared'
|
||||||
import { ServerService } from '../../../core'
|
import { ServerService } from '../../../core'
|
||||||
import { UserUpdate } from '../../../../../../shared/models/users/user-update.model'
|
|
||||||
import { User } from '../../../shared/users/user.model'
|
|
||||||
import { UserEdit } from './user-edit'
|
import { UserEdit } from './user-edit'
|
||||||
|
import { UserUpdate, UserRole } from '../../../../../../shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-user-update',
|
selector: 'my-user-update',
|
||||||
|
@ -25,10 +29,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
form: FormGroup
|
form: FormGroup
|
||||||
formErrors = {
|
formErrors = {
|
||||||
'email': '',
|
'email': '',
|
||||||
|
'role': '',
|
||||||
'videoQuota': ''
|
'videoQuota': ''
|
||||||
}
|
}
|
||||||
validationMessages = {
|
validationMessages = {
|
||||||
'email': USER_EMAIL.MESSAGES,
|
'email': USER_EMAIL.MESSAGES,
|
||||||
|
'role': USER_ROLE.MESSAGES,
|
||||||
'videoQuota': USER_VIDEO_QUOTA.MESSAGES
|
'videoQuota': USER_VIDEO_QUOTA.MESSAGES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +54,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
buildForm () {
|
buildForm () {
|
||||||
this.form = this.formBuilder.group({
|
this.form = this.formBuilder.group({
|
||||||
email: [ '', USER_EMAIL.VALIDATORS ],
|
email: [ '', USER_EMAIL.VALIDATORS ],
|
||||||
|
role: [ '', USER_ROLE.VALIDATORS ],
|
||||||
videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
|
videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -103,6 +110,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
email: userJson.email,
|
email: userJson.email,
|
||||||
|
role: userJson.role,
|
||||||
videoQuota: userJson.videoQuota
|
videoQuota: userJson.videoQuota
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<p-column field="username" header="Username" [sortable]="true"></p-column>
|
<p-column field="username" header="Username" [sortable]="true"></p-column>
|
||||||
<p-column field="email" header="Email"></p-column>
|
<p-column field="email" header="Email"></p-column>
|
||||||
<p-column field="videoQuota" header="Video quota"></p-column>
|
<p-column field="videoQuota" header="Video quota"></p-column>
|
||||||
<p-column field="role" header="Role"></p-column>
|
<p-column field="roleLabel" header="Role"></p-column>
|
||||||
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
|
<p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
|
||||||
<p-column header="Edit" styleClass="action-cell">
|
<p-column header="Edit" styleClass="action-cell">
|
||||||
<ng-template pTemplate="body" let-user="rowData">
|
<ng-template pTemplate="body" let-user="rowData">
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
import { UserRightGuard } from '../../core'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
import { UsersComponent } from './users.component'
|
import { UsersComponent } from './users.component'
|
||||||
import { UserAddComponent, UserUpdateComponent } from './user-edit'
|
import { UserAddComponent, UserUpdateComponent } from './user-edit'
|
||||||
import { UserListComponent } from './user-list'
|
import { UserListComponent } from './user-list'
|
||||||
|
@ -8,6 +10,10 @@ export const UsersRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
component: UsersComponent,
|
component: UsersComponent,
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_USERS
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
import { UserRightGuard } from '../../core'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
import { VideoAbusesComponent } from './video-abuses.component'
|
import { VideoAbusesComponent } from './video-abuses.component'
|
||||||
import { VideoAbuseListComponent } from './video-abuse-list'
|
import { VideoAbuseListComponent } from './video-abuse-list'
|
||||||
|
|
||||||
export const VideoAbusesRoutes: Routes = [
|
export const VideoAbusesRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'video-abuses',
|
path: 'video-abuses',
|
||||||
component: VideoAbusesComponent
|
component: VideoAbusesComponent,
|
||||||
,
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_VIDEO_ABUSES
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Routes } from '@angular/router'
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
import { UserRightGuard } from '../../core'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
import { VideoBlacklistComponent } from './video-blacklist.component'
|
import { VideoBlacklistComponent } from './video-blacklist.component'
|
||||||
import { VideoBlacklistListComponent } from './video-blacklist-list'
|
import { VideoBlacklistListComponent } from './video-blacklist-list'
|
||||||
|
|
||||||
|
@ -7,6 +9,10 @@ export const VideoBlacklistRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'video-blacklist',
|
path: 'video-blacklist',
|
||||||
component: VideoBlacklistComponent,
|
component: VideoBlacklistComponent,
|
||||||
|
canActivate: [ UserRightGuard ],
|
||||||
|
data: {
|
||||||
|
userRight: UserRight.MANAGE_VIDEO_BLACKLIST
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Do not use the barrel (dependency loop)
|
// Do not use the barrel (dependency loop)
|
||||||
import { UserRole } from '../../../../../shared/models/users/user-role.type'
|
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
|
||||||
import { User, UserConstructorHash } from '../../shared/users/user.model'
|
import { User, UserConstructorHash } from '../../shared/users/user.model'
|
||||||
|
import { UserRight } from '../../../../../shared/models/users/user-right.enum'
|
||||||
|
|
||||||
export type TokenOptions = {
|
export type TokenOptions = {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
|
@ -81,7 +82,7 @@ export class AuthUser extends User {
|
||||||
id: parseInt(localStorage.getItem(this.KEYS.ID), 10),
|
id: parseInt(localStorage.getItem(this.KEYS.ID), 10),
|
||||||
username: localStorage.getItem(this.KEYS.USERNAME),
|
username: localStorage.getItem(this.KEYS.USERNAME),
|
||||||
email: localStorage.getItem(this.KEYS.EMAIL),
|
email: localStorage.getItem(this.KEYS.EMAIL),
|
||||||
role: localStorage.getItem(this.KEYS.ROLE) as UserRole,
|
role: parseInt(localStorage.getItem(this.KEYS.ROLE), 10) as UserRole,
|
||||||
displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
|
displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
|
||||||
},
|
},
|
||||||
Tokens.load()
|
Tokens.load()
|
||||||
|
@ -122,11 +123,15 @@ export class AuthUser extends User {
|
||||||
this.tokens.refreshToken = refreshToken
|
this.tokens.refreshToken = refreshToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasRight(right: UserRight) {
|
||||||
|
return hasUserRight(this.role, right)
|
||||||
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
localStorage.setItem(AuthUser.KEYS.ID, this.id.toString())
|
localStorage.setItem(AuthUser.KEYS.ID, this.id.toString())
|
||||||
localStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
|
localStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
|
||||||
localStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
|
localStorage.setItem(AuthUser.KEYS.EMAIL, this.email)
|
||||||
localStorage.setItem(AuthUser.KEYS.ROLE, this.role)
|
localStorage.setItem(AuthUser.KEYS.ROLE, this.role.toString())
|
||||||
localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
|
localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
|
||||||
this.tokens.save()
|
this.tokens.save()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
// Do not use the barrel (dependency loop)
|
// Do not use the barrel (dependency loop)
|
||||||
import { RestExtractor } from '../../shared/rest'
|
import { RestExtractor } from '../../shared/rest'
|
||||||
import { UserLogin } from '../../../../../shared/models/users/user-login.model'
|
import { UserLogin } from '../../../../../shared/models/users/user-login.model'
|
||||||
import { User, UserConstructorHash } from '../../shared/users/user.model'
|
import { UserConstructorHash } from '../../shared/users/user.model'
|
||||||
|
|
||||||
interface UserLoginWithUsername extends UserLogin {
|
interface UserLoginWithUsername extends UserLogin {
|
||||||
access_token: string
|
access_token: string
|
||||||
|
@ -126,12 +126,6 @@ export class AuthService {
|
||||||
return this.user
|
return this.user
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdmin () {
|
|
||||||
if (this.user === null) return false
|
|
||||||
|
|
||||||
return this.user.isAdmin()
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoggedIn () {
|
isLoggedIn () {
|
||||||
return !!this.getAccessToken()
|
return !!this.getAccessToken()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './auth-status.model'
|
export * from './auth-status.model'
|
||||||
export * from './auth-user.model'
|
export * from './auth-user.model'
|
||||||
export * from './auth.service'
|
export * from './auth.service'
|
||||||
export * from './login-guard.service'
|
export * from '../routing/login-guard.service'
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||||
import { SimpleNotificationsModule } from 'angular2-notifications'
|
import { SimpleNotificationsModule } from 'angular2-notifications'
|
||||||
import { ModalModule } from 'ngx-bootstrap/modal'
|
import { ModalModule } from 'ngx-bootstrap/modal'
|
||||||
|
|
||||||
import { AuthService, LoginGuard } from './auth'
|
import { AuthService } from './auth'
|
||||||
|
import { LoginGuard, UserRightGuard } from './routing'
|
||||||
import { ServerService } from './server'
|
import { ServerService } from './server'
|
||||||
import { ConfirmComponent, ConfirmService } from './confirm'
|
import { ConfirmComponent, ConfirmService } from './confirm'
|
||||||
import { MenuComponent, MenuAdminComponent } from './menu'
|
import { MenuComponent, MenuAdminComponent } from './menu'
|
||||||
|
@ -42,7 +43,8 @@ import { throwIfAlreadyLoaded } from './module-import-guard'
|
||||||
AuthService,
|
AuthService,
|
||||||
ConfirmService,
|
ConfirmService,
|
||||||
ServerService,
|
ServerService,
|
||||||
LoginGuard
|
LoginGuard,
|
||||||
|
UserRightGuard
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
<menu>
|
<menu>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<a routerLink="/admin/users/list" routerLinkActive="active">
|
<a *ngIf="hasUsersRight()" routerLink="/admin/users" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-user"></span>
|
<span class="hidden-xs glyphicon glyphicon-user"></span>
|
||||||
List users
|
List users
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a routerLink="/admin/friends/list" routerLinkActive="active">
|
<a *ngIf="hasFriendsRight()" routerLink="/admin/friends" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-cloud"></span>
|
<span class="hidden-xs glyphicon glyphicon-cloud"></span>
|
||||||
List friends
|
List friends
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a routerLink="/admin/requests/stats" routerLinkActive="active">
|
<a *ngIf="hasRequestsStatRight()" routerLink="/admin/requests/stats" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-stats"></span>
|
<span class="hidden-xs glyphicon glyphicon-stats"></span>
|
||||||
Request stats
|
Request stats
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a routerLink="/admin/video-abuses/list" routerLinkActive="active">
|
<a *ngIf="hasVideoAbusesRight()" routerLink="/admin/video-abuses" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-alert"></span>
|
<span class="hidden-xs glyphicon glyphicon-alert"></span>
|
||||||
Video abuses
|
Video abuses
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a routerLink="/admin/video-blacklist/list" routerLinkActive="active">
|
<a *ngIf="hasVideoBlacklistRight()" routerLink="/admin/video-blacklist" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-eye-close"></span>
|
<span class="hidden-xs glyphicon glyphicon-eye-close"></span>
|
||||||
Video blacklist
|
Video blacklist
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,8 +1,33 @@
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
|
|
||||||
|
import { AuthService } from '../auth/auth.service'
|
||||||
|
import { UserRight } from '../../../../../shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-menu-admin',
|
selector: 'my-menu-admin',
|
||||||
templateUrl: './menu-admin.component.html',
|
templateUrl: './menu-admin.component.html',
|
||||||
styleUrls: [ './menu.component.scss' ]
|
styleUrls: [ './menu.component.scss' ]
|
||||||
})
|
})
|
||||||
export class MenuAdminComponent { }
|
export class MenuAdminComponent {
|
||||||
|
constructor (private auth: AuthService) {}
|
||||||
|
|
||||||
|
hasUsersRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_USERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFriendsRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_PODS)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRequestsStatRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_REQUEST_SCHEDULERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVideoAbusesRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVideoBlacklistRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,10 +39,10 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isUserAdmin()" class="panel-block">
|
<div *ngIf="userHasAdminAccess" class="panel-block">
|
||||||
<div class="block-title">Other</div>
|
<div class="block-title">Other</div>
|
||||||
|
|
||||||
<a routerLink="/admin" routerLinkActive="active">
|
<a [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active">
|
||||||
<span class="hidden-xs glyphicon glyphicon-cog"></span>
|
<span class="hidden-xs glyphicon glyphicon-cog"></span>
|
||||||
Administration
|
Administration
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Router } from '@angular/router'
|
||||||
|
|
||||||
import { AuthService, AuthStatus } from '../auth'
|
import { AuthService, AuthStatus } from '../auth'
|
||||||
import { ServerService } from '../server'
|
import { ServerService } from '../server'
|
||||||
|
import { UserRight } from '../../../../../shared/models/users/user-right.enum'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-menu',
|
selector: 'my-menu',
|
||||||
|
@ -11,6 +12,15 @@ import { ServerService } from '../server'
|
||||||
})
|
})
|
||||||
export class MenuComponent implements OnInit {
|
export class MenuComponent implements OnInit {
|
||||||
isLoggedIn: boolean
|
isLoggedIn: boolean
|
||||||
|
userHasAdminAccess = false
|
||||||
|
|
||||||
|
private routesPerRight = {
|
||||||
|
[UserRight.MANAGE_USERS]: '/admin/users',
|
||||||
|
[UserRight.MANAGE_PODS]: '/admin/friends',
|
||||||
|
[UserRight.MANAGE_REQUEST_SCHEDULERS]: '/admin/requests/stats',
|
||||||
|
[UserRight.MANAGE_VIDEO_ABUSES]: '/admin/video-abuses',
|
||||||
|
[UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/video-blacklist'
|
||||||
|
}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
@ -20,14 +30,17 @@ export class MenuComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.isLoggedIn = this.authService.isLoggedIn()
|
this.isLoggedIn = this.authService.isLoggedIn()
|
||||||
|
this.computeIsUserHasAdminAccess()
|
||||||
|
|
||||||
this.authService.loginChangedSource.subscribe(
|
this.authService.loginChangedSource.subscribe(
|
||||||
status => {
|
status => {
|
||||||
if (status === AuthStatus.LoggedIn) {
|
if (status === AuthStatus.LoggedIn) {
|
||||||
this.isLoggedIn = true
|
this.isLoggedIn = true
|
||||||
|
this.computeIsUserHasAdminAccess()
|
||||||
console.log('Logged in.')
|
console.log('Logged in.')
|
||||||
} else if (status === AuthStatus.LoggedOut) {
|
} else if (status === AuthStatus.LoggedOut) {
|
||||||
this.isLoggedIn = false
|
this.isLoggedIn = false
|
||||||
|
this.computeIsUserHasAdminAccess()
|
||||||
console.log('Logged out.')
|
console.log('Logged out.')
|
||||||
} else {
|
} else {
|
||||||
console.error('Unknown auth status: ' + status)
|
console.error('Unknown auth status: ' + status)
|
||||||
|
@ -40,8 +53,31 @@ export class MenuComponent implements OnInit {
|
||||||
return this.serverService.getConfig().signup.allowed
|
return this.serverService.getConfig().signup.allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserAdmin () {
|
getFirstAdminRightAvailable () {
|
||||||
return this.authService.isAdmin()
|
const user = this.authService.getUser()
|
||||||
|
if (!user) return undefined
|
||||||
|
|
||||||
|
const adminRights = [
|
||||||
|
UserRight.MANAGE_USERS,
|
||||||
|
UserRight.MANAGE_PODS,
|
||||||
|
UserRight.MANAGE_REQUEST_SCHEDULERS,
|
||||||
|
UserRight.MANAGE_VIDEO_ABUSES,
|
||||||
|
UserRight.MANAGE_VIDEO_BLACKLIST
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const adminRight of adminRights) {
|
||||||
|
if (user.hasRight(adminRight)) {
|
||||||
|
return adminRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstAdminRouteAvailable () {
|
||||||
|
const right = this.getFirstAdminRightAvailable()
|
||||||
|
|
||||||
|
return this.routesPerRight[right]
|
||||||
}
|
}
|
||||||
|
|
||||||
logout () {
|
logout () {
|
||||||
|
@ -49,4 +85,10 @@ export class MenuComponent implements OnInit {
|
||||||
// Redirect to home page
|
// Redirect to home page
|
||||||
this.router.navigate(['/videos/list'])
|
this.router.navigate(['/videos/list'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private computeIsUserHasAdminAccess () {
|
||||||
|
const right = this.getFirstAdminRightAvailable()
|
||||||
|
|
||||||
|
this.userHasAdminAccess = right !== undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
|
export * from './login-guard.service'
|
||||||
|
export * from './user-right-guard.service'
|
||||||
export * from './preload-selected-modules-list'
|
export * from './preload-selected-modules-list'
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Router
|
Router
|
||||||
} from '@angular/router'
|
} from '@angular/router'
|
||||||
|
|
||||||
import { AuthService } from './auth.service'
|
import { AuthService } from '../auth/auth.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoginGuard implements CanActivate, CanActivateChild {
|
export class LoginGuard implements CanActivate, CanActivateChild {
|
|
@ -7,10 +7,10 @@ import {
|
||||||
Router
|
Router
|
||||||
} from '@angular/router'
|
} from '@angular/router'
|
||||||
|
|
||||||
import { AuthService } from '../core'
|
import { AuthService } from '../auth'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminGuard implements CanActivate, CanActivateChild {
|
export class UserRightGuard implements CanActivate, CanActivateChild {
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
@ -18,7 +18,12 @@ export class AdminGuard implements CanActivate, CanActivateChild {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||||
if (this.auth.isAdmin() === true) return true
|
const user = this.auth.getUser()
|
||||||
|
if (user) {
|
||||||
|
const neededUserRight = route.data.userRight
|
||||||
|
|
||||||
|
if (user.hasRight(neededUserRight)) return true
|
||||||
|
}
|
||||||
|
|
||||||
this.router.navigate([ '/login' ])
|
this.router.navigate([ '/login' ])
|
||||||
return false
|
return false
|
|
@ -29,3 +29,9 @@ export const USER_VIDEO_QUOTA = {
|
||||||
'min': 'Quota must be greater than -1.'
|
'min': 'Quota must be greater than -1.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const USER_ROLE = {
|
||||||
|
VALIDATORS: [ Validators.required ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': 'User role is required.',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import {
|
import {
|
||||||
User as UserServerModel,
|
User as UserServerModel,
|
||||||
UserRole,
|
UserRole,
|
||||||
VideoChannel
|
VideoChannel,
|
||||||
|
UserRight,
|
||||||
|
hasUserRight
|
||||||
} from '../../../../../shared'
|
} from '../../../../../shared'
|
||||||
|
|
||||||
export type UserConstructorHash = {
|
export type UserConstructorHash = {
|
||||||
|
@ -56,7 +58,7 @@ export class User implements UserServerModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdmin () {
|
hasRight (right: UserRight) {
|
||||||
return this.role === 'admin'
|
return hasUserRight(this.role, right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { Video } from './video.model'
|
import { Video } from './video.model'
|
||||||
|
import { AuthUser } from '../../core'
|
||||||
import {
|
import {
|
||||||
VideoDetails as VideoDetailsServerModel,
|
VideoDetails as VideoDetailsServerModel,
|
||||||
VideoFile,
|
VideoFile,
|
||||||
VideoChannel,
|
VideoChannel,
|
||||||
VideoResolution
|
VideoResolution,
|
||||||
|
UserRight
|
||||||
} from '../../../../../shared'
|
} from '../../../../../shared'
|
||||||
|
|
||||||
export class VideoDetails extends Video implements VideoDetailsServerModel {
|
export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
|
@ -61,15 +63,15 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||||
return betterResolutionFile.magnetUri
|
return betterResolutionFile.magnetUri
|
||||||
}
|
}
|
||||||
|
|
||||||
isRemovableBy (user) {
|
isRemovableBy (user: AuthUser) {
|
||||||
return user && this.isLocal === true && (this.author === user.username || user.isAdmin() === true)
|
return user && this.isLocal === true && (this.author === user.username || user.hasRight(UserRight.REMOVE_ANY_VIDEO))
|
||||||
}
|
}
|
||||||
|
|
||||||
isBlackistableBy (user) {
|
isBlackistableBy (user: AuthUser) {
|
||||||
return user && user.isAdmin() === true && this.isLocal === false
|
return user && user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) === true && this.isLocal === false
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdatableBy (user) {
|
isUpdatableBy (user: AuthUser) {
|
||||||
return user && this.isLocal === true && user.username === this.author
|
return user && this.isLocal === true && user.username === this.author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
VideoService,
|
VideoService,
|
||||||
VideoPagination
|
VideoPagination
|
||||||
} from '../shared'
|
} from '../shared'
|
||||||
import { Search, SearchField, SearchService, User} from '../../shared'
|
import { Search, SearchField, SearchService, User } from '../../shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-list',
|
selector: 'my-videos-list',
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '../../lib'
|
} from '../../lib'
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight,
|
||||||
makeFriendsValidator,
|
makeFriendsValidator,
|
||||||
setBodyHostsPort,
|
setBodyHostsPort,
|
||||||
podRemoveValidator,
|
podRemoveValidator,
|
||||||
|
@ -20,6 +20,7 @@ import {
|
||||||
asyncMiddleware
|
asyncMiddleware
|
||||||
} from '../../middlewares'
|
} from '../../middlewares'
|
||||||
import { PodInstance } from '../../models'
|
import { PodInstance } from '../../models'
|
||||||
|
import { UserRight } from '../../../shared'
|
||||||
|
|
||||||
const podsRouter = express.Router()
|
const podsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -32,19 +33,19 @@ podsRouter.get('/',
|
||||||
)
|
)
|
||||||
podsRouter.post('/make-friends',
|
podsRouter.post('/make-friends',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_PODS),
|
||||||
makeFriendsValidator,
|
makeFriendsValidator,
|
||||||
setBodyHostsPort,
|
setBodyHostsPort,
|
||||||
asyncMiddleware(makeFriendsController)
|
asyncMiddleware(makeFriendsController)
|
||||||
)
|
)
|
||||||
podsRouter.get('/quit-friends',
|
podsRouter.get('/quit-friends',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_PODS),
|
||||||
asyncMiddleware(quitFriendsController)
|
asyncMiddleware(quitFriendsController)
|
||||||
)
|
)
|
||||||
podsRouter.delete('/:id',
|
podsRouter.delete('/:id',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_PODS),
|
||||||
podRemoveValidator,
|
podRemoveValidator,
|
||||||
asyncMiddleware(removeFriendController)
|
asyncMiddleware(removeFriendController)
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,14 +7,14 @@ import {
|
||||||
getRequestVideoQaduScheduler,
|
getRequestVideoQaduScheduler,
|
||||||
getRequestVideoEventScheduler
|
getRequestVideoEventScheduler
|
||||||
} from '../../lib'
|
} from '../../lib'
|
||||||
import { authenticate, ensureIsAdmin, asyncMiddleware } from '../../middlewares'
|
import { authenticate, ensureUserHasRight, asyncMiddleware } from '../../middlewares'
|
||||||
import { RequestSchedulerStatsAttributes } from '../../../shared'
|
import { RequestSchedulerStatsAttributes, UserRight } from '../../../shared'
|
||||||
|
|
||||||
const requestSchedulerRouter = express.Router()
|
const requestSchedulerRouter = express.Router()
|
||||||
|
|
||||||
requestSchedulerRouter.get('/stats',
|
requestSchedulerRouter.get('/stats',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_REQUEST_SCHEDULERS),
|
||||||
asyncMiddleware(getRequestSchedulersStats)
|
asyncMiddleware(getRequestSchedulersStats)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
|
|
||||||
import { database as db } from '../../initializers/database'
|
import { database as db, CONFIG } from '../../initializers'
|
||||||
import { USER_ROLES, CONFIG } from '../../initializers'
|
|
||||||
import { logger, getFormattedObjects, retryTransactionWrapper } from '../../helpers'
|
import { logger, getFormattedObjects, retryTransactionWrapper } from '../../helpers'
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight,
|
||||||
ensureUserRegistrationAllowed,
|
ensureUserRegistrationAllowed,
|
||||||
usersAddValidator,
|
usersAddValidator,
|
||||||
usersRegisterValidator,
|
usersRegisterValidator,
|
||||||
|
@ -25,7 +24,9 @@ import {
|
||||||
UserVideoRate as FormattedUserVideoRate,
|
UserVideoRate as FormattedUserVideoRate,
|
||||||
UserCreate,
|
UserCreate,
|
||||||
UserUpdate,
|
UserUpdate,
|
||||||
UserUpdateMe
|
UserUpdateMe,
|
||||||
|
UserRole,
|
||||||
|
UserRight
|
||||||
} from '../../../shared'
|
} from '../../../shared'
|
||||||
import { createUserAuthorAndChannel } from '../../lib'
|
import { createUserAuthorAndChannel } from '../../lib'
|
||||||
import { UserInstance } from '../../models'
|
import { UserInstance } from '../../models'
|
||||||
|
@ -58,7 +59,7 @@ usersRouter.get('/:id',
|
||||||
|
|
||||||
usersRouter.post('/',
|
usersRouter.post('/',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_USERS),
|
||||||
usersAddValidator,
|
usersAddValidator,
|
||||||
createUserRetryWrapper
|
createUserRetryWrapper
|
||||||
)
|
)
|
||||||
|
@ -77,14 +78,14 @@ usersRouter.put('/me',
|
||||||
|
|
||||||
usersRouter.put('/:id',
|
usersRouter.put('/:id',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_USERS),
|
||||||
usersUpdateValidator,
|
usersUpdateValidator,
|
||||||
asyncMiddleware(updateUser)
|
asyncMiddleware(updateUser)
|
||||||
)
|
)
|
||||||
|
|
||||||
usersRouter.delete('/:id',
|
usersRouter.delete('/:id',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_USERS),
|
||||||
usersRemoveValidator,
|
usersRemoveValidator,
|
||||||
asyncMiddleware(removeUser)
|
asyncMiddleware(removeUser)
|
||||||
)
|
)
|
||||||
|
@ -119,7 +120,7 @@ async function createUser (req: express.Request, res: express.Response, next: ex
|
||||||
password: body.password,
|
password: body.password,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
displayNSFW: false,
|
displayNSFW: false,
|
||||||
role: USER_ROLES.USER,
|
role: body.role,
|
||||||
videoQuota: body.videoQuota
|
videoQuota: body.videoQuota
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -136,7 +137,7 @@ async function registerUser (req: express.Request, res: express.Response, next:
|
||||||
password: body.password,
|
password: body.password,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
displayNSFW: false,
|
displayNSFW: false,
|
||||||
role: USER_ROLES.USER,
|
role: UserRole.USER,
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -203,6 +204,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
|
||||||
|
|
||||||
if (body.email !== undefined) user.email = body.email
|
if (body.email !== undefined) user.email = body.email
|
||||||
if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
|
if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
|
||||||
|
if (body.role !== undefined) user.role = body.role
|
||||||
|
|
||||||
await user.save()
|
await user.save()
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '../../../helpers'
|
} from '../../../helpers'
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight,
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
videoAbuseReportValidator,
|
videoAbuseReportValidator,
|
||||||
videoAbusesSortValidator,
|
videoAbusesSortValidator,
|
||||||
|
@ -18,13 +18,13 @@ import {
|
||||||
asyncMiddleware
|
asyncMiddleware
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import { VideoInstance } from '../../../models'
|
import { VideoInstance } from '../../../models'
|
||||||
import { VideoAbuseCreate } from '../../../../shared'
|
import { VideoAbuseCreate, UserRight } from '../../../../shared'
|
||||||
|
|
||||||
const abuseVideoRouter = express.Router()
|
const abuseVideoRouter = express.Router()
|
||||||
|
|
||||||
abuseVideoRouter.get('/abuse',
|
abuseVideoRouter.get('/abuse',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES),
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
videoAbusesSortValidator,
|
videoAbusesSortValidator,
|
||||||
setVideoAbusesSort,
|
setVideoAbusesSort,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { database as db } from '../../../initializers'
|
||||||
import { logger, getFormattedObjects } from '../../../helpers'
|
import { logger, getFormattedObjects } from '../../../helpers'
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight,
|
||||||
videosBlacklistAddValidator,
|
videosBlacklistAddValidator,
|
||||||
videosBlacklistRemoveValidator,
|
videosBlacklistRemoveValidator,
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
|
@ -14,20 +14,20 @@ import {
|
||||||
asyncMiddleware
|
asyncMiddleware
|
||||||
} from '../../../middlewares'
|
} from '../../../middlewares'
|
||||||
import { BlacklistedVideoInstance } from '../../../models'
|
import { BlacklistedVideoInstance } from '../../../models'
|
||||||
import { BlacklistedVideo } from '../../../../shared'
|
import { BlacklistedVideo, UserRight } from '../../../../shared'
|
||||||
|
|
||||||
const blacklistRouter = express.Router()
|
const blacklistRouter = express.Router()
|
||||||
|
|
||||||
blacklistRouter.post('/:videoId/blacklist',
|
blacklistRouter.post('/:videoId/blacklist',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||||
videosBlacklistAddValidator,
|
videosBlacklistAddValidator,
|
||||||
asyncMiddleware(addVideoToBlacklist)
|
asyncMiddleware(addVideoToBlacklist)
|
||||||
)
|
)
|
||||||
|
|
||||||
blacklistRouter.get('/blacklist',
|
blacklistRouter.get('/blacklist',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||||
paginationValidator,
|
paginationValidator,
|
||||||
blacklistSortValidator,
|
blacklistSortValidator,
|
||||||
setBlacklistSort,
|
setBlacklistSort,
|
||||||
|
@ -37,7 +37,7 @@ blacklistRouter.get('/blacklist',
|
||||||
|
|
||||||
blacklistRouter.delete('/:videoId/blacklist',
|
blacklistRouter.delete('/:videoId/blacklist',
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureIsAdmin,
|
ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||||
videosBlacklistRemoveValidator,
|
videosBlacklistRemoveValidator,
|
||||||
asyncMiddleware(removeVideoFromBlacklistController)
|
asyncMiddleware(removeVideoFromBlacklistController)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { values } from 'lodash'
|
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import 'express-validator'
|
import 'express-validator'
|
||||||
|
|
||||||
import { exists } from './misc'
|
import { exists } from './misc'
|
||||||
import { CONSTRAINTS_FIELDS, USER_ROLES } from '../../initializers'
|
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||||
import { UserRole } from '../../../shared'
|
import { UserRole } from '../../../shared'
|
||||||
|
|
||||||
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
|
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
|
||||||
|
@ -12,10 +11,6 @@ function isUserPasswordValid (value: string) {
|
||||||
return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
|
return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUserRoleValid (value: string) {
|
|
||||||
return values(USER_ROLES).indexOf(value as UserRole) !== -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUserVideoQuotaValid (value: string) {
|
function isUserVideoQuotaValid (value: string) {
|
||||||
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +25,10 @@ function isUserDisplayNSFWValid (value: any) {
|
||||||
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
|
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUserRoleValid (value: any) {
|
||||||
|
return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { join } from 'path'
|
||||||
import { root, isTestInstance } from '../helpers/core-utils'
|
import { root, isTestInstance } from '../helpers/core-utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UserRole,
|
|
||||||
VideoRateType,
|
VideoRateType,
|
||||||
RequestEndpoint,
|
RequestEndpoint,
|
||||||
RequestVideoEventType,
|
RequestVideoEventType,
|
||||||
|
@ -16,7 +15,7 @@ import {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 80
|
const LAST_MIGRATION_VERSION = 85
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -283,7 +282,6 @@ const JOB_STATES: { [ id: string ]: JobState } = {
|
||||||
}
|
}
|
||||||
// How many maximum jobs we fetch from the database per cycle
|
// How many maximum jobs we fetch from the database per cycle
|
||||||
const JOBS_FETCH_LIMIT_PER_CYCLE = 10
|
const JOBS_FETCH_LIMIT_PER_CYCLE = 10
|
||||||
const JOBS_CONCURRENCY = 1
|
|
||||||
// 1 minutes
|
// 1 minutes
|
||||||
let JOBS_FETCHING_INTERVAL = 60000
|
let JOBS_FETCHING_INTERVAL = 60000
|
||||||
|
|
||||||
|
@ -334,13 +332,6 @@ const CACHE = {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const USER_ROLES: { [ id: string ]: UserRole } = {
|
|
||||||
ADMIN: 'admin',
|
|
||||||
USER: 'user'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
|
const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -367,7 +358,6 @@ export {
|
||||||
EMBED_SIZE,
|
EMBED_SIZE,
|
||||||
FRIEND_SCORE,
|
FRIEND_SCORE,
|
||||||
JOB_STATES,
|
JOB_STATES,
|
||||||
JOBS_CONCURRENCY,
|
|
||||||
JOBS_FETCH_LIMIT_PER_CYCLE,
|
JOBS_FETCH_LIMIT_PER_CYCLE,
|
||||||
JOBS_FETCHING_INTERVAL,
|
JOBS_FETCHING_INTERVAL,
|
||||||
LAST_MIGRATION_VERSION,
|
LAST_MIGRATION_VERSION,
|
||||||
|
@ -401,7 +391,6 @@ export {
|
||||||
STATIC_MAX_AGE,
|
STATIC_MAX_AGE,
|
||||||
STATIC_PATHS,
|
STATIC_PATHS,
|
||||||
THUMBNAILS_SIZE,
|
THUMBNAILS_SIZE,
|
||||||
USER_ROLES,
|
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
VIDEO_LANGUAGES,
|
VIDEO_LANGUAGES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
|
|
|
@ -2,10 +2,11 @@ import * as passwordGenerator from 'password-generator'
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
|
|
||||||
import { database as db } from './database'
|
import { database as db } from './database'
|
||||||
import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
|
import { CONFIG, LAST_MIGRATION_VERSION, CACHE } from './constants'
|
||||||
import { clientsExist, usersExist } from './checker'
|
import { clientsExist, usersExist } from './checker'
|
||||||
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
|
import { logger, createCertsIfNotExist, mkdirpPromise, rimrafPromise } from '../helpers'
|
||||||
import { createUserAuthorAndChannel } from '../lib'
|
import { createUserAuthorAndChannel } from '../lib'
|
||||||
|
import { UserRole } from '../../shared'
|
||||||
|
|
||||||
async function installApplication () {
|
async function installApplication () {
|
||||||
await db.sequelize.sync()
|
await db.sequelize.sync()
|
||||||
|
@ -88,7 +89,7 @@ async function createOAuthAdminIfNotExist () {
|
||||||
logger.info('Creating the administrator.')
|
logger.info('Creating the administrator.')
|
||||||
|
|
||||||
const username = 'root'
|
const username = 'root'
|
||||||
const role = USER_ROLES.ADMIN
|
const role = UserRole.ADMINISTRATOR
|
||||||
const email = CONFIG.ADMIN.EMAIL
|
const email = CONFIG.ADMIN.EMAIL
|
||||||
let validatePassword = true
|
let validatePassword = true
|
||||||
let password = ''
|
let password = ''
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
import * as uuidv4 from 'uuid/v4'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction,
|
||||||
|
queryInterface: Sequelize.QueryInterface,
|
||||||
|
sequelize: Sequelize.Sequelize,
|
||||||
|
db: any
|
||||||
|
}): Promise<void> {
|
||||||
|
const q = utils.queryInterface
|
||||||
|
|
||||||
|
await q.renameColumn('Users', 'role', 'oldRole')
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
await q.addColumn('Users', 'role', data)
|
||||||
|
|
||||||
|
let query = 'UPDATE "Users" SET "role" = 0 WHERE "oldRole" = \'admin\''
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
|
||||||
|
query = 'UPDATE "Users" SET "role" = 2 WHERE "oldRole" = \'user\''
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
|
||||||
|
data.allowNull = false
|
||||||
|
await q.changeColumn('Users', 'role', data)
|
||||||
|
|
||||||
|
await q.removeColumn('Users', 'oldRole')
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
up,
|
||||||
|
down
|
||||||
|
}
|
|
@ -1,20 +0,0 @@
|
||||||
import 'express-validator'
|
|
||||||
import * as express from 'express'
|
|
||||||
|
|
||||||
import { logger } from '../helpers'
|
|
||||||
|
|
||||||
function ensureIsAdmin (req: express.Request, res: express.Response, next: express.NextFunction) {
|
|
||||||
const user = res.locals.oauth.token.user
|
|
||||||
if (user.isAdmin() === false) {
|
|
||||||
logger.info('A non admin user is trying to access to an admin content.')
|
|
||||||
return res.sendStatus(403)
|
|
||||||
}
|
|
||||||
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
ensureIsAdmin
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
export * from './validators'
|
export * from './validators'
|
||||||
export * from './admin'
|
|
||||||
export * from './async'
|
export * from './async'
|
||||||
export * from './oauth'
|
export * from './oauth'
|
||||||
export * from './pagination'
|
export * from './pagination'
|
||||||
|
@ -7,3 +6,4 @@ export * from './pods'
|
||||||
export * from './search'
|
export * from './search'
|
||||||
export * from './secure'
|
export * from './secure'
|
||||||
export * from './sort'
|
export * from './sort'
|
||||||
|
export * from './user-right'
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import 'express-validator'
|
||||||
|
import * as express from 'express'
|
||||||
|
|
||||||
|
import { UserInstance } from '../models'
|
||||||
|
import { UserRight } from '../../shared'
|
||||||
|
import { logger } from '../helpers'
|
||||||
|
|
||||||
|
function ensureUserHasRight (userRight: UserRight) {
|
||||||
|
return function (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const user: UserInstance = res.locals.oauth.token.user
|
||||||
|
if (user.hasRight(userRight) === false) {
|
||||||
|
logger.info('User %s does not have right %s to access to %s.', user.username, UserRight[userRight], req.path)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
ensureUserHasRight
|
||||||
|
}
|
|
@ -13,7 +13,8 @@ import {
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
isUserVideoQuotaValid,
|
isUserVideoQuotaValid,
|
||||||
isUserDisplayNSFWValid,
|
isUserDisplayNSFWValid,
|
||||||
isIdOrUUIDValid
|
isIdOrUUIDValid,
|
||||||
|
isUserRoleValid
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
import { UserInstance, VideoInstance } from '../../models'
|
import { UserInstance, VideoInstance } from '../../models'
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ const usersAddValidator = [
|
||||||
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
||||||
body('email').isEmail().withMessage('Should have a valid email'),
|
body('email').isEmail().withMessage('Should have a valid email'),
|
||||||
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
||||||
|
body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
|
||||||
|
|
||||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking usersAdd parameters', { parameters: req.body })
|
logger.debug('Checking usersAdd parameters', { parameters: req.body })
|
||||||
|
@ -75,6 +77,7 @@ const usersUpdateValidator = [
|
||||||
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
||||||
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
||||||
body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
||||||
|
body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
|
||||||
|
|
||||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking usersUpdate parameters', { parameters: req.body })
|
logger.debug('Checking usersUpdate parameters', { parameters: req.body })
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
checkVideoChannelExists,
|
checkVideoChannelExists,
|
||||||
checkVideoAuthorExists
|
checkVideoAuthorExists
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
|
import { UserInstance } from '../../models'
|
||||||
|
import { UserRight } from '../../../shared'
|
||||||
|
|
||||||
const listVideoAuthorChannelsValidator = [
|
const listVideoAuthorChannelsValidator = [
|
||||||
param('authorId').custom(isIdOrUUIDValid).withMessage('Should have a valid author id'),
|
param('authorId').custom(isIdOrUUIDValid).withMessage('Should have a valid author id'),
|
||||||
|
@ -106,7 +108,7 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function checkUserCanDeleteVideoChannel (res: express.Response, callback: () => void) {
|
function checkUserCanDeleteVideoChannel (res: express.Response, callback: () => void) {
|
||||||
const user = res.locals.oauth.token.User
|
const user: UserInstance = res.locals.oauth.token.User
|
||||||
|
|
||||||
// Retrieve the user who did the request
|
// Retrieve the user who did the request
|
||||||
if (res.locals.videoChannel.isOwned() === false) {
|
if (res.locals.videoChannel.isOwned() === false) {
|
||||||
|
@ -118,7 +120,7 @@ function checkUserCanDeleteVideoChannel (res: express.Response, callback: () =>
|
||||||
// Check if the user can delete the video channel
|
// Check if the user can delete the video channel
|
||||||
// The user can delete it if s/he is an admin
|
// The user can delete it if s/he is an admin
|
||||||
// Or if s/he is the video channel's author
|
// Or if s/he is the video channel's author
|
||||||
if (user.isAdmin() === false && res.locals.videoChannel.Author.userId !== user.id) {
|
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && res.locals.videoChannel.Author.userId !== user.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot remove video channel of another user' })
|
.json({ error: 'Cannot remove video channel of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
checkVideoExists,
|
checkVideoExists,
|
||||||
isIdValid
|
isIdValid
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
|
import { UserRight } from '../../../shared'
|
||||||
|
|
||||||
const videosAddValidator = [
|
const videosAddValidator = [
|
||||||
body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
|
body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
|
||||||
|
@ -231,7 +232,7 @@ function checkUserCanDeleteVideo (userId: number, res: express.Response, callbac
|
||||||
// Check if the user can delete the video
|
// Check if the user can delete the video
|
||||||
// The user can delete it if s/he is an admin
|
// The user can delete it if s/he is an admin
|
||||||
// Or if s/he is the video's author
|
// Or if s/he is the video's author
|
||||||
if (user.isAdmin() === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
|
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO) === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
|
||||||
return res.status(403)
|
return res.status(403)
|
||||||
.json({ error: 'Cannot remove video of another user' })
|
.json({ error: 'Cannot remove video of another user' })
|
||||||
.end()
|
.end()
|
||||||
|
|
|
@ -3,15 +3,16 @@ import * as Promise from 'bluebird'
|
||||||
|
|
||||||
// Don't use barrel, import just what we need
|
// Don't use barrel, import just what we need
|
||||||
import { User as FormattedUser } from '../../../shared/models/users/user.model'
|
import { User as FormattedUser } from '../../../shared/models/users/user.model'
|
||||||
import { UserRole } from '../../../shared/models/users/user-role.type'
|
|
||||||
import { ResultList } from '../../../shared/models/result-list.model'
|
import { ResultList } from '../../../shared/models/result-list.model'
|
||||||
import { AuthorInstance } from '../video/author-interface'
|
import { AuthorInstance } from '../video/author-interface'
|
||||||
|
import { UserRight } from '../../../shared/models/users/user-right.enum'
|
||||||
|
import { UserRole } from '../../../shared/models/users/user-role'
|
||||||
|
|
||||||
export namespace UserMethods {
|
export namespace UserMethods {
|
||||||
|
export type HasRight = (this: UserInstance, right: UserRight) => boolean
|
||||||
export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
|
export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
|
||||||
|
|
||||||
export type ToFormattedJSON = (this: UserInstance) => FormattedUser
|
export type ToFormattedJSON = (this: UserInstance) => FormattedUser
|
||||||
export type IsAdmin = (this: UserInstance) => boolean
|
|
||||||
export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean>
|
export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean>
|
||||||
|
|
||||||
export type CountTotal = () => Promise<number>
|
export type CountTotal = () => Promise<number>
|
||||||
|
@ -31,7 +32,7 @@ export namespace UserMethods {
|
||||||
export interface UserClass {
|
export interface UserClass {
|
||||||
isPasswordMatch: UserMethods.IsPasswordMatch,
|
isPasswordMatch: UserMethods.IsPasswordMatch,
|
||||||
toFormattedJSON: UserMethods.ToFormattedJSON,
|
toFormattedJSON: UserMethods.ToFormattedJSON,
|
||||||
isAdmin: UserMethods.IsAdmin,
|
hasRight: UserMethods.HasRight,
|
||||||
isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo,
|
isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo,
|
||||||
|
|
||||||
countTotal: UserMethods.CountTotal,
|
countTotal: UserMethods.CountTotal,
|
||||||
|
@ -62,7 +63,7 @@ export interface UserInstance extends UserClass, UserAttributes, Sequelize.Insta
|
||||||
|
|
||||||
isPasswordMatch: UserMethods.IsPasswordMatch
|
isPasswordMatch: UserMethods.IsPasswordMatch
|
||||||
toFormattedJSON: UserMethods.ToFormattedJSON
|
toFormattedJSON: UserMethods.ToFormattedJSON
|
||||||
isAdmin: UserMethods.IsAdmin
|
hasRight: UserMethods.HasRight
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
|
export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { values } from 'lodash'
|
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import * as Promise from 'bluebird'
|
import * as Promise from 'bluebird'
|
||||||
|
|
||||||
import { getSort } from '../utils'
|
import { getSort } from '../utils'
|
||||||
import { USER_ROLES } from '../../initializers'
|
|
||||||
import {
|
import {
|
||||||
cryptPassword,
|
cryptPassword,
|
||||||
comparePassword,
|
comparePassword,
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
isUserUsernameValid,
|
isUserUsernameValid,
|
||||||
isUserDisplayNSFWValid,
|
isUserDisplayNSFWValid,
|
||||||
isUserVideoQuotaValid
|
isUserVideoQuotaValid,
|
||||||
|
isUserRoleValid
|
||||||
} from '../../helpers'
|
} from '../../helpers'
|
||||||
|
import { UserRight, USER_ROLE_LABELS, hasUserRight } from '../../../shared'
|
||||||
|
|
||||||
import { addMethodsToModel } from '../utils'
|
import { addMethodsToModel } from '../utils'
|
||||||
import {
|
import {
|
||||||
|
@ -23,8 +23,8 @@ import {
|
||||||
|
|
||||||
let User: Sequelize.Model<UserInstance, UserAttributes>
|
let User: Sequelize.Model<UserInstance, UserAttributes>
|
||||||
let isPasswordMatch: UserMethods.IsPasswordMatch
|
let isPasswordMatch: UserMethods.IsPasswordMatch
|
||||||
|
let hasRight: UserMethods.HasRight
|
||||||
let toFormattedJSON: UserMethods.ToFormattedJSON
|
let toFormattedJSON: UserMethods.ToFormattedJSON
|
||||||
let isAdmin: UserMethods.IsAdmin
|
|
||||||
let countTotal: UserMethods.CountTotal
|
let countTotal: UserMethods.CountTotal
|
||||||
let getByUsername: UserMethods.GetByUsername
|
let getByUsername: UserMethods.GetByUsername
|
||||||
let listForApi: UserMethods.ListForApi
|
let listForApi: UserMethods.ListForApi
|
||||||
|
@ -76,8 +76,14 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: DataTypes.ENUM(values(USER_ROLES)),
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false
|
allowNull: false,
|
||||||
|
validate: {
|
||||||
|
roleValid: value => {
|
||||||
|
const res = isUserRoleValid(value)
|
||||||
|
if (res === false) throw new Error('Role is not valid.')
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
videoQuota: {
|
videoQuota: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.BIGINT,
|
||||||
|
@ -120,9 +126,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
loadByUsernameOrEmail
|
loadByUsernameOrEmail
|
||||||
]
|
]
|
||||||
const instanceMethods = [
|
const instanceMethods = [
|
||||||
|
hasRight,
|
||||||
isPasswordMatch,
|
isPasswordMatch,
|
||||||
toFormattedJSON,
|
toFormattedJSON,
|
||||||
isAdmin,
|
|
||||||
isAbleToUploadVideo
|
isAbleToUploadVideo
|
||||||
]
|
]
|
||||||
addMethodsToModel(User, classMethods, instanceMethods)
|
addMethodsToModel(User, classMethods, instanceMethods)
|
||||||
|
@ -139,6 +145,10 @@ function beforeCreateOrUpdate (user: UserInstance) {
|
||||||
|
|
||||||
// ------------------------------ METHODS ------------------------------
|
// ------------------------------ METHODS ------------------------------
|
||||||
|
|
||||||
|
hasRight = function (this: UserInstance, right: UserRight) {
|
||||||
|
return hasUserRight(this.role, right)
|
||||||
|
}
|
||||||
|
|
||||||
isPasswordMatch = function (this: UserInstance, password: string) {
|
isPasswordMatch = function (this: UserInstance, password: string) {
|
||||||
return comparePassword(password, this.password)
|
return comparePassword(password, this.password)
|
||||||
}
|
}
|
||||||
|
@ -150,6 +160,7 @@ toFormattedJSON = function (this: UserInstance) {
|
||||||
email: this.email,
|
email: this.email,
|
||||||
displayNSFW: this.displayNSFW,
|
displayNSFW: this.displayNSFW,
|
||||||
role: this.role,
|
role: this.role,
|
||||||
|
roleLabel: USER_ROLE_LABELS[this.role],
|
||||||
videoQuota: this.videoQuota,
|
videoQuota: this.videoQuota,
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
author: {
|
author: {
|
||||||
|
@ -174,10 +185,6 @@ toFormattedJSON = function (this: UserInstance) {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdmin = function (this: UserInstance) {
|
|
||||||
return this.role === USER_ROLES.ADMIN
|
|
||||||
}
|
|
||||||
|
|
||||||
isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) {
|
isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) {
|
||||||
if (this.videoQuota === -1) return Promise.resolve(true)
|
if (this.videoQuota === -1) return Promise.resolve(true)
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@ export * from './user-login.model'
|
||||||
export * from './user-refresh-token.model'
|
export * from './user-refresh-token.model'
|
||||||
export * from './user-update.model'
|
export * from './user-update.model'
|
||||||
export * from './user-update-me.model'
|
export * from './user-update-me.model'
|
||||||
export * from './user-role.type'
|
export * from './user-right.enum'
|
||||||
|
export * from './user-role'
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { UserRole } from './user-role'
|
||||||
|
|
||||||
export interface UserCreate {
|
export interface UserCreate {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
email: string
|
email: string
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
role: UserRole
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
export enum UserRight {
|
||||||
|
ALL,
|
||||||
|
MANAGE_USERS,
|
||||||
|
MANAGE_PODS,
|
||||||
|
MANAGE_VIDEO_ABUSES,
|
||||||
|
MANAGE_REQUEST_SCHEDULERS,
|
||||||
|
MANAGE_VIDEO_BLACKLIST,
|
||||||
|
REMOVE_ANY_VIDEO,
|
||||||
|
REMOVE_ANY_VIDEO_CHANNEL,
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { UserRight } from './user-right.enum'
|
||||||
|
|
||||||
|
// Keep the order
|
||||||
|
export enum UserRole {
|
||||||
|
ADMINISTRATOR = 0,
|
||||||
|
MODERATOR = 1,
|
||||||
|
USER = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export const USER_ROLE_LABELS = {
|
||||||
|
[UserRole.USER]: 'User',
|
||||||
|
[UserRole.MODERATOR]: 'Moderator',
|
||||||
|
[UserRole.ADMINISTRATOR]: 'Administrator'
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed
|
||||||
|
const userRoleRights: { [ id: number ]: UserRight[] } = {
|
||||||
|
[UserRole.ADMINISTRATOR]: [
|
||||||
|
UserRight.ALL
|
||||||
|
],
|
||||||
|
|
||||||
|
[UserRole.MODERATOR]: [
|
||||||
|
UserRight.MANAGE_VIDEO_BLACKLIST,
|
||||||
|
UserRight.MANAGE_VIDEO_ABUSES,
|
||||||
|
UserRight.REMOVE_ANY_VIDEO,
|
||||||
|
UserRight.REMOVE_ANY_VIDEO_CHANNEL
|
||||||
|
],
|
||||||
|
|
||||||
|
[UserRole.USER]: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasUserRight (userRole: UserRole, userRight: UserRight) {
|
||||||
|
const userRights = userRoleRights[userRole]
|
||||||
|
|
||||||
|
return userRights.indexOf(UserRight.ALL) !== -1 || userRights.indexOf(userRight) !== -1
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
export type UserRole = 'admin' | 'user'
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { UserRole } from './user-role'
|
||||||
|
|
||||||
export interface UserUpdate {
|
export interface UserUpdate {
|
||||||
email?: string
|
email?: string
|
||||||
videoQuota?: number
|
videoQuota?: number
|
||||||
|
role?: UserRole
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserRole } from './user-role.type'
|
|
||||||
import { VideoChannel } from '../videos/video-channel.model'
|
import { VideoChannel } from '../videos/video-channel.model'
|
||||||
|
import { UserRole } from './user-role'
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number
|
id: number
|
||||||
|
|
Loading…
Reference in New Issue