Compare commits

...
49 changed files with 1781 additions and 311 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@
"redirectURI": "$AUTH_REDIRECT_URI",
"silentRedirectURI": "$AUTH_SILENT_REDIRECT_URI",
"tokenSource": "$NETBIRD_TOKEN_SOURCE",
"disableServiceWorker": "$NETBIRD_DISABLE_SERVICE_WORKER",
"dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS",
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
"googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID",
+2 -1
View File
@@ -60,13 +60,14 @@ export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID}
export NETBIRD_GOOGLE_ANALYTICS_ID=${NETBIRD_GOOGLE_ANALYTICS_ID}
export NETBIRD_GOOGLE_TAG_MANAGER_ID=${NETBIRD_GOOGLE_TAG_MANAGER_ID}
export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
export NETBIRD_DISABLE_SERVICE_WORKER=${NETBIRD_DISABLE_SERVICE_WORKER:-false}
export NETBIRD_DRAG_QUERY_PARAMS=${NETBIRD_DRAG_QUERY_PARAMS:-false}
export NETBIRD_WASM_PATH=${NETBIRD_WASM_PATH}
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
# replace ENVs in the config
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$NETBIRD_GOOGLE_TAG_MANAGER_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS \$\$NETBIRD_WASM_PATH"
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$NETBIRD_GOOGLE_TAG_MANAGER_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DISABLE_SERVICE_WORKER \$\$NETBIRD_DRAG_QUERY_PARAMS \$\$NETBIRD_WASM_PATH"
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS".tmpl > "$OIDC_TRUSTED_DOMAINS"
+34 -76
View File
@@ -60,7 +60,7 @@
"js-cookie": "^3.0.5",
"lodash": "^4.17.23",
"lucide-react": "^0.566.0",
"next": "^16.1.7",
"next": "16.1.7",
"next-themes": "^0.2.1",
"punycode": "^2.3.1",
"react": "^19.2.4",
@@ -168,7 +168,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -3031,7 +3030,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3041,7 +3039,6 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -3100,7 +3097,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -3244,32 +3240,6 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
@@ -3607,8 +3577,7 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/@xyflow/react": {
"version": "12.10.0",
@@ -3647,7 +3616,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3998,10 +3966,13 @@
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
@@ -4025,13 +3996,15 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
@@ -4065,7 +4038,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -4320,12 +4292,6 @@
"node": ">= 10"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -4694,7 +4660,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -5223,7 +5188,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5421,7 +5385,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -6715,7 +6678,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT",
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -6889,9 +6851,9 @@
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash.merge": {
@@ -6973,15 +6935,18 @@
}
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "*"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
@@ -7412,9 +7377,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
@@ -7470,7 +7435,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -7678,7 +7642,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7719,7 +7682,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -8614,7 +8576,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -8777,11 +8738,10 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -8947,7 +8907,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -9275,7 +9234,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+6 -4
View File
@@ -6,11 +6,10 @@
"node": ">=20.9.0"
},
"scripts": {
"copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc/dist/OidcServiceWorker.js ./public",
"copytrusted": "copyfiles -f ./public/local/OidcTrustedDomains.js ./public",
"oidc-gen": "node ./node_modules/@axa-fr/react-oidc/bin/copy-service-worker-files.mjs public",
"dev": "next dev -p 3000",
"turbo": "next dev -p 3000 --turbo",
"build": "next build",
"build": "npm run oidc-gen && next build",
"start": "next start",
"lint": "next lint",
"cypress:open": "cypress open"
@@ -68,7 +67,7 @@
"js-cookie": "^3.0.5",
"lodash": "^4.17.23",
"lucide-react": "^0.566.0",
"next": "^16.1.7",
"next": "16.1.7",
"next-themes": "^0.2.1",
"punycode": "^2.3.1",
"react": "^19.2.4",
@@ -89,6 +88,9 @@
"timescape": "^0.7.1",
"typescript": "^5"
},
"overrides": {
"minimatch": ">=10.2.1"
},
"devDependencies": {
"@faker-js/faker": "^9.5.1",
"@types/chroma-js": "^3.1.1",
File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
// Add here trusted domains, access tokens will be send to
const trustedDomains = {
default:["http://localhost:3001","http://127.0.0.1:3001", "http://0.0.0.0:33071"],
auth0:[]
};
+8 -5
View File
@@ -1,6 +1,9 @@
// Add here trusted domains, access tokens will be send to
// Domains used by OIDC server must be also declared here
const trustedDomains = {
default:["$NETBIRD_MGMT_API_ENDPOINT"],
auth0:[]
};
default: [
"$NETBIRD_MGMT_API_ENDPOINT",
"$NETBIRD_MGMT_GRPC_API_ENDPOINT",
].filter((d) => d && d.length > 0),
};
@@ -23,6 +23,7 @@ export const idpIcon = (
zitadel: <ZitadelIcon size={size} />,
authentik: <AuthentikIcon size={size} />,
keycloak: <KeycloakIcon size={size} />,
adfs: <MicrosoftIcon size={size} />,
oidc: <KeyRound size={size} className="text-nb-gray-400" />,
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+97 -10
View File
@@ -2,21 +2,47 @@
import {
AuthorityConfiguration,
OidcClient,
OidcConfiguration,
OidcProvider,
} from "@axa-fr/react-oidc";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import loadConfig, { buildExtras } from "@utils/config";
import { usePathname, useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
import React, { useEffect, useState } from "react";
import { OIDCError } from "@/auth/OIDCError";
import { SecureProvider } from "@/auth/SecureProvider";
import { storeSessionLostRedirectPath } from "@/auth/sessionRedirect";
type Props = {
children: React.ReactNode;
};
const config = loadConfig();
const AUTHENTICATED_CALLBACK_REDIRECT_PATH = "/peers";
const getOidcRoutePath = (href: string, origin: string) => {
try {
const url = new URL(href, origin);
let path = url.pathname;
if (path.endsWith("/")) path = path.slice(0, -1);
let hash = url.hash;
if (hash === "#_=_") hash = "";
if (hash) path += hash.split("?")[0];
return path;
} catch {
return "";
}
};
const isOidcCallbackRoute = (href: string, redirectUri: string) =>
getOidcRoutePath(href, window.location.origin) ===
getOidcRoutePath(redirectUri, window.location.origin);
const hasUsableTokens = (tokens: { expiresAt?: number } | null | undefined) =>
typeof tokens?.expiresAt === "number" && tokens.expiresAt > Date.now() / 1000;
/**
* Unfortunately Auth0 https://<DOMAIN>/.well-known/openid-configuration doesn't contain end_session_endpoint that
@@ -40,7 +66,6 @@ const onEvent = (configurationName: any, eventName: any, data: any) => {
export default function OIDCProvider({ children }: Props) {
const [providerConfig, setProviderConfig] = useState<OidcConfiguration>();
const [mounted, setMounted] = useState(false);
const router = useRouter();
const path = usePathname();
const withCustomHistory = () => {
@@ -52,16 +77,30 @@ export default function OIDCProvider({ children }: Props) {
};
useEffect(() => {
setProviderConfig({
let isMounted = true;
// The service worker is disabled in two cases:
// 1. tokenSource === "idtoken" — the SW would overwrite the manually-set
// idToken header with the access_token, breaking the idToken path.
// 2. NETBIRD_DISABLE_SERVICE_WORKER=true — operator escape hatch for
// debugging or deployments that hit edge cases with the SW.
const useServiceWorker =
!config.disableServiceWorker &&
config.tokenSource?.toLowerCase() !== "idtoken";
const nextProviderConfig: OidcConfiguration = {
authority: config.authority,
client_id: config.clientId,
redirect_uri: window.location.origin + config.redirectURI,
refresh_time_before_tokens_expiration_in_second: 30,
silent_redirect_uri: window.location.origin + config.silentRedirectURI,
scope: config.scopesSupported,
// disabling service worker
//service_worker_relative_url: "/OidcServiceWorker.js",
service_worker_only: false,
...(useServiceWorker
? {
service_worker_relative_url: "/OidcServiceWorker.js",
service_worker_only: false,
}
: {}),
authority_configuration: config.auth0Auth
? auth0AuthorityConfig
: undefined,
@@ -69,10 +108,53 @@ export default function OIDCProvider({ children }: Props) {
...(config.clientSecret
? { token_request_extras: { client_secret: config.clientSecret } }
: null),
});
setMounted(true);
};
const finishMounting = () => {
if (isMounted) setMounted(true);
};
const redirectAuthenticatedCallback = async () => {
// OidcRoutes handles the callback route before OidcSession restores
// existing tokens. If a callback URL is reached while the app already
// has a valid OIDC session, leave the callback route instead of trying
// to process a stale/duplicate callback.
if (
!isOidcCallbackRoute(
window.location.href,
nextProviderConfig.redirect_uri,
)
) {
finishMounting();
return;
}
try {
const oidc = OidcClient.getOrCreate(() => fetch)(nextProviderConfig);
await oidc.tryKeepExistingSessionAsync();
if (!isMounted) return;
if (hasUsableTokens(oidc.tokens)) {
window.location.replace(AUTHENTICATED_CALLBACK_REDIRECT_PATH);
return;
}
} catch {}
finishMounting();
};
setProviderConfig(nextProviderConfig);
void redirectAuthenticatedCallback();
return () => {
isMounted = false;
};
}, []);
const handleSessionLost = () => {
storeSessionLostRedirectPath();
window.location.replace("/");
};
// We bypass authentication for pages that do not require auth.
// E.g., when we just want to show installation steps for public.
// Or the instance setup wizard for first-time setup.
@@ -89,8 +171,13 @@ export default function OIDCProvider({ children }: Props) {
loadingComponent={FullScreenLoading}
callbackSuccessComponent={FullScreenLoading}
onEvent={onEvent}
onSessionLost={() => void 0}
//sessionLostComponent={SessionLost}
// If session is lost, try to re-initiate authentication flow while
// preserving the current deep link for the post-login redirect.
onSessionLost={handleSessionLost}
// Another tab logged out — fires a separate event in @axa-fr/react-oidc,
// not covered by onSessionLost. Without this handler the library would
// render its default "Session timed out" UI instead of redirecting.
onLogoutFromAnotherTab={handleSessionLost}
>
<SecureProvider>{children}</SecureProvider>
</OidcProvider>
+14 -6
View File
@@ -2,6 +2,10 @@ import { OidcSecure, useOidc } from "@axa-fr/react-oidc";
import { usePathname } from "next/navigation";
import * as React from "react";
import { useEffect } from "react";
import {
clearSessionLostRedirectPath,
getSessionLostRedirectPath,
} from "@/auth/sessionRedirect";
const QUERY_PARAMS_KEY = "netbird-query-params";
const PRESERVE_QUERY_PARAMS_PATHS = ["/peer/ssh", "/peer/rdp"];
@@ -27,11 +31,15 @@ type Props = {
export const SecureProvider = ({ children }: Props) => {
const { isAuthenticated, login } = useOidc();
const currentPath = usePathname();
const callbackPath = getSessionLostRedirectPath() ?? currentPath;
useEffect(() => {
if (isAuthenticated && !PRESERVE_QUERY_PARAMS_PATHS.includes(currentPath)) {
localStorage.removeItem(QUERY_PARAMS_KEY);
} else if (!isAuthenticated) {
if (isAuthenticated) {
clearSessionLostRedirectPath();
if (!PRESERVE_QUERY_PARAMS_PATHS.includes(currentPath)) {
localStorage.removeItem(QUERY_PARAMS_KEY);
}
} else {
try {
const params = window.location.search.substring(1);
if (params) {
@@ -49,18 +57,18 @@ export const SecureProvider = ({ children }: Props) => {
if (!isAuthenticated) {
timeout = setTimeout(async () => {
if (!isAuthenticated) {
await login(currentPath);
await login(callbackPath);
}
}, 1500);
}
return () => {
clearTimeout(timeout);
};
}, [currentPath, isAuthenticated, login]);
}, [callbackPath, isAuthenticated, login]);
return (
<>
<OidcSecure callbackPath={currentPath}>{children}</OidcSecure>
<OidcSecure callbackPath={callbackPath}>{children}</OidcSecure>
</>
);
};
+47
View File
@@ -0,0 +1,47 @@
const SESSION_LOST_REDIRECT_PATH_KEY = "netbird-session-lost-redirect-path";
const isSafeRelativePath = (path: string) =>
path.startsWith("/") && !path.startsWith("//");
export const storeSessionLostRedirectPath = () => {
if (typeof window === "undefined") return;
const redirectPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
try {
if (
!redirectPath ||
redirectPath === "/" ||
!isSafeRelativePath(redirectPath)
) {
window.sessionStorage.removeItem(SESSION_LOST_REDIRECT_PATH_KEY);
return;
}
window.sessionStorage.setItem(
SESSION_LOST_REDIRECT_PATH_KEY,
redirectPath,
);
} catch {}
};
export const getSessionLostRedirectPath = () => {
if (typeof window === "undefined") return undefined;
try {
const redirectPath = window.sessionStorage.getItem(
SESSION_LOST_REDIRECT_PATH_KEY,
);
if (redirectPath && isSafeRelativePath(redirectPath)) return redirectPath;
} catch {}
return undefined;
};
export const clearSessionLostRedirectPath = () => {
if (typeof window === "undefined") return;
try {
window.sessionStorage.removeItem(SESSION_LOST_REDIRECT_PATH_KEY);
} catch {}
};
+2
View File
@@ -23,6 +23,7 @@ const variants = cva("", {
purple: ["bg-purple-950/50 border-purple-500 border text-purple-500"],
yellow: ["bg-yellow-950 border-yellow-500 border text-yellow-400"],
gray: ["bg-nb-gray-930/60 border-nb-gray-800/40 text-nb-gray-300 border"],
lightGray: ["bg-nb-gray-910 text-nb-gray-200 border border-nb-gray-900"],
grayer: [
"bg-nb-gray-900/40 border-nb-gray-800/40 text-nb-gray-300 border",
],
@@ -45,6 +46,7 @@ const variants = cva("", {
"blue-darker": ["hover:bg-sky-800"],
red: ["hover:bg-red-950/40"],
gray: ["hover:bg-nb-gray-900"],
lightGray: ["hover:bg-nb-gray-900"],
grayer: ["hover:bg-nb-gray-900"],
"gray-ghost": ["hover:bg-nb-gray-800 cursor-pointer"],
green: ["hover:bg-green-950/50"],
+7 -3
View File
@@ -8,7 +8,7 @@ import React from "react";
export const fancyToggleSwitchVariants = cva([], {
variants: {
variant: {
default: ["px-6 py-4 border rounded-md"],
default: ["px-5 py-4 border rounded-md"],
blank: null,
},
state: {
@@ -45,6 +45,8 @@ interface Props extends FancyToggleSwitchVariants {
disabled?: boolean;
dataCy?: string;
className?: string;
labelClassName?: string;
textWrapperClassName?: string;
}
export default function FancyToggleSwitch({
@@ -57,6 +59,8 @@ export default function FancyToggleSwitch({
dataCy,
className,
variant = "default",
labelClassName,
textWrapperClassName = "max-w-sm",
}: Readonly<Props>) {
const handleToggle = () => {
if (disabled) return;
@@ -87,8 +91,8 @@ export default function FancyToggleSwitch({
)}
>
<div className={"flex justify-between gap-10"}>
<div className={"max-w-sm"}>
<Label>{label}</Label>
<div className={cn(textWrapperClassName)}>
<Label className={labelClassName}>{label}</Label>
<HelpText margin={false}>{helpText}</HelpText>
</div>
<div className={"mt-2 pr-1"}>
+17
View File
@@ -0,0 +1,17 @@
import { useAccount } from "@/modules/account/useAccount";
import { SSOIdentityProvider } from "@/interfaces/IdentityProvider";
import useFetchApi from "@utils/api";
export function useEmbeddedIdentityProviders() {
const account = useAccount();
const isEmbeddedIdPEnabled = !!account?.settings?.embedded_idp_enabled;
const { data: providers } = useFetchApi<SSOIdentityProvider[]>(
"/identity-providers",
true,
true,
isEmbeddedIdPEnabled,
);
return { providers, isEmbeddedIdPEnabled };
}
+11 -15
View File
@@ -1,37 +1,33 @@
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useMemo } from "react";
export default function useUrlTab(
validTabs: string[],
defaultTab: string,
paramName: string = "tab",
): [string, (value: string) => void] {
const searchParams = useSearchParams();
const router = useRouter();
const getTab = useCallback(
(params: URLSearchParams) => {
const tabParam = params.get("tab");
const tabParam = params.get(paramName);
if (tabParam && validTabs.includes(tabParam)) return tabParam;
return defaultTab;
},
[validTabs, defaultTab],
[validTabs, defaultTab, paramName],
);
const [tab, setTabState] = useState(() => getTab(searchParams));
useEffect(() => {
const newTab = getTab(searchParams);
setTabState(newTab);
}, [searchParams, getTab]);
const tab = useMemo(() => getTab(searchParams), [searchParams, getTab]);
const setTab = useCallback(
(value: string) => {
const nextTab = validTabs.includes(value) ? value : defaultTab;
setTabState(nextTab);
const params = new URLSearchParams(window.location.search);
params.set("tab", nextTab);
window.history.replaceState(null, "", `?${params.toString()}`);
const params = new URLSearchParams(searchParams.toString());
params.set(paramName, nextTab);
router.replace(`?${params.toString()}`, { scroll: false });
},
[validTabs, defaultTab],
[searchParams, router, validTabs, defaultTab, paramName],
);
return [tab, setTab];
+12 -6
View File
@@ -1,20 +1,22 @@
export interface GoogleWorkspaceIntegration {
id: string;
customerId: string;
syncInterval: number;
customer_id: string;
sync_interval: number;
enabled: boolean;
group_prefixes: string[];
user_group_prefixes: string[];
connector_id?: string;
}
export interface AzureADIntegration {
id: string;
clientId: string;
tenantId: string;
syncInterval: number;
client_id: string;
tenant_id: string;
sync_interval: number;
enabled: boolean;
group_prefixes: string[];
user_group_prefixes: string[];
connector_id?: string;
}
export interface OktaIntegration {
@@ -23,6 +25,8 @@ export interface OktaIntegration {
group_prefixes: string[];
user_group_prefixes: string[];
auth_token: string;
connection_name?: string;
connector_id?: string;
}
export interface IdentityProviderLog {
@@ -40,7 +44,8 @@ export type SSOIdentityProviderType =
| "pocketid"
| "microsoft"
| "authentik"
| "keycloak";
| "keycloak"
| "adfs";
export const SSOIdentityProviderOptions: {
value: SSOIdentityProviderType;
@@ -55,6 +60,7 @@ export const SSOIdentityProviderOptions: {
{ value: "pocketid", label: "PocketID" },
{ value: "authentik", label: "Authentik" },
{ value: "keycloak", label: "Keycloak" },
{ value: "adfs", label: "Microsoft AD FS" },
];
export const getSSOIdentityProviderLabelByType = (
+11
View File
@@ -22,11 +22,20 @@ export interface ReverseProxy {
meta?: ReverseProxyMeta;
}
export const CrowdSecMode = {
OFF: "off",
ENFORCE: "enforce",
OBSERVE: "observe",
} as const;
export type CrowdSecMode = (typeof CrowdSecMode)[keyof typeof CrowdSecMode];
export interface AccessRestrictions {
allowed_cidrs?: string[];
blocked_cidrs?: string[];
allowed_countries?: string[];
blocked_countries?: string[];
crowdsec_mode?: CrowdSecMode;
}
export interface ReverseProxyMeta {
@@ -102,6 +111,7 @@ export interface ReverseProxyDomain {
target_cluster?: string;
supports_custom_ports?: boolean;
require_subdomain?: boolean;
supports_crowdsec?: boolean;
}
export enum ReverseProxyDomainType {
@@ -149,6 +159,7 @@ export interface ReverseProxyEvent {
bytes_upload: number;
bytes_download: number;
protocol?: EventProtocol;
metadata?: Record<string, string>;
}
export function isL4Event(event: ReverseProxyEvent): boolean {
@@ -821,6 +821,36 @@ export default function ActivityDescription({ event }: Props) {
</div>
);
/**
* Reverse Proxy
*/
if (event.activity_code == "service.create")
return (
<div className={"inline"}>
Service <Value>{m.domain}</Value> in cluster{" "}
<Value>{m.proxy_cluster}</Value> was created with authentication{" "}
<Value>{m.auth ? "Enabled" : "Disabled"}</Value>
</div>
);
if (event.activity_code == "service.update")
return (
<div className={"inline"}>
Service <Value>{m.domain}</Value> in cluster{" "}
<Value>{m.proxy_cluster}</Value> was updated with authentication{" "}
<Value>{m.auth === "true" ? "Enabled" : "Disabled"}</Value>
</div>
);
if (event.activity_code == "service.delete")
return (
<div className={"inline"}>
Service <Value>{m.domain}</Value> in cluster{" "}
<Value>{m.proxy_cluster}</Value> was deleted
</div>
);
return (
<div className={"flex gap-2.5 items-center"}>
<span className={"mb-[1px]"}>{event.activity}</span>
@@ -49,7 +49,9 @@ export function ActivityEventCodeSelector({
return {
activity_code: event.activity_code,
activity: event.activity,
group: event.activity_code.split(".")[0],
group: event.activity_code.startsWith("service.user")
? "Service User"
: event.activity_code.split(".")[0],
};
});
return items.reduce((acc, item) => {
Loaded 20 of 49 files, more files were not shown because too many files have changed in this diff. Show more