diff --git a/apps/leafcutter/app/(main)/about/page.tsx b/apps/leafcutter/app/(main)/about/page.tsx
index 2dc1b39..c92ba88 100644
--- a/apps/leafcutter/app/(main)/about/page.tsx
+++ b/apps/leafcutter/app/(main)/about/page.tsx
@@ -1,4 +1,4 @@
-import { About } from './_components/About';
+import { About } from "leafcutter-common";
export default function Page() {
return ;
diff --git a/apps/leafcutter/app/(main)/create/page.tsx b/apps/leafcutter/app/(main)/create/page.tsx
index 98a1342..ece4c15 100644
--- a/apps/leafcutter/app/(main)/create/page.tsx
+++ b/apps/leafcutter/app/(main)/create/page.tsx
@@ -1,8 +1,10 @@
import { getTemplates } from "app/_lib/opensearch";
-import { Create } from "./_components/Create";
+import { Create } from "leafcutter-common";
export default async function Page() {
const templates = await getTemplates(100);
return ;
}
+
+export const dynamic = "force-dynamic";
diff --git a/apps/leafcutter/app/(main)/faq/page.tsx b/apps/leafcutter/app/(main)/faq/page.tsx
index 93c7447..a908dfb 100644
--- a/apps/leafcutter/app/(main)/faq/page.tsx
+++ b/apps/leafcutter/app/(main)/faq/page.tsx
@@ -1,4 +1,4 @@
-import { FAQ } from "./_components/FAQ";
+import { FAQ } from "leafcutter-common";
export default function Page() {
return ;
diff --git a/apps/leafcutter/app/(main)/layout.tsx b/apps/leafcutter/app/(main)/layout.tsx
index d272093..9d6b1bc 100644
--- a/apps/leafcutter/app/(main)/layout.tsx
+++ b/apps/leafcutter/app/(main)/layout.tsx
@@ -7,16 +7,12 @@ import "@fontsource/roboto/700.css";
import "@fontsource/playfair-display/900.css";
// import getConfig from "next/config";
// import { LicenseInfo } from "@mui/x-data-grid-pro";
-import { InternalLayout } from "app/_components/InternalLayout";
-import { headers } from 'next/headers'
+import { InternalLayout } from "../_components/InternalLayout";
type LayoutProps = {
children: ReactNode;
};
export default function Layout({ children }: LayoutProps) {
- const allHeaders = headers();
- const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
-
- return {children};
+ return {children};
}
diff --git a/apps/leafcutter/app/page.tsx b/apps/leafcutter/app/(main)/page.tsx
similarity index 90%
rename from apps/leafcutter/app/page.tsx
rename to apps/leafcutter/app/(main)/page.tsx
index 95940b0..41ec7df 100644
--- a/apps/leafcutter/app/page.tsx
+++ b/apps/leafcutter/app/(main)/page.tsx
@@ -1,7 +1,7 @@
import { getServerSession } from "next-auth";
import { authOptions } from "app/_lib/auth";
import { getUserVisualizations } from "app/_lib/opensearch";
-import { Home } from "app/_components/Home";
+import { Home } from "leafcutter-common";
export default async function Page() {
const session = await getServerSession(authOptions);
diff --git a/apps/leafcutter/app/(main)/preview/[...visualizationID]/page.tsx b/apps/leafcutter/app/(main)/preview/[...visualizationID]/page.tsx
index bac4dad..88991c5 100644
--- a/apps/leafcutter/app/(main)/preview/[...visualizationID]/page.tsx
+++ b/apps/leafcutter/app/(main)/preview/[...visualizationID]/page.tsx
@@ -1,10 +1,10 @@
/* eslint-disable no-underscore-dangle */
// import { Client } from "@opensearch-project/opensearch";
-import { Preview } from "./_components/Preview";
+import { Preview } from "leafcutter-common";
// import { createVisualization } from "lib/opensearch";
export default function Page() {
- return ;
+ return ;
}
/*
diff --git a/apps/leafcutter/app/(main)/setup/_components/Setup.tsx b/apps/leafcutter/app/(main)/setup/_components/Setup.tsx
index 3092359..7b85439 100644
--- a/apps/leafcutter/app/(main)/setup/_components/Setup.tsx
+++ b/apps/leafcutter/app/(main)/setup/_components/Setup.tsx
@@ -5,7 +5,7 @@ import { useLayoutEffect } from "react";
import { useRouter } from "next/navigation";
import { Grid, CircularProgress } from "@mui/material";
import Iframe from "react-iframe";
-import { useAppContext } from "app/_components/AppProvider";
+import { useAppContext } from "../../../_components/AppProvider";
export const Setup: FC = () => {
const {
@@ -20,6 +20,7 @@ export const Setup: FC = () => {
;
}
-
diff --git a/apps/leafcutter/app/(main)/trends/page.tsx b/apps/leafcutter/app/(main)/trends/page.tsx
index 9a33786..9230267 100644
--- a/apps/leafcutter/app/(main)/trends/page.tsx
+++ b/apps/leafcutter/app/(main)/trends/page.tsx
@@ -1,8 +1,10 @@
import { getTrends } from "app/_lib/opensearch";
-import { Trends } from "./_components/Trends";
+import { Trends } from "../../../../../packages/leafcutter-common/components/Trends";
export default async function Page() {
const visualizations = await getTrends(25);
return ;
}
+
+export const dynamic = "force-dynamic";
diff --git a/apps/leafcutter/app/(main)/visualizations/[...visualizationID]/page.tsx b/apps/leafcutter/app/(main)/visualizations/[...visualizationID]/page.tsx
index aa8d839..0787645 100644
--- a/apps/leafcutter/app/(main)/visualizations/[...visualizationID]/page.tsx
+++ b/apps/leafcutter/app/(main)/visualizations/[...visualizationID]/page.tsx
@@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */
import { Client } from "@opensearch-project/opensearch";
-import { VisualizationDetail } from "app/_components/VisualizationDetail";
+import { VisualizationDetail } from "leafcutter-common";
const getVisualization = async (visualizationID: string) => {
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
@@ -18,7 +18,7 @@ const getVisualization = async (visualizationID: string) => {
const response = rawResponse.body;
const hits = response.hits.hits.filter(
- (hit: any) => hit._id.split(":")[1] === visualizationID[0]
+ (hit: any) => hit._id.split(":")[1] === visualizationID[0],
);
const hit = hits[0];
const visualization = {
diff --git a/apps/leafcutter/app/_components/AppProvider.tsx b/apps/leafcutter/app/_components/AppProvider.tsx
index e20d036..5fb065c 100644
--- a/apps/leafcutter/app/_components/AppProvider.tsx
+++ b/apps/leafcutter/app/_components/AppProvider.tsx
@@ -8,7 +8,7 @@ import {
useState,
PropsWithChildren,
} from "react";
-import { colors, typography } from "app/_styles/theme";
+import { colors, typography } from "leafcutter-common/styles/theme";
const basePath = process.env.GITLAB_CI
? "/link/link-stack/apps/leafcutter"
diff --git a/apps/leafcutter/app/_components/InternalLayout.tsx b/apps/leafcutter/app/_components/InternalLayout.tsx
index 33166f2..efaa5d2 100644
--- a/apps/leafcutter/app/_components/InternalLayout.tsx
+++ b/apps/leafcutter/app/_components/InternalLayout.tsx
@@ -7,7 +7,7 @@ import CookieConsent from "react-cookie-consent";
import { useCookies } from "react-cookie";
import { TopNav } from "./TopNav";
import { Sidebar } from "./Sidebar";
-import { GettingStartedDialog } from "./GettingStartedDialog";
+import { GettingStartedDialog } from "leafcutter-common";
import { useAppContext } from "./AppProvider";
// import { Footer } from "./Footer";
diff --git a/apps/leafcutter/app/_components/MultiProvider.tsx b/apps/leafcutter/app/_components/MultiProvider.tsx
index c6e6f04..114143a 100644
--- a/apps/leafcutter/app/_components/MultiProvider.tsx
+++ b/apps/leafcutter/app/_components/MultiProvider.tsx
@@ -8,10 +8,10 @@ import { CookiesProvider } from "react-cookie";
import { I18n } from "react-polyglot";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
-import { AppProvider } from "app/_components/AppProvider";
+import { AppProvider } from "./AppProvider";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
-import en from "app/_locales/en.json";
-import fr from "app/_locales/fr.json";
+import en from "leafcutter-common/locales/en.json";
+import fr from "leafcutter-common/locales/fr.json";
import "@fontsource/poppins/400.css";
import "@fontsource/poppins/700.css";
import "@fontsource/roboto/400.css";
@@ -21,7 +21,7 @@ import "app/_styles/global.css";
import { LicenseInfo } from "@mui/x-date-pickers-pro";
LicenseInfo.setLicenseKey(
- "7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI="
+ "7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
);
const messages: any = { en, fr };
diff --git a/apps/leafcutter/app/_components/Sidebar.tsx b/apps/leafcutter/app/_components/Sidebar.tsx
index 91732b6..397473f 100644
--- a/apps/leafcutter/app/_components/Sidebar.tsx
+++ b/apps/leafcutter/app/_components/Sidebar.tsx
@@ -20,8 +20,8 @@ import {
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useTranslate } from "react-polyglot";
-import { useAppContext } from "app/_components/AppProvider";
-import { Tooltip } from "app/_components/Tooltip";
+import { useAppContext } from "./AppProvider";
+import { Tooltip } from "leafcutter-common";
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
const MenuItem = ({
diff --git a/apps/leafcutter/app/_components/TopNav.tsx b/apps/leafcutter/app/_components/TopNav.tsx
index 720d2b1..71543a7 100644
--- a/apps/leafcutter/app/_components/TopNav.tsx
+++ b/apps/leafcutter/app/_components/TopNav.tsx
@@ -6,9 +6,9 @@ import Image from "next/legacy/image";
import { AppBar, Grid, Box } from "@mui/material";
import { useTranslate } from "react-polyglot";
import LeafcutterLogo from "images/leafcutter-logo.png";
-import { AccountButton } from "app/_components/AccountButton";
-import { HelpButton } from "app/_components/HelpButton";
-import { Tooltip } from "app/_components/Tooltip";
+import { AccountButton } from "./AccountButton";
+import { HelpButton } from "./HelpButton";
+import { Tooltip } from "leafcutter-common";
import { useAppContext } from "./AppProvider";
// import { LanguageSelect } from "./LanguageSelect";
@@ -43,50 +43,51 @@ export const TopNav: FC = () => {
wrap="nowrap"
spacing={4}
>
-
-
-
-
+
+
+
+
+
+
+
+ Leafcutter
+
-
-
-
- Leafcutter
-
-
-
-
- A Project of Center for Digital Resilience
-
-
+
+
+ A Project of Center for Digital Resilience
+
-
+
+
diff --git a/apps/leafcutter/app/_lib/auth.ts b/apps/leafcutter/app/_lib/auth.ts
index a8c9426..24a647a 100644
--- a/apps/leafcutter/app/_lib/auth.ts
+++ b/apps/leafcutter/app/_lib/auth.ts
@@ -1,8 +1,15 @@
import type { NextAuthOptions } from "next-auth";
import Google from "next-auth/providers/google";
import Apple from "next-auth/providers/apple";
+import Credentials from "next-auth/providers/credentials";
+import { checkAuth } from "./opensearch";
export const authOptions: NextAuthOptions = {
+ pages: {
+ signIn: "/login",
+ error: "/login",
+ signOut: "/logout",
+ },
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
@@ -12,6 +19,62 @@ export const authOptions: NextAuthOptions = {
clientId: process.env.APPLE_CLIENT_ID ?? "",
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
}),
+ Credentials({
+ name: "Link",
+ credentials: {
+ authToken: { label: "AuthToken", type: "text", },
+ },
+ async authorize(credentials, req) {
+ const { headers } = req;
+ console.log({ headers });
+ const leafcutterUser = headers?.["x-leafcutter-user"];
+ const authToken = credentials?.authToken;
+
+ if (!leafcutterUser || leafcutterUser.trim() === "") {
+ console.log("no leafcutter user");
+ return null;
+ }
+
+ console.log({ authToken });
+ return null;
+ /*
+ try {
+ // add role check
+ await checkAuth(username, password);
+ const user = {
+ id: leafcutterUser,
+ email: leafcutterUser
+ };
+
+ return user;
+ } catch (e) {
+ console.log({ e });
+ }
+
+ return null;
+ */
+ }
+ })
+
],
secret: process.env.NEXTAUTH_SECRET,
+ /*
+ callbacks: {
+ signIn: async ({ user, account, profile }) => {
+ const roles: any = [];
+ return roles.includes("admin") || roles.includes("agent");
+ },
+ session: async ({ session, user, token }) => {
+ // @ts-ignore
+ session.user.roles = token.roles;
+ return session;
+ },
+ jwt: async ({ token, user, account, profile, trigger }) => {
+ if (user) {
+ token.roles = [];
+ }
+ return token;
+ }
+ },*/
};
+
diff --git a/apps/leafcutter/app/_lib/opensearch.ts b/apps/leafcutter/app/_lib/opensearch.ts
index 46730f6..230e656 100644
--- a/apps/leafcutter/app/_lib/opensearch.ts
+++ b/apps/leafcutter/app/_lib/opensearch.ts
@@ -23,6 +23,24 @@ const createClient = () => new Client({
},
});
+const createUserClient = (username: string, password: string) => new Client({
+ node: baseURL,
+ auth: {
+ username,
+ password,
+ },
+ ssl: {
+ rejectUnauthorized: false,
+ },
+});
+
+export const checkAuth = async (username: string, password: string) => {
+ const client = createUserClient(username, password);
+ const res = await client.ping();
+
+ return res.statusCode === 200;
+};
+
const getDocumentID = (doc: any) => doc._id.split(":")[1];
const getEmbedURL = (tenant: string, visualizationID: string) =>
diff --git a/apps/leafcutter/app/_styles/global.css b/apps/leafcutter/app/_styles/global.css
index 4109f02..1546661 100644
--- a/apps/leafcutter/app/_styles/global.css
+++ b/apps/leafcutter/app/_styles/global.css
@@ -3,3 +3,7 @@ body {
overscroll-behavior-y: none;
text-size-adjust: none;
}
+
+a {
+ text-decoration: none;
+}
diff --git a/apps/leafcutter/app/api/link/auth/route.ts b/apps/leafcutter/app/api/link/auth/route.ts
new file mode 100644
index 0000000..37b356e
--- /dev/null
+++ b/apps/leafcutter/app/api/link/auth/route.ts
@@ -0,0 +1,15 @@
+import { NextRequest, NextResponse } from "next/server";
+
+export const GET = async (req: NextRequest) => {
+ const validDomains = "localhost";
+ console.log({ req });
+
+ return NextResponse.json({ response: "ok" });
+};
+
+export const POST = async (req: NextRequest) => {
+ const validDomains = "localhost";
+ console.log({ req });
+
+ return NextResponse.json({ response: "ok" });
+};
diff --git a/apps/leafcutter/app/api/proxy/[[...path]].ts b/apps/leafcutter/app/api/proxy/[[...path]].ts
deleted file mode 100644
index 4956739..0000000
--- a/apps/leafcutter/app/api/proxy/[[...path]].ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { createProxyMiddleware } from "http-proxy-middleware";
-import { NextApiRequest, NextApiResponse } from "next";
-import { getToken } from "next-auth/jwt";
-
-const withAuthInfo =
- (handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
- const session: any = await getToken({
- req,
- secret: process.env.NEXTAUTH_SECRET,
- });
-
- if (!session) {
- return res.redirect("/login");
- }
-
- req.headers["x-proxy-user"] = session.email.toLowerCase();
- req.headers["x-proxy-roles"] = "leafcutter_user";
- const auth = `${session.email.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
- const buff = Buffer.from(auth);
- const base64data = buff.toString("base64");
- req.headers.Authorization = `Basic ${base64data}`;
- return handler(req, res);
- };
-
-const proxy = createProxyMiddleware({
- target: process.env.OPENSEARCH_DASHBOARDS_URL,
- changeOrigin: true,
- xfwd: true,
-});
-
-export default withAuthInfo(proxy);
-
-export const config = {
- api: {
- bodyParser: false,
- externalResolver: true,
- },
-};
diff --git a/apps/leafcutter/app/api/trends/recent/_route.ts b/apps/leafcutter/app/api/trends/recent/route.ts
similarity index 74%
rename from apps/leafcutter/app/api/trends/recent/_route.ts
rename to apps/leafcutter/app/api/trends/recent/route.ts
index 95d78a8..a635d29 100644
--- a/apps/leafcutter/app/api/trends/recent/_route.ts
+++ b/apps/leafcutter/app/api/trends/recent/route.ts
@@ -3,7 +3,9 @@ import { getTrends } from "app/_lib/opensearch";
export const GET = async () => {
const results = await getTrends(5);
+ console.log({ results });
NextResponse.json(results);
};
+export const dynamic = 'force-dynamic';
diff --git a/apps/leafcutter/app/api/visualizations/list/route.ts b/apps/leafcutter/app/api/visualizations/list/route.ts
new file mode 100644
index 0000000..a66f8f4
--- /dev/null
+++ b/apps/leafcutter/app/api/visualizations/list/route.ts
@@ -0,0 +1,13 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+import { authOptions } from "app/_lib/auth";
+import { getUserVisualizations } from "app/_lib/opensearch";
+
+export const GET = async () => {
+ const session = await getServerSession(authOptions);
+ const { user: { email } }: any = session;
+ const visualizations = await getUserVisualizations(email, 20);
+
+ return NextResponse.json(visualizations);
+};
+
diff --git a/apps/leafcutter/app/layout.tsx b/apps/leafcutter/app/layout.tsx
index 102c9b7..63ac295 100644
--- a/apps/leafcutter/app/layout.tsx
+++ b/apps/leafcutter/app/layout.tsx
@@ -8,7 +8,7 @@ import "@fontsource/roboto/700.css";
import "@fontsource/playfair-display/900.css";
// import getConfig from "next/config";
// import { LicenseInfo } from "@mui/x-data-grid-pro";
-import { MultiProvider } from "app/_components/MultiProvider";
+import { MultiProvider } from "./_components/MultiProvider";
export const metadata: Metadata = {
title: "Leafcutter",
diff --git a/apps/leafcutter/charts/Chart.yaml b/apps/leafcutter/charts/Chart.yaml
index f3fa165..8ae83e9 100644
--- a/apps/leafcutter/charts/Chart.yaml
+++ b/apps/leafcutter/charts/Chart.yaml
@@ -2,5 +2,5 @@ apiVersion: v2
name: leafcutter
description: A Helm chart for Kubernetes
type: application
-version: 0.1.54
-appVersion: "0.1.54"
+version: 0.2.0
+appVersion: "0.2.0"
diff --git a/apps/leafcutter/middleware.ts b/apps/leafcutter/middleware.ts
index 1840b93..c2e5084 100644
--- a/apps/leafcutter/middleware.ts
+++ b/apps/leafcutter/middleware.ts
@@ -1,57 +1,6 @@
-import { NextResponse } from "next/server";
-import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
-import getConfig from "next/config";
-
-const rewriteURL = (request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => {
- if (request.nextUrl.protocol.startsWith('ws')) {
- return NextResponse.next();
- }
-
- if (request.nextUrl.pathname.includes('/_next/static/development/')) {
- return NextResponse.next();
- }
-
- const destinationURL = request.url.replace(originBaseURL, destinationBaseURL);
- console.log(`Rewriting ${request.url} to ${destinationURL}`);
-
- const requestHeaders = new Headers(request.headers);
- for (const [key, value] of Object.entries(headers)) {
- // @ts-ignore
- requestHeaders.set(key, value);
- }
- requestHeaders.delete('connection');
-
- // console.log({ finalHeaders: requestHeaders });
-
- return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders } });
-};
-
-const checkRewrites = async (request: NextRequestWithAuth) => {
- console.log({ currentURL: request.nextUrl.href });
-
- const leafcutterBaseURL = process.env.LEAFCUTTER_URL ?? "http://localhost:3000";
- const opensearchDashboardsURL = process.env.OPENSEARCH_URL ?? "http://localhost:5602";
-
- if (request.nextUrl.pathname.startsWith('/proxy/opensearch')) {
- console.log('proxying to zammad');
- const { token } = request.nextauth;
- const auth = `${token?.email?.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
- const buff = Buffer.from(auth);
- const base64data = buff.toString("base64");
- const headers = {
- 'X-Proxy-User': token?.email?.toLowerCase(),
- "X-Proxy-Roles": "leafcutter_user",
- "Authorization": `Basic ${base64data}`
- };
-
- console.log({ headers });
-
- return rewriteURL(request, `${leafcutterBaseURL}/proxy/opensearch`, opensearchDashboardsURL, headers);
- }
-};
+import { withAuth } from "next-auth/middleware";
export default withAuth(
- checkRewrites,
{
pages: {
signIn: `/login`,
@@ -60,25 +9,30 @@ export default withAuth(
authorized: ({ token, req }) => {
const {
url,
- headers,
} = req;
-
- // check login page
const parsedURL = new URL(url);
- if (parsedURL.pathname.startsWith('/login')) {
+
+ console.log({ url });
+ console.log({ pathname: parsedURL.pathname });
+ console.log({ allowed: parsedURL.pathname.startsWith("/app") });
+ const allowed = parsedURL.pathname.startsWith('/login') || parsedURL.pathname.startsWith('/api' || parsedURL.pathname.startsWith("/app"));
+ if (allowed) {
return true;
}
- // check session auth
- const authorizedDomains = ["redaranj.com", "digiresilience.org"];
- const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net";
-
- if (authorizedDomains.includes(userDomain)) {
+ if (token?.email) {
return true;
}
return false;
+
},
}
}
);
+
+export const config = {
+ matcher: [
+ '/((?!api|app|bootstrap|3961|ui|translations|internal|login|node_modules|_next/static|_next/image|favicon.ico).*)',
+ ],
+};
diff --git a/apps/leafcutter/next.config.js b/apps/leafcutter/next.config.js
index 128e197..9bd4d1d 100644
--- a/apps/leafcutter/next.config.js
+++ b/apps/leafcutter/next.config.js
@@ -7,9 +7,16 @@ const ContentSecurityPolicy = `
`;
module.exports = {
- publicRuntimeConfig: {
- embedded: true
- },/*
+ transpilePackages: ["leafcutter-common"],
+ rewrites: async () => ({
+ fallback: [
+ {
+ source: "/:path*",
+ destination: "/api/proxy/:path*",
+ },
+ ],
+ }),
+ /*
basePath: "/proxy/leafcutter",
assetPrefix: "/proxy/leafcutter",
i18n: {
@@ -17,25 +24,19 @@ module.exports = {
defaultLocale: "en",
},
*/
- /* rewrites: async () => ({
- fallback: [
- {
- source: "/:path*",
- destination: "/api/proxy/:path*",
- },
- ],
- }) */
+
+ /*
async headers() {
return [
{
source: '/:path*',
headers: [
- /*
+
{
key: 'Content-Security-Policy',
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
},
- */
+
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
@@ -52,4 +53,5 @@ module.exports = {
},
]
}
+ */
};
diff --git a/apps/leafcutter/package.json b/apps/leafcutter/package.json
index a3a161d..40647a0 100644
--- a/apps/leafcutter/package.json
+++ b/apps/leafcutter/package.json
@@ -6,7 +6,7 @@
"login": "aws sso login --sso-session cdr",
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
"fwd:opensearch": "kubectl port-forward opensearch-cluster-master-0 9200:9200 --namespace leafcutter",
- "fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-vgmtf 5602:5601 --namespace leafcutter",
+ "fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-mx4qq 5602:5601 --namespace leafcutter",
"build": "next build",
"start": "next start",
"export": "next export",
@@ -21,44 +21,46 @@
"@fontsource/poppins": "^5.0.8",
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5",
- "@mui/lab": "^5.0.0-alpha.138",
+ "@mui/lab": "^5.0.0-alpha.140",
"@mui/material": "^5",
- "@mui/x-data-grid-pro": "^6.11.0",
- "@mui/x-date-pickers-pro": "^6.11.0",
+ "@mui/x-data-grid-pro": "^6.11.1",
+ "@mui/x-date-pickers-pro": "^6.11.1",
"@opensearch-project/opensearch": "^2.3.1",
+ "cryptr": "^6.2.0",
"date-fns": "^2.30.0",
"http-proxy-middleware": "^2.0.6",
+ "leafcutter-common": "*",
"material-ui-popup-state": "^5.0.9",
- "next": "13.4.13",
- "next-auth": "^4.22.4",
+ "next": "13.4.7",
+ "next-auth": "^4.23.1",
"next-http-proxy-middleware": "^1.2.5",
"nodemailer": "^6.9.4",
"react": "18.2.0",
- "react-cookie": "^4.1.1",
+ "react-cookie": "^5.0.0",
"react-cookie-consent": "^8.0.1",
"react-dom": "18.2.0",
"react-iframe": "^1.8.5",
"react-markdown": "^8.0.7",
"react-polyglot": "^0.7.2",
- "sharp": "^0.32.4",
- "swr": "^2.2.0",
+ "sharp": "^0.32.5",
+ "swr": "^2.2.1",
"tss-react": "^4.8.8",
"uuid": "^9.0.0"
},
"devDependencies": {
- "@babel/core": "^7.22.9",
- "@types/node": "^20.4.8",
- "@types/react": "18.2.18",
+ "@babel/core": "^7.22.10",
+ "@types/node": "^20.5.0",
+ "@types/react": "18.2.20",
"@types/uuid": "^9.0.2",
"babel-loader": "^9.1.3",
- "eslint": "^8.46.0",
+ "eslint": "^8.47.0",
"eslint-config-airbnb": "^19.0.4",
- "eslint-config-next": "^13.4.13",
+ "eslint-config-next": "^13.4.16",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^5.0.0",
- "eslint-plugin-react": "^7.33.1",
+ "eslint-plugin-react": "^7.33.2",
"typescript": "5.1.6"
}
}
diff --git a/apps/leafcutter/pages/api/proxy/[[...path]].ts b/apps/leafcutter/pages/api/proxy/[[...path]].ts
new file mode 100644
index 0000000..913b7cb
--- /dev/null
+++ b/apps/leafcutter/pages/api/proxy/[[...path]].ts
@@ -0,0 +1,66 @@
+import { createProxyMiddleware } from "http-proxy-middleware";
+import { NextApiRequest, NextApiResponse } from "next";
+import { getToken } from "next-auth/jwt";
+
+/*
+
+ if (validDomains.includes(domain)) {
+ res.headers.set("Access-Control-Allow-Origin", origin);
+ res.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+ res.headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+ }
+
+
+ */
+
+const withAuthInfo =
+ (handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
+ const session: any = await getToken({
+ req,
+ secret: process.env.NEXTAUTH_SECRET,
+ });
+ let email = session?.email?.toLowerCase();
+
+ const requestSignature = req.query.signature;
+ const url = new URL(req.headers.referer as string);
+ const referrerSignature = url.searchParams.get("signature");
+
+ console.log({ requestSignature, referrerSignature });
+ const isAppPath = !!req.url?.startsWith("/app");
+ const isResourcePath = !!req.url?.match(/\/(api|app|bootstrap|3961|ui|translations|internal|login|node_modules)/);
+
+ if (requestSignature && isAppPath) {
+ console.log("Has Signature");
+ }
+
+ if (referrerSignature && isResourcePath) {
+ console.log("Has Signature");
+ }
+
+ if (!email) {
+ return res.status(401).json({ error: "Not authorized" });
+ }
+
+ req.headers["x-proxy-user"] = email;
+ req.headers["x-proxy-roles"] = "leafcutter_user";
+ const auth = `${email}:${process.env.OPENSEARCH_USER_PASSWORD}`;
+ const buff = Buffer.from(auth);
+ const base64data = buff.toString("base64");
+ req.headers.Authorization = `Basic ${base64data}`;
+ return handler(req, res);
+ };
+
+const proxy = createProxyMiddleware({
+ target: process.env.OPENSEARCH_DASHBOARDS_URL,
+ changeOrigin: true,
+ xfwd: true,
+});
+
+export default withAuthInfo(proxy);
+
+export const config = {
+ api: {
+ bodyParser: false,
+ externalResolver: true,
+ },
+};
diff --git a/apps/leafcutter/tsconfig.json b/apps/leafcutter/tsconfig.json
index bb36cfa..25ba45d 100644
--- a/apps/leafcutter/tsconfig.json
+++ b/apps/leafcutter/tsconfig.json
@@ -24,6 +24,17 @@
}
]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "../../packages/leafcutter-common/components/Create.tsx",
+ "../../packages/leafcutter-common/components/About.tsx",
+ "../../packages/leafcutter-common/components/FAQ.tsx",
+ "../../packages/leafcutter-common/components/Trends.tsx",
+ "../../packages/leafcutter-common/components/Preview.tsx",
+ "app/(main)/setup/_components/Setup.tsx"
+, "app/api/proxy/[[...path]]" ],
"exclude": ["node_modules", "babel__core"]
}
diff --git a/apps/link/app/(login)/login/_components/Login.tsx b/apps/link/app/(login)/login/_components/Login.tsx
index bacd448..bfbe6b2 100644
--- a/apps/link/app/(login)/login/_components/Login.tsx
+++ b/apps/link/app/(login)/login/_components/Login.tsx
@@ -1,9 +1,24 @@
"use client";
-import { FC } from "react";
-import { Box, Grid, Container, IconButton } from "@mui/material";
-import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
+import { FC, useState } from "react";
+import {
+ Box,
+ Grid,
+ Container,
+ IconButton,
+ Typography,
+ TextField,
+} from "@mui/material";
+import {
+ Apple as AppleIcon,
+ Google as GoogleIcon,
+ Key as KeyIcon,
+} from "@mui/icons-material";
import { signIn } from "next-auth/react";
+import Image from "next/image";
+import LinkLogo from "public/link-logo-small.png";
+import { colors } from "app/_styles/theme";
+import { useSearchParams } from "next/navigation";
type LoginProps = {
session: any;
@@ -14,62 +29,198 @@ export const Login: FC = ({ session }) => {
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const params = useSearchParams();
+ const error = params.get("error");
+ const { darkGray, cdrLinkOrange, white } = colors;
const buttonStyles = {
borderRadius: 500,
width: "100%",
fontSize: "16px",
fontWeight: "bold",
+ backgroundColor: white,
+ "&:hover": {
+ color: white,
+ backgroundColor: cdrLinkOrange,
+ },
+ };
+ const fieldStyles = {
+ "& label.Mui-focused": {
+ color: cdrLinkOrange,
+ },
+ "& .MuiInput-underline:after": {
+ borderBottomColor: cdrLinkOrange,
+ },
+ "& .MuiFilledInput-underline:after": {
+ borderBottomColor: cdrLinkOrange,
+ },
+ "& .MuiOutlinedInput-root": {
+ "&.Mui-focused fieldset": {
+ borderColor: cdrLinkOrange,
+ },
+ },
};
return (
- <>
-
-
-
-
+
+
-
-
-
-
-
-
- {!session ? (
-
+
+
-
-
- signIn("google", {
- callbackUrl: `${origin}/setup`,
- })
- }
- >
-
- Google
-
+
+
+
+
+
+ CDR Link
+
+
+
+
+
+ {!session ? (
+
+
+ {error ? (
+
+
+
+ {`${error} error`}
+
+
+
+ ) : null}
+
+
+ signIn("google", {
+ callbackUrl: `${origin}/setup`,
+ })
+ }
+ >
+
+ Sign in with Google
+
+
+
+
+ signIn("apple", {
+ callbackUrl: `${window.location.origin}/setup`,
+ })
+ }
+ >
+
+ Sign in with Apple
+
+
+
+
+ ⸺ or ⸺
+
+
+
+ setEmail(e.target.value)}
+ label="Email"
+ variant="filled"
+ size="small"
+ fullWidth
+ sx={{ ...fieldStyles, backgroundColor: white }}
+ />
+
+
+ setPassword(e.target.value)}
+ label="Password"
+ variant="filled"
+ size="small"
+ fullWidth
+ sx={{ backgroundColor: white }}
+ type="password"
+ />
+
+
+
+ signIn("credentials", {
+ email,
+ password,
+ callbackUrl: `${origin}/setup`,
+ })
+ }
+ >
+
+ Sign in with Zammad credentials
+
+
- {/*
-
-
- signIn("apple", {
- callbackUrl: `${window.location.origin}/setup`,
- })
- }
- >
-
-
- */}
-
-
+
) : null}
{session ? (
@@ -79,6 +230,6 @@ export const Login: FC = ({ session }) => {
- >
+
);
};
diff --git a/apps/link/app/(main)/_components/ClientOnly.tsx b/apps/link/app/(main)/_components/ClientOnly.tsx
new file mode 100644
index 0000000..46e79e8
--- /dev/null
+++ b/apps/link/app/(main)/_components/ClientOnly.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import dynamic from "next/dynamic";
+
+type ClientOnlyProps = { children: JSX.Element };
+const ClientOnly = (props: ClientOnlyProps) => {
+ const { children } = props;
+
+ return children;
+};
+
+export default dynamic(() => Promise.resolve(ClientOnly), {
+ ssr: false,
+});
diff --git a/apps/link/app/(main)/_components/Sidebar.tsx b/apps/link/app/(main)/_components/Sidebar.tsx
index 32f4e71..c762320 100644
--- a/apps/link/app/(main)/_components/Sidebar.tsx
+++ b/apps/link/app/(main)/_components/Sidebar.tsx
@@ -17,12 +17,14 @@ import {
import {
FeaturedPlayList as FeaturedPlayListIcon,
Person as PersonIcon,
- Analytics as AnalyticsIcon,
+ Insights as InsightsIcon,
Logout as LogoutIcon,
Cottage as CottageIcon,
Settings as SettingsIcon,
ExpandCircleDown as ExpandCircleDownIcon,
Dvr as DvrIcon,
+ Assessment as AssessmentIcon,
+ LibraryBooks as LibraryBooksIcon,
} from "@mui/icons-material";
import { usePathname } from "next/navigation";
import Link from "next/link";
@@ -161,7 +163,10 @@ interface SidebarProps {
export const Sidebar: FC = ({ open, setOpen }) => {
const pathname = usePathname();
const { data: session } = useSession();
+ console.log({ session });
const username = session?.user?.name || "User";
+ // @ts-ignore
+ const roles = session?.user?.roles || [];
const { data: overviewData, error: overviewError }: any = useSWR(
{
document: getTicketOverviewCountsQuery,
@@ -419,17 +424,25 @@ export const Sidebar: FC = ({ open, setOpen }) => {
+
= ({ open, setOpen }) => {