basic frontend implementation of new token system
This commit is contained in:
parent
e985eb941c
commit
f17bac399a
@ -3,12 +3,13 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"openmediacenter/apiGo/database/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VideoNode = "video"
|
VideoNode = "video"
|
||||||
TagNode = "tag"
|
TagNode = "tags"
|
||||||
SettingsNode = "setting"
|
SettingsNode = "settings"
|
||||||
ActorNode = "actor"
|
ActorNode = "actor"
|
||||||
TVShowNode = "tv"
|
TVShowNode = "tv"
|
||||||
LoginNode = "login"
|
LoginNode = "login"
|
||||||
@ -32,6 +33,12 @@ const (
|
|||||||
|
|
||||||
func AddHandler(action string, apiNode string, perm uint8, handler func(ctx Context)) {
|
func AddHandler(action string, apiNode string, perm uint8, handler func(ctx Context)) {
|
||||||
http.Handle(fmt.Sprintf("/api/%s/%s", apiNode, action), http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
http.Handle(fmt.Sprintf("/api/%s/%s", apiNode, action), http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
srvPwd := settings.GetPassword()
|
||||||
|
if srvPwd == nil {
|
||||||
|
// no password set
|
||||||
|
ctx := &apicontext{writer: writer, responseWritten: false, request: request, userid: -1, permid: PermUnauthorized}
|
||||||
|
callHandler(ctx, handler, writer)
|
||||||
|
} else {
|
||||||
tokenheader := request.Header.Get("Token")
|
tokenheader := request.Header.Get("Token")
|
||||||
|
|
||||||
id := -1
|
id := -1
|
||||||
@ -46,6 +53,15 @@ func AddHandler(action string, apiNode string, perm uint8, handler func(ctx Cont
|
|||||||
|
|
||||||
// check if rights are sufficient to perform the action
|
// check if rights are sufficient to perform the action
|
||||||
if permid <= perm {
|
if permid <= perm {
|
||||||
|
callHandler(ctx, handler, writer)
|
||||||
|
} else {
|
||||||
|
ctx.Error("insufficient permissions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func callHandler(ctx *apicontext, handler func(ctx Context), writer http.ResponseWriter) {
|
||||||
handler(ctx)
|
handler(ctx)
|
||||||
|
|
||||||
if !ctx.responseWritten {
|
if !ctx.responseWritten {
|
||||||
@ -53,11 +69,6 @@ func AddHandler(action string, apiNode string, perm uint8, handler func(ctx Cont
|
|||||||
ctx.Error("Unknown server Error occured")
|
ctx.Error("Unknown server Error occured")
|
||||||
writer.WriteHeader(501)
|
writer.WriteHeader(501)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.Error("insufficient permissions")
|
|
||||||
writer.WriteHeader(501)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerInit() {
|
func ServerInit() {
|
||||||
|
@ -48,7 +48,6 @@ func TokenValid(token string) (int, uint8) {
|
|||||||
func InitOAuth() {
|
func InitOAuth() {
|
||||||
AddHandler("login", LoginNode, PermUnauthorized, func(ctx Context) {
|
AddHandler("login", LoginNode, PermUnauthorized, func(ctx Context) {
|
||||||
var t struct {
|
var t struct {
|
||||||
Username string
|
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,28 +56,27 @@ func InitOAuth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// empty check
|
// empty check
|
||||||
if t.Password == "" || t.Username == "" {
|
if t.Password == "" {
|
||||||
ctx.Error("empty username or password")
|
ctx.Error("empty password")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate Argon2 Hash of passed pwd
|
// generate Argon2 Hash of passed pwd
|
||||||
pwd := HashPassword(t.Password)
|
HashPassword(t.Password)
|
||||||
|
// todo use hashed password
|
||||||
|
|
||||||
var id uint
|
var password string
|
||||||
var name string
|
|
||||||
var rightid uint8
|
|
||||||
|
|
||||||
err := database.QueryRow("SELECT userId,userName,rightId FROM User WHERE userName=? AND password=?", t.Username, *pwd).Scan(&id, &name, &rightid)
|
err := database.QueryRow("SELECT password FROM settings WHERE 1").Scan(&password)
|
||||||
if err != nil {
|
if err != nil || t.Password != password {
|
||||||
ctx.Error("unauthorized")
|
ctx.Error("unauthorized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expires := time.Now().Add(time.Hour * TokenExpireHours).Unix()
|
expires := time.Now().Add(time.Hour * TokenExpireHours).Unix()
|
||||||
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
|
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
|
||||||
Issuer: strconv.Itoa(int(id)),
|
Issuer: strconv.Itoa(int(0)),
|
||||||
Subject: strconv.Itoa(int(rightid)),
|
Subject: strconv.Itoa(int(PermUser)),
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -91,17 +89,11 @@ func InitOAuth() {
|
|||||||
|
|
||||||
type ResponseType struct {
|
type ResponseType struct {
|
||||||
Token Token
|
Token Token
|
||||||
Username string
|
|
||||||
UserPerm uint8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Json(ResponseType{
|
ctx.Json(Token{
|
||||||
Token: Token{
|
|
||||||
Token: token,
|
Token: token,
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
},
|
|
||||||
Username: t.Username,
|
|
||||||
UserPerm: rightid,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
168
src/App.tsx
168
src/App.tsx
@ -9,21 +9,17 @@ import style from './App.module.css';
|
|||||||
|
|
||||||
import SettingsPage from './pages/SettingsPage/SettingsPage';
|
import SettingsPage from './pages/SettingsPage/SettingsPage';
|
||||||
import CategoryPage from './pages/CategoryPage/CategoryPage';
|
import CategoryPage from './pages/CategoryPage/CategoryPage';
|
||||||
import {APINode, callAPI} from './utils/Api';
|
|
||||||
|
|
||||||
import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
|
import {NavLink, Route, Switch, useRouteMatch} from 'react-router-dom';
|
||||||
import Player from './pages/Player/Player';
|
import Player from './pages/Player/Player';
|
||||||
import ActorOverviewPage from './pages/ActorOverviewPage/ActorOverviewPage';
|
import ActorOverviewPage from './pages/ActorOverviewPage/ActorOverviewPage';
|
||||||
import ActorPage from './pages/ActorPage/ActorPage';
|
import ActorPage from './pages/ActorPage/ActorPage';
|
||||||
import {SettingsTypes} from './types/ApiTypes';
|
|
||||||
import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage';
|
import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage';
|
||||||
import TVShowPage from './pages/TVShowPage/TVShowPage';
|
import TVShowPage from './pages/TVShowPage/TVShowPage';
|
||||||
import TVPlayer from './pages/TVShowPage/TVPlayer';
|
import TVPlayer from './pages/TVShowPage/TVPlayer';
|
||||||
import {CookieTokenStore} from './utils/TokenStore/CookieTokenStore';
|
import {LoginContextProvider} from './utils/context/LoginContextProvider';
|
||||||
import {token} from './utils/TokenHandler';
|
|
||||||
|
|
||||||
interface state {
|
interface state {
|
||||||
password: boolean | null; // null if uninitialized - true if pwd needed false if not needed
|
|
||||||
mediacentername: string;
|
mediacentername: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,100 +30,63 @@ class App extends React.Component<{}, state> {
|
|||||||
constructor(props: {}) {
|
constructor(props: {}) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
token.init(new CookieTokenStore());
|
|
||||||
|
|
||||||
let pwdneeded: boolean | null = null;
|
|
||||||
|
|
||||||
if (token.apiTokenValid()) {
|
|
||||||
pwdneeded = false;
|
|
||||||
} else {
|
|
||||||
token.refreshAPIToken((err) => {
|
|
||||||
if (err === 'invalid_client') {
|
|
||||||
this.setState({password: true});
|
|
||||||
} else if (err === '') {
|
|
||||||
this.setState({password: false});
|
|
||||||
} else {
|
|
||||||
console.log('unimplemented token error: ' + err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
mediacentername: 'OpenMediaCenter',
|
mediacentername: 'OpenMediaCenter'
|
||||||
password: pwdneeded
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// force an update on theme change
|
// force an update on theme change
|
||||||
GlobalInfos.onThemeChange(() => {
|
GlobalInfos.onThemeChange(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
// set the hook to load passwordfield on global func call
|
|
||||||
GlobalInfos.loadPasswordPage = (callback?: () => void): void => {
|
|
||||||
// try refreshing the token
|
|
||||||
token.refreshAPIToken((err) => {
|
|
||||||
if (err !== '') {
|
|
||||||
this.setState({password: true});
|
|
||||||
} else {
|
|
||||||
// call callback if request was successful
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
initialAPICall(): void {
|
|
||||||
// this is the first api call so if it fails we know there is no connection to backend
|
|
||||||
callAPI(APINode.Settings, {action: 'loadInitialData'}, (result: SettingsTypes.initialApiCallData) => {
|
|
||||||
// set theme
|
|
||||||
GlobalInfos.enableDarkTheme(result.DarkMode);
|
|
||||||
|
|
||||||
GlobalInfos.setVideoPaths(result.VideoPath, result.TVShowPath);
|
|
||||||
|
|
||||||
GlobalInfos.setTVShowsEnabled(result.TVShowEnabled);
|
|
||||||
GlobalInfos.setFullDeleteEnabled(result.FullDeleteEnabled);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
mediacentername: result.MediacenterName
|
|
||||||
});
|
|
||||||
// set tab title to received mediacenter name
|
|
||||||
document.title = result.MediacenterName;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
this.initialAPICall();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
// add the main theme to the page body
|
// add the main theme to the page body
|
||||||
document.body.className = GlobalInfos.getThemeStyle().backgroundcolor;
|
document.body.className = GlobalInfos.getThemeStyle().backgroundcolor;
|
||||||
|
|
||||||
if (this.state.password === true) {
|
|
||||||
// render authentication page if auth is neccessary
|
|
||||||
return (
|
return (
|
||||||
|
<LoginContextProvider>
|
||||||
|
<Switch>
|
||||||
|
<Route path='/login'>
|
||||||
<AuthenticationPage
|
<AuthenticationPage
|
||||||
onSuccessLogin={(): void => {
|
onSuccessLogin={(): void => {
|
||||||
this.setState({password: false});
|
// this.setState({password: false});
|
||||||
// reinit general infos
|
// reinit general infos
|
||||||
this.initialAPICall();
|
// this.initialAPICall();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
</Route>
|
||||||
} else if (this.state.password === false) {
|
<Route path='/media'>
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<div className={style.app}>
|
|
||||||
{this.navBar()}
|
{this.navBar()}
|
||||||
{this.routing()}
|
<MyRouter />
|
||||||
</div>
|
</Route>
|
||||||
</Router>
|
</Switch>
|
||||||
|
</LoginContextProvider>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return <>still loading...</>;
|
// if (this.state.password === true) {
|
||||||
}
|
// // render authentication page if auth is neccessary
|
||||||
|
// return (
|
||||||
|
// <AuthenticationPage
|
||||||
|
// onSuccessLogin={(): void => {
|
||||||
|
// this.setState({password: false});
|
||||||
|
// // reinit general infos
|
||||||
|
// this.initialAPICall();
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// } else if (this.state.password === false) {
|
||||||
|
// return (
|
||||||
|
// <Router>
|
||||||
|
// <div className={style.app}>
|
||||||
|
// {this.navBar()}
|
||||||
|
// {this.routing()}
|
||||||
|
// </div>
|
||||||
|
// </Router>
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// return <>still loading...</>;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,73 +98,84 @@ class App extends React.Component<{}, state> {
|
|||||||
return (
|
return (
|
||||||
<div className={[style.navcontainer, themeStyle.backgroundcolor, themeStyle.textcolor, themeStyle.hrcolor].join(' ')}>
|
<div className={[style.navcontainer, themeStyle.backgroundcolor, themeStyle.textcolor, themeStyle.hrcolor].join(' ')}>
|
||||||
<div className={style.navbrand}>{this.state.mediacentername}</div>
|
<div className={style.navbrand}>{this.state.mediacentername}</div>
|
||||||
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/'} activeStyle={{opacity: '0.85'}}>
|
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/media'} activeStyle={{opacity: '0.85'}}>
|
||||||
Home
|
Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/random'} activeStyle={{opacity: '0.85'}}>
|
<NavLink
|
||||||
|
className={[style.navitem, themeStyle.navitem].join(' ')}
|
||||||
|
to={'/media/random'}
|
||||||
|
activeStyle={{opacity: '0.85'}}>
|
||||||
Random Video
|
Random Video
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/categories'} activeStyle={{opacity: '0.85'}}>
|
<NavLink
|
||||||
|
className={[style.navitem, themeStyle.navitem].join(' ')}
|
||||||
|
to={'/media/categories'}
|
||||||
|
activeStyle={{opacity: '0.85'}}>
|
||||||
Categories
|
Categories
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
{GlobalInfos.isTVShowEnabled() ? (
|
{GlobalInfos.isTVShowEnabled() ? (
|
||||||
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/tvshows'} activeStyle={{opacity: '0.85'}}>
|
<NavLink
|
||||||
|
className={[style.navitem, themeStyle.navitem].join(' ')}
|
||||||
|
to={'/media/tvshows'}
|
||||||
|
activeStyle={{opacity: '0.85'}}>
|
||||||
TV Shows
|
TV Shows
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/settings'} activeStyle={{opacity: '0.85'}}>
|
<NavLink
|
||||||
|
className={[style.navitem, themeStyle.navitem].join(' ')}
|
||||||
|
to={'/media/settings'}
|
||||||
|
activeStyle={{opacity: '0.85'}}>
|
||||||
Settings
|
Settings
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyRouter = (): JSX.Element => {
|
||||||
|
const match = useRouteMatch();
|
||||||
|
|
||||||
/**
|
|
||||||
* render the react router elements
|
|
||||||
*/
|
|
||||||
routing(): JSX.Element {
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path='/random'>
|
<Route exact path={`${match.url}/random`}>
|
||||||
<RandomPage />
|
<RandomPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/categories'>
|
<Route path={`${match.url}/categories`}>
|
||||||
<CategoryPage />
|
<CategoryPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/settings'>
|
<Route path={`${match.url}/settings`}>
|
||||||
<SettingsPage />
|
<SettingsPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path='/player/:id'>
|
<Route exact path={`${match.url}/player/:id`}>
|
||||||
<Player />
|
<Player />
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path='/actors'>
|
<Route exact path={`${match.url}/actors`}>
|
||||||
<ActorOverviewPage />
|
<ActorOverviewPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/actors/:id'>
|
<Route exact path={`${match.url}/actors/:id`}>
|
||||||
<ActorPage />
|
<ActorPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{GlobalInfos.isTVShowEnabled() ? (
|
{GlobalInfos.isTVShowEnabled() ? (
|
||||||
<Route path='/tvshows'>
|
<Route exact path={`${match.url}/tvshows`}>
|
||||||
<TVShowPage />
|
<TVShowPage />
|
||||||
</Route>
|
</Route>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{GlobalInfos.isTVShowEnabled() ? (
|
{GlobalInfos.isTVShowEnabled() ? (
|
||||||
<Route exact path='/tvplayer/:id'>
|
<Route exact path={`${match.url}/tvplayer/:id`}>
|
||||||
<TVPlayer />
|
<TVPlayer />
|
||||||
</Route>
|
</Route>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Route path='/'>
|
<Route path={`${match.url}/`}>
|
||||||
<HomePage />
|
<HomePage />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -26,7 +26,7 @@ const VideoContainer = (props: Props): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={el.MovieName}
|
name={el.MovieName}
|
||||||
linkPath={'/player/' + el.MovieId}
|
linkPath={'/media/player/' + el.MovieId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
data={props.data}>
|
data={props.data}>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import {BrowserRouter} from 'react-router-dom';
|
||||||
|
|
||||||
// don't allow console logs within production env
|
// don't allow console logs within production env
|
||||||
global.console.log = process.env.NODE_ENV !== 'development' ? (_: string | number | boolean): void => {} : global.console.log;
|
global.console.log = process.env.NODE_ENV !== 'development' ? (_: string | number | boolean): void => {} : global.console.log;
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,11 @@ import React from 'react';
|
|||||||
import {Button} from '../../elements/GPElements/Button';
|
import {Button} from '../../elements/GPElements/Button';
|
||||||
import style from './AuthenticationPage.module.css';
|
import style from './AuthenticationPage.module.css';
|
||||||
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
|
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
|
||||||
import {token} from '../../utils/TokenHandler';
|
|
||||||
import {faTimes} from '@fortawesome/free-solid-svg-icons';
|
import {faTimes} from '@fortawesome/free-solid-svg-icons';
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {LoginContext, LoginState} from '../../utils/context/LoginContext';
|
||||||
|
import {APINode, callApiUnsafe} from '../../utils/Api';
|
||||||
|
import {cookie, Token} from '../../utils/context/Cookie';
|
||||||
|
|
||||||
interface state {
|
interface state {
|
||||||
pwdText: string;
|
pwdText: string;
|
||||||
@ -36,6 +38,8 @@ class AuthenticationPage extends React.Component<Props, state> {
|
|||||||
removeKeyHandler(this.keypress);
|
removeKeyHandler(this.keypress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static contextType = LoginContext;
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -76,21 +80,17 @@ class AuthenticationPage extends React.Component<Props, state> {
|
|||||||
* request a new token and check if pwd was valid
|
* request a new token and check if pwd was valid
|
||||||
*/
|
*/
|
||||||
authenticate(): void {
|
authenticate(): void {
|
||||||
token.refreshAPIToken(
|
callApiUnsafe(
|
||||||
(error) => {
|
APINode.Login,
|
||||||
if (error !== '') {
|
{action: 'login', Password: this.state.pwdText},
|
||||||
this.setState({wrongPWDInfo: true});
|
(r: Token) => {
|
||||||
|
cookie.Store(r);
|
||||||
|
|
||||||
// set timeout to make the info auto-disappearing
|
this.context.setLoginState(LoginState.LoggedIn);
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({wrongPWDInfo: false});
|
|
||||||
}, 2000);
|
|
||||||
} else {
|
|
||||||
this.props.onSuccessLogin();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
true,
|
(e) => {
|
||||||
this.state.pwdText
|
console.log(e);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Route, Switch} from 'react-router-dom';
|
import {Route, Switch, useRouteMatch} from 'react-router-dom';
|
||||||
import {CategoryViewWR} from './CategoryView';
|
import {CategoryViewWR} from './CategoryView';
|
||||||
import TagView from './TagView';
|
import TagView from './TagView';
|
||||||
|
|
||||||
@ -7,19 +7,21 @@ import TagView from './TagView';
|
|||||||
* Component for Category Page
|
* Component for Category Page
|
||||||
* Contains a Tag Overview and loads specific Tag videos in VideoContainer
|
* Contains a Tag Overview and loads specific Tag videos in VideoContainer
|
||||||
*/
|
*/
|
||||||
class CategoryPage extends React.Component {
|
const CategoryPage = (): JSX.Element => {
|
||||||
render(): JSX.Element {
|
const match = useRouteMatch();
|
||||||
|
|
||||||
|
console.log(match.url);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path='/categories/:id'>
|
<Route exact path={`${match.url}/:id`}>
|
||||||
<CategoryViewWR />
|
<CategoryViewWR />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/categories'>
|
<Route exact path={`${match.url}/`}>
|
||||||
<TagView />
|
<TagView />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default CategoryPage;
|
export default CategoryPage;
|
||||||
|
@ -119,9 +119,10 @@ export class CategoryView extends React.Component<CategoryViewProps, CategoryVie
|
|||||||
this.videodata = result.Videos;
|
this.videodata = result.Videos;
|
||||||
this.setState({loaded: true, TagName: result.TagName});
|
this.setState({loaded: true, TagName: result.TagName});
|
||||||
},
|
},
|
||||||
(_) => {
|
(e) => {
|
||||||
|
console.log(e);
|
||||||
// if there is an load error redirect to home page
|
// if there is an load error redirect to home page
|
||||||
this.props.history.push('/');
|
// this.props.history.push('/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ class TagView extends React.Component<Props, TagViewState> {
|
|||||||
<DynamicContentContainer
|
<DynamicContentContainer
|
||||||
data={this.state.loadedtags}
|
data={this.state.loadedtags}
|
||||||
renderElement={(m): JSX.Element => (
|
renderElement={(m): JSX.Element => (
|
||||||
<Link to={'/categories/' + m.TagId} key={m.TagId}>
|
<Link to={'/media/categories/' + m.TagId} key={m.TagId}>
|
||||||
<TagPreview name={m.TagName} />
|
<TagPreview name={m.TagName} />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -3,52 +3,52 @@ import MovieSettings from './MovieSettings';
|
|||||||
import GeneralSettings from './GeneralSettings';
|
import GeneralSettings from './GeneralSettings';
|
||||||
import style from './SettingsPage.module.css';
|
import style from './SettingsPage.module.css';
|
||||||
import GlobalInfos from '../../utils/GlobalInfos';
|
import GlobalInfos from '../../utils/GlobalInfos';
|
||||||
import {NavLink, Redirect, Route, Switch} from 'react-router-dom';
|
import {NavLink, Redirect, Route, Switch, useRouteMatch} from 'react-router-dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Settingspage handles all kinds of settings for the mediacenter
|
* The Settingspage handles all kinds of settings for the mediacenter
|
||||||
* and is basically a wrapper for child-tabs
|
* and is basically a wrapper for child-tabs
|
||||||
*/
|
*/
|
||||||
class SettingsPage extends React.Component {
|
const SettingsPage = (): JSX.Element => {
|
||||||
render(): JSX.Element {
|
|
||||||
const themestyle = GlobalInfos.getThemeStyle();
|
const themestyle = GlobalInfos.getThemeStyle();
|
||||||
|
const match = useRouteMatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={style.SettingsSidebar + ' ' + themestyle.secbackground}>
|
<div className={style.SettingsSidebar + ' ' + themestyle.secbackground}>
|
||||||
<div className={style.SettingsSidebarTitle + ' ' + themestyle.lighttextcolor}>Settings</div>
|
<div className={style.SettingsSidebarTitle + ' ' + themestyle.lighttextcolor}>Settings</div>
|
||||||
<NavLink to='/settings/general'>
|
<NavLink to='/media/settings/general'>
|
||||||
<div className={style.SettingSidebarElement}>General</div>
|
<div className={style.SettingSidebarElement}>General</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to='/settings/movies'>
|
<NavLink to='/media/settings/movies'>
|
||||||
<div className={style.SettingSidebarElement}>Movies</div>
|
<div className={style.SettingSidebarElement}>Movies</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{GlobalInfos.isTVShowEnabled() ? (
|
{GlobalInfos.isTVShowEnabled() ? (
|
||||||
<NavLink to='/settings/tv'>
|
<NavLink to='/media/settings/tv'>
|
||||||
<div className={style.SettingSidebarElement}>TV Shows</div>
|
<div className={style.SettingSidebarElement}>TV Shows</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={style.SettingsContent}>
|
<div className={style.SettingsContent}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path='/settings/general'>
|
<Route path={`${match.url}/general`}>
|
||||||
<GeneralSettings />
|
<GeneralSettings />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='/settings/movies'>
|
<Route path={`${match.url}/movies`}>
|
||||||
<MovieSettings />
|
<MovieSettings />
|
||||||
</Route>
|
</Route>
|
||||||
{GlobalInfos.isTVShowEnabled() ? (
|
{GlobalInfos.isTVShowEnabled() ? (
|
||||||
<Route path='/settings/tv'>
|
<Route path={`${match.url}/tv`}>
|
||||||
<span />
|
<span />
|
||||||
</Route>
|
</Route>
|
||||||
) : null}
|
) : null}
|
||||||
<Route path='/settings'>
|
<Route path={`${match.url}/`}>
|
||||||
<Redirect to='/settings/general' />
|
<Redirect to='/media/settings/general' />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default SettingsPage;
|
export default SettingsPage;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import GlobalInfos from './GlobalInfos';
|
import GlobalInfos from './GlobalInfos';
|
||||||
import {token} from './TokenHandler';
|
import {cookie} from './context/Cookie';
|
||||||
|
|
||||||
const APIPREFIX: string = '/api/';
|
const APIPREFIX: string = '/api/';
|
||||||
|
|
||||||
@ -25,9 +25,7 @@ export function callAPI<T>(
|
|||||||
callback: (_: T) => void,
|
callback: (_: T) => void,
|
||||||
errorcallback: (_: string) => void = (_: string): void => {}
|
errorcallback: (_: string) => void = (_: string): void => {}
|
||||||
): void {
|
): void {
|
||||||
token.checkAPITokenValid((mytoken) => {
|
generalAPICall<T>(apinode, fd, callback, errorcallback, false, true);
|
||||||
generalAPICall<T>(apinode, fd, callback, errorcallback, false, true, mytoken);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +41,7 @@ export function callApiUnsafe<T>(
|
|||||||
callback: (_: T) => void,
|
callback: (_: T) => void,
|
||||||
errorcallback?: (_: string) => void
|
errorcallback?: (_: string) => void
|
||||||
): void {
|
): void {
|
||||||
generalAPICall(apinode, fd, callback, errorcallback, true, true, '');
|
generalAPICall(apinode, fd, callback, errorcallback, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,9 +51,7 @@ export function callApiUnsafe<T>(
|
|||||||
* @param callback the callback with PLAIN text reply from backend
|
* @param callback the callback with PLAIN text reply from backend
|
||||||
*/
|
*/
|
||||||
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
|
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
|
||||||
token.checkAPITokenValid((mytoken) => {
|
generalAPICall(apinode, fd, callback, () => {}, false, false);
|
||||||
generalAPICall(apinode, fd, callback, () => {}, false, false, mytoken);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generalAPICall<T>(
|
function generalAPICall<T>(
|
||||||
@ -64,16 +60,16 @@ function generalAPICall<T>(
|
|||||||
callback: (_: T) => void,
|
callback: (_: T) => void,
|
||||||
errorcallback: (_: string) => void = (_: string): void => {},
|
errorcallback: (_: string) => void = (_: string): void => {},
|
||||||
unsafe: boolean,
|
unsafe: boolean,
|
||||||
json: boolean,
|
json: boolean
|
||||||
mytoken: string
|
|
||||||
): void {
|
): void {
|
||||||
(async function (): Promise<void> {
|
(async function (): Promise<void> {
|
||||||
|
const tkn = cookie.Load();
|
||||||
const response = await fetch(APIPREFIX + apinode + '/' + fd.action, {
|
const response = await fetch(APIPREFIX + apinode + '/' + fd.action, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(fd),
|
body: JSON.stringify(fd),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': json ? 'application/json' : 'text/plain',
|
'Content-Type': json ? 'application/json' : 'text/plain',
|
||||||
...(!unsafe && {Authorization: 'Bearer ' + mytoken})
|
...(!unsafe && tkn !== null && {Token: tkn.Token})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -110,6 +106,7 @@ function generalAPICall<T>(
|
|||||||
|
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
export enum APINode {
|
export enum APINode {
|
||||||
|
Login = 'login',
|
||||||
Settings = 'settings',
|
Settings = 'settings',
|
||||||
Tags = 'tags',
|
Tags = 'tags',
|
||||||
Actor = 'actor',
|
Actor = 'actor',
|
||||||
|
@ -1,135 +0,0 @@
|
|||||||
import {TokenStore} from './TokenStore/TokenStore';
|
|
||||||
|
|
||||||
export namespace token {
|
|
||||||
// store api token - empty if not set
|
|
||||||
let apiToken = '';
|
|
||||||
|
|
||||||
// a callback que to be called after api token refresh
|
|
||||||
let callQue: ((error: string) => void)[] = [];
|
|
||||||
// flag to check wheter a api refresh is currently pending
|
|
||||||
let refreshInProcess = false;
|
|
||||||
// store the expire seconds of token
|
|
||||||
let expireSeconds = -1;
|
|
||||||
|
|
||||||
let tokenStore: TokenStore;
|
|
||||||
let APiHost: string = '/';
|
|
||||||
|
|
||||||
export function init(ts: TokenStore, apiHost?: string): void {
|
|
||||||
tokenStore = ts;
|
|
||||||
if (apiHost) {
|
|
||||||
APiHost = apiHost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* refresh the api token or use that one in cookie if still valid
|
|
||||||
* @param callback to be called after successful refresh
|
|
||||||
* @param password
|
|
||||||
* @param force
|
|
||||||
*/
|
|
||||||
export function refreshAPIToken(callback: (error: string) => void, force?: boolean, password?: string): void {
|
|
||||||
callQue.push(callback);
|
|
||||||
|
|
||||||
// check if already is a token refresh is in process
|
|
||||||
if (refreshInProcess) {
|
|
||||||
// if yes return
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// if not set flat
|
|
||||||
refreshInProcess = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiTokenValid() && !force) {
|
|
||||||
console.log('token still valid...');
|
|
||||||
callFuncQue('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('grant_type', 'client_credentials');
|
|
||||||
formData.append('client_id', 'openmediacenter');
|
|
||||||
formData.append('client_secret', password ? password : 'openmediacenter');
|
|
||||||
formData.append('scope', 'all');
|
|
||||||
|
|
||||||
interface APIToken {
|
|
||||||
error?: string;
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
access_token: string; // no camel case allowed because of backendlib
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
expires_in: number; // no camel case allowed because of backendlib
|
|
||||||
scope: string;
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
token_type: string; // no camel case allowed because of backendlib
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(APiHost);
|
|
||||||
|
|
||||||
fetch(APiHost + 'token', {method: 'POST', body: formData})
|
|
||||||
.then((response) =>
|
|
||||||
response.json().then((result: APIToken) => {
|
|
||||||
if (result.error) {
|
|
||||||
callFuncQue(result.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// set api token
|
|
||||||
apiToken = result.access_token;
|
|
||||||
// set expire time
|
|
||||||
expireSeconds = new Date().getTime() / 1000 + result.expires_in;
|
|
||||||
// setTokenCookie(apiToken, expireSeconds);
|
|
||||||
tokenStore.setToken({accessToken: apiToken, expireTime: expireSeconds, tokenType: '', scope: ''});
|
|
||||||
// call all handlers and release flag
|
|
||||||
callFuncQue('');
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch((e) => {
|
|
||||||
callback(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apiTokenValid(): boolean {
|
|
||||||
// check if a cookie with token is available
|
|
||||||
// const token = getTokenCookie();
|
|
||||||
const tmptoken = tokenStore.loadToken();
|
|
||||||
if (tmptoken !== null) {
|
|
||||||
// check if token is at least valid for the next minute
|
|
||||||
if (tmptoken.expireTime > new Date().getTime() / 1000 + 60) {
|
|
||||||
apiToken = tmptoken.accessToken;
|
|
||||||
expireSeconds = tmptoken.expireTime;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* call all qued callbacks
|
|
||||||
*/
|
|
||||||
function callFuncQue(error: string): void {
|
|
||||||
// call all pending handlers
|
|
||||||
callQue.map((func) => {
|
|
||||||
return func(error);
|
|
||||||
});
|
|
||||||
// reset pending que
|
|
||||||
callQue = [];
|
|
||||||
// release flag to be able to start new refresh
|
|
||||||
refreshInProcess = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check if api token is valid -- if not request new one
|
|
||||||
* when finished call callback
|
|
||||||
* @param callback function to be called afterwards
|
|
||||||
*/
|
|
||||||
export function checkAPITokenValid(callback: (mytoken: string) => void): void {
|
|
||||||
// check if token is valid and set
|
|
||||||
if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
|
|
||||||
console.log('token not valid...');
|
|
||||||
refreshAPIToken(() => {
|
|
||||||
callback(apiToken);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
callback(apiToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import {Token, TokenStore} from './TokenStore';
|
|
||||||
|
|
||||||
export class CookieTokenStore extends TokenStore {
|
|
||||||
loadToken(): Token | null {
|
|
||||||
const token = this.decodeCookie('token');
|
|
||||||
const expireInString = this.decodeCookie('token_expire');
|
|
||||||
const expireIn = parseInt(expireInString, 10);
|
|
||||||
|
|
||||||
if (expireIn !== 0 && token !== '') {
|
|
||||||
return {accessToken: token, expireTime: expireIn, scope: '', tokenType: ''};
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set the cookie for the currently gotten token
|
|
||||||
* @param token the token to set
|
|
||||||
*/
|
|
||||||
setToken(token: Token): void {
|
|
||||||
let d = new Date();
|
|
||||||
d.setTime(token.expireTime * 1000);
|
|
||||||
console.log('token set' + d.toUTCString());
|
|
||||||
let expires = 'expires=' + d.toUTCString();
|
|
||||||
document.cookie = 'token=' + token.accessToken + ';' + expires + ';path=/';
|
|
||||||
document.cookie = 'token_expire=' + token.expireTime + ';' + expires + ';path=/';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* decode a simple cookie with key specified
|
|
||||||
* @param key cookie key
|
|
||||||
*/
|
|
||||||
decodeCookie(key: string): string {
|
|
||||||
let name = key + '=';
|
|
||||||
let decodedCookie = decodeURIComponent(document.cookie);
|
|
||||||
let ca = decodedCookie.split(';');
|
|
||||||
for (let i = 0; i < ca.length; i++) {
|
|
||||||
let c = ca[i];
|
|
||||||
while (c.charAt(0) === ' ') {
|
|
||||||
c = c.substring(1);
|
|
||||||
}
|
|
||||||
if (c.indexOf(name) === 0) {
|
|
||||||
return c.substring(name.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
export interface Token {
|
|
||||||
accessToken: string;
|
|
||||||
expireTime: number; // second time when token will be invalidated
|
|
||||||
scope: string;
|
|
||||||
tokenType: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class TokenStore {
|
|
||||||
abstract loadToken(): Token | null;
|
|
||||||
abstract setToken(token: Token): void;
|
|
||||||
}
|
|
55
src/utils/context/Cookie.ts
Normal file
55
src/utils/context/Cookie.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
export interface Token {
|
||||||
|
Token: string;
|
||||||
|
ExpiresAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace cookie {
|
||||||
|
const jwtcookiename = 'jwt';
|
||||||
|
|
||||||
|
export function Store(data: Token): void {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(data.ExpiresAt * 1000);
|
||||||
|
const expires = 'expires=' + d.toUTCString();
|
||||||
|
|
||||||
|
document.cookie = jwtcookiename + '=' + JSON.stringify(data) + ';' + expires + ';path=/';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Load(): Token | null {
|
||||||
|
const datastr = decodeCookie(jwtcookiename);
|
||||||
|
if (datastr === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(datastr);
|
||||||
|
} catch (e) {
|
||||||
|
// if cookie not decodeable delete it and return null
|
||||||
|
Delete();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Delete(): void {
|
||||||
|
document.cookie = `${jwtcookiename}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decode a simple cookie with key specified
|
||||||
|
* @param key cookie key
|
||||||
|
*/
|
||||||
|
function decodeCookie(key: string): string {
|
||||||
|
let name = key + '=';
|
||||||
|
let decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
let ca = decodedCookie.split(';');
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) === ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) === 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
34
src/utils/context/LoginContext.ts
Normal file
34
src/utils/context/LoginContext.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* global context definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum LoginState {
|
||||||
|
LoggedIn,
|
||||||
|
LoggedOut
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LoginPerm {
|
||||||
|
Admin,
|
||||||
|
User
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginContextType {
|
||||||
|
logout: () => void;
|
||||||
|
setPerm: (permission: LoginPerm) => void;
|
||||||
|
loginstate: LoginState;
|
||||||
|
setLoginState: (state: LoginState) => void;
|
||||||
|
permission: LoginPerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global context providing a way to interact with user login states
|
||||||
|
*/
|
||||||
|
export const LoginContext = React.createContext<LoginContextType>({
|
||||||
|
setLoginState(): void {},
|
||||||
|
setPerm(): void {},
|
||||||
|
logout: () => {},
|
||||||
|
loginstate: LoginState.LoggedOut,
|
||||||
|
permission: LoginPerm.User
|
||||||
|
});
|
105
src/utils/context/LoginContextProvider.tsx
Normal file
105
src/utils/context/LoginContextProvider.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import {LoginContext, LoginPerm, LoginState} from './LoginContext';
|
||||||
|
import React, {FunctionComponent, useContext, useEffect, useState} from 'react';
|
||||||
|
import {useHistory, useLocation} from 'react-router';
|
||||||
|
import {cookie} from './Cookie';
|
||||||
|
import {APINode, callAPI} from '../Api';
|
||||||
|
import {SettingsTypes} from '../../types/ApiTypes';
|
||||||
|
import GlobalInfos from '../GlobalInfos';
|
||||||
|
|
||||||
|
export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
|
||||||
|
let initialLoginState = LoginState.LoggedIn;
|
||||||
|
let initialUserPerm = LoginPerm.User;
|
||||||
|
|
||||||
|
const t = cookie.Load();
|
||||||
|
// we are already logged in so we can set the token and redirect to dashboard
|
||||||
|
if (t !== null) {
|
||||||
|
initialLoginState = LoginState.LoggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialAPICall = (): void => {
|
||||||
|
// this is the first api call so if it fails we know there is no connection to backend
|
||||||
|
callAPI(
|
||||||
|
APINode.Settings,
|
||||||
|
{action: 'loadInitialData'},
|
||||||
|
(result: SettingsTypes.initialApiCallData) => {
|
||||||
|
// set theme
|
||||||
|
GlobalInfos.enableDarkTheme(result.DarkMode);
|
||||||
|
|
||||||
|
GlobalInfos.setVideoPaths(result.VideoPath, result.TVShowPath);
|
||||||
|
|
||||||
|
GlobalInfos.setTVShowsEnabled(result.TVShowEnabled);
|
||||||
|
GlobalInfos.setFullDeleteEnabled(result.FullDeleteEnabled);
|
||||||
|
//
|
||||||
|
// this.setState({
|
||||||
|
// mediacentername: result.MediacenterName
|
||||||
|
// });
|
||||||
|
// set tab title to received mediacenter name
|
||||||
|
document.title = result.MediacenterName;
|
||||||
|
|
||||||
|
setLoginState(LoginState.LoggedIn);
|
||||||
|
},
|
||||||
|
(_) => {
|
||||||
|
setLoginState(LoginState.LoggedOut);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initialAPICall();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [loginState, setLoginState] = useState<LoginState>(initialLoginState);
|
||||||
|
const [permission, setPermission] = useState<LoginPerm>(initialUserPerm);
|
||||||
|
|
||||||
|
const hist = useHistory();
|
||||||
|
const loc = useLocation();
|
||||||
|
|
||||||
|
// trigger redirect on loginstate change
|
||||||
|
useEffect(() => {
|
||||||
|
if (loginState === LoginState.LoggedIn) {
|
||||||
|
// if we arent already in dashboard tree we want to redirect to default dashboard page
|
||||||
|
console.log('redirecting to dashboard' + loc.pathname);
|
||||||
|
if (!loc.pathname.startsWith('/media')) {
|
||||||
|
hist.replace('/media');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!loc.pathname.startsWith('/login')) {
|
||||||
|
hist.replace('/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [hist, loc.pathname, loginState]);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
logout: (): void => {
|
||||||
|
setLoginState(LoginState.LoggedOut);
|
||||||
|
cookie.Delete();
|
||||||
|
},
|
||||||
|
setPerm: (perm: LoginPerm): void => {
|
||||||
|
setPermission(perm);
|
||||||
|
},
|
||||||
|
setLoginState: (state: LoginState): void => {
|
||||||
|
setLoginState(state);
|
||||||
|
},
|
||||||
|
loginstate: loginState,
|
||||||
|
permission: permission
|
||||||
|
};
|
||||||
|
|
||||||
|
return <LoginContext.Provider value={value}>{props.children}</LoginContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
perm: LoginPerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper element to render children only if permissions are sufficient
|
||||||
|
*/
|
||||||
|
export const AuthorizedContext: FunctionComponent<Props> = (props): JSX.Element => {
|
||||||
|
const loginctx = useContext(LoginContext);
|
||||||
|
|
||||||
|
if (loginctx.permission <= props.perm) {
|
||||||
|
return props.children as JSX.Element;
|
||||||
|
} else {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user