Skip to content

Commit

Permalink
Showing 7 changed files with 279 additions and 289 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci-build-deploy.yaml
Original file line number Diff line number Diff line change
@@ -209,7 +209,9 @@ jobs:
EXTERNAL_URL:
value: 'https://api-services-portal-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}.apps.silver.devops.gov.bc.ca'
NEXT_PUBLIC_API_ROOT:
value: 'https://api-services-portal-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}.apps.silver.devops.gov.bc.ca'
value: 'https://api-services-portal-${{ steps.set-deploy-id.outputs.DEPLOY_ID }}.apps.silver.devops.gov.bc.ca'
NEXT_PUBLIC_GRAFANA_URL:
value: 'https://grafana-apps-gov-bc-ca.dev.api.gov.bc.ca'
GWA_API_URL:
value: 'https://gwa-api-gov-bc-ca.dev.api.gov.bc.ca'
GWA_RES_SVR_CLIENT_ID:
6 changes: 2 additions & 4 deletions src/auth/auth-oauth2-proxy.js
Original file line number Diff line number Diff line change
@@ -266,7 +266,6 @@ class Oauth2ProxyAuthStrategy {
const name = oauthUser['name'];
const email = oauthUser['email'];
const username = oauthUser['preferred_username'];
const namespace = oauthUser['namespace'];
const groups = JSON.stringify(oauthUser['groups']);
const _roles = [];
const clientId = process.env.GWA_RES_SVR_CLIENT_ID;
@@ -342,8 +341,8 @@ class Oauth2ProxyAuthStrategy {
logger.debug('Temporary Credential NOT FOUND - CREATING AUTOMATICALLY');
const { errors } = await this.keystone.executeGraphQL({
context: this.keystone.createContext({ skipAccessControl: true }),
query: `mutation ($jti: String, $sub: String, $name: String, $email: String, $username: String, $namespace: String, $groups: String, $roles: String, $scopes: String, $userId: String) {
createTemporaryIdentity(data: {jti: $jti, sub: $sub, name: $name, username: $username, email: $email, isAdmin: false, namespace: $namespace, groups: $groups, roles: $roles, scopes: $scopes, userId: $userId }) {
query: `mutation ($jti: String, $sub: String, $name: String, $email: String, $username: String, $groups: String, $roles: String, $scopes: String, $userId: String) {
createTemporaryIdentity(data: {jti: $jti, sub: $sub, name: $name, username: $username, email: $email, isAdmin: false, groups: $groups, roles: $roles, scopes: $scopes, userId: $userId }) {
id
} }`,
variables: {
@@ -352,7 +351,6 @@ class Oauth2ProxyAuthStrategy {
name,
email,
username,
namespace,
groups,
roles,
scopes: '[]',
226 changes: 109 additions & 117 deletions src/nextapp/pages/manager/services/[id].tsx
Original file line number Diff line number Diff line change
@@ -2,18 +2,14 @@ import * as React from 'react';
import {
Badge,
Box,
Button,
Container,
Divider,
Heading,
Icon,
Skeleton,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
Wrap,
WrapItem,
@@ -24,16 +20,15 @@ import PageHeader from '@/components/page-header';
import api, { useApi } from '@/shared/services/api';
import { dateRange } from '@/components/services-list/utils';
import { GET_GATEWAY_SERVICE } from '@/shared/queries/gateway-service-queries';
import { FaExternalLinkSquareAlt } from 'react-icons/fa';
import EnvironmentBadge from '@/components/environment-badge';
import MetricGraph from '@/components/services-list/metric-graph';
import ServiceRoutes from '@/components/service-routes';
import { dehydrate } from 'react-query/hydration';
import { QueryClient } from 'react-query';
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { Query } from '@/shared/types/query.types';

import breadcrumbs from '@/components/ns-breadcrumb';
import { useNamespaceBreadcrumbs } from '@/shared/hooks';
import Head from 'next/head';

export const getServerSideProps: GetServerSideProps = async (context) => {
const queryClient = new QueryClient();
@@ -61,6 +56,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
const ServicePage: React.FC<
InferGetServerSidePropsType<typeof getServerSideProps>
> = ({ id }) => {
const breadcrumb = useNamespaceBreadcrumbs([
{ href: '/manager/services', text: 'Services' },
]);
const range = dateRange();
const { data } = useApi(
['gateway-service', id],
@@ -72,82 +70,43 @@ const ServicePage: React.FC<
},
{ enabled: Boolean(id), suspense: false }
);
const breadcrumb = breadcrumbs([
{ href: '/manager/services', text: 'Services' },
]);
const tags: string[] = !isEmpty(data?.GatewayService?.tags)
? (JSON.parse(data.GatewayService.tags) as string[])
: [];

return (
<Container maxW="6xl">
<PageHeader
actions={
<Button
as="a"
variant="primary"
href="https://grafana.apps.gov.bc.ca/"
rightIcon={<Icon as={FaExternalLinkSquareAlt} mt={-1} />}
>
View Full Metrics
</Button>
}
breadcrumb={breadcrumb}
title={
<Box as="span">
{data?.GatewayService.name}
<EnvironmentBadge
data={data?.GatewayService.environment}
ml={2}
fontSize="1rem"
/>
</Box>
}
/>
<Box bgColor="white" mb={4}>
<Box
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading size="md">Metrics</Heading>
</Box>
<Divider />
<Box minHeight="100px" p={4}>
{id && data && (
<ClientRequest fallback={<Skeleton width="100%" height="100%" />}>
<MetricGraph
days={range}
height={100}
id={data?.GatewayService.name}
service={data?.GatewayService}
<>
<Head>
<title>{`API Program Services | Services | ${data?.GatewayService.name}`}</title>
</Head>
<Container maxW="6xl">
<PageHeader
breadcrumb={breadcrumb}
title={
<Box as="span">
{data?.GatewayService.name}
<EnvironmentBadge
data={data?.GatewayService.environment}
ml={2}
fontSize="1rem"
/>
</ClientRequest>
)}
</Box>
</Box>
<Box display="grid" gridGap={4} gridTemplateColumns="repeat(12, 1fr)">
<Box
bgColor="white"
gridColumn="span 4"
display="flex"
flexDir="column"
>
</Box>
}
/>
<Box bgColor="white" mb={4}>
<Box
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading size="md">Stats</Heading>
<Heading size="md">Metrics</Heading>
</Box>
<Divider />
<Box p={4} display="flex" alignItems="center" flex={1}>
<Box minHeight="100px" p={4}>
{id && data && (
<ClientRequest fallback={<Skeleton width="100%" height="100%" />}>
<MetricGraph
alt
days={range}
height={100}
id={data?.GatewayService.name}
@@ -157,67 +116,100 @@ const ServicePage: React.FC<
)}
</Box>
</Box>
<Box bgColor="white" gridColumn="span 5">
<Box display="grid" gridGap={4} gridTemplateColumns="repeat(12, 1fr)">
<Box
p={4}
bgColor="white"
gridColumn="span 4"
display="flex"
alignItems="center"
justifyContent="space-between"
flexDir="column"
>
<Heading size="md">Routes</Heading>
<Box
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading size="md">Stats</Heading>
</Box>
<Divider />
<Box p={4} display="flex" alignItems="center" flex={1}>
{id && data && (
<ClientRequest
fallback={<Skeleton width="100%" height="100%" />}
>
<MetricGraph
alt
days={range}
height={100}
id={data?.GatewayService.name}
service={data?.GatewayService}
/>
</ClientRequest>
)}
</Box>
</Box>
<Divider />
<Table variant="simple">
<Tbody>
<Tr>
<Td>
<ServiceRoutes routes={data?.GatewayService.routes} />
</Td>
</Tr>
</Tbody>
</Table>
</Box>
<Box bgColor="white" gridColumn="span 3">
<Box
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading size="md">Details</Heading>
<Box bgColor="white" gridColumn="span 5">
<Box
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading size="md">Routes</Heading>
</Box>
<Divider />
<Table variant="simple">
<Tbody>
<Tr>
<Td>
<ServiceRoutes routes={data?.GatewayService.routes} />
</Td>
</Tr>
</Tbody>
</Table>
</Box>
<Divider />
<Box p={4}>
<Box bgColor="white" gridColumn="span 3">
<Box
as="dl"
display="grid"
fontSize="sm"
flexWrap="wrap"
gridColumnGap={4}
gridRowGap={2}
gridTemplateColumns="1fr 2fr"
p={4}
display="flex"
alignItems="center"
justifyContent="space-between"
>
<Text as="dt" fontWeight="bold">
Host
</Text>
<Text as="dd">{data?.GatewayService.host}</Text>
<Text as="dt" fontWeight="bold">
Tags
</Text>
<Text as="dd">
<Wrap>
{tags.map((t) => (
<WrapItem key={t}>
<Badge>{t}</Badge>
</WrapItem>
))}
</Wrap>
</Text>
<Heading size="md">Details</Heading>
</Box>
<Divider />
<Box p={4}>
<Box
as="dl"
display="grid"
fontSize="sm"
flexWrap="wrap"
gridColumnGap={4}
gridRowGap={2}
gridTemplateColumns="1fr 2fr"
>
<Text as="dt" fontWeight="bold">
Host
</Text>
<Text as="dd">{data?.GatewayService.host}</Text>
<Text as="dt" fontWeight="bold">
Tags
</Text>
<Text as="dd">
<Wrap>
{tags.map((t) => (
<WrapItem key={t}>
<Badge>{t}</Badge>
</WrapItem>
))}
</Wrap>
</Text>
</Box>
</Box>
</Box>
</Box>
</Box>
</Container>
</Container>
</>
);
};

143 changes: 79 additions & 64 deletions src/nextapp/pages/manager/services/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {
Box,
Button,
Container,
Divider,
Heading,
@@ -11,33 +12,29 @@ import {
Skeleton,
} from '@chakra-ui/react';
import ClientRequest from '@/components/client-request';
import { FaExternalLinkSquareAlt } from 'react-icons/fa';
import PageHeader from '@/components/page-header';
import ServicesList from '@/components/services-list';
import { useAuth /*, withAuth*/ } from '@/shared/services/auth';
import SearchInput from '@/components/search-input';
import { FaCaretSquareUp, FaFilter } from 'react-icons/fa';
import ServicesFilters from '@/components/services-list/services-filters';
import { useNamespaceBreadcrumbs } from '@/shared/hooks';
import Head from 'next/head';
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';

import breadcrumbs from '@/components/ns-breadcrumb'

// export const getServerSideProps = withAuth(async (context) => {
// const { user } = context;

// if (!user) {
// return {
// redirect: {
// destination: '/unauthorized',
// permanent: false,
// },
// };
// }

// return {
// props: {},
// };
// });
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
metricsUrl: process.env.NEXT_PUBLIC_GRAFANA_URL,
},
};
};

const ServicesPage: React.FC = () => {
const ServicesPage: React.FC<
InferGetServerSidePropsType<typeof getServerSideProps>
> = ({ metricsUrl }) => {
const breadcrumb = useNamespaceBreadcrumbs();
const { user } = useAuth();
const [search, setSearch] = React.useState<string>('');
const [showFilters, setShowFilters] = React.useState<boolean>(false);
@@ -46,53 +43,71 @@ const ServicesPage: React.FC = () => {
};

return (
<Container maxW="6xl">
<PageHeader title="Services" breadcrumb={breadcrumbs()}>
<p>
List of services from the API Owner perspective. This should pull in
details from Prometheus and gwa-api Status.
</p>
</PageHeader>
<Divider my={4} />
<Box d="flex" flexDir="column">
<Box
as="header"
my={4}
d="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading as="h3" size="md">
7 Day Metrics
</Heading>
<HStack>
<SearchInput
onChange={setSearch}
placeholder="Search Gateway Services"
value={search}
/>
<IconButton
aria-label="Show Filters"
icon={<Icon as={showFilters ? FaCaretSquareUp : FaFilter} />}
<>
<Head>
<title>API Program Services | Services</title>
</Head>
<Container maxW="6xl">
<PageHeader
actions={
<Button
as="a"
variant="primary"
onClick={onFilterClick}
/>
</HStack>
</Box>
{showFilters && <ServicesFilters />}
{user && (
<SimpleGrid columns={{ base: 1, sm: 2, md: 3 }} spacing={4} mb={8}>
<ClientRequest
fallback={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((d) => (
<Skeleton key={d} height="200px" />
))}
href={metricsUrl}
rightIcon={<Icon as={FaExternalLinkSquareAlt} mt={-1} />}
>
<ServicesList search={search} />
</ClientRequest>
</SimpleGrid>
)}
</Box>
</Container>
View Full Metrics
</Button>
}
title="Services"
breadcrumb={breadcrumb}
>
<p>
List of services from the API Owner perspective. This should pull in
details from Prometheus and gwa-api Status.
</p>
</PageHeader>
<Divider my={4} />
<Box d="flex" flexDir="column">
<Box
as="header"
my={4}
d="flex"
alignItems="center"
justifyContent="space-between"
>
<Heading as="h3" size="md">
7 Day Metrics
</Heading>
<HStack>
<SearchInput
onChange={setSearch}
placeholder="Search Gateway Services"
value={search}
/>
<IconButton
aria-label="Show Filters"
icon={<Icon as={showFilters ? FaCaretSquareUp : FaFilter} />}
variant="primary"
onClick={onFilterClick}
/>
</HStack>
</Box>
{showFilters && <ServicesFilters />}
{user && (
<SimpleGrid columns={{ base: 1, sm: 2, md: 3 }} spacing={4} mb={8}>
<ClientRequest
fallback={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((d) => (
<Skeleton key={d} height="200px" />
))}
>
<ServicesList search={search} />
</ClientRequest>
</SimpleGrid>
)}
</Box>
</Container>
</>
);
};

1 change: 1 addition & 0 deletions src/nextapp/shared/config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const apiHost = process.env.NEXT_PUBLIC_API_ROOT || '';
export const grafanaUrl = process.env.NEXT_PUBLIC_GRAFANA_URL || '';
95 changes: 43 additions & 52 deletions src/services/keycloak/templates/client-template-client-jwt.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,45 @@

export const clientTemplateClientJwt = {
"clientId": "",
"name": "",
"description": "",
"surrogateAuthRequired": false,
"enabled": false,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-jwt",
"redirectUris": ["https://*"],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.multivalued.roles": "false",
"saml.force.post.binding": "false",
"saml.encrypt": "false",
"saml.server.signature": "false",
"saml.server.signature.keyinfo.ext": "false",
"exclude.session.state.from.auth.response": "false",
"client_credentials.use_refresh_token": "false",
"saml_force_name_id_format": "false",
"saml.client.signature": "false",
"tls.client.certificate.bound.access.tokens": "false",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"saml.onetimeuse.condition": "false",
"jwt.credential.certificate": ""
clientId: '',
name: '',
description: '',
surrogateAuthRequired: false,
enabled: false,
alwaysDisplayInConsole: false,
clientAuthenticatorType: 'client-jwt',
redirectUris: ['https://*'],
webOrigins: ['*'],
notBefore: 0,
bearerOnly: false,
consentRequired: false,
standardFlowEnabled: false,
implicitFlowEnabled: false,
directAccessGrantsEnabled: false,
serviceAccountsEnabled: true,
publicClient: false,
frontchannelLogout: false,
protocol: 'openid-connect',
attributes: {
'saml.assertion.signature': 'false',
'saml.multivalued.roles': 'false',
'saml.force.post.binding': 'false',
'saml.encrypt': 'false',
'saml.server.signature': 'false',
'saml.server.signature.keyinfo.ext': 'false',
'exclude.session.state.from.auth.response': 'false',
'client_credentials.use_refresh_token': 'false',
saml_force_name_id_format: 'false',
'saml.client.signature': 'false',
'tls.client.certificate.bound.access.tokens': 'false',
'saml.authnstatement': 'false',
'display.on.consent.screen': 'false',
'saml.onetimeuse.condition': 'false',
'jwt.credential.certificate': '',
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
] as string[],
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles"
],
"optionalClientScopes": [
"microprofile-jwt"
],
"access": { "view": true, "configure": true, "manage": true }
}
authenticationFlowBindingOverrides: {},
fullScopeAllowed: false,
nodeReRegistrationTimeout: -1,
protocolMappers: [] as string[],
defaultClientScopes: [] as string[],
optionalClientScopes: [] as string[],
access: { view: true, configure: true, manage: true },
};
93 changes: 42 additions & 51 deletions src/services/keycloak/templates/client-template-client-secret.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,44 @@

export const clientTemplateClientSecret = {
"clientId": "",
"name": "",
"description": "",
"surrogateAuthRequired": false,
"enabled": false,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": ["https://*"],
"webOrigins": ["*"],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {
"saml.assertion.signature": "false",
"saml.multivalued.roles": "false",
"saml.force.post.binding": "false",
"saml.encrypt": "false",
"saml.server.signature": "false",
"saml.server.signature.keyinfo.ext": "false",
"exclude.session.state.from.auth.response": "false",
"client_credentials.use_refresh_token": "false",
"saml_force_name_id_format": "false",
"saml.client.signature": "false",
"tls.client.certificate.bound.access.tokens": "false",
"saml.authnstatement": "false",
"display.on.consent.screen": "false",
"saml.onetimeuse.condition": "false"
clientId: '',
name: '',
description: '',
surrogateAuthRequired: false,
enabled: false,
alwaysDisplayInConsole: false,
clientAuthenticatorType: 'client-secret',
redirectUris: ['https://*'],
webOrigins: ['*'],
notBefore: 0,
bearerOnly: false,
consentRequired: false,
standardFlowEnabled: false,
implicitFlowEnabled: false,
directAccessGrantsEnabled: false,
serviceAccountsEnabled: true,
publicClient: false,
frontchannelLogout: false,
protocol: 'openid-connect',
attributes: {
'saml.assertion.signature': 'false',
'saml.multivalued.roles': 'false',
'saml.force.post.binding': 'false',
'saml.encrypt': 'false',
'saml.server.signature': 'false',
'saml.server.signature.keyinfo.ext': 'false',
'exclude.session.state.from.auth.response': 'false',
'client_credentials.use_refresh_token': 'false',
saml_force_name_id_format: 'false',
'saml.client.signature': 'false',
'tls.client.certificate.bound.access.tokens': 'false',
'saml.authnstatement': 'false',
'display.on.consent.screen': 'false',
'saml.onetimeuse.condition': 'false',
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
] as string[],
"defaultClientScopes": [
"web-origins",
"role_list",
"profile",
"roles"
],
"optionalClientScopes": [
"microprofile-jwt"
],
"access": { "view": true, "configure": true, "manage": true }
}
authenticationFlowBindingOverrides: {},
fullScopeAllowed: false,
nodeReRegistrationTimeout: -1,
protocolMappers: [] as string[],
defaultClientScopes: [] as string[],
optionalClientScopes: [] as string[],
access: { view: true, configure: true, manage: true },
};

0 comments on commit 32229cd

Please sign in to comment.