KEYCLOAK-13978 onTokenExpired + onAuthRefreshError
implemented handlers and use context for "services"
This commit is contained in:
parent
c0744daa5b
commit
f37fa31639
19 changed files with 211 additions and 151 deletions
|
@ -30,22 +30,23 @@ import {
|
|||
PageSidebar,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { KeycloakContext } from './keycloak-service/KeycloakContext';
|
||||
|
||||
declare function toggleReact(): void;
|
||||
declare function isWelcomePage(): boolean;
|
||||
declare function loggedInUserName(): string;
|
||||
|
||||
declare const locale: string;
|
||||
declare const resourceUrl: string;
|
||||
|
||||
declare const brandImg: string;
|
||||
declare const brandUrl: string;
|
||||
|
||||
export interface AppProps {};
|
||||
export class App extends React.Component<AppProps> {
|
||||
private kcSvc: KeycloakService = KeycloakService.Instance;
|
||||
static contextType = KeycloakContext;
|
||||
context: React.ContextType<typeof KeycloakContext>;
|
||||
|
||||
public constructor(props: AppProps) {
|
||||
public constructor(props: AppProps, context: React.ContextType<typeof KeycloakContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
toggleReact();
|
||||
}
|
||||
|
||||
|
@ -53,8 +54,8 @@ export class App extends React.Component<AppProps> {
|
|||
toggleReact();
|
||||
|
||||
// check login
|
||||
if (!this.kcSvc.authenticated() && !isWelcomePage()) {
|
||||
this.kcSvc.login();
|
||||
if (!this.context!.authenticated() && !isWelcomePage()) {
|
||||
this.context!.login();
|
||||
}
|
||||
|
||||
const username = (
|
||||
|
|
|
@ -22,6 +22,13 @@ import {HashRouter} from 'react-router-dom';
|
|||
import {App} from './App';
|
||||
import {ContentItem, ModulePageDef, flattenContent, initGroupAndItemIds, isExpansion, isModulePageDef} from './ContentPages';
|
||||
|
||||
import { KeycloakClient, KeycloakService } from './keycloak-service/keycloak.service';
|
||||
import { KeycloakContext } from './keycloak-service/KeycloakContext';
|
||||
import { AccountServiceClient } from './account-service/account.service';
|
||||
import { AccountServiceContext } from './account-service/AccountServiceContext';
|
||||
|
||||
declare const keycloak: KeycloakClient;
|
||||
|
||||
declare let isReactLoading: boolean;
|
||||
declare function toggleReact(): void;
|
||||
|
||||
|
@ -38,9 +45,14 @@ export class Main extends React.Component<MainProps> {
|
|||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const keycloakService = new KeycloakService(keycloak);
|
||||
return (
|
||||
<HashRouter>
|
||||
<App/>
|
||||
<KeycloakContext.Provider value={keycloakService}>
|
||||
<AccountServiceContext.Provider value={new AccountServiceClient(keycloakService)}>
|
||||
<App/>
|
||||
</AccountServiceContext.Provider>
|
||||
</KeycloakContext.Provider>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import * as React from 'react';
|
||||
import { AccountServiceClient } from './account.service';
|
||||
|
||||
export const AccountServiceContext = React.createContext<AccountServiceClient | undefined>(undefined);
|
|
@ -38,11 +38,14 @@ export class AccountServiceError extends Error {
|
|||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
class AccountServiceClient {
|
||||
private kcSvc: KeycloakService = KeycloakService.Instance;
|
||||
private accountUrl: string = this.kcSvc.authServerUrl() + 'realms/' + this.kcSvc.realm() + '/account';
|
||||
export class AccountServiceClient {
|
||||
private kcSvc: KeycloakService;
|
||||
private accountUrl: string;
|
||||
|
||||
constructor() {}
|
||||
public constructor(keycloakService: KeycloakService) {
|
||||
this.kcSvc = keycloakService;
|
||||
this.accountUrl = this.kcSvc.authServerUrl() + 'realms/' + this.kcSvc.realm() + '/account';
|
||||
}
|
||||
|
||||
public async doGet<T>(endpoint: string,
|
||||
config?: RequestInitWithParams): Promise<HttpResponse<T>> {
|
||||
|
@ -130,9 +133,6 @@ class AccountServiceClient {
|
|||
|
||||
}
|
||||
|
||||
const AccountService: AccountServiceClient = new AccountServiceClient();
|
||||
export default AccountService;
|
||||
|
||||
window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
|
||||
event.promise.catch(error => {
|
||||
if (error instanceof AccountServiceError) {
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
import * as React from 'react';
|
||||
import { ActionGroup, Button, Form, FormGroup, TextInput } from '@patternfly/react-core';
|
||||
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import { HttpResponse, AccountServiceClient } from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { Features } from '../../widgets/features';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
import { ContentPage } from '../ContentPage';
|
||||
|
@ -46,6 +47,8 @@ interface AccountPageState {
|
|||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
export class AccountPage extends React.Component<AccountPageProps, AccountPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername;
|
||||
private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed;
|
||||
private readonly DEFAULT_STATE: AccountPageState = {
|
||||
|
@ -66,13 +69,15 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
|
||||
public state: AccountPageState = this.DEFAULT_STATE;
|
||||
|
||||
public constructor(props: AccountPageProps) {
|
||||
public constructor(props: AccountPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.fetchPersonalInfo();
|
||||
}
|
||||
|
||||
private fetchPersonalInfo(): void {
|
||||
AccountService.doGet<FormFields>("/")
|
||||
this.context!.doGet<FormFields>("/")
|
||||
.then((response: HttpResponse<FormFields>) => {
|
||||
this.setState(this.DEFAULT_STATE);
|
||||
const formFields = response.data;
|
||||
|
@ -104,7 +109,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
const isValid = form.checkValidity();
|
||||
if (isValid) {
|
||||
const reqData: FormFields = { ...this.state.formFields };
|
||||
AccountService.doPost<void>("/", reqData)
|
||||
this.context!.doPost<void>("/", reqData)
|
||||
.then(() => {
|
||||
ContentAlert.success('accountUpdatedMessage');
|
||||
if (locale !== this.state.formFields.attributes!.locale![0]) {
|
||||
|
|
|
@ -31,6 +31,8 @@ import {
|
|||
EmptyStateBody
|
||||
} from '@patternfly/react-core';
|
||||
import { PassportIcon } from '@patternfly/react-icons';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
||||
|
||||
// Note: This class demonstrates two features of the ContentPages framework:
|
||||
// 1) The PageDef is available as a React property.
|
||||
|
@ -57,8 +59,8 @@ class ApplicationInitiatedActionPage extends React.Component<AppInitiatedActionP
|
|||
super(props);
|
||||
}
|
||||
|
||||
private handleClick = (): void => {
|
||||
new AIACommand(this.props.pageDef.kcAction).execute();
|
||||
private handleClick = (keycloak: KeycloakService): void => {
|
||||
new AIACommand(keycloak, this.props.pageDef.kcAction).execute();
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
|
@ -71,9 +73,14 @@ class ApplicationInitiatedActionPage extends React.Component<AppInitiatedActionP
|
|||
<EmptyStateBody>
|
||||
<Msg msgKey="actionRequiresIDP"/>
|
||||
</EmptyStateBody>
|
||||
<Button variant="primary"
|
||||
onClick={this.handleClick}
|
||||
target="_blank"><Msg msgKey="continue"/></Button>
|
||||
<KeycloakContext.Consumer>
|
||||
{ keycloak => (
|
||||
<Button variant="primary"
|
||||
onClick={() => this.handleClick(keycloak!)}
|
||||
target="_blank"><Msg msgKey="continue"/></Button>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
|
||||
</EmptyState>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ import {
|
|||
import { InfoAltIcon, CheckIcon, BuilderImageIcon } from '@patternfly/react-icons';
|
||||
import { ContentPage } from '../ContentPage';
|
||||
import { ContinueCancelModal } from '../../widgets/ContinueCancelModal';
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import { HttpResponse } from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
|
||||
declare const locale: string;
|
||||
|
@ -69,9 +70,12 @@ interface Application {
|
|||
}
|
||||
|
||||
export class ApplicationsPage extends React.Component<ApplicationsPageProps, ApplicationsPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: ApplicationsPageProps) {
|
||||
public constructor(props: ApplicationsPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
this.state = {
|
||||
isRowOpen: [],
|
||||
applications: []
|
||||
|
@ -81,7 +85,7 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
}
|
||||
|
||||
private removeConsent = (clientId: string) => {
|
||||
AccountService.doDelete("/applications/" + clientId + "/consent")
|
||||
this.context!.doDelete("/applications/" + clientId + "/consent")
|
||||
.then(() => {
|
||||
this.fetchApplications();
|
||||
});
|
||||
|
@ -94,7 +98,7 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
};
|
||||
|
||||
private fetchApplications(): void {
|
||||
AccountService.doGet<Application[]>("/applications")
|
||||
this.context!.doGet<Application[]>("/applications")
|
||||
.then((response: HttpResponse<Application[]>) => {
|
||||
const applications = response.data || [];
|
||||
this.setState({
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
import * as React from 'react';
|
||||
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import {HttpResponse} from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import TimeUtil from '../../util/TimeUtil';
|
||||
|
||||
import {
|
||||
|
@ -46,12 +47,12 @@ import {
|
|||
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
import {ContinueCancelModal} from '../../widgets/ContinueCancelModal';
|
||||
import {KeycloakService} from '../../keycloak-service/keycloak.service';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
||||
|
||||
import {ContentPage} from '../ContentPage';
|
||||
import { ContentAlert } from '../ContentAlert';
|
||||
|
||||
declare const baseUrl: string;
|
||||
|
||||
export interface DeviceActivityPageProps {
|
||||
}
|
||||
|
||||
|
@ -91,9 +92,12 @@ interface Client {
|
|||
* @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
|
||||
*/
|
||||
export class DeviceActivityPage extends React.Component<DeviceActivityPageProps, DeviceActivityPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: DeviceActivityPageProps) {
|
||||
public constructor(props: DeviceActivityPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
devices: []
|
||||
|
@ -102,15 +106,15 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
this.fetchDevices();
|
||||
}
|
||||
|
||||
private signOutAll = () => {
|
||||
AccountService.doDelete("/sessions")
|
||||
private signOutAll = (keycloakService: KeycloakService) => {
|
||||
this.context!.doDelete("/sessions")
|
||||
.then( () => {
|
||||
KeycloakService.Instance.logout(baseUrl);
|
||||
keycloakService.logout();
|
||||
});
|
||||
}
|
||||
|
||||
private signOutSession = (device: Device, session: Session) => {
|
||||
AccountService.doDelete("/sessions/" + session.id)
|
||||
this.context!.doDelete("/sessions/" + session.id)
|
||||
.then (() => {
|
||||
this.fetchDevices();
|
||||
ContentAlert.success('signedOutSession', [session.browser, device.os]);
|
||||
|
@ -118,7 +122,7 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
}
|
||||
|
||||
private fetchDevices(): void {
|
||||
AccountService.doGet<Device[]>("/sessions/devices")
|
||||
this.context!.doGet<Device[]>("/sessions/devices")
|
||||
.then((response: HttpResponse<Device[]>) => {
|
||||
console.log({response});
|
||||
|
||||
|
@ -232,16 +236,20 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
</p>
|
||||
</div>
|
||||
</DataListCell>,
|
||||
<DataListCell key='signOutAllButton' width={1}>
|
||||
{this.isShowSignOutAll(this.state.devices) &&
|
||||
<ContinueCancelModal buttonTitle='signOutAllDevices'
|
||||
buttonId='sign-out-all'
|
||||
modalTitle='signOutAllDevices'
|
||||
modalMessage='signOutAllDevicesWarning'
|
||||
onContinue={this.signOutAll}
|
||||
/>
|
||||
}
|
||||
</DataListCell>
|
||||
<KeycloakContext.Consumer>
|
||||
{ (keycloak: KeycloakService) => (
|
||||
<DataListCell key='signOutAllButton' width={1}>
|
||||
{this.isShowSignOutAll(this.state.devices) &&
|
||||
<ContinueCancelModal buttonTitle='signOutAllDevices'
|
||||
buttonId='sign-out-all'
|
||||
modalTitle='signOutAllDevices'
|
||||
modalMessage='signOutAllDevicesWarning'
|
||||
onContinue={() => this.signOutAll(keycloak)}
|
||||
/>
|
||||
}
|
||||
</DataListCell>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
|
|
|
@ -50,7 +50,8 @@ import {
|
|||
UnlinkIcon
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import {HttpResponse} from '../../account-service/account.service';
|
||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
import {ContentPage} from '../ContentPage';
|
||||
import {createRedirect} from '../../util/RedirectUri';
|
||||
|
@ -76,9 +77,13 @@ interface LinkedAccountsPageState {
|
|||
* @author Stan Silvert
|
||||
*/
|
||||
class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, LinkedAccountsPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: LinkedAccountsPageProps) {
|
||||
public constructor(props: LinkedAccountsPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
linkedAccounts: [],
|
||||
unLinkedAccounts: []
|
||||
|
@ -88,7 +93,7 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
}
|
||||
|
||||
private getLinkedAccounts(): void {
|
||||
AccountService.doGet<LinkedAccount[]>("/linked-accounts")
|
||||
this.context!.doGet<LinkedAccount[]>("/linked-accounts")
|
||||
.then((response: HttpResponse<LinkedAccount[]>) => {
|
||||
console.log({response});
|
||||
const linkedAccounts = response.data!.filter((account) => account.connected);
|
||||
|
@ -100,7 +105,7 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
private unLinkAccount(account: LinkedAccount): void {
|
||||
const url = '/linked-accounts/' + account.providerName;
|
||||
|
||||
AccountService.doDelete<void>(url)
|
||||
this.context!.doDelete<void>(url)
|
||||
.then((response: HttpResponse<void>) => {
|
||||
console.log({response});
|
||||
this.getLinkedAccounts();
|
||||
|
@ -112,7 +117,7 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
|
||||
const redirectUri: string = createRedirect(this.props.location.pathname);
|
||||
|
||||
AccountService.doGet<{accountLinkUri: string}>(url, { params: {providerId: account.providerName, redirectUri}})
|
||||
this.context!.doGet<{accountLinkUri: string}>(url, { params: {providerId: account.providerName, redirectUri}})
|
||||
.then((response: HttpResponse<{accountLinkUri: string}>) => {
|
||||
console.log({response});
|
||||
window.location.href = response.data!.accountLinkUri;
|
||||
|
|
|
@ -28,7 +28,7 @@ import { OkIcon } from '@patternfly/react-icons';
|
|||
|
||||
import { Resource, Permission, Scope } from './MyResourcesPage';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
import AccountService from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { ContentAlert } from '../ContentAlert';
|
||||
import { PermissionSelect } from './PermissionSelect';
|
||||
|
||||
|
@ -46,9 +46,12 @@ interface EditTheResourceState {
|
|||
|
||||
export class EditTheResource extends React.Component<EditTheResourceProps, EditTheResourceState> {
|
||||
protected static defaultProps = { permissions: [] };
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: EditTheResourceProps) {
|
||||
public constructor(props: EditTheResourceProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
changed: [],
|
||||
|
@ -77,7 +80,7 @@ export class EditTheResource extends React.Component<EditTheResourceProps, EditT
|
|||
}
|
||||
|
||||
async savePermission(permission: Permission): Promise<void> {
|
||||
await AccountService.doPut(`/resources/${this.props.resource._id}/permissions`, [permission]);
|
||||
await this.context!.doPut(`/resources/${this.props.resource._id}/permissions`, [permission]);
|
||||
ContentAlert.success(Msg.localize('updateSuccess'));
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ import parse from '../../util/ParseLink';
|
|||
|
||||
import { Button, Level, LevelItem, Stack, StackItem, Tab, Tabs, TextInput } from '@patternfly/react-core';
|
||||
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import {HttpResponse} from '../../account-service/account.service';
|
||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
||||
|
||||
import {ResourcesTable} from './ResourcesTable';
|
||||
import {ContentPage} from '../ContentPage';
|
||||
|
@ -83,11 +84,15 @@ const MY_RESOURCES_TAB = 0;
|
|||
const SHARED_WITH_ME_TAB = 1;
|
||||
|
||||
export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyResourcesPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
private first = 0;
|
||||
private max = 5;
|
||||
|
||||
public constructor(props: MyResourcesPageProps) {
|
||||
public constructor(props: MyResourcesPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
activeTabKey: MY_RESOURCES_TAB,
|
||||
nameFilter: '',
|
||||
|
@ -136,7 +141,7 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
|||
}
|
||||
|
||||
private fetchResources(url: string, extraParams?: Record<string, string|number>): void {
|
||||
AccountService.doGet<Resource[]>(url, {params: extraParams})
|
||||
this.context!.doGet<Resource[]>(url, {params: extraParams})
|
||||
.then((response: HttpResponse<Resource[]>) => {
|
||||
const resources: Resource[] = response.data || [];
|
||||
resources.forEach((resource: Resource) => resource.shareRequests = []);
|
||||
|
@ -163,7 +168,7 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
|||
}
|
||||
|
||||
private fetchShareRequests(resource: Resource): void {
|
||||
AccountService.doGet('/resources/' + resource._id + '/permissions/requests')
|
||||
this.context!.doGet('/resources/' + resource._id + '/permissions/requests')
|
||||
.then((response: HttpResponse<Permission[]>) => {
|
||||
resource.shareRequests = response.data || [];
|
||||
if (resource.shareRequests.length > 0) {
|
||||
|
@ -173,7 +178,7 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
|||
}
|
||||
|
||||
private fetchPending = async () => {
|
||||
const response: HttpResponse<Resource[]> = await AccountService.doGet(`/resources/pending-requests`);
|
||||
const response: HttpResponse<Resource[]> = await this.context!.doGet(`/resources/pending-requests`);
|
||||
const resources: Resource[] = response.data || [];
|
||||
resources.forEach((pendingRequest: Resource) => {
|
||||
this.state.sharedWithMe.data.forEach(resource => {
|
||||
|
|
|
@ -31,7 +31,8 @@ import {
|
|||
} from '@patternfly/react-core';
|
||||
import { UserCheckIcon } from '@patternfly/react-icons';
|
||||
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import { HttpResponse } from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
import { ContentAlert } from '../ContentAlert';
|
||||
import { Resource, Scope, Permission } from './MyResourcesPage';
|
||||
|
@ -48,10 +49,13 @@ interface PermissionRequestState {
|
|||
|
||||
export class PermissionRequest extends React.Component<PermissionRequestProps, PermissionRequestState> {
|
||||
protected static defaultProps = { permissions: [], row: 0 };
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: PermissionRequestProps) {
|
||||
public constructor(props: PermissionRequestProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
@ -71,7 +75,7 @@ export class PermissionRequest extends React.Component<PermissionRequestProps, P
|
|||
const id = this.props.resource._id
|
||||
this.handleToggleDialog();
|
||||
|
||||
const permissionsRequest: HttpResponse<Permission[]> = await AccountService.doGet(`/resources/${id}/permissions`);
|
||||
const permissionsRequest: HttpResponse<Permission[]> = await this.context!.doGet(`/resources/${id}/permissions`);
|
||||
const permissions = permissionsRequest.data || [];
|
||||
const foundPermission = permissions.find(p => p.username === username);
|
||||
const userScopes = foundPermission ? (foundPermission.scopes as Scope[]): [];
|
||||
|
@ -79,7 +83,7 @@ export class PermissionRequest extends React.Component<PermissionRequestProps, P
|
|||
userScopes.push(...scopes);
|
||||
}
|
||||
try {
|
||||
await AccountService.doPut(`/resources/${id}/permissions`, [{ username: username, scopes: userScopes }] )
|
||||
await this.context!.doPut(`/resources/${id}/permissions`, [{ username: username, scopes: userScopes }] )
|
||||
ContentAlert.success(Msg.localize('shareSuccess'));
|
||||
this.props.onClose();
|
||||
} catch (e) {
|
||||
|
|
|
@ -38,7 +38,8 @@ import { css } from '@patternfly/react-styles';
|
|||
|
||||
import { Remove2Icon, RepositoryIcon, ShareAltIcon, EditAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
import AccountService, { HttpResponse } from '../../account-service/account.service';
|
||||
import { HttpResponse } from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { PermissionRequest } from "./PermissionRequest";
|
||||
import { ShareTheResource } from "./ShareTheResource";
|
||||
import { Permission, Resource } from "./MyResourcesPage";
|
||||
|
@ -56,9 +57,13 @@ export interface CollapsibleResourcesTableState extends ResourcesTableState {
|
|||
}
|
||||
|
||||
export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesTableState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: ResourcesTableProps) {
|
||||
public constructor(props: ResourcesTableProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
isRowOpen: [],
|
||||
contextOpen: [],
|
||||
|
@ -87,7 +92,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
|||
}
|
||||
|
||||
private fetchPermissions(resource: Resource, row: number): void {
|
||||
AccountService.doGet(`/resources/${resource._id}/permissions`)
|
||||
this.context!.doGet(`/resources/${resource._id}/permissions`)
|
||||
.then((response: HttpResponse<Permission[]>) => {
|
||||
const newPermissions: Map<number, Permission[]> = new Map(this.state.permissions);
|
||||
newPermissions.set(row, response.data || []);
|
||||
|
@ -97,7 +102,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
|||
|
||||
private removeShare(resource: Resource, row: number): Promise<void> {
|
||||
const permissions = this.state.permissions.get(row)!.map(a => ({ username: a.username, scopes: [] }));
|
||||
return AccountService.doPut(`/resources/${resource._id}/permissions`, permissions)
|
||||
return this.context!.doPut(`/resources/${resource._id}/permissions`, permissions)
|
||||
.then(() => {
|
||||
ContentAlert.success(Msg.localize('unShareSuccess'));
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
TextInput
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import AccountService from '../../account-service/account.service';
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { Resource, Permission, Scope } from './MyResourcesPage';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
import {ContentAlert} from '../ContentAlert';
|
||||
|
@ -58,10 +58,13 @@ interface ShareTheResourceState {
|
|||
*/
|
||||
export class ShareTheResource extends React.Component<ShareTheResourceProps, ShareTheResourceState> {
|
||||
protected static defaultProps = {permissions: []};
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: ShareTheResourceProps) {
|
||||
public constructor(props: ShareTheResourceProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
permissionsSelected: [],
|
||||
|
@ -96,7 +99,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
|
||||
this.handleToggleDialog();
|
||||
|
||||
AccountService.doPut(`/resources/${rscId}/permissions`, permissions)
|
||||
this.context!.doPut(`/resources/${rscId}/permissions`, permissions)
|
||||
.then(() => {
|
||||
ContentAlert.success('shareSuccess');
|
||||
this.props.onClose();
|
||||
|
@ -119,7 +122,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
|
||||
private handleAddUsername = async () => {
|
||||
if ((this.state.usernameInput !== '') && (!this.state.usernames.includes(this.state.usernameInput))) {
|
||||
const response = await AccountService.doGet<{username: string}>(`/resources/${this.props.resource._id}/user`, { params: { value: this.state.usernameInput } });
|
||||
const response = await this.context!.doGet<{username: string}>(`/resources/${this.props.resource._id}/user`, { params: { value: this.state.usernameInput } });
|
||||
if (response.data && response.data.username) {
|
||||
this.setState({ usernameInput: '', usernames: [...this.state.usernames, this.state.usernameInput] });
|
||||
} else {
|
||||
|
|
|
@ -33,12 +33,15 @@ import {
|
|||
|
||||
import {AIACommand} from '../../util/AIACommand';
|
||||
import TimeUtil from '../../util/TimeUtil';
|
||||
import AccountService, {HttpResponse} from '../../account-service/account.service';
|
||||
import {HttpResponse, AccountServiceClient} from '../../account-service/account.service';
|
||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
||||
import {ContinueCancelModal} from '../../widgets/ContinueCancelModal';
|
||||
import {Features} from '../../widgets/features';
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
import {ContentPage} from '../ContentPage';
|
||||
import {ContentAlert} from '../ContentAlert';
|
||||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
|
||||
declare const features: Features;
|
||||
|
||||
|
@ -84,9 +87,13 @@ interface SigningInPageState {
|
|||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
class SigningInPage extends React.Component<SigningInPageProps, SigningInPageState> {
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
public constructor(props: SigningInPageProps) {
|
||||
public constructor(props: SigningInPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
||||
super(props);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
credentialContainers: new Map()
|
||||
}
|
||||
|
@ -95,7 +102,7 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
}
|
||||
|
||||
private getCredentialContainers(): void {
|
||||
AccountService.doGet("/credentials")
|
||||
this.context!.doGet("/credentials")
|
||||
.then((response: HttpResponse<CredentialContainer[]>) => {
|
||||
|
||||
const allContainers: CredContainerMap = new Map();
|
||||
|
@ -115,7 +122,7 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
}
|
||||
|
||||
private handleRemove = (credentialId: string, userLabel: string) => {
|
||||
AccountService.doDelete("/credentials/" + credentialId)
|
||||
this.context!.doDelete("/credentials/" + credentialId)
|
||||
.then(() => {
|
||||
this.getCredentialContainers();
|
||||
ContentAlert.success('successRemovedMessage', [userLabel]);
|
||||
|
@ -154,13 +161,19 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
}
|
||||
|
||||
private renderTypes(credTypeMap: CredTypeMap): React.ReactNode {
|
||||
return (<> {
|
||||
return (
|
||||
<KeycloakContext.Consumer>
|
||||
{ keycloak => (
|
||||
<>{
|
||||
Array.from(credTypeMap.keys()).map((credType: CredType, index: number, typeArray: string[]) => ([
|
||||
this.renderCredTypeTitle(credTypeMap.get(credType)!),
|
||||
this.renderUserCredentials(credTypeMap, credType),
|
||||
this.renderCredTypeTitle(credTypeMap.get(credType)!, keycloak!),
|
||||
this.renderUserCredentials(credTypeMap, credType, keycloak!),
|
||||
this.renderEmptyRow(credTypeMap.get(credType)!.type, index === typeArray.length - 1)
|
||||
]))
|
||||
}</>)
|
||||
}</>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
private renderEmptyRow(type: string, isLast: boolean): React.ReactNode {
|
||||
|
@ -175,7 +188,7 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
)
|
||||
}
|
||||
|
||||
private renderUserCredentials(credTypeMap: CredTypeMap, credType: CredType): React.ReactNode {
|
||||
private renderUserCredentials(credTypeMap: CredTypeMap, credType: CredType, keycloak: KeycloakService): React.ReactNode {
|
||||
const credContainer: CredentialContainer = credTypeMap.get(credType)!;
|
||||
const userCredentials: UserCredential[] = credContainer.userCredentials;
|
||||
const removeable: boolean = credContainer.removeable;
|
||||
|
@ -207,7 +220,7 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
|
||||
let updateAIA: AIACommand;
|
||||
if (credContainer.updateAction) {
|
||||
updateAIA = new AIACommand(credContainer.updateAction);
|
||||
updateAIA = new AIACommand(keycloak, credContainer.updateAction);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -239,12 +252,12 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
return credRowCells;
|
||||
}
|
||||
|
||||
private renderCredTypeTitle(credContainer: CredentialContainer): React.ReactNode {
|
||||
private renderCredTypeTitle(credContainer: CredentialContainer, keycloak: KeycloakService): React.ReactNode {
|
||||
if (!credContainer.hasOwnProperty('helptext') && !credContainer.hasOwnProperty('createAction')) return;
|
||||
|
||||
let setupAction: AIACommand;
|
||||
if (credContainer.createAction) {
|
||||
setupAction = new AIACommand(credContainer.createAction);
|
||||
setupAction = new AIACommand(keycloak, credContainer.createAction);
|
||||
}
|
||||
const credContainerDisplayName: string = Msg.localize(credContainer.displayName);
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import * as React from 'react';
|
||||
import { KeycloakService } from './keycloak.service';
|
||||
|
||||
export const KeycloakContext = React.createContext<KeycloakService | undefined>(undefined);
|
|
@ -14,90 +14,59 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {KeycloakLoginOptions, KeycloakError} from "../../../../../../../../../../adapters/oidc/js/src/main/resources/keycloak";
|
||||
|
||||
// keycloak.js downloaded in index.ftl
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
import {KeycloakLoginOptions} from "../../../../../../../../../../adapters/oidc/js/src/main/resources/keycloak";
|
||||
|
||||
declare const baseUrl: string;
|
||||
export type KeycloakClient = Keycloak.KeycloakInstance;
|
||||
type InitOptions = Keycloak.KeycloakInitOptions;
|
||||
|
||||
declare const keycloak: KeycloakClient;
|
||||
|
||||
export class KeycloakService {
|
||||
private static keycloakAuth: KeycloakClient = keycloak;
|
||||
private static instance: KeycloakService = new KeycloakService();
|
||||
private keycloakAuth: KeycloakClient;
|
||||
|
||||
private constructor() {
|
||||
|
||||
}
|
||||
|
||||
public static get Instance(): KeycloakService {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure and initialize the Keycloak adapter.
|
||||
*
|
||||
* @param configOptions Optionally, a path to keycloak.json, or an object containing
|
||||
* url, realm, and clientId.
|
||||
* @param adapterOptions Optional initiaization options. See javascript adapter docs
|
||||
* for details.
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
public static init(configOptions?: string|{}, initOptions: InitOptions = {}): Promise<void> {
|
||||
KeycloakService.keycloakAuth = Keycloak(configOptions);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
KeycloakService.keycloakAuth.init(initOptions)
|
||||
.success(() => {
|
||||
resolve();
|
||||
})
|
||||
.error((errorData: KeycloakError) => {
|
||||
reject(errorData);
|
||||
});
|
||||
});
|
||||
public constructor(keycloak: KeycloakClient) {
|
||||
this.keycloakAuth = keycloak;
|
||||
this.keycloakAuth.onTokenExpired = () => this.getToken(true).catch(() => this.logout());
|
||||
this.keycloakAuth.onAuthRefreshError = () => this.logout();
|
||||
}
|
||||
|
||||
public authenticated(): boolean {
|
||||
return KeycloakService.keycloakAuth.authenticated ? KeycloakService.keycloakAuth.authenticated : false;
|
||||
return this.keycloakAuth.authenticated ? this.keycloakAuth.authenticated : false;
|
||||
}
|
||||
|
||||
public login(options?: KeycloakLoginOptions): void {
|
||||
KeycloakService.keycloakAuth.login(options);
|
||||
this.keycloakAuth.login(options);
|
||||
}
|
||||
|
||||
public logout(redirectUri?: string): void {
|
||||
KeycloakService.keycloakAuth.logout({redirectUri: redirectUri});
|
||||
public logout(redirectUri: string = baseUrl): void {
|
||||
this.keycloakAuth.logout({redirectUri: redirectUri});
|
||||
}
|
||||
|
||||
public account(): void {
|
||||
KeycloakService.keycloakAuth.accountManagement();
|
||||
this.keycloakAuth.accountManagement();
|
||||
}
|
||||
|
||||
public authServerUrl(): string | undefined {
|
||||
const authServerUrl = KeycloakService.keycloakAuth.authServerUrl;
|
||||
return authServerUrl!.charAt(authServerUrl!.length - 1) === '/' ? authServerUrl : authServerUrl + '/';
|
||||
const authServerUrl = this.keycloakAuth.authServerUrl;
|
||||
return authServerUrl!.charAt(authServerUrl!.length - 1) === '/' ? authServerUrl : authServerUrl + '/';
|
||||
}
|
||||
|
||||
public realm(): string | undefined {
|
||||
return KeycloakService.keycloakAuth.realm;
|
||||
return this.keycloakAuth.realm;
|
||||
}
|
||||
|
||||
public getToken(): Promise<string> {
|
||||
public getToken(force: boolean = false): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (KeycloakService.keycloakAuth.token) {
|
||||
KeycloakService.keycloakAuth
|
||||
.updateToken(5)
|
||||
if (this.keycloakAuth.token) {
|
||||
this.keycloakAuth
|
||||
.updateToken(force ? -1 : 5)
|
||||
.success(() => {
|
||||
resolve(KeycloakService.keycloakAuth.token as string);
|
||||
resolve(this.keycloakAuth.token as string);
|
||||
})
|
||||
.error(() => {
|
||||
reject('Failed to refresh token');
|
||||
});
|
||||
} else {
|
||||
reject('Not loggen in');
|
||||
reject('Not logged in');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,10 @@ import {KeycloakService} from '../keycloak-service/keycloak.service';
|
|||
*/
|
||||
export class AIACommand {
|
||||
|
||||
constructor(private action: string) {}
|
||||
constructor(private keycloak: KeycloakService, private action: string) {}
|
||||
|
||||
public execute(): void {
|
||||
KeycloakService.Instance.login({
|
||||
public execute(): void {
|
||||
this.keycloak.login({
|
||||
action: this.action,
|
||||
})
|
||||
|
||||
|
|
|
@ -18,20 +18,24 @@ import * as React from 'react';
|
|||
|
||||
import {Msg} from './Msg';
|
||||
import {KeycloakService} from '../keycloak-service/keycloak.service';
|
||||
import { KeycloakContext } from '../keycloak-service/KeycloakContext';
|
||||
|
||||
import {Button, DropdownItem} from '@patternfly/react-core';
|
||||
|
||||
declare const baseUrl: string;
|
||||
|
||||
function handleLogout(): void {
|
||||
KeycloakService.Instance.logout(baseUrl);
|
||||
function handleLogout(keycloak: KeycloakService): void {
|
||||
keycloak.logout();
|
||||
}
|
||||
|
||||
interface LogoutProps {}
|
||||
export class LogoutButton extends React.Component<LogoutProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<Button id="signOutButton" onClick={handleLogout}><Msg msgKey="doSignOut"/></Button>
|
||||
<KeycloakContext.Consumer>
|
||||
{ keycloak => (
|
||||
<Button id="signOutButton" onClick={() => handleLogout(keycloak!)}><Msg msgKey="doSignOut"/></Button>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +44,13 @@ interface LogoutDropdownItemProps {}
|
|||
export class LogoutDropdownItem extends React.Component<LogoutDropdownItemProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<DropdownItem id="signOutLink" key="logout" onClick={handleLogout}>
|
||||
{Msg.localize('doSignOut')}
|
||||
</DropdownItem>
|
||||
<KeycloakContext.Consumer>
|
||||
{ keycloak => (
|
||||
<DropdownItem id="signOutLink" key="logout" onClick={() => handleLogout(keycloak!)}>
|
||||
{Msg.localize('doSignOut')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue