diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-11-09 16:25:27 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-25 11:07:56 +0100 |
commit | 5beb89f223539f1e415a976ff104f772526b4d20 (patch) | |
tree | 2164677d16a2965d63499e249aa75ab0e06e3a6c | |
parent | afff310e50f2fa8419bb4242470cbde46ab54463 (diff) | |
download | PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.tar.gz PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.tar.zst PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.zip |
refactor scoped token service
26 files changed, 223 insertions, 176 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index e74b5daeb..92ecd5263 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -3,7 +3,7 @@ import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core' | |||
3 | import { ActivatedRoute } from '@angular/router' | 3 | import { ActivatedRoute } from '@angular/router' |
4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | 4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' |
5 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
6 | import { copyToClipboard } from '../../../assets/player/utils' | 6 | import { copyToClipboard } from '../../../root-helpers/utils' |
7 | import { InstanceService } from '@app/shared/shared-instance' | 7 | import { InstanceService } from '@app/shared/shared-instance' |
8 | import { ServerConfig } from '@shared/models' | 8 | import { ServerConfig } from '@shared/models' |
9 | import { ResolverData } from './about-instance.resolver' | 9 | import { ResolverData } from './about-instance.resolver' |
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts index 1d0e56bfd..aa6b5d0a9 100644 --- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts +++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts | |||
@@ -161,7 +161,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV | |||
161 | getVideoEmbed (entry: VideoBlacklist) { | 161 | getVideoEmbed (entry: VideoBlacklist) { |
162 | return buildVideoOrPlaylistEmbed( | 162 | return buildVideoOrPlaylistEmbed( |
163 | buildVideoLink({ | 163 | buildVideoLink({ |
164 | baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`, | 164 | baseUrl: `${environment.originServerUrl}/videos/embed/${entry.video.uuid}`, |
165 | title: false, | 165 | title: false, |
166 | warningTitle: false | 166 | warningTitle: false |
167 | }) | 167 | }) |
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html index 62e2cb59b..53a9c91ac 100644 --- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.html +++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.html | |||
@@ -7,8 +7,8 @@ | |||
7 | <div class="form-group col-12 col-lg-4 col-xl-3"> | 7 | <div class="form-group col-12 col-lg-4 col-xl-3"> |
8 | <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2> | 8 | <h2 i18n class="applications-title">SUBSCRIPTION FEED</h2> |
9 | <div i18n class="applications-description"> | 9 | <div i18n class="applications-description"> |
10 | Used to retrieve the list of videos of the creators | 10 | Use third-party feed aggregators to retrieve the list of videos from |
11 | you subscribed to from outside PeerTube | 11 | channels you subscribed to. Make sure to keep your token private. |
12 | </div> | 12 | </div> |
13 | </div> | 13 | </div> |
14 | 14 | ||
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts index c3f09dfe3..233e42c83 100644 --- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts +++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | 1 | ||
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, OnInit } from '@angular/core' |
3 | import { AuthService, Notifier, ConfirmService } from '@app/core' | 3 | import { AuthService, Notifier, ConfirmService, ScopedTokensService } from '@app/core' |
4 | import { VideoService } from '@app/shared/shared-main' | 4 | import { VideoService } from '@app/shared/shared-main' |
5 | import { FeedFormat } from '@shared/models' | 5 | import { FeedFormat } from '@shared/models' |
6 | import { Subject, merge } from 'rxjs' | 6 | import { ScopedToken } from '@shared/models/users/user-scoped-token' |
7 | import { debounceTime } from 'rxjs/operators' | 7 | import { environment } from '../../../environments/environment' |
8 | 8 | ||
9 | @Component({ | 9 | @Component({ |
10 | selector: 'my-account-applications', | 10 | selector: 'my-account-applications', |
@@ -15,11 +15,11 @@ export class MyAccountApplicationsComponent implements OnInit { | |||
15 | feedUrl: string | 15 | feedUrl: string |
16 | feedToken: string | 16 | feedToken: string |
17 | 17 | ||
18 | private baseURL = window.location.protocol + '//' + window.location.host | 18 | private baseURL = environment.originServerUrl |
19 | private tokenStream = new Subject() | ||
20 | 19 | ||
21 | constructor ( | 20 | constructor ( |
22 | private authService: AuthService, | 21 | private authService: AuthService, |
22 | private scopedTokensService: ScopedTokensService, | ||
23 | private videoService: VideoService, | 23 | private videoService: VideoService, |
24 | private notifier: Notifier, | 24 | private notifier: Notifier, |
25 | private confirmService: ConfirmService | 25 | private confirmService: ConfirmService |
@@ -27,31 +27,40 @@ export class MyAccountApplicationsComponent implements OnInit { | |||
27 | 27 | ||
28 | ngOnInit () { | 28 | ngOnInit () { |
29 | this.feedUrl = this.baseURL | 29 | this.feedUrl = this.baseURL |
30 | this.scopedTokensService.getScopedTokens() | ||
31 | .subscribe( | ||
32 | tokens => this.regenApplications(tokens), | ||
30 | 33 | ||
31 | merge( | 34 | err => { |
32 | this.tokenStream, | 35 | this.notifier.error(err.message) |
33 | this.authService.userInformationLoaded | 36 | } |
34 | ).pipe(debounceTime(400)) | 37 | ) |
35 | .subscribe( | ||
36 | _ => { | ||
37 | const user = this.authService.getUser() | ||
38 | this.videoService.getVideoSubscriptionFeedUrls(user.account.id) | ||
39 | .then(feeds => this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url) | ||
40 | .then(_ => this.authService.getScopedTokens().then(tokens => this.feedToken = tokens.feedToken)) | ||
41 | }, | ||
42 | |||
43 | err => { | ||
44 | this.notifier.error(err.message) | ||
45 | } | ||
46 | ) | ||
47 | } | 38 | } |
48 | 39 | ||
49 | async renewToken () { | 40 | async renewToken () { |
50 | const res = await this.confirmService.confirm('Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?', 'Renew token') | 41 | const res = await this.confirmService.confirm( |
42 | $localize`Renewing the token will disallow previously configured clients from retrieving the feed until they use the new token. Proceed?`, | ||
43 | $localize`Renew token` | ||
44 | ) | ||
51 | if (res === false) return | 45 | if (res === false) return |
52 | 46 | ||
53 | await this.authService.renewScopedTokens() | 47 | this.scopedTokensService.renewScopedTokens().subscribe( |
54 | this.notifier.success('Token renewed. Update your client configuration accordingly.') | 48 | tokens => { |
55 | this.tokenStream.next() | 49 | this.regenApplications(tokens) |
50 | this.notifier.success($localize`Token renewed. Update your client configuration accordingly.`) | ||
51 | }, | ||
52 | |||
53 | err => { | ||
54 | this.notifier.error(err.message) | ||
55 | } | ||
56 | ) | ||
57 | |||
58 | } | ||
59 | |||
60 | private regenApplications (tokens: ScopedToken) { | ||
61 | const user = this.authService.getUser() | ||
62 | const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken) | ||
63 | this.feedUrl = this.baseURL + feeds.find(f => f.format === FeedFormat.RSS).url | ||
64 | this.feedToken = tokens.feedToken | ||
56 | } | 65 | } |
57 | } | 66 | } |
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index 12966aebb..eaf8a72e9 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts | |||
@@ -41,11 +41,6 @@ export class MyAccountComponent implements OnInit { | |||
41 | label: $localize`Abuse reports`, | 41 | label: $localize`Abuse reports`, |
42 | routerLink: '/my-account/abuses', | 42 | routerLink: '/my-account/abuses', |
43 | iconName: 'flag' | 43 | iconName: 'flag' |
44 | }, | ||
45 | { | ||
46 | label: $localize`Applications`, | ||
47 | routerLink: '/my-account/applications', | ||
48 | iconName: 'codesandbox' | ||
49 | } | 44 | } |
50 | ] | 45 | ] |
51 | } | 46 | } |
@@ -61,6 +56,11 @@ export class MyAccountComponent implements OnInit { | |||
61 | routerLink: '/my-account/notifications' | 56 | routerLink: '/my-account/notifications' |
62 | }, | 57 | }, |
63 | 58 | ||
59 | { | ||
60 | label: $localize`Applications`, | ||
61 | routerLink: '/my-account/applications' | ||
62 | }, | ||
63 | |||
64 | moderationEntries | 64 | moderationEntries |
65 | ] | 65 | ] |
66 | } | 66 | } |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 70bf58aae..076864563 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -20,8 +20,8 @@ import { MyAccountDangerZoneComponent } from './my-account-settings/my-account-d | |||
20 | import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences' | 20 | import { MyAccountNotificationPreferencesComponent } from './my-account-settings/my-account-notification-preferences' |
21 | import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component' | 21 | import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component' |
22 | import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' | 22 | import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' |
23 | import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component' | ||
23 | import { MyAccountComponent } from './my-account.component' | 24 | import { MyAccountComponent } from './my-account.component' |
24 | import { VideoChangeOwnershipComponent } from './my-account-applications/my-account-applications.component' | ||
25 | 25 | ||
26 | @NgModule({ | 26 | @NgModule({ |
27 | imports: [ | 27 | imports: [ |
@@ -46,13 +46,13 @@ import { VideoChangeOwnershipComponent } from './my-account-applications/my-acco | |||
46 | MyAccountChangePasswordComponent, | 46 | MyAccountChangePasswordComponent, |
47 | MyAccountProfileComponent, | 47 | MyAccountProfileComponent, |
48 | MyAccountChangeEmailComponent, | 48 | MyAccountChangeEmailComponent, |
49 | MyAccountApplicationsComponent, | ||
49 | 50 | ||
50 | MyAccountDangerZoneComponent, | 51 | MyAccountDangerZoneComponent, |
51 | MyAccountBlocklistComponent, | 52 | MyAccountBlocklistComponent, |
52 | MyAccountAbusesListComponent, | 53 | MyAccountAbusesListComponent, |
53 | MyAccountServerBlocklistComponent, | 54 | MyAccountServerBlocklistComponent, |
54 | MyAccountNotificationsComponent, | 55 | MyAccountNotificationsComponent, |
55 | MyAccountNotificationPreferencesComponent, | ||
56 | MyAccountNotificationPreferencesComponent | 56 | MyAccountNotificationPreferencesComponent |
57 | ], | 57 | ], |
58 | 58 | ||
diff --git a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts index 10031d6cc..03881c295 100644 --- a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts +++ b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' | 3 | import { AuthService, LocalStorageService, Notifier, ScopedTokensService, ScreenService, ServerService, UserService } from '@app/core' |
4 | import { HooksService } from '@app/core/plugins/hooks.service' | 4 | import { HooksService } from '@app/core/plugins/hooks.service' |
5 | import { immutableAssign } from '@app/helpers' | 5 | import { immutableAssign } from '@app/helpers' |
6 | import { VideoService } from '@app/shared/shared-main' | 6 | import { VideoService } from '@app/shared/shared-main' |
@@ -9,6 +9,7 @@ import { AbstractVideoList, OwnerDisplayType } from '@app/shared/shared-video-mi | |||
9 | import { VideoSortField, FeedFormat } from '@shared/models' | 9 | import { VideoSortField, FeedFormat } from '@shared/models' |
10 | import { copyToClipboard } from '../../../root-helpers/utils' | 10 | import { copyToClipboard } from '../../../root-helpers/utils' |
11 | import { environment } from '../../../environments/environment' | 11 | import { environment } from '../../../environments/environment' |
12 | import { forkJoin } from 'rxjs' | ||
12 | 13 | ||
13 | @Component({ | 14 | @Component({ |
14 | selector: 'my-videos-user-subscriptions', | 15 | selector: 'my-videos-user-subscriptions', |
@@ -32,7 +33,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
32 | protected storageService: LocalStorageService, | 33 | protected storageService: LocalStorageService, |
33 | private userSubscription: UserSubscriptionService, | 34 | private userSubscription: UserSubscriptionService, |
34 | private hooks: HooksService, | 35 | private hooks: HooksService, |
35 | private videoService: VideoService | 36 | private videoService: VideoService, |
37 | private scopedTokensService: ScopedTokensService | ||
36 | ) { | 38 | ) { |
37 | super() | 39 | super() |
38 | 40 | ||
@@ -49,9 +51,19 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement | |||
49 | super.ngOnInit() | 51 | super.ngOnInit() |
50 | 52 | ||
51 | const user = this.authService.getUser() | 53 | const user = this.authService.getUser() |
52 | let feedUrl = environment.embedUrl | 54 | let feedUrl = environment.originServerUrl |
53 | this.videoService.getVideoSubscriptionFeedUrls(user.account.id) | 55 | |
54 | .then((feeds: any) => feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url) | 56 | this.scopedTokensService.getScopedTokens().subscribe( |
57 | tokens => { | ||
58 | const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken) | ||
59 | feedUrl = feedUrl + feeds.find((f: any) => f.format === FeedFormat.RSS).url | ||
60 | }, | ||
61 | |||
62 | err => { | ||
63 | this.notifier.error(err.message) | ||
64 | } | ||
65 | ) | ||
66 | |||
55 | this.actions.unshift({ | 67 | this.actions.unshift({ |
56 | label: $localize`Feed`, | 68 | label: $localize`Feed`, |
57 | iconName: 'syndication', | 69 | iconName: 'syndication', |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index 224f35f82..fd6062d3f 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -11,7 +11,6 @@ import { environment } from '../../../environments/environment' | |||
11 | import { RestExtractor } from '../rest/rest-extractor.service' | 11 | import { RestExtractor } from '../rest/rest-extractor.service' |
12 | import { AuthStatus } from './auth-status.model' | 12 | import { AuthStatus } from './auth-status.model' |
13 | import { AuthUser } from './auth-user.model' | 13 | import { AuthUser } from './auth-user.model' |
14 | import { ScopedTokenType, ScopedToken } from '@shared/models/users/user-scoped-token' | ||
15 | 14 | ||
16 | interface UserLoginWithUsername extends UserLogin { | 15 | interface UserLoginWithUsername extends UserLogin { |
17 | access_token: string | 16 | access_token: string |
@@ -27,7 +26,6 @@ export class AuthService { | |||
27 | private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local' | 26 | private static BASE_CLIENT_URL = environment.apiUrl + '/api/v1/oauth-clients/local' |
28 | private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token' | 27 | private static BASE_TOKEN_URL = environment.apiUrl + '/api/v1/users/token' |
29 | private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token' | 28 | private static BASE_REVOKE_TOKEN_URL = environment.apiUrl + '/api/v1/users/revoke-token' |
30 | private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens' | ||
31 | private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me' | 29 | private static BASE_USER_INFORMATION_URL = environment.apiUrl + '/api/v1/users/me' |
32 | private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = { | 30 | private static LOCAL_STORAGE_OAUTH_CLIENT_KEYS = { |
33 | CLIENT_ID: 'client_id', | 31 | CLIENT_ID: 'client_id', |
@@ -43,7 +41,6 @@ export class AuthService { | |||
43 | private loginChanged: Subject<AuthStatus> | 41 | private loginChanged: Subject<AuthStatus> |
44 | private user: AuthUser = null | 42 | private user: AuthUser = null |
45 | private refreshingTokenObservable: Observable<any> | 43 | private refreshingTokenObservable: Observable<any> |
46 | private scopedTokens: ScopedToken | ||
47 | 44 | ||
48 | constructor ( | 45 | constructor ( |
49 | private http: HttpClient, | 46 | private http: HttpClient, |
@@ -247,48 +244,6 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular | |||
247 | ) | 244 | ) |
248 | } | 245 | } |
249 | 246 | ||
250 | getScopedTokens (): Promise<ScopedToken> { | ||
251 | return new Promise((res, rej) => { | ||
252 | if (this.scopedTokens) return res(this.scopedTokens) | ||
253 | |||
254 | const authHeaderValue = this.getRequestHeaderValue() | ||
255 | const headers = new HttpHeaders().set('Authorization', authHeaderValue) | ||
256 | |||
257 | this.http.get<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, { headers }) | ||
258 | .subscribe( | ||
259 | scopedTokens => { | ||
260 | this.scopedTokens = scopedTokens | ||
261 | res(this.scopedTokens) | ||
262 | }, | ||
263 | |||
264 | err => { | ||
265 | console.error(err) | ||
266 | rej(err) | ||
267 | } | ||
268 | ) | ||
269 | }) | ||
270 | } | ||
271 | |||
272 | renewScopedTokens (): Promise<ScopedToken> { | ||
273 | return new Promise((res, rej) => { | ||
274 | const authHeaderValue = this.getRequestHeaderValue() | ||
275 | const headers = new HttpHeaders().set('Authorization', authHeaderValue) | ||
276 | |||
277 | this.http.post<ScopedToken>(AuthService.BASE_SCOPED_TOKENS_URL, {}, { headers }) | ||
278 | .subscribe( | ||
279 | scopedTokens => { | ||
280 | this.scopedTokens = scopedTokens | ||
281 | res(this.scopedTokens) | ||
282 | }, | ||
283 | |||
284 | err => { | ||
285 | console.error(err) | ||
286 | rej(err) | ||
287 | } | ||
288 | ) | ||
289 | }) | ||
290 | } | ||
291 | |||
292 | private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { | 247 | private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { |
293 | // User is not loaded yet, set manually auth header | 248 | // User is not loaded yet, set manually auth header |
294 | const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) | 249 | const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) |
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 6c0a2245d..f51f1920d 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts | |||
@@ -12,6 +12,7 @@ import { LoadingBarModule } from '@ngx-loading-bar/core' | |||
12 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' | 12 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' |
13 | import { LoadingBarRouterModule } from '@ngx-loading-bar/router' | 13 | import { LoadingBarRouterModule } from '@ngx-loading-bar/router' |
14 | import { AuthService } from './auth' | 14 | import { AuthService } from './auth' |
15 | import { ScopedTokensService } from './scoped-tokens' | ||
15 | import { ConfirmService } from './confirm' | 16 | import { ConfirmService } from './confirm' |
16 | import { CheatSheetComponent } from './hotkeys' | 17 | import { CheatSheetComponent } from './hotkeys' |
17 | import { MenuService } from './menu' | 18 | import { MenuService } from './menu' |
@@ -57,6 +58,7 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra | |||
57 | 58 | ||
58 | providers: [ | 59 | providers: [ |
59 | AuthService, | 60 | AuthService, |
61 | ScopedTokensService, | ||
60 | ConfirmService, | 62 | ConfirmService, |
61 | ServerService, | 63 | ServerService, |
62 | ThemeService, | 64 | ThemeService, |
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts index a0c34543d..9245ff6fc 100644 --- a/client/src/app/core/index.ts +++ b/client/src/app/core/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './auth' | 1 | export * from './auth' |
2 | export * from './scoped-tokens' | ||
2 | export * from './confirm' | 3 | export * from './confirm' |
3 | export * from './hotkeys' | 4 | export * from './hotkeys' |
4 | export * from './menu' | 5 | export * from './menu' |
diff --git a/client/src/app/core/scoped-tokens/index.ts b/client/src/app/core/scoped-tokens/index.ts new file mode 100644 index 000000000..c9a48ffcd --- /dev/null +++ b/client/src/app/core/scoped-tokens/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './scoped-tokens.service' | |||
diff --git a/client/src/app/core/scoped-tokens/scoped-tokens.service.ts b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts new file mode 100644 index 000000000..8e3697c31 --- /dev/null +++ b/client/src/app/core/scoped-tokens/scoped-tokens.service.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { environment } from '../../../environments/environment' | ||
4 | import { AuthService } from '../auth' | ||
5 | import { ScopedToken } from '@shared/models/users/user-scoped-token' | ||
6 | import { catchError } from 'rxjs/operators' | ||
7 | import { RestExtractor } from '../rest' | ||
8 | |||
9 | @Injectable() | ||
10 | export class ScopedTokensService { | ||
11 | private static BASE_SCOPED_TOKENS_URL = environment.apiUrl + '/api/v1/users/scoped-tokens' | ||
12 | |||
13 | constructor ( | ||
14 | private authHttp: HttpClient, | ||
15 | private restExtractor: RestExtractor | ||
16 | ) {} | ||
17 | |||
18 | getScopedTokens () { | ||
19 | return this.authHttp | ||
20 | .get<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL) | ||
21 | .pipe( | ||
22 | catchError(res => this.restExtractor.handleError(res)) | ||
23 | ) | ||
24 | } | ||
25 | |||
26 | renewScopedTokens () { | ||
27 | return this.authHttp | ||
28 | .post<ScopedToken>(ScopedTokensService.BASE_SCOPED_TOKENS_URL, {}) | ||
29 | .pipe( | ||
30 | catchError(res => this.restExtractor.handleError(res)) | ||
31 | ) | ||
32 | } | ||
33 | } | ||
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index a22507f46..9c805b4ca 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts | |||
@@ -58,7 +58,7 @@ function getAbsoluteAPIUrl () { | |||
58 | } | 58 | } |
59 | 59 | ||
60 | function getAbsoluteEmbedUrl () { | 60 | function getAbsoluteEmbedUrl () { |
61 | let absoluteEmbedUrl = environment.embedUrl | 61 | let absoluteEmbedUrl = environment.originServerUrl |
62 | if (!absoluteEmbedUrl) { | 62 | if (!absoluteEmbedUrl) { |
63 | // The Embed is on the same domain | 63 | // The Embed is on the same domain |
64 | absoluteEmbedUrl = window.location.origin | 64 | absoluteEmbedUrl = window.location.origin |
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts index ca0d23699..807665b9c 100644 --- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts +++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts | |||
@@ -112,7 +112,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV | |||
112 | getVideoEmbed (abuse: AdminAbuse) { | 112 | getVideoEmbed (abuse: AdminAbuse) { |
113 | return buildVideoOrPlaylistEmbed( | 113 | return buildVideoOrPlaylistEmbed( |
114 | buildVideoLink({ | 114 | buildVideoLink({ |
115 | baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`, | 115 | baseUrl: `${environment.originServerUrl}/videos/embed/${abuse.video.uuid}`, |
116 | title: false, | 116 | title: false, |
117 | warningTitle: false, | 117 | warningTitle: false, |
118 | startTime: abuse.video.startAt, | 118 | startTime: abuse.video.startAt, |
diff --git a/client/src/app/shared/shared-forms/input-readonly-copy.component.ts b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts index 520827a53..b04d69d05 100644 --- a/client/src/app/shared/shared-forms/input-readonly-copy.component.ts +++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { FormGroup } from '@angular/forms' | ||
4 | 3 | ||
5 | @Component({ | 4 | @Component({ |
6 | selector: 'my-input-readonly-copy', | 5 | selector: 'my-input-readonly-copy', |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index b81540e8d..70be5d7d2 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -18,8 +18,7 @@ import { | |||
18 | VideoFilter, | 18 | VideoFilter, |
19 | VideoPrivacy, | 19 | VideoPrivacy, |
20 | VideoSortField, | 20 | VideoSortField, |
21 | VideoUpdate, | 21 | VideoUpdate |
22 | VideoCreate | ||
23 | } from '@shared/models' | 22 | } from '@shared/models' |
24 | import { environment } from '../../../../environments/environment' | 23 | import { environment } from '../../../../environments/environment' |
25 | import { Account } from '../account/account.model' | 24 | import { Account } from '../account/account.model' |
@@ -44,13 +43,13 @@ export interface VideosProvider { | |||
44 | export class VideoService implements VideosProvider { | 43 | export class VideoService implements VideosProvider { |
45 | static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' | 44 | static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' |
46 | static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.' | 45 | static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.' |
46 | static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.' | ||
47 | 47 | ||
48 | constructor ( | 48 | constructor ( |
49 | private authHttp: HttpClient, | 49 | private authHttp: HttpClient, |
50 | private restExtractor: RestExtractor, | 50 | private restExtractor: RestExtractor, |
51 | private restService: RestService, | 51 | private restService: RestService, |
52 | private serverService: ServerService, | 52 | private serverService: ServerService |
53 | private authService: AuthService | ||
54 | ) {} | 53 | ) {} |
55 | 54 | ||
56 | getVideoViewUrl (uuid: string) { | 55 | getVideoViewUrl (uuid: string) { |
@@ -238,22 +237,22 @@ export class VideoService implements VideosProvider { | |||
238 | ) | 237 | ) |
239 | } | 238 | } |
240 | 239 | ||
241 | buildBaseFeedUrls (params: HttpParams) { | 240 | buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) { |
242 | const feeds = [ | 241 | const feeds = [ |
243 | { | 242 | { |
244 | format: FeedFormat.RSS, | 243 | format: FeedFormat.RSS, |
245 | label: 'media rss 2.0', | 244 | label: 'media rss 2.0', |
246 | url: VideoService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase() | 245 | url: base + FeedFormat.RSS.toLowerCase() |
247 | }, | 246 | }, |
248 | { | 247 | { |
249 | format: FeedFormat.ATOM, | 248 | format: FeedFormat.ATOM, |
250 | label: 'atom 1.0', | 249 | label: 'atom 1.0', |
251 | url: VideoService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase() | 250 | url: base + FeedFormat.ATOM.toLowerCase() |
252 | }, | 251 | }, |
253 | { | 252 | { |
254 | format: FeedFormat.JSON, | 253 | format: FeedFormat.JSON, |
255 | label: 'json 1.0', | 254 | label: 'json 1.0', |
256 | url: VideoService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase() | 255 | url: base + FeedFormat.JSON.toLowerCase() |
257 | } | 256 | } |
258 | ] | 257 | ] |
259 | 258 | ||
@@ -294,14 +293,12 @@ export class VideoService implements VideosProvider { | |||
294 | return this.buildBaseFeedUrls(params) | 293 | return this.buildBaseFeedUrls(params) |
295 | } | 294 | } |
296 | 295 | ||
297 | async getVideoSubscriptionFeedUrls (accountId: number) { | 296 | getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) { |
298 | let params = this.restService.addRestGetParams(new HttpParams()) | 297 | let params = this.restService.addRestGetParams(new HttpParams()) |
299 | params = params.set('accountId', accountId.toString()) | 298 | params = params.set('accountId', accountId.toString()) |
300 | |||
301 | const { feedToken } = await this.authService.getScopedTokens() | ||
302 | params = params.set('token', feedToken) | 299 | params = params.set('token', feedToken) |
303 | 300 | ||
304 | return this.buildBaseFeedUrls(params) | 301 | return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL) |
305 | } | 302 | } |
306 | 303 | ||
307 | getVideoFileMetadata (metadataUrl: string) { | 304 | getVideoFileMetadata (metadataUrl: string) { |
diff --git a/client/src/environments/environment.e2e.ts b/client/src/environments/environment.e2e.ts index b33ff9f86..a1a58e36f 100644 --- a/client/src/environments/environment.e2e.ts +++ b/client/src/environments/environment.e2e.ts | |||
@@ -2,5 +2,5 @@ export const environment = { | |||
2 | production: false, | 2 | production: false, |
3 | hmr: false, | 3 | hmr: false, |
4 | apiUrl: 'http://localhost:9001', | 4 | apiUrl: 'http://localhost:9001', |
5 | embedUrl: 'http://localhost:9001' | 5 | originServerUrl: 'http://localhost:9001' |
6 | } | 6 | } |
diff --git a/client/src/environments/environment.hmr.ts b/client/src/environments/environment.hmr.ts index 3b6eff302..ab7631920 100644 --- a/client/src/environments/environment.hmr.ts +++ b/client/src/environments/environment.hmr.ts | |||
@@ -2,5 +2,5 @@ export const environment = { | |||
2 | production: false, | 2 | production: false, |
3 | hmr: true, | 3 | hmr: true, |
4 | apiUrl: '', | 4 | apiUrl: '', |
5 | embedUrl: 'http://localhost:9000' | 5 | originServerUrl: 'http://localhost:9000' |
6 | } | 6 | } |
diff --git a/client/src/environments/environment.prod.ts b/client/src/environments/environment.prod.ts index 2e9b9fefe..e1b736c61 100644 --- a/client/src/environments/environment.prod.ts +++ b/client/src/environments/environment.prod.ts | |||
@@ -2,5 +2,5 @@ export const environment = { | |||
2 | production: true, | 2 | production: true, |
3 | hmr: false, | 3 | hmr: false, |
4 | apiUrl: '', | 4 | apiUrl: '', |
5 | embedUrl: '' | 5 | originServerUrl: '' |
6 | } | 6 | } |
diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts index e00523976..5d7011265 100644 --- a/client/src/environments/environment.ts +++ b/client/src/environments/environment.ts | |||
@@ -12,5 +12,5 @@ export const environment = { | |||
12 | production: true, | 12 | production: true, |
13 | hmr: false, | 13 | hmr: false, |
14 | apiUrl: '', | 14 | apiUrl: '', |
15 | embedUrl: '' | 15 | originServerUrl: '' |
16 | } | 16 | } |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 4d70110fe..e6491b492 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -225,7 +225,7 @@ | |||
225 | line-height: $button-height; | 225 | line-height: $button-height; |
226 | border-radius: 3px; | 226 | border-radius: 3px; |
227 | text-align: center; | 227 | text-align: center; |
228 | padding: 0 13px 0 13px; | 228 | padding: 0 17px 0 13px; |
229 | cursor: pointer; | 229 | cursor: pointer; |
230 | } | 230 | } |
231 | 231 | ||
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 6e9f7e60c..5c95069fc 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -18,7 +18,6 @@ import { cacheRoute } from '../middlewares/cache' | |||
18 | import { VideoModel } from '../models/video/video' | 18 | import { VideoModel } from '../models/video/video' |
19 | import { VideoCommentModel } from '../models/video/video-comment' | 19 | import { VideoCommentModel } from '../models/video/video-comment' |
20 | import { VideoFilter } from '../../shared/models/videos/video-query.type' | 20 | import { VideoFilter } from '../../shared/models/videos/video-query.type' |
21 | import { logger } from '../helpers/logger' | ||
22 | 21 | ||
23 | const feedsRouter = express.Router() | 22 | const feedsRouter = express.Router() |
24 | 23 | ||
@@ -47,10 +46,24 @@ feedsRouter.get('/feeds/videos.:format', | |||
47 | })(ROUTE_CACHE_LIFETIME.FEEDS)), | 46 | })(ROUTE_CACHE_LIFETIME.FEEDS)), |
48 | commonVideosFiltersValidator, | 47 | commonVideosFiltersValidator, |
49 | asyncMiddleware(videoFeedsValidator), | 48 | asyncMiddleware(videoFeedsValidator), |
50 | asyncMiddleware(videoSubscriptonFeedsValidator), | ||
51 | asyncMiddleware(generateVideoFeed) | 49 | asyncMiddleware(generateVideoFeed) |
52 | ) | 50 | ) |
53 | 51 | ||
52 | feedsRouter.get('/feeds/subscriptions.:format', | ||
53 | videosSortValidator, | ||
54 | setDefaultVideosSort, | ||
55 | feedsFormatValidator, | ||
56 | setFeedFormatContentType, | ||
57 | asyncMiddleware(cacheRoute({ | ||
58 | headerBlacklist: [ | ||
59 | 'Content-Type' | ||
60 | ] | ||
61 | })(ROUTE_CACHE_LIFETIME.FEEDS)), | ||
62 | commonVideosFiltersValidator, | ||
63 | asyncMiddleware(videoSubscriptonFeedsValidator), | ||
64 | asyncMiddleware(generateVideoFeedForSubscriptions) | ||
65 | ) | ||
66 | |||
54 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
55 | 68 | ||
56 | export { | 69 | export { |
@@ -61,7 +74,6 @@ export { | |||
61 | 74 | ||
62 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { | 75 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { |
63 | const start = 0 | 76 | const start = 0 |
64 | |||
65 | const video = res.locals.videoAll | 77 | const video = res.locals.videoAll |
66 | const account = res.locals.account | 78 | const account = res.locals.account |
67 | const videoChannel = res.locals.videoChannel | 79 | const videoChannel = res.locals.videoChannel |
@@ -125,10 +137,8 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
125 | 137 | ||
126 | async function generateVideoFeed (req: express.Request, res: express.Response) { | 138 | async function generateVideoFeed (req: express.Request, res: express.Response) { |
127 | const start = 0 | 139 | const start = 0 |
128 | |||
129 | const account = res.locals.account | 140 | const account = res.locals.account |
130 | const videoChannel = res.locals.videoChannel | 141 | const videoChannel = res.locals.videoChannel |
131 | const token = req.query.token | ||
132 | const nsfw = buildNSFWFilter(res, req.query.nsfw) | 142 | const nsfw = buildNSFWFilter(res, req.query.nsfw) |
133 | 143 | ||
134 | let name: string | 144 | let name: string |
@@ -152,21 +162,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
152 | queryString: new URL(WEBSERVER.URL + req.url).search | 162 | queryString: new URL(WEBSERVER.URL + req.url).search |
153 | }) | 163 | }) |
154 | 164 | ||
155 | /** | 165 | const options = { |
156 | * We have two ways to query video results: | 166 | accountId: account ? account.id : null, |
157 | * - one with account and token -> get subscription videos | 167 | videoChannelId: videoChannel ? videoChannel.id : null |
158 | * - one with either account, channel, or nothing: just videos with these filters | 168 | } |
159 | */ | ||
160 | const options = token && token !== '' && res.locals.user | ||
161 | ? { | ||
162 | followerActorId: res.locals.user.Account.Actor.id, | ||
163 | user: res.locals.user, | ||
164 | includeLocalVideos: false | ||
165 | } | ||
166 | : { | ||
167 | accountId: account ? account.id : null, | ||
168 | videoChannelId: videoChannel ? videoChannel.id : null | ||
169 | } | ||
170 | 169 | ||
171 | const resultList = await VideoModel.listForApi({ | 170 | const resultList = await VideoModel.listForApi({ |
172 | start, | 171 | start, |
@@ -179,10 +178,86 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
179 | ...options | 178 | ...options |
180 | }) | 179 | }) |
181 | 180 | ||
181 | addVideosToFeed(feed, resultList.data) | ||
182 | |||
183 | // Now the feed generation is done, let's send it! | ||
184 | return sendFeed(feed, req, res) | ||
185 | } | ||
186 | |||
187 | async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) { | ||
188 | const start = 0 | ||
189 | const account = res.locals.account | ||
190 | const nsfw = buildNSFWFilter(res, req.query.nsfw) | ||
191 | const name = account.getDisplayName() | ||
192 | const description = account.description | ||
193 | |||
194 | const feed = initFeed({ | ||
195 | name, | ||
196 | description, | ||
197 | resourceType: 'videos', | ||
198 | queryString: new URL(WEBSERVER.URL + req.url).search | ||
199 | }) | ||
200 | |||
201 | const options = { | ||
202 | followerActorId: res.locals.user.Account.Actor.id, | ||
203 | user: res.locals.user | ||
204 | } | ||
205 | |||
206 | const resultList = await VideoModel.listForApi({ | ||
207 | start, | ||
208 | count: FEEDS.COUNT, | ||
209 | sort: req.query.sort, | ||
210 | includeLocalVideos: true, | ||
211 | nsfw, | ||
212 | filter: req.query.filter as VideoFilter, | ||
213 | withFiles: true, | ||
214 | ...options | ||
215 | }) | ||
216 | |||
217 | addVideosToFeed(feed, resultList.data) | ||
218 | |||
219 | // Now the feed generation is done, let's send it! | ||
220 | return sendFeed(feed, req, res) | ||
221 | } | ||
222 | |||
223 | function initFeed (parameters: { | ||
224 | name: string | ||
225 | description: string | ||
226 | resourceType?: 'videos' | 'video-comments' | ||
227 | queryString?: string | ||
228 | }) { | ||
229 | const webserverUrl = WEBSERVER.URL | ||
230 | const { name, description, resourceType, queryString } = parameters | ||
231 | |||
232 | return new Feed({ | ||
233 | title: name, | ||
234 | description, | ||
235 | // updated: TODO: somehowGetLatestUpdate, // optional, default = today | ||
236 | id: webserverUrl, | ||
237 | link: webserverUrl, | ||
238 | image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', | ||
239 | favicon: webserverUrl + '/client/assets/images/favicon.png', | ||
240 | copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + | ||
241 | ` and potential licenses granted by each content's rightholder.`, | ||
242 | generator: `Toraifōsu`, // ^.~ | ||
243 | feedLinks: { | ||
244 | json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, | ||
245 | atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, | ||
246 | rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` | ||
247 | }, | ||
248 | author: { | ||
249 | name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, | ||
250 | email: CONFIG.ADMIN.EMAIL, | ||
251 | link: `${webserverUrl}/about` | ||
252 | } | ||
253 | }) | ||
254 | } | ||
255 | |||
256 | function addVideosToFeed (feed, videos: VideoModel[]) { | ||
182 | /** | 257 | /** |
183 | * Adding video items to the feed object, one at a time | 258 | * Adding video items to the feed object, one at a time |
184 | */ | 259 | */ |
185 | resultList.data.forEach(video => { | 260 | for (const video of videos) { |
186 | const formattedVideoFiles = video.getFormattedVideoFilesJSON() | 261 | const formattedVideoFiles = video.getFormattedVideoFilesJSON() |
187 | 262 | ||
188 | const torrents = formattedVideoFiles.map(videoFile => ({ | 263 | const torrents = formattedVideoFiles.map(videoFile => ({ |
@@ -252,43 +327,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
252 | } | 327 | } |
253 | ] | 328 | ] |
254 | }) | 329 | }) |
255 | }) | 330 | } |
256 | |||
257 | // Now the feed generation is done, let's send it! | ||
258 | return sendFeed(feed, req, res) | ||
259 | } | ||
260 | |||
261 | function initFeed (parameters: { | ||
262 | name: string | ||
263 | description: string | ||
264 | resourceType?: 'videos' | 'video-comments' | ||
265 | queryString?: string | ||
266 | }) { | ||
267 | const webserverUrl = WEBSERVER.URL | ||
268 | const { name, description, resourceType, queryString } = parameters | ||
269 | |||
270 | return new Feed({ | ||
271 | title: name, | ||
272 | description, | ||
273 | // updated: TODO: somehowGetLatestUpdate, // optional, default = today | ||
274 | id: webserverUrl, | ||
275 | link: webserverUrl, | ||
276 | image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', | ||
277 | favicon: webserverUrl + '/client/assets/images/favicon.png', | ||
278 | copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + | ||
279 | ` and potential licenses granted by each content's rightholder.`, | ||
280 | generator: `Toraifōsu`, // ^.~ | ||
281 | feedLinks: { | ||
282 | json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, | ||
283 | atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, | ||
284 | rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` | ||
285 | }, | ||
286 | author: { | ||
287 | name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, | ||
288 | email: CONFIG.ADMIN.EMAIL, | ||
289 | link: `${webserverUrl}/about` | ||
290 | } | ||
291 | }) | ||
292 | } | 331 | } |
293 | 332 | ||
294 | function sendFeed (feed, req: express.Request, res: express.Response) { | 333 | function sendFeed (feed, req: express.Request, res: express.Response) { |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 9be80167c..fa4a51e6c 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -28,8 +28,7 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se | |||
28 | if (!account) { | 28 | if (!account) { |
29 | if (sendNotFound === true) { | 29 | if (sendNotFound === true) { |
30 | res.status(404) | 30 | res.status(404) |
31 | .send({ error: 'Account not found' }) | 31 | .json({ error: 'Account not found' }) |
32 | .end() | ||
33 | } | 32 | } |
34 | 33 | ||
35 | return false | 34 | return false |
@@ -41,12 +40,11 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se | |||
41 | } | 40 | } |
42 | 41 | ||
43 | async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) { | 42 | async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) { |
44 | const user = await UserModel.loadById(parseInt(id + '', 10)) | 43 | const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) |
45 | 44 | ||
46 | if (token !== user.feedToken) { | 45 | if (token !== user.feedToken) { |
47 | res.status(401) | 46 | res.status(401) |
48 | .send({ error: 'User and token mismatch' }) | 47 | .json({ error: 'User and token mismatch' }) |
49 | .end() | ||
50 | 48 | ||
51 | return false | 49 | return false |
52 | } | 50 | } |
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 5c76a679f..35080ffca 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts | |||
@@ -64,8 +64,8 @@ const videoFeedsValidator = [ | |||
64 | ] | 64 | ] |
65 | 65 | ||
66 | const videoSubscriptonFeedsValidator = [ | 66 | const videoSubscriptonFeedsValidator = [ |
67 | query('accountId').optional().custom(isIdValid), | 67 | query('accountId').custom(isIdValid), |
68 | query('token').optional(), | 68 | query('token'), |
69 | 69 | ||
70 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 70 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
71 | logger.debug('Checking feeds parameters', { parameters: req.query }) | 71 | logger.debug('Checking feeds parameters', { parameters: req.query }) |
@@ -74,6 +74,7 @@ const videoSubscriptonFeedsValidator = [ | |||
74 | 74 | ||
75 | // a token alone is erroneous | 75 | // a token alone is erroneous |
76 | if (req.query.token && !req.query.accountId) return | 76 | if (req.query.token && !req.query.accountId) return |
77 | if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return | ||
77 | if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return | 78 | if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return |
78 | 79 | ||
79 | return next() | 80 | return next() |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 2cd9b2d0a..175ea9102 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -326,7 +326,7 @@ describe('Test syndication feeds', () => { | |||
326 | const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) | 326 | const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) |
327 | expect(res.body.total).to.equal(0) | 327 | expect(res.body.total).to.equal(0) |
328 | 328 | ||
329 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: feeduserAccountId, token: feeduserFeedToken }) | 329 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken }) |
330 | const jsonObj = JSON.parse(json.text) | 330 | const jsonObj = JSON.parse(json.text) |
331 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos | 331 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos |
332 | } | 332 | } |
@@ -337,7 +337,7 @@ describe('Test syndication feeds', () => { | |||
337 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) | 337 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) |
338 | expect(res.body.total).to.equal(0) | 338 | expect(res.body.total).to.equal(0) |
339 | 339 | ||
340 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken }) | 340 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken }) |
341 | const jsonObj = JSON.parse(json.text) | 341 | const jsonObj = JSON.parse(json.text) |
342 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos | 342 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos |
343 | } | 343 | } |
@@ -354,7 +354,7 @@ describe('Test syndication feeds', () => { | |||
354 | expect(res.body.total).to.equal(1) | 354 | expect(res.body.total).to.equal(1) |
355 | expect(res.body.data[0].name).to.equal('user video') | 355 | expect(res.body.data[0].name).to.equal('user video') |
356 | 356 | ||
357 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 1 }) | 357 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 }) |
358 | const jsonObj = JSON.parse(json.text) | 358 | const jsonObj = JSON.parse(json.text) |
359 | expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's | 359 | expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's |
360 | } | 360 | } |
@@ -370,7 +370,7 @@ describe('Test syndication feeds', () => { | |||
370 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) | 370 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) |
371 | expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") | 371 | expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") |
372 | 372 | ||
373 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 2 }) | 373 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 }) |
374 | const jsonObj = JSON.parse(json.text) | 374 | const jsonObj = JSON.parse(json.text) |
375 | expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's | 375 | expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's |
376 | } | 376 | } |
diff --git a/shared/extra-utils/feeds/feeds.ts b/shared/extra-utils/feeds/feeds.ts index af6df2b20..bafbb9f94 100644 --- a/shared/extra-utils/feeds/feeds.ts +++ b/shared/extra-utils/feeds/feeds.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | 2 | ||
3 | type FeedType = 'videos' | 'video-comments' | 3 | type FeedType = 'videos' | 'video-comments' | 'subscriptions' |
4 | 4 | ||
5 | function getXMLfeed (url: string, feed: FeedType, format?: string) { | 5 | function getXMLfeed (url: string, feed: FeedType, format?: string) { |
6 | const path = '/feeds/' + feed + '.xml' | 6 | const path = '/feeds/' + feed + '.xml' |