From 9dcc4857ad120cd87c9f1331bed7f0535a4fd32c Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 30 Aug 2022 12:00:53 +0300 Subject: [PATCH 1/3] GAME-73 --- README.md | 1 + .../profile-factory/profile.factory.ts | 2 ++ .../profile-provider/user-profile.model.ts | 3 ++- .../RestrictedPage.module.scss | 27 +++++++++++++++++++ src-ts/lib/restricted-page/RestrictedPage.tsx | 17 ++++++++++++ src-ts/lib/restricted-page/index.ts | 1 + .../route-provider/platform-route.model.ts | 1 + .../require-auth.provider.tsx | 21 ++++++++++++--- src-ts/lib/route-provider/route.provider.tsx | 2 +- .../gamification-admin/GamificationAdmin.tsx | 1 + .../gamification-admin.routes.tsx | 3 ++- 11 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 src-ts/lib/restricted-page/RestrictedPage.module.scss create mode 100644 src-ts/lib/restricted-page/RestrictedPage.tsx create mode 100644 src-ts/lib/restricted-page/index.ts diff --git a/README.md b/README.md index 9375a8333..8e34fdd33 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ The PlatformRoute model has several useful options: | `authRequired?: boolean` | Requiring authentication for a route means that users who are not logged in will be redirected to the Login Form when they try to access the route. | | `route: string` | The route property is the path to the route, relative to its parent(s). | | `title: string` | The title property is the text that will appear in the Tools or Utils Selectors (this is irrelevant on hidden routes). | +| `rolesRequired: Array` | Requiring roles for a route means that users who do not own the roles will be presented with restricted page when they try to access the route. | ## Git diff --git a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts index e70a17e06..7e5fb0aff 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts @@ -14,5 +14,7 @@ export function create(profile: UserProfile, token: TokenModel): UserProfile { // rolees. profile.isCustomer = !!token.roles?.some(role => role === UserRole.customer) profile.isMember = !profile.isCustomer + // store roles for custom capability checks + profile.roles = token.roles return profile } diff --git a/src-ts/lib/profile-provider/user-profile.model.ts b/src-ts/lib/profile-provider/user-profile.model.ts index 54c66ed49..52dc7fe5f 100644 --- a/src-ts/lib/profile-provider/user-profile.model.ts +++ b/src-ts/lib/profile-provider/user-profile.model.ts @@ -10,7 +10,8 @@ export interface UserProfile { isMember?: boolean lastName: string photoURL?: string + roles?: Array, status: string updatedAt: number - userId: number + userId: number, } diff --git a/src-ts/lib/restricted-page/RestrictedPage.module.scss b/src-ts/lib/restricted-page/RestrictedPage.module.scss new file mode 100644 index 000000000..8b7f47684 --- /dev/null +++ b/src-ts/lib/restricted-page/RestrictedPage.module.scss @@ -0,0 +1,27 @@ +.contentLayout { + width: 100%; + padding-bottom: 0; + + .contentLayout-outer { + width: 100%; + + .contentLayout-inner { + width: 100%; + overflow: visible; + } + } +} + +.container { + display: flex; + padding-top: 26px; + + a { + color: #0D61BF; + + &:hover { + color: #0D61BF; + text-decoration: underline; + } + } +} diff --git a/src-ts/lib/restricted-page/RestrictedPage.tsx b/src-ts/lib/restricted-page/RestrictedPage.tsx new file mode 100644 index 000000000..8ae4e3fd4 --- /dev/null +++ b/src-ts/lib/restricted-page/RestrictedPage.tsx @@ -0,0 +1,17 @@ +import { ReactElement } from 'react' + +import { ContentLayout } from '..' + +import styles from './RestrictedPage.module.scss' + +export const RestrictedPage: ReactElement = + +
+

Unfortenatly, you are not permitted to access the site. If you feel you should be able to, please contact us at support@topcoder.com.

