deal with refresh token in embed

This commit is contained in:
Rigel Kent 2020-08-03 18:06:49 +02:00 committed by Chocobozzz
parent 71ab65d02f
commit 4504f09f6e
25 changed files with 320 additions and 194 deletions

View file

@ -2,7 +2,7 @@ import { BytesPipe } from 'ngx-pipes'
import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { RedundancyService } from '@app/shared/shared-main'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'

View file

@ -14,7 +14,7 @@ import { DomSanitizer } from '@angular/platform-browser'
@Component({
selector: 'my-video-block-list',
templateUrl: './video-block-list.component.html',
styleUrls: [ '../../../shared/shared-moderation/moderation.scss', '../../../shared/shared-abuse-list/abuse-list-table.component.scss', './video-block-list.component.scss' ]
styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
})
export class VideoBlockListComponent extends RestTable implements OnInit, AfterViewInit {
blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = []

View file

@ -1,7 +1,7 @@
import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core'
import { Notifier, RestPagination, RestTable } from '@app/core'
import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { Job, JobState, JobType } from '@shared/models'
import { JobStateClient } from '../../../../types/job-state-client.type'

View file

@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core'
import { Router } from '@angular/router'
import { AuthService, ComponentPagination, LocalStorageService, Notifier, SessionStorageService, UserService } from '@app/core'
import { peertubeLocalStorage, peertubeSessionStorage } from '@app/helpers/peertube-web-storage'
import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'

View file

@ -7,7 +7,8 @@ import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, AuthUser, ConfirmService, MarkdownService, Notifier, RestExtractor, ServerService, UserService } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
import { RedirectService } from '@app/core/routing/redirect.service'
import { isXPercentInViewport, peertubeLocalStorage, scrollToTop } from '@app/helpers'
import { isXPercentInViewport, scrollToTop } from '@app/helpers'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'

View file

