diff --git a/.env.example b/.env.example index 56d746a..bf73ed0 100644 --- a/.env.example +++ b/.env.example @@ -1,52 +1,34 @@ +# =========================================== +# Auth0 Configuration +# =========================================== + +# Your Auth0 tenant domain (e.g., your-tenant.us.auth0.com) +NEXT_PUBLIC_AUTH0_DOMAIN= + +# Auth0 Application Client ID +NEXT_PUBLIC_AUTH0_CLIENT_ID= + +# =========================================== # Permit.io Configuration +# =========================================== -# Environment ID - your Permit.io environment identifier -NEXT_PUBLIC_PERMIT_ENV_ID=40e1b80613334bcc92f10abc49b1f4f2 +# Environment ID — find this in your Permit.io dashboard +NEXT_PUBLIC_PERMIT_ENV_ID= -# Default tenant ID +# Tenant ID — use "default" unless you've created a custom tenant NEXT_PUBLIC_PERMIT_TENANT_ID=default -# Default user email for testing -NEXT_PUBLIC_DEFAULT_USER_EMAIL=raz@permit.io +# Permit API base URL (use https://api.permit.io for production) +NEXT_PUBLIC_PERMIT_API_URL=https://api.permit.io -# Embed URLs for different elements -NEXT_PUBLIC_USER_MANAGEMENT_EMBED_URL=https://embed.permit.io/to-do-user-management +# =========================================== +# Element Embed URLs +# =========================================== + +# User Management element embed URL from your Permit.io dashboard +NEXT_PUBLIC_USER_MANAGEMENT_EMBED_URL= + +# Other element embed URLs (used by other pages in the demo) NEXT_PUBLIC_AUDIT_LOGS_EMBED_URL=https://embed.permit.io/to-do-audit-logs NEXT_PUBLIC_APPROVAL_FLOWS_EMBED_URL=https://embed.permit.io/to-do-approval-flows NEXT_PUBLIC_RESOURCE_EXPLORER_EMBED_URL=https://embed.permit.io/resource-explorer - -# Private key for JWT signing (multi-line string) -JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCajQfUBont9kCw -8hr2VQYQdCVUrw//mXEsXSjIW3igR0bkdh92lvSp3SrBjfxrtU2G6G7bMM5Euln7 -ah0bpk0SP/Y6pukoWxELJPyXifKQ+JTRCWPsXH+dHQQ5IxHkfpEad770soNdpob1 -Rm2GJu0luGHAPMg0E0ofdnJ/qoNS8eRKlZDMWPhle5TSfzs4te4x106pVPWbtXmg -QrX8cg8csGv52yRo9LuTQa/Ran2aQ7n6rPihEPYPjHQbQIXtMKp9Hm7zmQuAzY8d -eLDfiDLSpmBwnYFOGNlPsGeC+example+cTaYbRhct8YlsL3 -YyEpFkg/AgMBAAECggEAF9+17YcTuBefHKD5kJhyYbOfWfqE99CFzqWWuYSFGLU0 -pUaooOgRY/VPN9ZYFMdNR4wQ0SPek81O6GsW6KuCzPvmbF8A4K6t7lEeAnFZGr3b -jILtKu41pFkpdYNjrdlDTu8KfMA1Ug/PBIUxlqndXkNM46ZAAuir6DpNgPJ1Fihx -PNcVBmP52WIOsGS3NNhlqgSd0w/jcAZ6fw54pGRqe8vwJV92LMTg24FwaXx+2Wak -egXiOE0GgQmc8HVHeviq/TaVsXGiZkrL9u7VSboBoh4aBR11SspmbeSDGEXBa8pg -ukXTD9TzNxN2jF5lon7ZdFYpW8dLAEEpRnjPyWbYOQKBgQDZAA9UdKMhxUazhlfZ -i8BF3FD4DHoc29QSFd5qz4CW+CDMCgfz/5JOUKCzzmI/jee5XmsmMmdmdq1Aagdh -UTEkhQLsYd/d23BGXOcmvZDii0az8xSijVmJv45ZhQVfLzrJIrpoC37kfBhIpREQ -bn6yW6PcUL42yZ4dO0aUMBD4uwKBgQC2U78CUldIfmiDzrOrK+uSaG67gA9Wv/M1 -2rrzzZMzkz/pikxK+5X0cd3Nau55fixl4KZs7tINAO0TqPC4xy9gU+2MbjhcROWZ -9waL8ph12Bl7MzHfID1ni3kk9VVctj4avRmKKedX/2Oe18XZJJIvo2At3/xlBaR6 -WeKV75boTQKBgGfemXfppLRjg6YnJiHhvRUzkiZJgqIRr39ArIgf7wNUng5MtuXa -baPDAElQ/sJBIKwgP9W2xrvLDr8IgaPZg1ZhvLZ9WvUNNr7XdWWb06A83okAT95H -HpbK9E5xV9Lp/o+MagGnjwO7SrxrFrHw0gB50QOCOqpq8542Y2BHabhFAoGBAJ+v -HR01bIhrXbIC+MyzvLAPktvYvpZlqubB2PbqVz/WeSVbIJKTYbQjffUI8C+r18qq -RFgnmgQViY2sapgKq22cU/KoHeiB1u1PsoYjRHABgd+q6MTYga8MMl+yFr8KRb45 -XALrzFf/rwpRRFB3fSkH15aWE4GOvY0C6DYVDoAJAoGBALuuekFNJ+L1l4EFLg7K -eTzvizdndmI/EosFwBRko0jPmqoUX/P5wEsLwOFmBLpcG6fec/rdCRok9F918M1J -Yx95q5CDDhZLjuaYFucZgKDbDz5zTMGsl9kgEdryR4CTL38quB/HS5qAqhjUZjmy -ekAB4stleYg+PZ0lpAv7yy6A ------END PRIVATE KEY-----" - -# Pre-generated JWT Token (optional) -# NEXT_PUBLIC_USER_JWT=eyJhbGciOiJSUzI1NiIsI... - -# Pre-generated JWKS JSON (optional) -# NEXT_PUBLIC_JWKS_JSON={"keys":[{"kty":"RSA","kid":"permit-demo",...}]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d0104a1..20f0aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "permit-demo-element", "version": "0.1.0", "dependencies": { + "@auth0/auth0-react": "^2.15.0", "@headlessui/react": "^2.2.1", "@heroicons/react": "^2.2.0", "@permitio/permit-js": "^0.5.4", @@ -51,6 +52,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@auth0/auth0-auth-js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-auth-js/-/auth0-auth-js-1.4.0.tgz", + "integrity": "sha512-ShA7KT4KvcBEtxsXZTcrmoNxai5q1JXhB2aEBFnZD1L6LNLzzmiUWiFTtGMsaaITCylr8TJ/onEQk6XZmUHXbg==", + "license": "MIT", + "dependencies": { + "jose": "^6.0.8", + "openid-client": "^6.8.0" + } + }, + "node_modules/@auth0/auth0-react": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-2.15.0.tgz", + "integrity": "sha512-LbRU87U54/YW/N3UHtNVoj3mCBBz+iYAdAByQjbXOkpI6IYnjMBwIwDusW3N23XNXq9WnihD57Dyi2R3/Q9btw==", + "license": "MIT", + "dependencies": { + "@auth0/auth0-spa-js": "^2.16.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17 || ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1", + "react-dom": "^16.11.0 || ^17 || ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" + } + }, + "node_modules/@auth0/auth0-spa-js": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-2.17.0.tgz", + "integrity": "sha512-Zdpu4qdOlBXBUDIxSOFAAK6bhs3WlJl1LzkL6kDvamJYDgImi4pMFSzGjS0lobFdotDX1KBnxuyiDSEV0nEBMA==", + "license": "MIT", + "dependencies": { + "@auth0/auth0-auth-js": "^1.4.0", + "browser-tabs-lock": "^1.2.15", + "dpop": "^2.1.1", + "es-cookie": "~1.3.2" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -1592,7 +1628,6 @@ "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1603,7 +1638,6 @@ "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -1650,7 +1684,6 @@ "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.30.1", "@typescript-eslint/types": "8.30.1", @@ -2084,7 +2117,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2495,6 +2527,16 @@ "node": ">=8" } }, + "node_modules/browser-tabs-lock": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.3.0.tgz", + "integrity": "sha512-g6nHaobTiT0eMZ7jh16YpD2kcjAp+PInbiVq3M1x6KKaEIVhT4v9oURNIpZLOZ3LQbQ3XYfNhMAb/9hzNLIWrw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "lodash": ">=4.17.21" + } + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -2515,7 +2557,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -2942,6 +2983,15 @@ "node": ">=0.10.0" } }, + "node_modules/dpop": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-2.1.1.tgz", + "integrity": "sha512-J0Of2JTiM4h5si0tlbPQ/lkqfZ5wAEVkKYBhkwyyANnPJfWH4VsR5uIkZ+T+OSPIwDYUg1fbd5Mmodd25HjY1w==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3041,6 +3091,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==", + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3174,7 +3230,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3348,7 +3403,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -4653,15 +4707,14 @@ "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" } }, "node_modules/jose": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", - "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4827,6 +4880,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5129,6 +5188,15 @@ "node": ">=0.10.0" } }, + "node_modules/oauth4webapi": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5260,6 +5328,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/openid-client": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.2.tgz", + "integrity": "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==", + "license": "MIT", + "dependencies": { + "jose": "^6.1.3", + "oauth4webapi": "^3.8.4" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5453,7 +5534,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -5637,26 +5717,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "19.2.4", + "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" } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "version": "19.2.4", + "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.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^19.2.4" } }, "node_modules/react-is": { @@ -5860,9 +5938,9 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/semver": { @@ -6420,7 +6498,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -6549,7 +6626,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6704,7 +6780,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 4fba85d..49cf7ba 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint": "next lint" }, "dependencies": { + "@auth0/auth0-react": "^2.15.0", "@headlessui/react": "^2.2.1", "@heroicons/react": "^2.2.0", "@permitio/permit-js": "^0.5.4", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0f1ee0b..7cadd3f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme-provider"; +import Auth0ProviderWrapper from "@/components/auth0-provider"; import Header from "@/components/header"; const geistSans = Geist({ @@ -29,17 +30,19 @@ export default function RootLayout({ - -
-
-
{children}
-
-
+ + +
+
+
{children}
+
+
+
); diff --git a/src/app/user-management/page.tsx b/src/app/user-management/page.tsx index 66af51b..4861bd5 100644 --- a/src/app/user-management/page.tsx +++ b/src/app/user-management/page.tsx @@ -1,179 +1,238 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { ClipboardList, FileCheck, Users } from "lucide-react"; -import { motion } from "framer-motion"; - -import { AuthenticationStep } from "@/components/steps/AuthenticationStep"; -import { ImplementationStep } from "@/components/steps/ImplementationStep"; -import { JwksSetupStep } from "@/components/steps/JwksSetupStep"; -import NavigationTabs from "@/components/navigation-tabs"; -import { PreviewStep } from "@/components/steps/PreviewStep"; -import { - DEFAULT_USER_EMAIL, - JWKS_JSON, - PERMIT_ENV_ID, - PERMIT_TENANT_ID, - USER_JWT, - USER_MANAGEMENT_EMBED_URL, - getEmbedUrlWithDarkMode -} from "@/utils/env"; -import { fetchJWT } from "@/utils/client-jwt"; -import { JWKS } from "@/utils/jwt"; +import React, { useEffect, useRef, useState, useCallback } from "react"; +import { useAuth0 } from "@auth0/auth0-react"; +import { Button } from "@/components/ui/button"; + +const ENV_ID = process.env.NEXT_PUBLIC_PERMIT_ENV_ID || ""; +const TENANT_ID = process.env.NEXT_PUBLIC_PERMIT_TENANT_ID || "default"; +const PERMIT_API_URL = + process.env.NEXT_PUBLIC_PERMIT_API_URL || "https://api.permit.io"; +const EMBED_URL = process.env.NEXT_PUBLIC_USER_MANAGEMENT_EMBED_URL || ""; export default function UserManagementPage() { - // State for Permit.io credentials - const [embedUrl, setEmbedUrl] = useState(USER_MANAGEMENT_EMBED_URL); - const [environmentId] = useState(PERMIT_ENV_ID); - const [tenantId] = useState(PERMIT_TENANT_ID); - const [userEmail] = useState(DEFAULT_USER_EMAIL); - const [userJwt, setUserJwt] = useState(USER_JWT || ""); - const [isGeneratingJwt, setIsGeneratingJwt] = useState(false); - const [jwks] = useState(JWKS_JSON ? JSON.parse(JWKS_JSON) : null); - const [isDarkMode, setIsDarkMode] = useState(false); - - // Detect dark mode and update URL - useEffect(() => { - const updateDarkMode = () => { - const darkModeActive = document.documentElement.classList.contains('dark') || - window.matchMedia('(prefers-color-scheme: dark)').matches; - setIsDarkMode(darkModeActive); - }; + const { loginWithRedirect, isAuthenticated, isLoading, getIdTokenClaims, user } = useAuth0(); + const [error, setError] = useState(null); + const [loggingIn, setLoggingIn] = useState(false); + const [permitToken, setPermitToken] = useState(null); + const [elementReady, setElementReady] = useState(false); + const iframeRef = useRef(null); + + // Step A: Call elements_fe_login_as to get the permit token + const loginToPermit = useCallback(async () => { + if (loggingIn || permitToken) return; + setLoggingIn(true); + setError(null); - // Set initial state - updateDarkMode(); + try { + const claims = await getIdTokenClaims(); + if (!claims?.__raw) { + throw new Error("Could not get ID token from Auth0"); + } + const idToken = claims.__raw; + + console.log("Auth0 ID token sub:", claims.sub); + console.log("Calling elements_fe_login_as on:", PERMIT_API_URL); + + const response = await fetch( + `${PERMIT_API_URL}/v2/auth/${ENV_ID}/elements_fe_login_as`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + tenant_id: TENANT_ID, + user_jwt: idToken, + user_key_claim: "email", + }), + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + errorData.message || errorData.error || `HTTP ${response.status}` + ); + } - // Listen for changes - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const handleChange = () => updateDarkMode(); - mediaQuery.addEventListener('change', handleChange); + const data = await response.json(); + console.log("elements_fe_login_as response:", data); - // Observer for class changes on document element - const observer = new MutationObserver(updateDarkMode); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ['class'] - }); + if (data.error) { + throw new Error(`${data.error} (${data.extra || ""})`); + } - return () => { - mediaQuery.removeEventListener('change', handleChange); - observer.disconnect(); - }; - }, []); + const token = data.element_bearer_token; + if (!token) { + throw new Error("No element_bearer_token in login response"); + } - // Update embed URL when dark mode changes + console.log("Got permit token, will send to iframe via postMessage"); + setPermitToken(token); + } catch (err) { + console.error("Permit login error:", err); + setError(String(err)); + } finally { + setLoggingIn(false); + } + }, [getIdTokenClaims, loggingIn, permitToken]); + + // Auto-login to Permit once Auth0 is authenticated useEffect(() => { - try { - const url = getEmbedUrlWithDarkMode(USER_MANAGEMENT_EMBED_URL, isDarkMode); - setEmbedUrl(url); - } catch (error) { - console.error('Error updating embed URL:', error); + if (isAuthenticated && !permitToken && !loggingIn) { + loginToPermit(); } - }, [isDarkMode]); + }, [isAuthenticated, permitToken, loggingIn, loginToPermit]); - - // Generate JWT on initial render if not already available + // Step B: Once iframe loads AND we have the token, send it via postMessage useEffect(() => { - const generateToken = async () => { - if (USER_JWT) return; - - try { - const token = await fetchJWT(userEmail, tenantId, environmentId); - setUserJwt(token); - } catch (error) { - console.error('Error generating JWT:', error); + if (!permitToken || !iframeRef.current) return; + + const iframe = iframeRef.current; + + const sendToken = () => { + const iframeWindow = iframe.contentWindow; + if (!iframeWindow) return; + console.log("Sending permit token to iframe via postMessage"); + iframeWindow.postMessage( + { type: "permitToken", permitToken: permitToken }, + "*" + ); + }; + + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === "permitTokenReceived") { + console.log("iframe acknowledged token receipt!"); + setElementReady(true); } }; + window.addEventListener("message", handleMessage); + + let attempts = 0; + const maxAttempts = 60; + const interval = setInterval(() => { + if (elementReady || attempts >= maxAttempts) { + clearInterval(interval); + if (attempts >= maxAttempts && !elementReady) { + console.error("iframe never acknowledged the token"); + } + return; + } + sendToken(); + attempts++; + }, 500); - if (userEmail && tenantId && environmentId) { - generateToken(); - } - }, [userEmail, tenantId, environmentId]); - - // Define tabs for navigation - const tabs = [ - { - name: "User Management", - href: "/user-management", - icon: , - }, - { - name: "Audit Logs", - href: "/audit-logs", - icon: , - }, - { - name: "Approval Flows", - href: "/approval-flows", - icon: , - }, - ]; - - const handleCredentialsSubmit = async () => { - try { - setIsGeneratingJwt(true); - const jwt = await fetchJWT(userEmail, tenantId, environmentId); - setUserJwt(jwt); - } catch (error) { - console.error("Error generating JWT:", error); - } finally { - setIsGeneratingJwt(false); - } - }; + iframe.addEventListener("load", sendToken); + + return () => { + clearInterval(interval); + window.removeEventListener("message", handleMessage); + iframe.removeEventListener("load", sendToken); + }; + }, [permitToken, elementReady]); + + // Build iframe URL with elementsToken flag so the embed app waits for postMessage + const iframeUrl = EMBED_URL + ? (() => { + const url = new URL(EMBED_URL); + if (!url.searchParams.has("envId")) url.searchParams.set("envId", ENV_ID); + url.searchParams.set("elementsToken", "true"); + return url.toString(); + })() + : ""; + + if (isLoading) { + return ( +
+

Loading...

+
+ ); + } return ( -
- -
-
-

User Management

-

- Allow your users to manage access control and permissions within safe boundaries. +

+

User Management Element Test

+

+ Testing the embedded User Management element with Auth0 staging authentication. +

+ + {/* Step 1: Auth0 Login */} + {!isAuthenticated && ( +
+

Step 1: Login with Auth0

+

+ You need to log in first so we can get your Auth0 ID token. +

+ +
+ )} + + {/* Step 2: Permit login status */} + {isAuthenticated && !permitToken && ( +
+

Step 2: Connecting to Permit

+ {loggingIn && ( +

+ Logging in as {user?.email} (sub: {user?.sub})... +

+ )} + {error && ( +
+

Error: {error}

+ +
+ )} +
+ )} + + {/* Step 3: The actual element */} + {permitToken && iframeUrl && ( +
+
+

User Management Element

+

+ Logged in as {user?.email} | Tenant: {TENANT_ID} + {!elementReady && " | Sending token to iframe..."} + {elementReady && " | Connected"}

- - - - {/* Step 1: JWKS Setup */} - - - {/* Step 2: Implementation */} - - - {/* Step 3: Login */} - - - {/* Step 4: Preview */} -
- + )} + + {/* Debug info */} +
+ + Debug Info + +
+          {JSON.stringify(
+            {
+              auth0Authenticated: isAuthenticated,
+              userEmail: user?.email,
+              userSub: user?.sub,
+              permitTokenReceived: !!permitToken,
+              iframeAcknowledged: elementReady,
+              envId: ENV_ID,
+              tenantId: TENANT_ID,
+              permitApiUrl: PERMIT_API_URL,
+              embedUrl: EMBED_URL,
+            },
+            null,
+            2
+          )}
+        
+
); -} \ No newline at end of file +} diff --git a/src/components/auth0-provider.tsx b/src/components/auth0-provider.tsx new file mode 100644 index 0000000..ee300ea --- /dev/null +++ b/src/components/auth0-provider.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { Auth0Provider } from "@auth0/auth0-react"; + +export default function Auth0ProviderWrapper({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +}