aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-05-20 13:53:51 +0200
committerChocobozzz <me@florianbigard.com>2020-05-20 13:53:51 +0200
commit745437e3ab402d5b8f85de6e9c09c3b6092e5904 (patch)
treef93e8cc7bdffe19b6c19251512b2fd31bfe4f41d
parent148ab2f0f043a4c7cc28f9497cb6ae3819e8b838 (diff)
parentf33dc6ab2db1c733f09dcb039b8bf46c69854753 (diff)
downloadPeerTube-745437e3ab402d5b8f85de6e9c09c3b6092e5904.tar.gz
PeerTube-745437e3ab402d5b8f85de6e9c09c3b6092e5904.tar.zst
PeerTube-745437e3ab402d5b8f85de6e9c09c3b6092e5904.zip
Merge branch 'release/2.2.0' into develop
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html2
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html2
-rw-r--r--client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html2
-rw-r--r--client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html2
-rw-r--r--client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html2
-rw-r--r--client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html3
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html2
-rw-r--r--client/src/app/header/header.component.scss1
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.scss3
-rw-r--r--client/src/app/shared/misc/peertube-web-storage.ts42
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comments.component.html8
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comments.component.scss4
-rw-r--r--client/src/sass/application.scss3
-rw-r--r--client/src/sass/bootstrap.scss2
-rw-r--r--client/src/sass/include/_mixins.scss6
-rw-r--r--client/src/sass/include/_variables.scss8
-rw-r--r--client/src/sass/primeng-custom.scss10
-rw-r--r--server/lib/oauth-model.ts8
-rw-r--r--server/middlewares/validators/users.ts9
-rw-r--r--server/tests/api/check-params/users.ts2
-rw-r--r--server/tests/api/videos/video-imports.ts2
-rw-r--r--server/tests/plugins/external-auth.ts10
-rw-r--r--shared/extra-utils/users/users.ts4
24 files changed, 92 insertions, 47 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index 93378a533..298871fce 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -23,7 +23,7 @@
23 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> 23 <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
24 <th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th> 24 <th style="width: 100px;" i18n pSortableColumn="score">Score <p-sortIcon field="score"></p-sortIcon></th>
25 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 25 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
26 <th style="width: 100px;"></th> 26 <th style="width: 150px;"></th>
27 </tr> 27 </tr>
28 </ng-template> 28 </ng-template>
29 29
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
index 28d57f83c..c08154bcd 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
@@ -22,7 +22,7 @@
22 <th style="width: 160px;" i18n *ngIf="isDisplayingRemoteVideos()">Strategy</th> 22 <th style="width: 160px;" i18n *ngIf="isDisplayingRemoteVideos()">Strategy</th>
23 <th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th > 23 <th i18n pSortableColumn="name">Video <p-sortIcon field="name"></p-sortIcon></th >
24 <th style="width: 100px;" i18n *ngIf="isDisplayingRemoteVideos()">Total size</th> 24 <th style="width: 100px;" i18n *ngIf="isDisplayingRemoteVideos()">Total size</th>
25 <th style="width: 80px;"></th> 25 <th style="width: 150px;"></th>
26 </tr> 26 </tr>
27 </ng-template> 27 </ng-template>
28 28
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
index a4ab2a58c..b7d40be60 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
@@ -21,7 +21,7 @@
21 <tr> 21 <tr>
22 <th style="width: 100%;" i18n>Account</th> 22 <th style="width: 100%;" i18n>Account</th>
23 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> 23 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
24 <th style="width: 100px;"></th> <!-- column for action buttons --> 24 <th style="width: 150px;"></th> <!-- column for action buttons -->
25 </tr> 25 </tr>
26 </ng-template> 26 </ng-template>
27 27
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
index dab068dd6..589a11b7b 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html
@@ -25,7 +25,7 @@
25 <tr> 25 <tr>
26 <th style="width: 100%;" i18n>Instance</th> 26 <th style="width: 100%;" i18n>Instance</th>
27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> 27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
28 <th style="width: 100px;"></th> <!-- column for action buttons --> 28 <th style="width: 150px;"></th> <!-- column for action buttons -->
29 </tr> 29 </tr>
30 </ng-template> 30 </ng-template>
31 31
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
index 1c9530152..d30475794 100644
--- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
+++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
@@ -41,7 +41,7 @@
41 <th i18n>Video</th> 41 <th i18n>Video</th>
42 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 42 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
43 <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th> 43 <th i18n pSortableColumn="state" style="width: 80px;">State <p-sortIcon field="state"></p-sortIcon></th>
44 <th style="width: 120px;"></th> 44 <th style="width: 150px;"></th>
45 </tr> 45 </tr>
46 </ng-template> 46 </ng-template>
47 47
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
index c4c4e765a..cfa04514f 100644
--- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
+++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
@@ -25,7 +25,7 @@
25 <th style="width: 100px;" i18n>Sensitive</th> 25 <th style="width: 100px;" i18n>Sensitive</th>
26 <th style="width: 120px;" i18n>Unfederated</th> 26 <th style="width: 120px;" i18n>Unfederated</th>
27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> 27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
28 <th style="width: 120px;"></th> 28 <th style="width: 150px;"></th>
29 </tr> 29 </tr>
30 </ng-template> 30 </ng-template>
31 31
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
index f39f66696..ce176d682 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
@@ -9,7 +9,7 @@
9 <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification 9 <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification
10</div> 10</div>
11 11
12<form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form"> 12<form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form" *ngIf="user.pluginAuth === null">
13 13
14 <div class="form-group"> 14 <div class="form-group">
15 <label i18n for="new-email">New email</label> 15 <label i18n for="new-email">New email</label>
@@ -23,6 +23,7 @@
23 </div> 23 </div>
24 24
25 <div class="form-group"> 25 <div class="form-group">
26 <label i18n for="new-email">Your current password</label>
26 <input 27 <input
27 type="password" id="password" i18n-placeholder placeholder="Your password" autocomplete="off" 28 type="password" id="password" i18n-placeholder placeholder="Your password" autocomplete="off"
28 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" class="form-control" 29 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" class="form-control"
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
index f1c466545..b4e4d29f0 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -58,7 +58,7 @@
58 </div> 58 </div>
59</div> 59</div>
60 60
61<div class="form-row mt-5"> <!-- password grid --> 61<div class="form-row mt-5" *ngIf="user.pluginAuth === null"> <!-- password grid -->
62 <div class="form-group col-12 col-lg-4 col-xl-3"> 62 <div class="form-group col-12 col-lg-4 col-xl-3">
63 <div i18n class="account-title">PASSWORD</div> 63 <div i18n class="account-title">PASSWORD</div>
64 </div> 64 </div>
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index 91b390773..1e4ce2c56 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -10,7 +10,6 @@ my-search-typeahead {
10 @include orange-button; 10 @include orange-button;
11 @include button-with-icon(22px, 3px, -1px); 11 @include button-with-icon(22px, 3px, -1px);
12 12
13 color: var(--mainBackgroundColor) !important;
14 margin-right: 25px; 13 margin-right: 25px;
15 14
16 @media screen and (max-width: 600px) { 15 @media screen and (max-width: 600px) {
diff --git a/client/src/app/shared/forms/markdown-textarea.component.scss b/client/src/app/shared/forms/markdown-textarea.component.scss
index 8e5739e45..16f319587 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.scss
+++ b/client/src/app/shared/forms/markdown-textarea.component.scss
@@ -14,7 +14,8 @@ $input-border-radius: 3px;
14 textarea { 14 textarea {
15 @include peertube-textarea(100%, 150px); 15 @include peertube-textarea(100%, 150px);
16 16
17 background-color: var(--textareaBackgroundColor); 17 background-color: var(--markdownTextareaBackgroundColor);
18
18 font-family: monospace; 19 font-family: monospace;
19 font-size: 13px; 20 font-size: 13px;
20 border-bottom: none; 21 border-bottom: none;
diff --git a/client/src/app/shared/misc/peertube-web-storage.ts b/client/src/app/shared/misc/peertube-web-storage.ts
index fff209678..6a152dd98 100644
--- a/client/src/app/shared/misc/peertube-web-storage.ts
+++ b/client/src/app/shared/misc/peertube-web-storage.ts
@@ -47,26 +47,32 @@ try {
47 peertubeLocalStorage = localStorage 47 peertubeLocalStorage = localStorage
48 peertubeSessionStorage = sessionStorage 48 peertubeSessionStorage = sessionStorage
49} catch (err) { 49} catch (err) {
50 const instance = new MemoryStorage() 50 const instanceLocalStorage = new MemoryStorage()
51 const instanceSessionStorage = new MemoryStorage()
51 52
52 peertubeLocalStorage = sessionStorage = new Proxy(instance, { 53 function proxify (instance: MemoryStorage) {
53 set: function (obj, prop: string | number, value) { 54 return new Proxy(instance, {
54 if (MemoryStorage.prototype.hasOwnProperty(prop)) { 55 set: function (obj, prop: string | number, value) {
55 instance[prop] = value 56 if (MemoryStorage.prototype.hasOwnProperty(prop)) {
56 } else { 57 instance[prop] = value
57 instance.setItem(prop, value) 58 } else {
59 instance.setItem(prop, value)
60 }
61 return true
62 },
63 get: function (target, name: string | number) {
64 if (MemoryStorage.prototype.hasOwnProperty(name)) {
65 return instance[name]
66 }
67 if (valuesMap.has(name)) {
68 return instance.getItem(name)
69 }
58 } 70 }
59 return true 71 })
60 }, 72 }
61 get: function (target, name: string | number) { 73
62 if (MemoryStorage.prototype.hasOwnProperty(name)) { 74 peertubeLocalStorage = proxify(instanceLocalStorage)
63 return instance[name] 75 peertubeSessionStorage = proxify(instanceSessionStorage)
64 }
65 if (valuesMap.has(name)) {
66 return instance.getItem(name)
67 }
68 }
69 })
70} 76}
71 77
72export { 78export {
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html
index a21042f09..affbd4793 100644
--- a/client/src/app/videos/+video-watch/comment/video-comments.component.html
+++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html
@@ -12,10 +12,10 @@
12 <my-feed [syndicationItems]="syndicationItems"></my-feed> 12 <my-feed [syndicationItems]="syndicationItems"></my-feed>
13 13
14 <div ngbDropdown class="d-inline-block ml-4"> 14 <div ngbDropdown class="d-inline-block ml-4">
15 <button class="btn btn-sm btn-outline-secondary" id="dropdownSortComments" ngbDropdownToggle i18n> 15 <button class="btn btn-sm btn-outline-secondary" id="dropdown-sort-comments" ngbDropdownToggle i18n>
16 SORT BY 16 SORT BY
17 </button> 17 </button>
18 <div ngbDropdownMenu aria-labelledby="dropdownSortComments"> 18 <div ngbDropdownMenu aria-labelledby="dropdown-sort-comments">
19 <button (click)="handleSortChange('-createdAt')" ngbDropdownItem i18n>Most recent first (default)</button> 19 <button (click)="handleSortChange('-createdAt')" ngbDropdownItem i18n>Most recent first (default)</button>
20 <button (click)="handleSortChange('-totalReplies')" ngbDropdownItem i18n>Most replies first</button> 20 <button (click)="handleSortChange('-totalReplies')" ngbDropdownItem i18n>Most replies first</button>
21 </div> 21 </div>
@@ -72,7 +72,7 @@
72 > 72 >
73 <div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment.id)" class="view-replies mb-2"> 73 <div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment.id)" class="view-replies mb-2">
74 <span class="glyphicon glyphicon-menu-down"></span> 74 <span class="glyphicon glyphicon-menu-down"></span>
75 75
76 <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container> 76 <ng-container *ngIf="comment.totalRepliesFromVideoAuthor > 0; then hasAuthorComments; else noAuthorComments"></ng-container>
77 <ng-template #hasAuthorComments> 77 <ng-template #hasAuthorComments>
78 <ng-container *ngIf="comment.totalReplies !== comment.totalRepliesFromVideoAuthor; else onlyAuthorComments" i18n> 78 <ng-container *ngIf="comment.totalReplies !== comment.totalRepliesFromVideoAuthor; else onlyAuthorComments" i18n>
@@ -83,7 +83,7 @@
83 </ng-template> 83 </ng-template>
84 </ng-template> 84 </ng-template>
85 <ng-template i18n #noAuthorComments>View {{ comment.totalReplies }} replies</ng-template> 85 <ng-template i18n #noAuthorComments>View {{ comment.totalReplies }} replies</ng-template>
86 86
87 <my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader> 87 <my-small-loader class="comment-thread-loading ml-1" [loading]="threadLoading[comment.id]"></my-small-loader>
88 </div> 88 </div>
89 </my-video-comment> 89 </my-video-comment>
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.scss b/client/src/app/videos/+video-watch/comment/video-comments.component.scss
index 5ed1ac629..df42fae73 100644
--- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss
+++ b/client/src/app/videos/+video-watch/comment/video-comments.component.scss
@@ -21,7 +21,7 @@
21 .title-page { 21 .title-page {
22 margin-right: 0; 22 margin-right: 0;
23 } 23 }
24 24
25 my-feed { 25 my-feed {
26 display: inline-block; 26 display: inline-block;
27 margin-left: 5px; 27 margin-left: 5px;
@@ -33,7 +33,7 @@
33 } 33 }
34} 34}
35 35
36#dropdownSortComments { 36#dropdown-sort-comments {
37 font-weight: 600; 37 font-weight: 600;
38 text-transform: uppercase; 38 text-transform: uppercase;
39 border: none; 39 border: none;
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index d637c94d9..17ed5c8f8 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -35,10 +35,13 @@ body {
35 --menuForegroundColor: #{$menu-color}; 35 --menuForegroundColor: #{$menu-color};
36 --submenuColor: #{$sub-menu-color}; 36 --submenuColor: #{$sub-menu-color};
37 37
38 --inputForegroundColor: #{$input-foreground-color};
38 --inputBackgroundColor: #{$input-background-color}; 39 --inputBackgroundColor: #{$input-background-color};
39 --inputPlaceholderColor: #{$input-placeholder-color}; 40 --inputPlaceholderColor: #{$input-placeholder-color};
40 41
42 --textareaForegroundColor: #{$textarea-foreground-color};
41 --textareaBackgroundColor: #{$textarea-background-color}; 43 --textareaBackgroundColor: #{$textarea-background-color};
44 --markdownTextareaBackgroundColor: #{$markdown-textarea-background-color};
42 45
43 --actionButtonColor: #{$grey-foreground-color}; 46 --actionButtonColor: #{$grey-foreground-color};
44 --supportButtonBackgroundColor: #{transparent}; 47 --supportButtonBackgroundColor: #{transparent};
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index cb266cc68..7985472ed 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -37,6 +37,8 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
37} 37}
38 38
39.dropdown-menu { 39.dropdown-menu {
40 z-index: z(dropdown) + 1 !important;
41
40 border-radius: 3px; 42 border-radius: 3px;
41 box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 43 box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
42 font-size: 15px; 44 font-size: 15px;
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index f157ded5e..99ca25f9c 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -90,7 +90,8 @@
90 display: inline-block; 90 display: inline-block;
91 height: $button-height; 91 height: $button-height;
92 width: $width; 92 width: $width;
93 background: var(--inputBackgroundColor); 93 color: var(--inputForegroundColor);
94 background-color: var(--inputBackgroundColor);
94 border: 1px solid #C6C6C6; 95 border: 1px solid #C6C6C6;
95 border-radius: 3px; 96 border-radius: 3px;
96 padding-left: 15px; 97 padding-left: 15px;
@@ -121,6 +122,8 @@
121@mixin peertube-textarea ($width, $height) { 122@mixin peertube-textarea ($width, $height) {
122 @include peertube-input-text($width); 123 @include peertube-input-text($width);
123 124
125 color: var(--textareaForegroundColor);
126 background-color: var(--textareaBackgroundColor);
124 height: $height; 127 height: $height;
125 padding: 5px 15px; 128 padding: 5px 15px;
126 font-size: 15px; 129 font-size: 15px;
@@ -280,6 +283,7 @@
280 margin: 0; 283 margin: 0;
281 width: $width; 284 width: $width;
282 border-radius: 3px; 285 border-radius: 3px;
286 color: var(--inputForegroundColor);
283 background: var(--inputBackgroundColor); 287 background: var(--inputBackgroundColor);
284 position: relative; 288 position: relative;
285 font-size: 15px; 289 font-size: 15px;
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index 46f1e99f7..cdac8ae6f 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -63,10 +63,13 @@ $video-thumbnail-ratio: $video-thumbnail-width / $video-thumbnail-height;
63 63
64$theater-bottom-space: 115px; 64$theater-bottom-space: 115px;
65 65
66$input-foreground-color: $fg-color;
66$input-background-color: $bg-color; 67$input-background-color: $bg-color;
67$input-placeholder-color: #898989; 68$input-placeholder-color: #898989;
68 69
69$textarea-background-color: $grey-background-hover-color; 70$textarea-foreground-color: $fg-color;
71$textarea-background-color: $bg-color;
72$markdown-textarea-background-color: $grey-background-hover-color;
70 73
71$sub-menu-margin-bottom: 30px; 74$sub-menu-margin-bottom: 30px;
72$sub-menu-margin-bottom-small-view: 10px; 75$sub-menu-margin-bottom-small-view: 10px;
@@ -92,10 +95,13 @@ $variables: (
92 --menuForegroundColor: var(--menuForegroundColor), 95 --menuForegroundColor: var(--menuForegroundColor),
93 --submenuColor: var(--submenuColor), 96 --submenuColor: var(--submenuColor),
94 97
98 --inputForegroundColor: var(--inputForegroundColor),
95 --inputBackgroundColor: var(--inputBackgroundColor), 99 --inputBackgroundColor: var(--inputBackgroundColor),
96 --inputPlaceholderColor: var(--inputPlaceholderColor), 100 --inputPlaceholderColor: var(--inputPlaceholderColor),
97 101
102 --textareaForegroundColor: var(--textareaForegroundColor),
98 --textareaBackgroundColor: var(--textareaBackgroundColor), 103 --textareaBackgroundColor: var(--textareaBackgroundColor),
104 --markdownTextareaBackgroundColor: var(--markdownTextareaBackgroundColor),
99 105
100 --actionButtonColor: var(--actionButtonColor), 106 --actionButtonColor: var(--actionButtonColor),
101 --supportButtonColor: var(--supportButtonColor), 107 --supportButtonColor: var(--supportButtonColor),
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss
index d48f2dfc4..33483533e 100644
--- a/client/src/sass/primeng-custom.scss
+++ b/client/src/sass/primeng-custom.scss
@@ -140,13 +140,13 @@ p-table {
140 font-size: 11px !important; 140 font-size: 11px !important;
141 top: 0 !important; 141 top: 0 !important;
142 142
143 &.pi-sort-up { 143 &.pi-sort-amount-up-alt {
144 @extend .glyphicon-triangle-top; 144 @extend .glyphicon-triangle-top;
145 145
146 color: var(--mainForegroundColor) !important; 146 color: var(--mainForegroundColor) !important;
147 } 147 }
148 148
149 &.pi-sort-down { 149 &.pi-sort-amount-down {
150 @extend .glyphicon-triangle-bottom; 150 @extend .glyphicon-triangle-bottom;
151 151
152 color: var(--mainForegroundColor) !important; 152 color: var(--mainForegroundColor) !important;
@@ -302,12 +302,12 @@ p-table {
302 @if $mobile-paginator { 302 @if $mobile-paginator {
303 p-paginator .ui-paginator-bottom { 303 p-paginator .ui-paginator-bottom {
304 display: block; 304 display: block;
305 305
306 .ui-paginator-current { 306 .ui-paginator-current {
307 position: relative; 307 position: relative;
308 display: block; 308 display: block;
309 } 309 }
310 310
311 a, .ui-paginator-pages { 311 a, .ui-paginator-pages {
312 vertical-align: middle; 312 vertical-align: middle;
313 } 313 }
@@ -345,7 +345,7 @@ p-multiselect {
345 } 345 }
346 } 346 }
347 347
348 .pi.pi-chevron-down{ 348 .pi.pi-chevron-down {
349 margin-left: 0 !important; 349 margin-left: 0 !important;
350 350
351 &::after { 351 &::after {
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index e5ea4636e..db546efb1 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -14,6 +14,7 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
14import { createUserAccountAndChannelAndPlaylist } from './user' 14import { createUserAccountAndChannelAndPlaylist } from './user'
15import { UserRole } from '@shared/models/users/user-role' 15import { UserRole } from '@shared/models/users/user-role'
16import { PluginManager } from '@server/lib/plugins/plugin-manager' 16import { PluginManager } from '@server/lib/plugins/plugin-manager'
17import { ActorModel } from '@server/models/activitypub/actor'
17 18
18type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } 19type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
19 20
@@ -109,6 +110,9 @@ async function getUser (usernameOrEmail?: string, password?: string) {
109 let user = await UserModel.loadByEmail(obj.user.email) 110 let user = await UserModel.loadByEmail(obj.user.email)
110 if (!user) user = await createUserFromExternal(obj.pluginName, obj.user) 111 if (!user) user = await createUserFromExternal(obj.pluginName, obj.user)
111 112
113 // Cannot create a user
114 if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.')
115
112 // If the user does not belongs to a plugin, it was created before its installation 116 // If the user does not belongs to a plugin, it was created before its installation
113 // Then we just go through a regular login process 117 // Then we just go through a regular login process
114 if (user.pluginAuth !== null) { 118 if (user.pluginAuth !== null) {
@@ -208,6 +212,10 @@ async function createUserFromExternal (pluginAuth: string, options: {
208 role: UserRole 212 role: UserRole
209 displayName: string 213 displayName: string
210}) { 214}) {
215 // Check an actor does not already exists with that name (removed user)
216 const actor = await ActorModel.loadLocalByName(options.username)
217 if (actor) return null
218
211 const userToCreate = new UserModel({ 219 const userToCreate = new UserModel({
212 username: options.username, 220 username: options.username,
213 password: null, 221 password: null,
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 840b9fc74..3bdbcdf6a 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -234,14 +234,19 @@ const usersUpdateMeValidator = [
234 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 234 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
235 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) 235 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
236 236
237 const user = res.locals.oauth.token.User
238
237 if (req.body.password || req.body.email) { 239 if (req.body.password || req.body.email) {
240 if (user.pluginAuth !== null) {
241 return res.status(400)
242 .json({ error: 'You cannot update your email or password that is associated with an external auth system.' })
243 }
244
238 if (!req.body.currentPassword) { 245 if (!req.body.currentPassword) {
239 return res.status(400) 246 return res.status(400)
240 .json({ error: 'currentPassword parameter is missing.' }) 247 .json({ error: 'currentPassword parameter is missing.' })
241 .end()
242 } 248 }
243 249
244 const user = res.locals.oauth.token.User
245 if (await user.isPasswordMatch(req.body.currentPassword) !== true) { 250 if (await user.isPasswordMatch(req.body.currentPassword) !== true) {
246 return res.status(401) 251 return res.status(401)
247 .json({ error: 'currentPassword is invalid.' }) 252 .json({ error: 'currentPassword is invalid.' })
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 4d597f0a3..6e737af15 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -1044,7 +1044,7 @@ describe('Test users API validators', function () {
1044 } 1044 }
1045 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() })) 1045 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }))
1046 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { magnetUri: getMagnetURI() })) 1046 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { magnetUri: getMagnetURI() }))
1047 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { torrentfile: 'video-720p.torrent' })) 1047 await importVideo(server.url, server.accessToken, immutableAssign(baseAttributes, { torrentfile: 'video-720p.torrent' as any }))
1048 1048
1049 await waitJobs([ server ]) 1049 await waitJobs([ server ])
1050 1050
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index 4d5989f43..d211859e4 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -175,7 +175,7 @@ Ajouter un sous-titre est vraiment facile`)
175 175
176 { 176 {
177 const attributes = immutableAssign(baseAttributes, { 177 const attributes = immutableAssign(baseAttributes, {
178 torrentfile: 'video-720p.torrent', 178 torrentfile: 'video-720p.torrent' as any,
179 description: 'this is a super torrent description', 179 description: 'this is a super torrent description',
180 tags: [ 'tag_torrent1', 'tag_torrent2' ] 180 tags: [ 'tag_torrent1', 'tag_torrent2' ]
181 }) 181 })
diff --git a/server/tests/plugins/external-auth.ts b/server/tests/plugins/external-auth.ts
index a85672782..57361be05 100644
--- a/server/tests/plugins/external-auth.ts
+++ b/server/tests/plugins/external-auth.ts
@@ -255,6 +255,16 @@ describe('Test external auth plugins', function () {
255 expect(body.role).to.equal(UserRole.USER) 255 expect(body.role).to.equal(UserRole.USER)
256 }) 256 })
257 257
258 it('Should not update an external auth email', async function () {
259 await updateMyUser({
260 url: server.url,
261 accessToken: cyanAccessToken,
262 email: 'toto@example.com',
263 currentPassword: 'toto',
264 statusCodeExpected: 400
265 })
266 })
267
258 it('Should reject token of Kefka by the plugin hook', async function () { 268 it('Should reject token of Kefka by the plugin hook', async function () {
259 this.timeout(10000) 269 this.timeout(10000)
260 270
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 54b506bce..08b7743a6 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -216,7 +216,7 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
216 .expect(expectedStatus) 216 .expect(expectedStatus)
217} 217}
218 218
219function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) { 219function updateMyUser (options: { url: string, accessToken: string, statusCodeExpected?: number } & UserUpdateMe) {
220 const path = '/api/v1/users/me' 220 const path = '/api/v1/users/me'
221 221
222 const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') 222 const toSend: UserUpdateMe = omit(options, 'url', 'accessToken')
@@ -226,7 +226,7 @@ function updateMyUser (options: { url: string, accessToken: string } & UserUpdat
226 path, 226 path,
227 token: options.accessToken, 227 token: options.accessToken,
228 fields: toSend, 228 fields: toSend,
229 statusCodeExpected: 204 229 statusCodeExpected: options.statusCodeExpected || 204
230 }) 230 })
231} 231}
232 232