@ -15,7 +15,8 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { BroadcastMessageLevel, getShortLocale, is18nPath, ServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service'
import { peertubeLocalStorage, POP_STATE_MODAL_DISMISS } from './helpers'
import { POP_STATE_MODAL_DISMISS } from './helpers'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { InstanceService } from './shared/shared-instance'
@Component({

View file

@ -1,7 +1,7 @@
import { Observable, of } from 'rxjs'
import { map } from 'rxjs/operators'
import { User } from '@app/core/users/user.model'
import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import {
hasUserRight,
MyUser as ServerMyUserModel,
@ -12,66 +12,7 @@ import {
UserRole,
UserVideoQuota
} from '@shared/models'
export type TokenOptions = {
accessToken: string
refreshToken: string
tokenType: string
}
// Private class only used by User
class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type'
}
accessToken: string
refreshToken: string
tokenType: string
static load () {
const accessTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.ACCESS_TOKEN)
const refreshTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.REFRESH_TOKEN)
const tokenTypeLocalStorage = peertubeLocalStorage.getItem(this.KEYS.TOKEN_TYPE)
if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
return new Tokens({
accessToken: accessTokenLocalStorage,
refreshToken: refreshTokenLocalStorage,
tokenType: tokenTypeLocalStorage
})
}
return null
}
static flush () {
peertubeLocalStorage.removeItem(this.KEYS.ACCESS_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.REFRESH_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.TOKEN_TYPE)
}
constructor (hash?: TokenOptions) {
if (hash) {
this.accessToken = hash.accessToken
this.refreshToken = hash.refreshToken
if (hash.tokenType === 'bearer') {
this.tokenType = 'Bearer'
} else {
this.tokenType = hash.tokenType
}
}
}
save () {
peertubeLocalStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken)
peertubeLocalStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken)
peertubeLocalStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType)
}
}
import { TokenOptions, Tokens } from '../../../root-helpers/pure-auth-user.model'
export class AuthUser extends User implements ServerMyUserModel {
tokens: Tokens

View file

@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { Notifier } from '@app/core/notification/notifier.service'
import { objectToUrlEncoded, peertubeLocalStorage } from '@app/helpers'
import { objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
import { environment } from '../../../environments/environment'

View file

@ -1,4 +1,4 @@
import { peertubeLocalStorage } from '@app/helpers/peertube-web-storage'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { LazyLoadEvent, SortMeta } from 'primeng/api'
import { RestPagination } from './rest-pagination'
import { Subject } from 'rxjs'

View file

@ -2,7 +2,8 @@ import { Observable, of, Subject } from 'rxjs'
import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http'
import { Inject, Injectable, LOCALE_ID } from '@angular/core'
import { getDevLocale, isOnDevLocale, peertubeLocalStorage, sortBy } from '@app/helpers'
import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import {
getCompleteLocale,
isDefaultLocale,

View file

@ -10,23 +10,10 @@ import {
UserRole,
VideoChannel
} from '@shared/models'
import { UserKeys } from '@root-helpers/user-keys'
export class User implements UserServerModel {
static KEYS = {
ID: 'id',
ROLE: 'role',
EMAIL: 'email',
VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
USERNAME: 'username',
NSFW_POLICY: 'nsfw_policy',
WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
AUTO_PLAY_VIDEO: 'auto_play_video',
SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
THEME: 'theme',
LAST_ACTIVE_THEME: 'last_active_theme',
VIDEO_LANGUAGES: 'video_languages'
}
static KEYS = UserKeys
id: number
username: string

View file

@ -1,7 +1,7 @@
import { Observable, Subject } from 'rxjs'
import { filter } from 'rxjs/operators'
import { Injectable } from '@angular/core'
import { peertubeLocalStorage, peertubeSessionStorage } from '@app/helpers'
import { peertubeLocalStorage, peertubeSessionStorage } from '@root-helpers/peertube-web-storage'
abstract class StorageService {
protected instance: Storage

View file

@ -1,6 +1,5 @@
export * from './locales'
export * from './constants'
export * from './i18n-utils'
export * from './peertube-web-storage'
export * from './utils'
export * from './zone'

View file

@ -81,15 +81,6 @@ function immutableAssign <A, B> (target: A, source: B) {
return Object.assign({}, target, source)
}
function objectToUrlEncoded (obj: any) {
const str: string[] = []
for (const key of Object.keys(obj)) {
str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
}
return str.join('&')
}
// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
function objectToFormData (obj: any, form?: FormData, namespace?: string) {
const fd = form || new FormData()
@ -207,7 +198,6 @@ export {
sortBy,
durationToString,
lineFeedToHtml,
objectToUrlEncoded,
getParameterByName,
populateAsyncUserVideoChannels,
getAbsoluteAPIUrl,

View file

@ -1,98 +1,9 @@
@import 'variables';
@import 'mixins';
@import 'miniature';
.table-video-link {
@include disable-outline;
position: relative;
top: 3px;
}
.table-comment-link,
.table-account-link {
@include disable-outline;
color: var(--mainForegroundColor);
::ng-deep p:last-child {
margin: 0;
}
}
.table-account-link {
display: flex;
flex-direction: column;
}
.comment-flagged-account,
.account-flagged-handle {
font-size: 11px;
color: var(--greyForegroundColor);
}
.table-video {
display: inline-flex;
.table-video-image {
@include miniature-thumbnail;
$image-height: 45px;
height: $image-height;
width: #{(16/9) * $image-height};
margin-right: 0.5rem;
border-radius: 2px;
border: none;
background: transparent;
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
img {
height: 100%;
width: 100%;
border-radius: 2px;
}
span {
color: pvar(--inputPlaceholderColor);
}
.table-video-image-label {
@include static-thumbnail-overlay;
position: absolute;
border-radius: 3px;
font-size: 10px;
padding: 0 3px;
line-height: 1.3;
bottom: 2px;
right: 2px;
}
}
.table-video-text {
display: inline-flex;
flex-direction: column;
justify-content: center;
font-size: 90%;
color: pvar(--mainForegroundColor);
line-height: 1rem;
div .glyphicon {
font-size: 80%;
color: gray;
margin-left: 0.1rem;
}
div + div {
color: var(--greyForegroundColor);
font-size: 11px;
}
}
}
.abuse-states .glyphicon-comment {
margin-left: 0.5rem;
}

View file

@ -65,3 +65,88 @@ my-action-dropdown.show {
display: block !important;
}
}
.table-video-link {
@include disable-outline;
position: relative;
top: 3px;
}
.table-comment-link,
.table-account-link {
@include disable-outline;
color: var(--mainForegroundColor);
::ng-deep p:last-child {
margin: 0;
}
}
.table-account-link {
display: flex;
flex-direction: column;
}
.table-video {
display: inline-flex;
.table-video-image {
@include miniature-thumbnail;
$image-height: 45px;
height: $image-height;
width: #{(16/9) * $image-height};
margin-right: 0.5rem;
border-radius: 2px;
border: none;
background: transparent;
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
img {
height: 100%;
width: 100%;
border-radius: 2px;
}
span {
color: pvar(--inputPlaceholderColor);
}
.table-video-image-label {
@include static-thumbnail-overlay;
position: absolute;
border-radius: 3px;
font-size: 10px;
padding: 0 3px;
line-height: 1.3;
bottom: 2px;
right: 2px;
}
}
.table-video-text {
display: inline-flex;
flex-direction: column;
justify-content: center;
font-size: 90%;
color: pvar(--mainForegroundColor);
line-height: 1rem;
div .glyphicon {
font-size: 80%;
color: gray;
margin-left: 0.1rem;
}
div + div {
color: var(--greyForegroundColor);
font-size: 11px;
}
}
}

View file

@ -3,7 +3,7 @@ import { catchError, map, switchMap } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
import { peertubeLocalStorage } from '@app/helpers'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { ResultList, SearchTargetType, Video as VideoServerModel, VideoChannel as VideoChannelServerModel } from '@shared/models'
import { environment } from '../../../environments/environment'

View file

@ -0,0 +1,4 @@
export * from './peertube-web-storage'
export * from './utils'
export * from './user-keys'
export * from './pure-auth-user.model'

View file

@ -0,0 +1,123 @@
// pure version of auth-user, that doesn't import app packages
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import {
MyUser as ServerMyUserModel,
MyUserSpecialPlaylist,
NSFWPolicyType,
UserRole
} from '@shared/models'
import { UserKeys } from '@root-helpers/user-keys'
export type TokenOptions = {
accessToken: string
refreshToken: string
tokenType: string
}
// Private class only used by User
export class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type'
}
accessToken: string
refreshToken: string
tokenType: string
static load () {
const accessTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.ACCESS_TOKEN)
const refreshTokenLocalStorage = peertubeLocalStorage.getItem(this.KEYS.REFRESH_TOKEN)
const tokenTypeLocalStorage = peertubeLocalStorage.getItem(this.KEYS.TOKEN_TYPE)
if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
return new Tokens({
accessToken: accessTokenLocalStorage,
refreshToken: refreshTokenLocalStorage,
tokenType: tokenTypeLocalStorage
})
}
return null
}
static flush () {
peertubeLocalStorage.removeItem(this.KEYS.ACCESS_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.REFRESH_TOKEN)
peertubeLocalStorage.removeItem(this.KEYS.TOKEN_TYPE)
}
constructor (hash?: TokenOptions) {
if (hash) {
this.accessToken = hash.accessToken
this.refreshToken = hash.refreshToken
if (hash.tokenType === 'bearer') {
this.tokenType = 'Bearer'
} else {
this.tokenType = hash.tokenType
}
}
}
save () {
peertubeLocalStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken)
peertubeLocalStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken)
peertubeLocalStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType)
}
}
export class PureAuthUser {
tokens: Tokens
specialPlaylists: MyUserSpecialPlaylist[]
canSeeVideosLink = true
static load () {
const usernameLocalStorage = peertubeLocalStorage.getItem(UserKeys.USERNAME)
if (usernameLocalStorage) {
return new PureAuthUser(
{
id: parseInt(peertubeLocalStorage.getItem(UserKeys.ID), 10),
username: peertubeLocalStorage.getItem(UserKeys.USERNAME),
email: peertubeLocalStorage.getItem(UserKeys.EMAIL),
role: parseInt(peertubeLocalStorage.getItem(UserKeys.ROLE), 10) as UserRole,
nsfwPolicy: peertubeLocalStorage.getItem(UserKeys.NSFW_POLICY) as NSFWPolicyType,
webTorrentEnabled: peertubeLocalStorage.getItem(UserKeys.WEBTORRENT_ENABLED) === 'true',
autoPlayVideo: peertubeLocalStorage.getItem(UserKeys.AUTO_PLAY_VIDEO) === 'true',
videosHistoryEnabled: peertubeLocalStorage.getItem(UserKeys.VIDEOS_HISTORY_ENABLED) === 'true'
},
Tokens.load()
)
}
return null
}
constructor (userHash: Partial<ServerMyUserModel>, hashTokens: TokenOptions) {
this.tokens = new Tokens(hashTokens)
this.specialPlaylists = userHash.specialPlaylists
}
getAccessToken () {
return this.tokens.accessToken
}
getRefreshToken () {
return this.tokens.refreshToken
}
getTokenType () {
return this.tokens.tokenType
}
refreshTokens (accessToken: string, refreshToken: string) {
this.tokens.accessToken = accessToken
this.tokens.refreshToken = refreshToken
}
save () {
this.tokens.save()
}
}