+
+
diff --git a/src-ts/lib/restricted-page/index.ts b/src-ts/lib/restricted-page/index.ts new file mode 100644 index 000000000..fa77e8bc3 --- /dev/null +++ b/src-ts/lib/restricted-page/index.ts @@ -0,0 +1 @@ +export { RestrictedPage } from './RestrictedPage' diff --git a/src-ts/lib/route-provider/platform-route.model.ts b/src-ts/lib/route-provider/platform-route.model.ts index 71d1ba775..558ddf185 100644 --- a/src-ts/lib/route-provider/platform-route.model.ts +++ b/src-ts/lib/route-provider/platform-route.model.ts @@ -7,6 +7,7 @@ export interface PlatformRoute { element: JSX.Element hidden?: boolean memberOnly?: boolean + rolesRequired?: Array route: string title?: string } diff --git a/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx b/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx index e6930755c..0adb100ae 100644 --- a/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx +++ b/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx @@ -1,10 +1,12 @@ import { useContext } from 'react' import { profileContext, ProfileContextData } from '../../profile-provider' +import { RestrictedPage } from '../../restricted-page' interface RequireAuthProviderProps { children: JSX.Element - loginUrl: string + loginUrl: string, + rolesRequired?: Array, } function RequireAuthProvider(props: RequireAuthProviderProps): JSX.Element { @@ -12,11 +14,24 @@ function RequireAuthProvider(props: RequireAuthProviderProps): JSX.Element { const profileContextData: ProfileContextData = useContext(profileContext) const { profile, initialized }: ProfileContextData = profileContextData - // if we have a profile or we're not initialized yet, just return the children - if (!initialized || !!profile) { + // if we're not initialized yet, just return the children + if (!initialized) { return props.children } + // if we have a profile and `rolesRequired` is configured for the route + // check the user's roles, allow access or show restricted page + if (!!profile) { + if (props.rolesRequired) { + if (!profile.roles) { return RestrictedPage } + const intersection: Array = profile.roles?.filter(r => props.rolesRequired?.includes(r)) + if (intersection.length !== props.rolesRequired.length) { return RestrictedPage } + return props.children + } else { + return props.children + } + } + // redirect to the login page window.location.href = props.loginUrl return <> diff --git a/src-ts/lib/route-provider/route.provider.tsx b/src-ts/lib/route-provider/route.provider.tsx index 0d01ea973..afffcd83c 100644 --- a/src-ts/lib/route-provider/route.provider.tsx +++ b/src-ts/lib/route-provider/route.provider.tsx @@ -124,7 +124,7 @@ export const RouteProvider: FC = (props: RouteProviderProps) const routeElement: JSX.Element = !route.authRequired ? route.element : ( - + {route.element} ) diff --git a/src-ts/tools/gamification-admin/GamificationAdmin.tsx b/src-ts/tools/gamification-admin/GamificationAdmin.tsx index e5d408058..260408c3a 100644 --- a/src-ts/tools/gamification-admin/GamificationAdmin.tsx +++ b/src-ts/tools/gamification-admin/GamificationAdmin.tsx @@ -8,6 +8,7 @@ import { export const toolTitle: string = 'Gamification Admin' export const baseUrl: string = '/gamification-admin' +export const rolesRequired: Array = ['Gamification Admin'] const GamificationAdmin: FC<{}> = () => { diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 39fa50119..425534b64 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -1,6 +1,6 @@ import { PlatformRoute } from '../../lib' -import GamificationAdmin, { baseUrl, toolTitle } from './GamificationAdmin' +import GamificationAdmin, { baseUrl, rolesRequired, toolTitle } from './GamificationAdmin' import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' @@ -24,6 +24,7 @@ export const gamificationAdminRoutes: Array = [ ], element: , hidden: true, + rolesRequired, route: baseUrl, title: toolTitle, }, From de13b3a50fba654c13419bc39adcd704c245bd31 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 31 Aug 2022 11:23:58 +0300 Subject: [PATCH 2/3] PR review fixes --- .../profile-factory/user-role.enum.ts | 1 + src-ts/lib/profile-provider/user-profile.model.ts | 4 ++-- src-ts/lib/restricted-page/RestrictedPage.tsx | 2 +- .../require-auth.provider.tsx | 14 +++++++++----- .../tools/gamification-admin/GamificationAdmin.tsx | 2 -- .../gamification-admin.routes.tsx | 5 ++++- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src-ts/lib/profile-provider/profile-functions/profile-factory/user-role.enum.ts b/src-ts/lib/profile-provider/profile-functions/profile-factory/user-role.enum.ts index a3c7abb05..f56a29f43 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile-factory/user-role.enum.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile-factory/user-role.enum.ts @@ -1,4 +1,5 @@ export enum UserRole { + gamificationAdmin = 'Gamification Admin', customer = 'Self-Service Customer', member = 'Topcoder User', } diff --git a/src-ts/lib/profile-provider/user-profile.model.ts b/src-ts/lib/profile-provider/user-profile.model.ts index 52dc7fe5f..51ae0c2e1 100644 --- a/src-ts/lib/profile-provider/user-profile.model.ts +++ b/src-ts/lib/profile-provider/user-profile.model.ts @@ -10,8 +10,8 @@ export interface UserProfile { isMember?: boolean lastName: string photoURL?: string - roles?: Array, + roles: Array status: string updatedAt: number - userId: number, + userId: number } diff --git a/src-ts/lib/restricted-page/RestrictedPage.tsx b/src-ts/lib/restricted-page/RestrictedPage.tsx index 8ae4e3fd4..91d6a8db9 100644 --- a/src-ts/lib/restricted-page/RestrictedPage.tsx +++ b/src-ts/lib/restricted-page/RestrictedPage.tsx @@ -12,6 +12,6 @@ export const RestrictedPage: ReactElement = title='Thanks for visiting' >
-

Unfortenatly, you are not permitted to access the site. If you feel you should be able to, please contact us at support@topcoder.com.

+

Unfortunately, you are not permitted to access the site. If you feel you should be able to, please contact us.

diff --git a/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx b/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx index 0adb100ae..4b6fa5e19 100644 --- a/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx +++ b/src-ts/lib/route-provider/require-auth-provider/require-auth.provider.tsx @@ -5,8 +5,8 @@ import { RestrictedPage } from '../../restricted-page' interface RequireAuthProviderProps { children: JSX.Element - loginUrl: string, - rolesRequired?: Array, + loginUrl: string + rolesRequired?: Array } function RequireAuthProvider(props: RequireAuthProviderProps): JSX.Element { @@ -23,9 +23,13 @@ function RequireAuthProvider(props: RequireAuthProviderProps): JSX.Element { // check the user's roles, allow access or show restricted page if (!!profile) { if (props.rolesRequired) { - if (!profile.roles) { return RestrictedPage } - const intersection: Array = profile.roles?.filter(r => props.rolesRequired?.includes(r)) - if (intersection.length !== props.rolesRequired.length) { return RestrictedPage } + if (!profile.roles) { + return RestrictedPage + } + // if the profile doesn't include all the required roles, show the restricted page + if (props.rolesRequired.some(role => !profile.roles.includes(role))) { + return RestrictedPage + } return props.children } else { return props.children diff --git a/src-ts/tools/gamification-admin/GamificationAdmin.tsx b/src-ts/tools/gamification-admin/GamificationAdmin.tsx index 260408c3a..860b2c3ae 100644 --- a/src-ts/tools/gamification-admin/GamificationAdmin.tsx +++ b/src-ts/tools/gamification-admin/GamificationAdmin.tsx @@ -7,8 +7,6 @@ import { } from '../../lib' export const toolTitle: string = 'Gamification Admin' -export const baseUrl: string = '/gamification-admin' -export const rolesRequired: Array = ['Gamification Admin'] const GamificationAdmin: FC<{}> = () => { diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 425534b64..036f3228f 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -1,10 +1,13 @@ import { PlatformRoute } from '../../lib' -import GamificationAdmin, { baseUrl, rolesRequired, toolTitle } from './GamificationAdmin' +import GamificationAdmin, { toolTitle } from './GamificationAdmin' import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' +export const baseUrl: string = '/gamification-admin' +export const rolesRequired: Array = ['Gamification Admin'] + export const gamificationAdminRoutes: Array = [ { authRequired: true, From 8419b68a808c9fa65bb22767811c67c7ad94902f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 31 Aug 2022 11:39:38 +0300 Subject: [PATCH 3/3] fix build & lint --- src-ts/tools/gamification-admin/gamification-admin.routes.tsx | 3 ++- .../gamification-admin/pages/badge-detail/BadgeDetailPage.tsx | 3 ++- .../gamification-admin/pages/create-badge/CreateBadgePage.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 036f3228f..b5ed435d1 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -1,4 +1,5 @@ import { PlatformRoute } from '../../lib' +import { UserRole } from '../../lib/profile-provider/profile-functions/profile-factory/user-role.enum' import GamificationAdmin, { toolTitle } from './GamificationAdmin' import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' @@ -6,7 +7,7 @@ import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' export const baseUrl: string = '/gamification-admin' -export const rolesRequired: Array = ['Gamification Admin'] +export const rolesRequired: Array = [UserRole.gamificationAdmin] export const gamificationAdminRoutes: Array = [ { diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index 11ad903d3..543b3be27 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -2,7 +2,8 @@ import { FC, useMemo } from 'react' import { Breadcrumb, ContentLayout } from '../../../../lib' import { BreadcrumbItemModel } from '../../../../lib/breadcrumb/breadcrumb-item/breadcrumb-item.model' -import { baseUrl, toolTitle } from '../../GamificationAdmin' +import { baseUrl } from '../../gamification-admin.routes' +import { toolTitle } from '../../GamificationAdmin' import styles from './BadgeDetailPage.module.scss' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 9dc33c6de..c6bc16dd9 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -2,7 +2,8 @@ import { FC, useMemo } from 'react' import { Breadcrumb, ContentLayout } from '../../../../lib' import { BreadcrumbItemModel } from '../../../../lib/breadcrumb/breadcrumb-item/breadcrumb-item.model' -import { baseUrl, toolTitle } from '../../GamificationAdmin' +import { baseUrl } from '../../gamification-admin.routes' +import { toolTitle } from '../../GamificationAdmin' import styles from './CreateBadgePage.module.scss'