summaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/framework
diff options
context:
space:
mode:
authorDan Timoney <dtimoney@att.com>2019-03-25 14:27:10 +0000
committerGerrit Code Review <gerrit@onap.org>2019-03-25 14:27:10 +0000
commit1f68c64e8bcf88ee485c69d36c67b0ad6590a293 (patch)
tree34ab01010c3a8b3bb4086d4b683a6fd8040f2992 /sdnr/wt/odlux/framework
parent528f6391de8495f3346b5e22ede404c00b9955b7 (diff)
parent2d4424c28ac35763ef44c42ae2f01664d42b268c (diff)
Merge "Security provider for UX-Client-Login"
Diffstat (limited to 'sdnr/wt/odlux/framework')
-rw-r--r--sdnr/wt/odlux/framework/package.json1
-rw-r--r--sdnr/wt/odlux/framework/src/actions/authentication.ts3
-rw-r--r--sdnr/wt/odlux/framework/src/app.tsx15
-rw-r--r--sdnr/wt/odlux/framework/src/components/navigationMenu.tsx12
-rw-r--r--sdnr/wt/odlux/framework/src/components/titleBar.tsx6
-rw-r--r--sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts10
-rw-r--r--sdnr/wt/odlux/framework/src/index.html2
-rw-r--r--sdnr/wt/odlux/framework/src/middleware/navigation.ts15
-rw-r--r--sdnr/wt/odlux/framework/src/models/authentication.ts59
-rw-r--r--sdnr/wt/odlux/framework/src/services/authenticationService.ts36
-rw-r--r--sdnr/wt/odlux/framework/src/services/notificationService.ts6
-rw-r--r--sdnr/wt/odlux/framework/src/services/restService.ts33
-rw-r--r--sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts17
-rw-r--r--sdnr/wt/odlux/framework/src/views/login.tsx336
-rw-r--r--sdnr/wt/odlux/framework/tsconfig.json1
-rw-r--r--sdnr/wt/odlux/framework/webpack.config.js6
16 files changed, 332 insertions, 226 deletions
diff --git a/sdnr/wt/odlux/framework/package.json b/sdnr/wt/odlux/framework/package.json
index 51449b6f7..b3a48375e 100644
--- a/sdnr/wt/odlux/framework/package.json
+++ b/sdnr/wt/odlux/framework/package.json
@@ -24,6 +24,7 @@
"author": "Matthias Fischer",
"license": "MIT",
"peerDependencies": {
+ "@types/node" : "11.9.5",
"@types/react": "16.4.14",
"@types/react-dom": "16.0.8",
"@types/react-router-dom": "4.3.1",
diff --git a/sdnr/wt/odlux/framework/src/actions/authentication.ts b/sdnr/wt/odlux/framework/src/actions/authentication.ts
index 8cbc22271..0cca179db 100644
--- a/sdnr/wt/odlux/framework/src/actions/authentication.ts
+++ b/sdnr/wt/odlux/framework/src/actions/authentication.ts
@@ -1,8 +1,9 @@
import { Action } from '../flux/action';
+import { AuthToken } from '../models/authentication';
export class UpdateAuthentication extends Action {
- constructor(public bearerToken: string | null) {
+ constructor (public bearerToken: AuthToken | null) {
super();
}
} \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/app.tsx b/sdnr/wt/odlux/framework/src/app.tsx
index 1879c7bc6..6a56056d3 100644
--- a/sdnr/wt/odlux/framework/src/app.tsx
+++ b/sdnr/wt/odlux/framework/src/app.tsx
@@ -1,12 +1,12 @@
/******************************************************************************
* Copyright 2018 highstreet technologies GmbH
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,9 +27,12 @@ import { applicationStoreCreator } from './store/applicationStore';
import { ApplicationStoreProvider } from './flux/connect';
import { startHistoryListener } from './middleware/navigation';
+import { startRestService } from './services/restService';
+
import theme from './design/default';
import '!style-loader!css-loader!./app.css';
+import { ReplaceAction } from './actions/navigationActions';
declare module '@material-ui/core/styles/createMuiTheme' {
@@ -54,7 +57,7 @@ export const runApplication = () => {
const applicationStore = applicationStoreCreator();
window.onerror = function (msg: string, url: string, line: number, col: number, error: Error) {
- // Note that col & error are new to the HTML 5 spec and may not be
+ // Note that col & error are new to the HTML 5 spec and may not be
// supported in every browser. It worked for me in Chrome.
var extra = !col ? '' : '\ncolumn: ' + col;
extra += !error ? '' : '\nerror: ' + error;
@@ -63,11 +66,12 @@ export const runApplication = () => {
applicationStore.dispatch(new AddErrorInfoAction({ error, message: msg, url, line, col, info: { extra } }));
var suppressErrorAlert = true;
- // If you return true, then error alerts (like in older versions of
+ // If you return true, then error alerts (like in older versions of
// Internet Explorer) will be suppressed.
return suppressErrorAlert;
};
+ startRestService(applicationStore);
startHistoryListener(applicationStore);
const App = (): JSX.Element => (
@@ -79,4 +83,5 @@ export const runApplication = () => {
);
ReactDOM.render(<App />, document.getElementById('app'));
+
};
diff --git a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
index f6df244a0..3a7725b1b 100644
--- a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
+++ b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx
@@ -25,6 +25,7 @@ const styles = (theme: Theme) => createStyles({
});
export const NavigationMenu = withStyles(styles)(connect()(({ classes, state }: WithStyles<typeof styles> & Connect) => {
+ const { user } = state.framework.authenticationState
return (
<Drawer
variant="permanent"
@@ -32,10 +33,11 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state }:
paper: classes.drawerPaper,
}}
>
- <div className={classes.toolbar} />
+ {user && user.isValid && <>
+ <div className={classes.toolbar} />
{ /* https://fiffty.github.io/react-treeview-mui/ */}
<List component="nav">
- { process.env.NODE_ENV === "development" ? <ListItemLink exact to="/" primary="Home" icon={<FontAwesomeIcon icon={faHome} />} /> : null }
+ { process.env.NODE_ENV === "development" ? <ListItemLink exact to="/" primary="Home" icon={<FontAwesomeIcon icon={faHome} />} /> : null }
<Divider />
{
state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => {
@@ -51,8 +53,10 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state }:
}) || null
}
<Divider />
- { process.env.NODE_ENV === "development" ? <ListItemLink to="/about" primary="About" icon={<FontAwesomeIcon icon={faAddressBook} />} /> : null }
- </List>
+ { process.env.NODE_ENV === "development" ? <ListItemLink to="/about" primary="About" icon={<FontAwesomeIcon icon={faAddressBook} />} /> : null }
+ </List>
+ </> || null
+ }
</Drawer>)
}));
diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx
index ed6eb2ccc..439e9bc12 100644
--- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx
+++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx
@@ -15,6 +15,7 @@ import Menu from '@material-ui/core/Menu';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { UpdateAuthentication } from '../actions/authentication';
+import { ReplaceAction } from '../actions/navigationActions';
import connect, { Connect, IDispatcher } from '../flux/connect';
import Logo from './logo';
@@ -37,7 +38,10 @@ const styles = (theme: Theme) => createStyles({
const mapDispatch = (dispatcher: IDispatcher) => {
return {
- logout: () => { dispatcher.dispatch(new UpdateAuthentication(null)); }
+ logout: () => {
+ dispatcher.dispatch(new UpdateAuthentication(null));
+ dispatcher.dispatch(new ReplaceAction("/login"));
+ }
}
};
diff --git a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts
index e0ae1aa8d..2abe82142 100644
--- a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts
+++ b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts
@@ -10,19 +10,19 @@ export interface IAuthenticationState {
const initialToken = localStorage.getItem("userToken");
const authenticationStateInit: IAuthenticationState = {
- user: initialToken && new User(initialToken) || undefined
+ user: initialToken && User.fromString(initialToken) || undefined
};
export const authenticationStateHandler: IActionHandler<IAuthenticationState> = (state = authenticationStateInit, action) => {
if (action instanceof UpdateAuthentication) {
-
- if (action.bearerToken) {
- localStorage.setItem("userToken", action.bearerToken);
+
+ const user = action.bearerToken && new User(action.bearerToken) || undefined;
+ if (user) {
+ localStorage.setItem("userToken", user.toString());
} else {
localStorage.removeItem("userToken");
}
- const user = action.bearerToken && new User(action.bearerToken) || undefined;
state = {
...state,
user
diff --git a/sdnr/wt/odlux/framework/src/index.html b/sdnr/wt/odlux/framework/src/index.html
index 3c59b1580..36d937775 100644
--- a/sdnr/wt/odlux/framework/src/index.html
+++ b/sdnr/wt/odlux/framework/src/index.html
@@ -16,7 +16,7 @@
<script>
// run the application
require(["run"], function (run) {
- run.runApplication();
+ run.runApplication();
});
</script>
</body>
diff --git a/sdnr/wt/odlux/framework/src/middleware/navigation.ts b/sdnr/wt/odlux/framework/src/middleware/navigation.ts
index 758b51845..8db01b18c 100644
--- a/sdnr/wt/odlux/framework/src/middleware/navigation.ts
+++ b/sdnr/wt/odlux/framework/src/middleware/navigation.ts
@@ -6,14 +6,14 @@ import { Dispatch } from '../flux/store';
import { LocationChanged, NavigateToApplication } from "../actions/navigationActions";
import { PushAction, ReplaceAction, GoAction, GoBackAction, GoForwardeAction } from '../actions/navigationActions';
-import applicationManager from "../services/applicationManager";
+import { applicationManager } from "../services/applicationManager";
const routerMiddlewareCreator = (history: History) => () => (next: Dispatch): Dispatch => (action) => {
-
+
if (action instanceof NavigateToApplication) {
const application = applicationManager.applications && applicationManager.applications[action.applicationName];
if (application) {
- const href = `/${ application.path || application.name }${ action.href ? '/' + action.href : '' }`.replace(/\/{2,}/i, '/');
+ const href = `/${application.path || application.name}${action.href ? '/' + action.href : ''}`.replace(/\/{2,}/i, '/');
if (action.replace) {
history.replace(href, action.state);
} else {
@@ -30,6 +30,13 @@ const routerMiddlewareCreator = (history: History) => () => (next: Dispatch): Di
history.goBack();
} else if (action instanceof GoForwardeAction) {
history.goForward();
+ } else if (action instanceof LocationChanged) {
+ // ensure user is logged in and token is valid
+ if (!action.pathname.startsWith("/login") && applicationStore && (!applicationStore.state.framework.authenticationState.user || !applicationStore.state.framework.authenticationState.user.isValid)) {
+ history.replace(`/login?returnTo=${action.pathname}`);
+ } else {
+ return next(action);
+ }
} else {
return next(action);
}
@@ -44,8 +51,10 @@ function startListener(history: History, store: ApplicationStore) {
}
const history = createHashHistory();
+let applicationStore: ApplicationStore | null = null;
export function startHistoryListener(store: ApplicationStore) {
+ applicationStore = store;
startListener(history, store);
}
diff --git a/sdnr/wt/odlux/framework/src/models/authentication.ts b/sdnr/wt/odlux/framework/src/models/authentication.ts
index 44b5ae436..6c463ad05 100644
--- a/sdnr/wt/odlux/framework/src/models/authentication.ts
+++ b/sdnr/wt/odlux/framework/src/models/authentication.ts
@@ -1,50 +1,41 @@
-import * as JWT from 'jsonwebtoken';
-
-export interface IUserInfo {
- iss: string,
- iat: number,
- exp: number,
- aud: string,
- sub: string,
- firstName: string,
- lastName: string,
- email: string,
- role: string[]
+
+export type AuthToken = {
+ username: string;
+ access_token: string;
+ token_type: string;
+ expires: number;
}
export class User {
- public _userInfo: IUserInfo | null;
-
- constructor(private _bearerToken: string) {
- //const pem = require('raw-loader!../assets/publicKey.pem');
- const pem = "kFfAgpf806IKa4z88EEk6Lim7NMGicrw99OmIB38myM9CS44nEmMNJxnFu3ImViS248wSwkuZ3HvrhsPrA1ZFRNb1a6CEtGN4DaPJbfuo35qMp50tIEpy8nsSFpayOBE";
-
- try {
- const dec = (JWT.verify(_bearerToken, pem)) as IUserInfo;
- this._userInfo = dec;
- } catch (ex) {
- this._userInfo = null;
- }
+ constructor (private _bearerToken: AuthToken) {
+
}
public get user(): string | null {
- return this._userInfo && this._userInfo.email;
+ return this._bearerToken && this._bearerToken.username;
};
- public get roles(): string[] | null {
- return this._userInfo && this._userInfo.role;
- }
public get token(): string | null {
- return this._userInfo && this._bearerToken;
+ return this._bearerToken && this._bearerToken.access_token;
}
- public isInRole(role: string | string[]): boolean {
- return false;
+ public get tokenType(): string | null {
+ return this._bearerToken && this._bearerToken.token_type;
}
-}
+ public get isValid(): boolean {
+ return (this._bearerToken && (new Date().valueOf()) < this._bearerToken.expires) || false;
+ }
-// key:kFfAgpf806IKa4z88EEk6Lim7NMGicrw99OmIB38myM9CS44nEmMNJxnFu3ImViS248wSwkuZ3HvrhsPrA1ZFRNb1a6CEtGN4DaPJbfuo35qMp50tIEpy8nsSFpayOBE
-// token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPRExVWCIsImlhdCI6MTUzODQ2NDMyMCwiZXhwIjoxNTcwMDAwMzIwLCJhdWQiOiJsb2NhbGhvc3QiLCJzdWIiOiJsb2NhbGhvc3QiLCJmaXJzdE5hbWUiOiJNYXgiLCJsYXN0TmFtZSI6Ik11c3Rlcm1hbm4iLCJlbWFpbCI6Im1heEBvZGx1eC5jb20iLCJyb2xlIjpbInVzZXIiLCJhZG1pbiJdfQ.9e5hDi2uxmIXNwHkJoScBZsHBk0jQ8CcZ7YIcZhDtuI \ No newline at end of file
+ public toString() {
+ return JSON.stringify(this._bearerToken);
+ }
+
+ public static fromString(data: string) {
+ return new User(JSON.parse(data));
+ }
+
+
+}
diff --git a/sdnr/wt/odlux/framework/src/services/authenticationService.ts b/sdnr/wt/odlux/framework/src/services/authenticationService.ts
index 5e6fc81a8..42ce061f1 100644
--- a/sdnr/wt/odlux/framework/src/services/authenticationService.ts
+++ b/sdnr/wt/odlux/framework/src/services/authenticationService.ts
@@ -1,14 +1,34 @@
-function timeout(ms:number) {
- return new Promise(resolve => setTimeout(resolve, ms));
+import { requestRest, formEncode } from "./restService";
+import { AuthToken } from "../models/authentication";
+
+type AuthTokenResponse = {
+ access_token: string;
+ token_type: string;
+ expires_in: number;
}
+
class AuthenticationService {
- public async authenticateUser(email: string, password: string) : Promise<string | null> {
- await timeout(650);
- if (email === "max@odlux.com" && password === "geheim") {
- return "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPRExVWCIsImlhdCI6MTUzODQ2NDMyMCwiZXhwIjoxNTcwMDAwMzIwLCJhdWQiOiJsb2NhbGhvc3QiLCJzdWIiOiJsb2NhbGhvc3QiLCJmaXJzdE5hbWUiOiJNYXgiLCJsYXN0TmFtZSI6Ik11c3Rlcm1hbm4iLCJlbWFpbCI6Im1heEBvZGx1eC5jb20iLCJyb2xlIjpbInVzZXIiLCJhZG1pbiJdfQ.9e5hDi2uxmIXNwHkJoScBZsHBk0jQ8CcZ7YIcZhDtuI"
- }
- return null;
+ public async authenticateUser(email: string, password: string, scope: string): Promise<AuthToken | null> {
+ const result = await requestRest<string>(`oauth2/token`, {
+ method: "POST",
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: formEncode({
+ grant_type: "password",
+ username: email,
+ password: password,
+ scope: scope
+ })
+ }, false);
+ const resultObj: AuthTokenResponse| null = result && JSON.parse(result);
+ return resultObj && {
+ username: email,
+ access_token: resultObj.access_token,
+ token_type: resultObj.token_type,
+ expires: (new Date().valueOf()) + (resultObj.expires_in * 1000)
+ } || null;
}
}
diff --git a/sdnr/wt/odlux/framework/src/services/notificationService.ts b/sdnr/wt/odlux/framework/src/services/notificationService.ts
index 242a6c03b..76132f843 100644
--- a/sdnr/wt/odlux/framework/src/services/notificationService.ts
+++ b/sdnr/wt/odlux/framework/src/services/notificationService.ts
@@ -1,6 +1,6 @@
import * as X2JS from 'x2js';
-const socketUrl = [ location.protocol === 'https:' ? 'wss://' : 'ws://', 'admin', ':', 'admin', '@', location.hostname, ':',location.port,'/websocket'].join('');
+const socketUrl = [location.protocol === 'https:' ? 'wss://' : 'ws://', 'admin', ':', 'admin', '@', location.hostname, ':', location.port, '/websocket'].join('');
const subscriptions: { [scope: string]: SubscriptionCallback[] } = { };
export interface IFormatedMessage {
@@ -29,7 +29,7 @@ function formatData(event: MessageEvent) : IFormatedMessage | undefined {
export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {
return socketReady.then((notificationSocket) => {
const scopes = scope instanceof Array ? scope : [scope];
-
+
// send all new scopes to subscribe
const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => {
const currentCallbacks = subscriptions[cur];
@@ -102,7 +102,7 @@ const connect = (): Promise<WebSocket> => {
const callbacks = subscriptions[formated.notifType];
if (callbacks) {
callbacks.forEach(cb => {
- // ensure all callbacks will be called
+ // ensure all callbacks will be called
try {
return cb(formated);
} catch (reason) {
diff --git a/sdnr/wt/odlux/framework/src/services/restService.ts b/sdnr/wt/odlux/framework/src/services/restService.ts
index 83c005c13..cfa8ec159 100644
--- a/sdnr/wt/odlux/framework/src/services/restService.ts
+++ b/sdnr/wt/odlux/framework/src/services/restService.ts
@@ -1,8 +1,19 @@
+import { ApplicationStore } from "../store/applicationStore";
+import { ReplaceAction } from "../actions/navigationActions";
const baseUri = `${ window.location.origin }`;
-const absUrlPattern = /^https?:\/\//;
+const absUrlPattern = /^https?:\/\//;
+let applicationStore: ApplicationStore | null = null;
-export async function requestRest<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = false): Promise<TData|false|null> {
+export const startRestService = (store: ApplicationStore) => {
+ applicationStore = store;
+};
+
+export const formEncode = (params: { [key: string]: string | number }) => Object.keys(params).map((key) => {
+ return encodeURIComponent(key) + '=' + encodeURIComponent(params[key].toString());
+}).join('&');
+
+export async function requestRest<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true): Promise<TData|false|null> {
const isAbsUrl = absUrlPattern.test(path);
const uri = isAbsUrl ? path : (baseUri) + ('/' + path).replace(/\/{2,}/i, '/');
init.headers = {
@@ -11,13 +22,23 @@ export async function requestRest<TData>(path: string = '', init: RequestInit =
'Accept': 'application/json',
...init.headers
};
- if (!isAbsUrl && authenticate) {
- init.headers = {
+ if (!isAbsUrl && authenticate && applicationStore) {
+ const { state: { framework: { authenticationState: { user } } } } = applicationStore;
+ // do not request if the user is not valid
+ if (!user || !user.isValid) {
+ return null;
+ }
+ (init.headers = {
...init.headers,
- 'Authorization': 'Basic YWRtaW46S3A4Yko0U1hzek0wV1hsaGFrM2VIbGNzZTJnQXc4NHZhb0dHbUp2VXkyVQ=='
- };
+ 'Authorization': `${user.tokenType} ${user.token}`
+ //'Authorization': 'Basic YWRtaW46YWRtaW4='
+ });
}
const result = await fetch(uri, init);
+ if (result.status === 401 || result.status === 403) {
+ applicationStore && applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${applicationStore.state.framework.navigationState.pathname}`));
+ return null;
+ }
const contentType = result.headers.get("Content-Type") || result.headers.get("content-type");
const isJson = contentType && contentType.toLowerCase().startsWith("application/json");
try {
diff --git a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts
index aeab8a0d6..690324e1c 100644
--- a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts
+++ b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts
@@ -2,12 +2,14 @@
import { DataCallback } from '../components/material-table';
import { Result, HitEntry } from '../models';
+import { requestRest } from '../services/restService';
+
type propType = string | number | null | undefined | (string | number)[];
type dataType = { [prop: string]: propType };
type resultType<TData = dataType> = { page: number, rowCount: number, rows: TData[] };
export function createSearchDataHandler<TResult extends {} = dataType>(uri: string, additionalParameters?: {}): DataCallback<(TResult & { _id: string })>;
-export function createSearchDataHandler<TResult extends {} = dataType, TData = dataType>(uri: string, additionalParameters: {} | null | undefined, mapResult: (res: HitEntry<TResult>, index: number, arr: HitEntry<TResult>[]) => (TData & { _id: string }), mapRequest?: (name?: string | null) => string): DataCallback<(TData & { _id: string })>
+export function createSearchDataHandler<TResult extends {} = dataType, TData = dataType>(uri: string, additionalParameters: {} | null | undefined, mapResult: (res: HitEntry<TResult>, index: number, arr: HitEntry<TResult>[]) => (TData & { _id: string }), mapRequest?: (name?: string | null) => string): DataCallback<(TData & { _id: string })>
export function createSearchDataHandler<TResult, TData>(uri: string, additionalParameters?: {} | null | undefined, mapResult?: (res: HitEntry<TResult>, index: number, arr: HitEntry<TResult>[]) => (TData & { _id: string }), mapRequest?: (name?: string | null) => string): DataCallback<(TData & { _id: string })> {
const url = `${ window.location.origin }/database/${uri}/_search`;
const fetchData: DataCallback<(TData & { _id: string }) > = async (page, rowsPerPage, orderBy, order, filter) => {
@@ -16,7 +18,7 @@ export function createSearchDataHandler<TResult, TData>(uri: string, additionalP
: null;
const filterKeys = filter && Object.keys(filter) || [];
-
+
const query = {
...filterKeys.length > 0 ? {
query: {
@@ -35,7 +37,7 @@ export function createSearchDataHandler<TResult, TData>(uri: string, additionalP
...orderBy && order ? { "sort": [{ [mapRequest ? mapRequest(orderBy) : orderBy]: order }] } : {},
...additionalParameters ? additionalParameters : {}
};
- const result = await fetch(url, {
+ const result = await requestRest<Result<TResult & { _id: string }>>(url, {
method: "POST", // *GET, POST, PUT, DELETE, etc.
mode: "no-cors", // no-cors, cors, *same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
@@ -46,18 +48,17 @@ export function createSearchDataHandler<TResult, TData>(uri: string, additionalP
body: JSON.stringify(query), // body data type must match "Content-Type" header
});
- if (result.ok) {
- const queryResult: Result<TResult & { _id: string }> = await result.json();
+ if (result) {
let rows: (TData & { _id: string })[] = [];
- if (queryResult && queryResult.hits && queryResult.hits.hits) {
- rows = queryResult.hits.hits.map( mapResult ? mapResult : h => (
+ if (result && result.hits && result.hits.hits) {
+ rows = result.hits.hits.map( mapResult ? mapResult : h => (
{ ...(h._source as any as TData), _id: h._id }
)) || []
}
const data = {
- page: Math.min(page || 0, queryResult.hits.total || 0 / (rowsPerPage || 1)), rowCount: queryResult.hits.total, rows: rows
+ page: Math.min(page || 0, result.hits.total || 0 / (rowsPerPage || 1)), rowCount: result.hits.total, rows: rows
};
return data;
}
diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx
index 6fa24e4ab..513e24712 100644
--- a/sdnr/wt/odlux/framework/src/views/login.tsx
+++ b/sdnr/wt/odlux/framework/src/views/login.tsx
@@ -1,145 +1,193 @@
-import * as React from 'react';
-import { withRouter, RouteComponentProps } from 'react-router-dom';
-
-import Avatar from '@material-ui/core/Avatar';
-import Button from '@material-ui/core/Button';
-import CssBaseline from '@material-ui/core/CssBaseline';
-import FormControl from '@material-ui/core/FormControl';
-import FormControlLabel from '@material-ui/core/FormControlLabel';
-import Checkbox from '@material-ui/core/Checkbox';
-import Input from '@material-ui/core/Input';
-import InputLabel from '@material-ui/core/InputLabel';
-import LockIcon from '@material-ui/icons/LockOutlined';
-import Paper from '@material-ui/core/Paper';
-import Typography from '@material-ui/core/Typography';
-import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
-
-import connect, { Connect } from '../flux/connect';
-import authenticationService from '../services/authenticationService';
-
-import { UpdateAuthentication } from '../actions/authentication';
-
-const styles = (theme: Theme) => createStyles({
- layout: {
- width: 'auto',
- display: 'block', // Fix IE11 issue.
- marginLeft: theme.spacing.unit * 3,
- marginRight: theme.spacing.unit * 3,
- [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
- width: 400,
- marginLeft: 'auto',
- marginRight: 'auto',
- },
- },
- paper: {
- marginTop: theme.spacing.unit * 8,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- padding: `${ theme.spacing.unit * 2 }px ${ theme.spacing.unit * 3 }px ${ theme.spacing.unit * 3 }px`,
- },
- avatar: {
- margin: theme.spacing.unit,
- backgroundColor: theme.palette.secondary.main,
- },
- form: {
- width: '100%', // Fix IE11 issue.
- marginTop: theme.spacing.unit,
- },
- submit: {
- marginTop: theme.spacing.unit * 3,
- },
-});
-
-type LoginProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect ;
-
-interface ILoginState {
- busy: boolean;
- email: string;
- password: string;
-}
-
-
-// todo: ggf. redirect to einbauen
-class LoginComponent extends React.Component<LoginProps, ILoginState> {
-
- constructor(props: LoginProps) {
- super(props);
-
- this.state = {
- busy: false,
- email: '',
- password: ''
- };
- }
-
- render(): JSX.Element {
- const { classes } = this.props;
- return (
- <React.Fragment>
- <CssBaseline />
- <main className={ classes.layout }>
- <Paper className={ classes.paper }>
- <Avatar className={ classes.avatar }>
- <LockIcon />
- </Avatar>
- <Typography variant="caption">Sign in</Typography>
- <form className={ classes.form }>
- <FormControl margin="normal" required fullWidth>
- <InputLabel htmlFor="email">Email Address</InputLabel>
- <Input id="email" name="email" autoComplete="email" autoFocus
- disabled={ this.state.busy }
- value = {this.state.email }
- onChange={ event => { this.setState({ email: event.target.value }) } }/>
- </FormControl>
- <FormControl margin="normal" required fullWidth>
- <InputLabel htmlFor="password">Password</InputLabel>
- <Input
- name="password"
- type="password"
- id="password"
- autoComplete="current-password"
- disabled={ this.state.busy }
- value={ this.state.password }
- onChange={ event => { this.setState({ password: event.target.value }) } }
- />
- </FormControl>
- <FormControlLabel
- control={ <Checkbox value="remember" color="primary" /> }
- label="Remember me"
- />
- <Button
- type="submit"
- fullWidth
- variant="raised"
- color="primary"
- disabled = { this.state.busy }
- className={ classes.submit }
- onClick = { this.onSignIn }
- >
- Sign in
- </Button>
- </form>
- </Paper>
- </main>
- </React.Fragment>
- );
- }
-
- private onSignIn = async (event: React.MouseEvent<HTMLButtonElement>) => {
- event.preventDefault();
-
- this.setState({ busy: true });
- const token = await authenticationService.authenticateUser(this.state.email, this.state.password);
- this.props.dispatch(new UpdateAuthentication(token));
- this.setState({ busy: false });
-
- if (token) {
- this.props.history.replace("/");
- }
-
- }
-}
-
-export const Login = withStyles(styles)(withRouter(connect()(LoginComponent)));
+import * as React from 'react';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
+
+import Avatar from '@material-ui/core/Avatar';
+import Button from '@material-ui/core/Button';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogContentText from '@material-ui/core/DialogContentText';
+import DialogTitle from '@material-ui/core/DialogTitle';
+import CssBaseline from '@material-ui/core/CssBaseline';
+import FormControl from '@material-ui/core/FormControl';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Checkbox from '@material-ui/core/Checkbox';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import LockIcon from '@material-ui/icons/LockOutlined';
+import Paper from '@material-ui/core/Paper';
+import Typography from '@material-ui/core/Typography';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+import connect, { Connect } from '../flux/connect';
+import authenticationService from '../services/authenticationService';
+
+import { UpdateAuthentication } from '../actions/authentication';
+
+const styles = (theme: Theme) => createStyles({
+ layout: {
+ width: 'auto',
+ display: 'block', // Fix IE11 issue.
+ marginLeft: theme.spacing.unit * 3,
+ marginRight: theme.spacing.unit * 3,
+ [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: {
+ width: 400,
+ marginLeft: 'auto',
+ marginRight: 'auto',
+ },
+ },
+ paper: {
+ marginTop: theme.spacing.unit * 8,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ padding: `${ theme.spacing.unit * 2 }px ${ theme.spacing.unit * 3 }px ${ theme.spacing.unit * 3 }px`,
+ },
+ avatar: {
+ margin: theme.spacing.unit,
+ backgroundColor: theme.palette.secondary.main,
+ },
+ form: {
+ width: '100%', // Fix IE11 issue.
+ marginTop: theme.spacing.unit,
+ },
+ submit: {
+ marginTop: theme.spacing.unit * 3,
+ },
+});
+
+type LoginProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect ;
+
+interface ILoginState {
+ busy: boolean;
+ email: string;
+ password: string;
+ scope: string;
+ message: string;
+}
+
+
+// todo: ggf. redirect to einbauen
+class LoginComponent extends React.Component<LoginProps, ILoginState> {
+
+ constructor(props: LoginProps) {
+ super(props);
+
+ this.state = {
+ busy: false,
+ email: '',
+ password: '',
+ scope: 'sdn',
+ message: ''
+ };
+ }
+
+ render(): JSX.Element {
+ const { classes } = this.props;
+ return (
+ <React.Fragment>
+ <CssBaseline />
+ <main className={ classes.layout }>
+ <Paper className={ classes.paper }>
+ <Avatar className={ classes.avatar }>
+ <LockIcon />
+ </Avatar>
+ <Typography variant="caption">Sign in</Typography>
+ <form className={ classes.form }>
+ <FormControl margin="normal" required fullWidth>
+ <InputLabel htmlFor="email">Email Address</InputLabel>
+ <Input id="email" name="email" autoComplete="email" autoFocus
+ disabled={ this.state.busy }
+ value = {this.state.email }
+ onChange={ event => { this.setState({ email: event.target.value }) } }/>
+ </FormControl>
+ <FormControl margin="normal" required fullWidth>
+ <InputLabel htmlFor="password">Password</InputLabel>
+ <Input
+ name="password"
+ type="password"
+ id="password"
+ autoComplete="current-password"
+ disabled={ this.state.busy }
+ value={ this.state.password }
+ onChange={ event => { this.setState({ password: event.target.value }) } }
+ />
+ </FormControl>
+ <FormControl margin="normal" required fullWidth>
+ <InputLabel htmlFor="password">Scope</InputLabel>
+ <Input
+ name="scope"
+ type="scope"
+ id="scope"
+ disabled={this.state.busy}
+ value={this.state.scope}
+ onChange={event => { this.setState({ scope: event.target.value }) }}
+ />
+ </FormControl>
+ <FormControlLabel
+ control={ <Checkbox value="remember" color="primary" /> }
+ label="Remember me"
+ />
+ <Button
+ type="submit"
+ fullWidth
+ variant="raised"
+ color="primary"
+ disabled = { this.state.busy }
+ className={ classes.submit }
+ onClick = { this.onSignIn }
+ >
+ Sign in
+ </Button>
+ </form>
+ </Paper>
+ </main>
+ <Dialog
+ open={!!this.state.message}
+ onClose={() => { this.setState({message: ''})}}
+ aria-labelledby="alert-dialog-title"
+ aria-describedby="alert-dialog-description"
+ >
+ <DialogTitle id="alert-dialog-title">{"Error"}</DialogTitle>
+ <DialogContent>
+ <DialogContentText id="alert-dialog-description">
+ { this.state.message }
+ </DialogContentText>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={() => { this.setState({ message: '' }) }} color="secondary" autoFocus>
+ OK
+ </Button>
+ </DialogActions>
+ </Dialog>
+ </React.Fragment>
+ );
+ }
+
+ private onSignIn = async (event: React.MouseEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+
+ this.setState({ busy: true });
+ const token = await authenticationService.authenticateUser(this.state.email, this.state.password, this.state.scope);
+ this.props.dispatch(new UpdateAuthentication(token));
+ this.setState({ busy: false });
+
+ if (token) {
+ const query =
+ this.props.state.framework.navigationState.search &&
+ this.props.state.framework.navigationState.search.replace(/^\?/, "")
+ .split('&').map(e => e.split("="));
+ const returnTo = query && query.find(e => e[0] === "returnTo");
+ this.props.history.replace(returnTo && returnTo[1] || "/");
+ }
+ else {
+ this.setState({
+ message: "Could not log in. Please check your credentials or ask your administrator for assistence.",
+ password: ""
+ })
+ }
+ }
+}
+
+export const Login = withStyles(styles)(withRouter(connect()(LoginComponent)));
export default Login; \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/tsconfig.json b/sdnr/wt/odlux/framework/tsconfig.json
index f66b910b9..d801f7c8e 100644
--- a/sdnr/wt/odlux/framework/tsconfig.json
+++ b/sdnr/wt/odlux/framework/tsconfig.json
@@ -25,6 +25,7 @@
"es2016"
],
"types": [
+ "node",
"prop-types",
"react",
"react-dom",
diff --git a/sdnr/wt/odlux/framework/webpack.config.js b/sdnr/wt/odlux/framework/webpack.config.js
index 4231d67e5..c732af6ec 100644
--- a/sdnr/wt/odlux/framework/webpack.config.js
+++ b/sdnr/wt/odlux/framework/webpack.config.js
@@ -82,7 +82,7 @@ module.exports = (env) => {
minimizer: env !== "release" ? [] : [new TerserPlugin({
terserOptions: {
mangle:{
- reserved:["./app.tsx"]
+ reserved:["./app.tsx"]
},
warnings: false, // false, true, "verbose"
compress: {
@@ -173,8 +173,8 @@ module.exports = (env) => {
colors: true
},
proxy: {
- "/api/**/*": {
- target: "http://localhost:3001",
+ "/oauth2/**/*": {
+ target: "http://localhost:3000",
secure: false
}
}