View file

@ -0,0 +1,15 @@
export const UserKeys = {
ID: 'id',
ROLE: 'role',
EMAIL: 'email',
VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
USERNAME: 'username',
NSFW_POLICY: 'nsfw_policy',
WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
AUTO_PLAY_VIDEO: 'auto_play_video',
SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
THEME: 'theme',
LAST_ACTIVE_THEME: 'last_active_theme',
VIDEO_LANGUAGES: 'video_languages'
}

View file

@ -0,0 +1,12 @@
function objectToUrlEncoded (obj: any) {
const str: string[] = []
for (const key of Object.keys(obj)) {
str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
}
return str.join('&')
}
export {
objectToUrlEncoded
}

View file

@ -4,7 +4,8 @@ import {
peertubeTranslate,
ResultList,
ServerConfig,
VideoDetails
VideoDetails,
UserRefreshToken
} from '../../../../shared'
import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
import {
@ -17,7 +18,7 @@ import { PeerTubeEmbedApi } from './embed-api'
import { TranslationsManager } from '../../assets/player/translations-manager'
import videojs from 'video.js'
import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
import { AuthUser } from '@app/core/auth/auth-user.model'
import { PureAuthUser, objectToUrlEncoded, peertubeLocalStorage } from '@root-helpers/index'
type Translations = { [ id: string ]: string }
@ -43,8 +44,12 @@ export class PeerTubeEmbed {
mode: PlayerMode
scope = 'peertube'
user: AuthUser
user: PureAuthUser
headers = new Headers()
LOCAL_STORAGE_OAUTH_CLIENT_KEYS = {
CLIENT_ID: 'client_id',
CLIENT_SECRET: 'client_secret'
}
static async main () {
const videoContainerId = 'video-container'
@ -60,12 +65,62 @@ export class PeerTubeEmbed {
return window.location.origin + '/api/v1/videos/' + id
}
refreshFetch (url: string, options?: Object) {
return fetch(url, options)
.then((res: Response) => {
if (res.status !== 401) return res
// 401 unauthorized is not catch-ed, but then-ed
const error = res
const refreshingTokenPromise = new Promise((resolve, reject) => {
const clientId: string = peertubeLocalStorage.getItem(this.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
const clientSecret: string = peertubeLocalStorage.getItem(this.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
const headers = new Headers()
headers.set('Content-Type', 'application/x-www-form-urlencoded')
const data = {
refresh_token: this.user.getRefreshToken(),
client_id: clientId,
client_secret: clientSecret,
response_type: 'code',
grant_type: 'refresh_token'
}
fetch('/api/v1/users/token', {
headers,
method: 'POST',
body: objectToUrlEncoded(data)
})
.then(res => res.json())
.then((obj: UserRefreshToken) => {
this.user.refreshTokens(obj.access_token, obj.refresh_token)
this.user.save()
this.headers.set('Authorization', `${this.user.getTokenType()} ${this.user.getAccessToken()}`)
resolve()
})
.catch((refreshTokenError: any) => {
reject(refreshTokenError)
})
})
return refreshingTokenPromise
.catch(() => {
// If refreshing fails, continue with original error
throw error
})
.then(() => fetch(url, {
...options,
headers: this.headers
}))
})
}
loadVideoInfo (videoId: string): Promise<Response> {
return fetch(this.getVideoUrl(videoId), { headers: this.headers })
return this.refreshFetch(this.getVideoUrl(videoId), { headers: this.headers })
}
loadVideoCaptions (videoId: string): Promise<Response> {
return fetch(this.getVideoUrl(videoId) + '/captions', { headers: this.headers })
return fetch(this.getVideoUrl(videoId) + '/captions')
}
loadConfig (): Promise<Response> {
@ -115,7 +170,7 @@ export class PeerTubeEmbed {
async init () {
try {
this.user = AuthUser.load()
this.user = PureAuthUser.load()
await this.initCore()
} catch (e) {
console.error(e)

View file

@ -28,6 +28,7 @@
"video.js": [ "node_modules/video.js/core" ],
"@app/*": [ "src/app/*" ],
"@shared/*": [ "../shared/*" ],
"@root-helpers/*": [ "src/root-helpers/*" ],
"fs": [ "src/shims/noop.ts" ],
"http": [ "src/shims/http.ts" ],
"https": [ "src/shims/https.ts" ],

View file

@ -28,7 +28,7 @@ module.exports = function () {
alias: {
'video.js$': path.resolve('node_modules/video.js/core.js'),
'@app': path.resolve('src/app'),
'@root-helpers': path.resolve('src/root-helpers'),
'@shared': path.resolve('../shared')
}
},