@lukoweb/apitogo 0.1.23 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -0
- package/cli.js +13 -13
- package/client.d.ts +8 -8
- package/dist/cli/cli.js +1117 -624
- package/dist/declarations/config/config.d.ts +4 -0
- package/dist/declarations/config/loader.d.ts +2 -0
- package/dist/declarations/config/resolve-modules.d.ts +34 -0
- package/dist/declarations/config/validators/InputNavigationSchema.d.ts +7 -7
- package/dist/declarations/config/validators/ModulesSchema.d.ts +417 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +123 -4
- package/dist/declarations/index.d.ts +3 -1
- package/dist/declarations/lib/authentication/components/ProductionUnlockPage.d.ts +1 -0
- package/dist/declarations/lib/authentication/hook.d.ts +3 -19
- package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +16 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +9 -0
- package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +39 -0
- package/dist/declarations/lib/authentication/state.d.ts +12 -26
- package/dist/declarations/lib/components/LandingLayout.d.ts +4 -0
- package/dist/declarations/lib/components/index.d.ts +2 -0
- package/dist/declarations/lib/plugins/api-keys/index.d.ts +2 -1
- package/dist/flat-config.d.ts +209 -24
- package/docs/components/alert.mdx +130 -0
- package/docs/components/badge.mdx +70 -0
- package/docs/components/button.mdx +132 -0
- package/docs/components/callout.mdx +112 -0
- package/docs/components/card.mdx +104 -0
- package/docs/components/checkbox.mdx +72 -0
- package/docs/components/client-only.mdx +79 -0
- package/docs/components/code-tabs.mdx +179 -0
- package/docs/components/dialog.mdx +167 -0
- package/docs/components/head.mdx +200 -0
- package/docs/components/icons.mdx +27 -0
- package/docs/components/input.mdx +96 -0
- package/docs/components/label.mdx +86 -0
- package/docs/components/link.mdx +242 -0
- package/docs/components/markdown.mdx +151 -0
- package/docs/components/mermaid.mdx +81 -0
- package/docs/components/playground.mdx +87 -0
- package/docs/components/secret.mdx +79 -0
- package/docs/components/select.mdx +176 -0
- package/docs/components/shadcn.mdx +73 -0
- package/docs/components/slider.mdx +108 -0
- package/docs/components/slot.mdx +119 -0
- package/docs/components/stepper.mdx +138 -0
- package/docs/components/switch.mdx +96 -0
- package/docs/components/syntax-highlight.mdx +602 -0
- package/docs/components/textarea.mdx +78 -0
- package/docs/components/tooltip.mdx +195 -0
- package/docs/components/typography.mdx +61 -0
- package/docs/concepts/auth-provider-api-identities.md +109 -0
- package/docs/configuration/ai-assistants.md +65 -0
- package/docs/configuration/api-catalog.md +109 -0
- package/docs/configuration/api-reference.md +397 -0
- package/docs/configuration/authentication-auth0.md +174 -0
- package/docs/configuration/authentication-azure-ad.md +238 -0
- package/docs/configuration/authentication-clerk.md +110 -0
- package/docs/configuration/authentication-firebase.md +62 -0
- package/docs/configuration/authentication-pingfederate.md +136 -0
- package/docs/configuration/authentication-supabase.md +226 -0
- package/docs/configuration/authentication.md +231 -0
- package/docs/configuration/build-configuration.mdx +147 -0
- package/docs/configuration/docs.md +282 -0
- package/docs/configuration/footer.mdx +214 -0
- package/docs/configuration/llms.md +90 -0
- package/docs/configuration/make-config.mdx +90 -0
- package/docs/configuration/navigation.mdx +422 -0
- package/docs/configuration/overview.md +380 -0
- package/docs/configuration/protected-routes.md +150 -0
- package/docs/configuration/search.md +163 -0
- package/docs/configuration/sentry.mdx +44 -0
- package/docs/configuration/site.md +124 -0
- package/docs/configuration/slots.mdx +125 -0
- package/docs/configuration/vite-config.md +18 -0
- package/docs/custom-plugins.md +285 -0
- package/docs/customization/colors-theme.mdx +276 -0
- package/docs/customization/fonts.md +110 -0
- package/docs/deploy/apache-nginx.md +41 -0
- package/docs/deploy/apitogo.md +139 -0
- package/docs/deploy/cloudflare-pages.md +75 -0
- package/docs/deploy/direct-upload.md +18 -0
- package/docs/deploy/github-pages.md +50 -0
- package/docs/deploy/vercel.md +103 -0
- package/docs/deployment.md +21 -0
- package/docs/extending/events.md +124 -0
- package/docs/guides/custom-pages.md +106 -0
- package/docs/guides/environment-variables.md +99 -0
- package/docs/guides/managing-api-keys-and-identities.md +172 -0
- package/docs/guides/mermaid.mdx +70 -0
- package/docs/guides/navigation-migration.md +87 -0
- package/docs/guides/navigation-rules.mdx +203 -0
- package/docs/guides/processors.mdx +234 -0
- package/docs/guides/static-files.md +55 -0
- package/docs/guides/transforming-examples.md +156 -0
- package/docs/guides/using-multiple-apis.md +87 -0
- package/docs/markdown/admonitions.md +128 -0
- package/docs/markdown/code-blocks.md +196 -0
- package/docs/markdown/frontmatter.md +173 -0
- package/docs/markdown/mdx.md +68 -0
- package/docs/markdown/overview.md +275 -0
- package/docs/plugins.md +5 -0
- package/docs/quickstart.md +87 -0
- package/docs/writing.mdx +72 -0
- package/package.json +170 -116
- package/src/app/ZuploBuildConfig.ts +36 -36
- package/src/app/defaultTheme.css +77 -77
- package/src/app/demo-cdn.html +37 -37
- package/src/app/demo.html +21 -21
- package/src/app/demo.tsx +77 -77
- package/src/app/entry.client.tsx +118 -118
- package/src/app/entry.server.tsx +160 -160
- package/src/app/env.ts +40 -40
- package/src/app/font.geist.css +73 -73
- package/src/app/main.css +390 -390
- package/src/app/main.tsx +8 -1
- package/src/app/polyfills.ts +47 -47
- package/src/app/processRoutes.tsx +51 -25
- package/src/app/sentry.ts +24 -24
- package/src/app/standalone.html +23 -23
- package/src/app/standalone.tsx +60 -60
- package/src/app/utils/createRedirectRoutes.ts +11 -11
- package/src/config/config.ts +32 -0
- package/src/config/create-plugin.ts +71 -71
- package/src/config/file-exists.ts +6 -6
- package/src/config/loader.ts +11 -1
- package/src/config/resolve-modules.ts +260 -0
- package/src/config/validators/BuildSchema.ts +95 -95
- package/src/config/validators/HeaderNavigationSchema.ts +41 -41
- package/src/config/validators/InputNavigationSchema.test-d.ts +178 -178
- package/src/config/validators/InputNavigationSchema.ts +206 -206
- package/src/config/validators/ModulesSchema.ts +312 -0
- package/src/config/validators/ProtectedRoutesSchema.ts +35 -35
- package/src/config/validators/ZudokuConfig.ts +766 -729
- package/src/config/validators/auth.ts +3 -3
- package/src/config/validators/reason-codes.ts +7 -7
- package/src/env.d.ts +11 -11
- package/src/index.ts +31 -0
- package/src/lib/MissingIcon.tsx +22 -22
- package/src/lib/assets/language-icons/bun.tsx +50 -50
- package/src/lib/assets/language-icons/c.tsx +31 -31
- package/src/lib/assets/language-icons/commonlisp.tsx +22 -22
- package/src/lib/assets/language-icons/cpp.tsx +35 -35
- package/src/lib/assets/language-icons/csharp.tsx +35 -35
- package/src/lib/assets/language-icons/css.tsx +36 -36
- package/src/lib/assets/language-icons/dart.tsx +39 -39
- package/src/lib/assets/language-icons/elixir.tsx +19 -19
- package/src/lib/assets/language-icons/go.tsx +19 -19
- package/src/lib/assets/language-icons/graphql.tsx +19 -19
- package/src/lib/assets/language-icons/html.tsx +24 -24
- package/src/lib/assets/language-icons/java.tsx +35 -35
- package/src/lib/assets/language-icons/javascript.tsx +11 -11
- package/src/lib/assets/language-icons/json.tsx +19 -19
- package/src/lib/assets/language-icons/kotlin.tsx +30 -30
- package/src/lib/assets/language-icons/markdown.tsx +19 -19
- package/src/lib/assets/language-icons/mdx.tsx +23 -23
- package/src/lib/assets/language-icons/npm.tsx +15 -15
- package/src/lib/assets/language-icons/objectivec.tsx +23 -23
- package/src/lib/assets/language-icons/ocaml.tsx +34 -34
- package/src/lib/assets/language-icons/php.tsx +19 -19
- package/src/lib/assets/language-icons/pnpm.tsx +32 -32
- package/src/lib/assets/language-icons/powershell.tsx +27 -27
- package/src/lib/assets/language-icons/python.tsx +23 -23
- package/src/lib/assets/language-icons/react.tsx +21 -21
- package/src/lib/assets/language-icons/ruby.tsx +19 -19
- package/src/lib/assets/language-icons/rust.tsx +19 -19
- package/src/lib/assets/language-icons/scala.tsx +19 -19
- package/src/lib/assets/language-icons/shell.tsx +19 -19
- package/src/lib/assets/language-icons/swift.tsx +19 -19
- package/src/lib/assets/language-icons/toml.tsx +23 -23
- package/src/lib/assets/language-icons/typescript.tsx +23 -23
- package/src/lib/assets/language-icons/xml.tsx +19 -19
- package/src/lib/assets/language-icons/yaml.tsx +23 -23
- package/src/lib/assets/language-icons/yarn.tsx +17 -17
- package/src/lib/assets/language-icons/zig.tsx +32 -32
- package/src/lib/auth/issuer.ts +34 -31
- package/src/lib/authentication/AuthenticationPlugin.tsx +33 -33
- package/src/lib/authentication/authentication.ts +48 -48
- package/src/lib/authentication/components/OAuthErrorPage.tsx +197 -197
- package/src/lib/authentication/components/ProductionUnlockPage.tsx +27 -0
- package/src/lib/authentication/components/SignOut.tsx +15 -15
- package/src/lib/authentication/constants.ts +1 -1
- package/src/lib/authentication/errors.ts +34 -34
- package/src/lib/authentication/hook.ts +141 -139
- package/src/lib/authentication/providers/auth0.tsx +91 -91
- package/src/lib/authentication/providers/azureb2c.tsx +223 -223
- package/src/lib/authentication/providers/dev-portal-constants.ts +21 -0
- package/src/lib/authentication/providers/dev-portal-utils.ts +107 -0
- package/src/lib/authentication/providers/dev-portal.tsx +270 -0
- package/src/lib/authentication/providers/firebase.tsx +486 -486
- package/src/lib/authentication/providers/openid.tsx +516 -516
- package/src/lib/authentication/providers/supabase.tsx +412 -412
- package/src/lib/authentication/providers/util.ts +26 -26
- package/src/lib/authentication/state.ts +179 -91
- package/src/lib/authentication/ui/icons/Apple.tsx +10 -10
- package/src/lib/authentication/ui/icons/Facebook.tsx +15 -15
- package/src/lib/authentication/ui/icons/Github.tsx +16 -16
- package/src/lib/authentication/ui/icons/Google.tsx +16 -16
- package/src/lib/authentication/ui/icons/Microsoft.tsx +12 -12
- package/src/lib/authentication/ui/icons/X.tsx +10 -10
- package/src/lib/authentication/utils/relativeRedirectUrl.ts +16 -16
- package/src/lib/components/AnchorLink.tsx +24 -24
- package/src/lib/components/Banner.tsx +52 -52
- package/src/lib/components/Bootstrap.tsx +77 -77
- package/src/lib/components/BuildCheck.tsx +87 -87
- package/src/lib/components/CategoryHeading.tsx +19 -19
- package/src/lib/components/ClientOnly.tsx +19 -19
- package/src/lib/components/ErrorPage.tsx +26 -26
- package/src/lib/components/Footer.tsx +140 -140
- package/src/lib/components/Framed.tsx +51 -51
- package/src/lib/components/Header.tsx +11 -3
- package/src/lib/components/HeaderNavigation.tsx +148 -148
- package/src/lib/components/Heading.tsx +84 -84
- package/src/lib/components/InlineCode.tsx +15 -15
- package/src/lib/components/LandingLayout.tsx +21 -0
- package/src/lib/components/LanguageIcon.tsx +199 -199
- package/src/lib/components/Markdown.tsx +55 -55
- package/src/lib/components/Meta.tsx +46 -46
- package/src/lib/components/MobileTopNavigation.tsx +11 -3
- package/src/lib/components/NotFoundPage.tsx +37 -37
- package/src/lib/components/PageProgress.tsx +28 -28
- package/src/lib/components/PagefindSearchMeta.tsx +14 -14
- package/src/lib/components/Pagination.tsx +44 -44
- package/src/lib/components/PathRenderer.tsx +61 -61
- package/src/lib/components/PluginHeads.tsx +17 -17
- package/src/lib/components/Search.tsx +70 -70
- package/src/lib/components/Slot.tsx +64 -64
- package/src/lib/components/Spinner.tsx +5 -5
- package/src/lib/components/StatusPage.tsx +96 -96
- package/src/lib/components/ThemeSwitch.tsx +49 -49
- package/src/lib/components/Typography.tsx +12 -12
- package/src/lib/components/Zudoku.tsx +106 -106
- package/src/lib/components/cache.ts +27 -27
- package/src/lib/components/context/RenderContext.ts +11 -11
- package/src/lib/components/context/RouterEventsEmitter.tsx +19 -19
- package/src/lib/components/context/SlotProvider.tsx +149 -149
- package/src/lib/components/context/ViewportAnchorContext.tsx +137 -137
- package/src/lib/components/context/ZudokuContext.ts +140 -140
- package/src/lib/components/context/ZudokuProvider.tsx +22 -22
- package/src/lib/components/context/ZudokuReactContext.tsx +17 -17
- package/src/lib/components/index.ts +2 -0
- package/src/lib/components/navigation/Navigation.tsx +51 -51
- package/src/lib/components/navigation/NavigationBadge.tsx +48 -48
- package/src/lib/components/navigation/NavigationFilterContext.tsx +28 -28
- package/src/lib/components/navigation/NavigationWrapper.tsx +49 -49
- package/src/lib/components/navigation/Toc.tsx +137 -137
- package/src/lib/components/navigation/ZudokuLogo.tsx +25 -25
- package/src/lib/components/navigation/ZuploLogo.tsx +14 -14
- package/src/lib/components/navigation/utils.ts +234 -234
- package/src/lib/core/RouteGuard.tsx +11 -1
- package/src/lib/core/ZudokuContext.ts +271 -271
- package/src/lib/core/plugins.ts +138 -138
- package/src/lib/core/react-query.ts +1 -1
- package/src/lib/core/router.ts +1 -1
- package/src/lib/core/transform-config.ts +67 -67
- package/src/lib/demo/DemoAnnouncement.tsx +17 -17
- package/src/lib/errors/ErrorAlert.tsx +28 -28
- package/src/lib/errors/RouterError.tsx +18 -18
- package/src/lib/errors/ServerError.tsx +5 -5
- package/src/lib/errors/TopLevelError.tsx +6 -6
- package/src/lib/hooks/index.ts +13 -13
- package/src/lib/hooks/useEvent.ts +41 -41
- package/src/lib/hooks/useHighlighter.ts +15 -15
- package/src/lib/hooks/useHotkey.ts +70 -70
- package/src/lib/icons.ts +2 -2
- package/src/lib/navigation/applyRules.ts +127 -127
- package/src/lib/navigation/pathMatcher.ts +81 -81
- package/src/lib/oas/graphql/circular.ts +69 -69
- package/src/lib/oas/graphql/constants.ts +2 -2
- package/src/lib/oas/graphql/index.ts +791 -791
- package/src/lib/oas/parser/dereference/index.ts +84 -84
- package/src/lib/oas/parser/dereference/resolveRef.ts +32 -32
- package/src/lib/oas/parser/index.ts +129 -129
- package/src/lib/oas/parser/schemas/v3.0.json +1489 -1489
- package/src/lib/oas/parser/schemas/v3.1.json +1298 -1298
- package/src/lib/oas/parser/upgrade/index.ts +174 -174
- package/src/lib/plugins/api-catalog/index.tsx +119 -119
- package/src/lib/plugins/api-keys/ProtectedRoute.tsx +33 -33
- package/src/lib/plugins/api-keys/index.tsx +274 -272
- package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +67 -67
- package/src/lib/plugins/custom-pages/index.tsx +32 -32
- package/src/lib/plugins/markdown/assets/ChatGPTLogo.tsx +11 -11
- package/src/lib/plugins/markdown/assets/ClaudeLogo.tsx +19 -19
- package/src/lib/plugins/markdown/index.tsx +62 -62
- package/src/lib/plugins/openapi/ColorizedParam.tsx +115 -115
- package/src/lib/plugins/openapi/Endpoint.tsx +85 -85
- package/src/lib/plugins/openapi/MCPEndpoint.tsx +273 -273
- package/src/lib/plugins/openapi/OasProvider.tsx +74 -74
- package/src/lib/plugins/openapi/OperationList.tsx +275 -275
- package/src/lib/plugins/openapi/ParamInfos.tsx +96 -96
- package/src/lib/plugins/openapi/ParameterList.tsx +53 -53
- package/src/lib/plugins/openapi/ParameterListItem.tsx +161 -161
- package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +67 -67
- package/src/lib/plugins/openapi/SidecarBox.tsx +57 -57
- package/src/lib/plugins/openapi/SimpleSelect.tsx +46 -46
- package/src/lib/plugins/openapi/client/GraphQLClient.tsx +73 -73
- package/src/lib/plugins/openapi/client/GraphQLContext.tsx +16 -16
- package/src/lib/plugins/openapi/client/createServer.ts +35 -35
- package/src/lib/plugins/openapi/client/useCreateQuery.ts +44 -44
- package/src/lib/plugins/openapi/components/ConstValue.tsx +24 -24
- package/src/lib/plugins/openapi/components/NonHighlightedCode.tsx +22 -22
- package/src/lib/plugins/openapi/components/SelectOnClick.tsx +29 -29
- package/src/lib/plugins/openapi/context.tsx +16 -16
- package/src/lib/plugins/openapi/graphql/fragment-masking.ts +104 -104
- package/src/lib/plugins/openapi/graphql/gql.ts +95 -95
- package/src/lib/plugins/openapi/graphql/graphql.ts +835 -835
- package/src/lib/plugins/openapi/graphql/index.ts +2 -2
- package/src/lib/plugins/openapi/index.tsx +221 -221
- package/src/lib/plugins/openapi/interfaces.ts +100 -100
- package/src/lib/plugins/openapi/playground/Headers.tsx +6 -6
- package/src/lib/plugins/openapi/playground/PathParams.tsx +53 -53
- package/src/lib/plugins/openapi/playground/Spinner.tsx +87 -87
- package/src/lib/plugins/openapi/playground/createUrl.ts +25 -25
- package/src/lib/plugins/openapi/playground/fileUtils.ts +36 -36
- package/src/lib/plugins/openapi/playground/rememberedIdentity.ts +26 -26
- package/src/lib/plugins/openapi/playground/request-panel/UrlPath.tsx +31 -31
- package/src/lib/plugins/openapi/playground/request-panel/UrlQueryParams.tsx +29 -29
- package/src/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.ts +340 -340
- package/src/lib/plugins/openapi/playground/result-panel/ResponseStatusBar.tsx +117 -117
- package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.ts +36 -36
- package/src/lib/plugins/openapi/playground/serializeQueryParams.ts +125 -125
- package/src/lib/plugins/openapi/playground/useRememberSkipLoginDialog.tsx +23 -23
- package/src/lib/plugins/openapi/processors/removeExtensions.ts +29 -29
- package/src/lib/plugins/openapi/processors/removeParameters.ts +103 -103
- package/src/lib/plugins/openapi/processors/removePaths.ts +57 -57
- package/src/lib/plugins/openapi/processors/traverse.ts +1 -1
- package/src/lib/plugins/openapi/schema/SchemaExampleAndDefault.tsx +39 -39
- package/src/lib/plugins/openapi/schema/UnionView.tsx +132 -132
- package/src/lib/plugins/openapi/schema/union-helpers.ts +123 -123
- package/src/lib/plugins/openapi/schema/utils.ts +49 -49
- package/src/lib/plugins/openapi/state.ts +36 -36
- package/src/lib/plugins/openapi/util/buildTagCategories.ts +54 -54
- package/src/lib/plugins/openapi/util/createHttpSnippet.ts +135 -135
- package/src/lib/plugins/openapi/util/createNavigationCategory.tsx +39 -39
- package/src/lib/plugins/openapi/util/generateSchemaExample.ts +191 -191
- package/src/lib/plugins/openapi/util/getRoutes.tsx +265 -265
- package/src/lib/plugins/openapi/util/methodColorMap.tsx +11 -11
- package/src/lib/plugins/openapi/util/methodToColor.ts +27 -27
- package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +32 -32
- package/src/lib/plugins/openapi/util/useWarmupSchema.ts +23 -23
- package/src/lib/plugins/search-inkeep/index.tsx +108 -108
- package/src/lib/plugins/search-inkeep/inkeep.ts +24 -24
- package/src/lib/plugins/search-pagefind/get-results.tsx +74 -74
- package/src/lib/plugins/search-pagefind/index.tsx +21 -21
- package/src/lib/plugins/search-pagefind/types.ts +118 -118
- package/src/lib/shiki-constants.ts +24 -24
- package/src/lib/testing/index.tsx +146 -146
- package/src/lib/ui/Accordion.tsx +55 -55
- package/src/lib/ui/Alert.tsx +84 -84
- package/src/lib/ui/AlertDialog.tsx +195 -195
- package/src/lib/ui/AspectRatio.tsx +5 -5
- package/src/lib/ui/Badge.tsx +48 -48
- package/src/lib/ui/Breadcrumb.tsx +115 -115
- package/src/lib/ui/Button.tsx +65 -65
- package/src/lib/ui/ButtonGroup.tsx +80 -80
- package/src/lib/ui/Callout.tsx +113 -113
- package/src/lib/ui/Card.tsx +95 -95
- package/src/lib/ui/Carousel.tsx +259 -259
- package/src/lib/ui/Checkbox.tsx +28 -28
- package/src/lib/ui/CodeBlock.tsx +92 -92
- package/src/lib/ui/CodeTabPanel.tsx +13 -13
- package/src/lib/ui/CodeTabs.tsx +161 -161
- package/src/lib/ui/Collapsible.tsx +31 -31
- package/src/lib/ui/Dialog.tsx +138 -138
- package/src/lib/ui/DismissibleAlert.tsx +74 -74
- package/src/lib/ui/Drawer.tsx +121 -121
- package/src/lib/ui/DropdownMenu.tsx +254 -254
- package/src/lib/ui/EmbeddedCodeBlock.tsx +99 -99
- package/src/lib/ui/Form.tsx +176 -176
- package/src/lib/ui/Frame.tsx +81 -81
- package/src/lib/ui/HoverCard.tsx +26 -26
- package/src/lib/ui/Input.tsx +23 -23
- package/src/lib/ui/InputGroup.tsx +168 -168
- package/src/lib/ui/Item.tsx +213 -213
- package/src/lib/ui/Kbd.tsx +28 -28
- package/src/lib/ui/Label.tsx +23 -23
- package/src/lib/ui/NativeSelect.tsx +48 -48
- package/src/lib/ui/NavigationMenu.tsx +169 -169
- package/src/lib/ui/Pagination.tsx +115 -115
- package/src/lib/ui/Popover.tsx +28 -28
- package/src/lib/ui/Progress.tsx +25 -25
- package/src/lib/ui/RadioGroup.tsx +41 -41
- package/src/lib/ui/ReactComponentDoc.tsx +68 -68
- package/src/lib/ui/ScrollArea.tsx +45 -45
- package/src/lib/ui/Secret.tsx +133 -133
- package/src/lib/ui/Select.tsx +184 -184
- package/src/lib/ui/Separator.tsx +25 -25
- package/src/lib/ui/Skeleton.tsx +15 -15
- package/src/lib/ui/Slider.tsx +25 -25
- package/src/lib/ui/Stepper.tsx +8 -8
- package/src/lib/ui/Switch.tsx +26 -26
- package/src/lib/ui/SyntaxHighlight.tsx +54 -54
- package/src/lib/ui/Tabs.tsx +52 -52
- package/src/lib/ui/Textarea.tsx +22 -22
- package/src/lib/ui/Toggle.tsx +42 -42
- package/src/lib/ui/ToggleGroup.tsx +58 -58
- package/src/lib/ui/Tooltip.tsx +65 -65
- package/src/lib/ui/Value.tsx +42 -42
- package/src/lib/ui/util.tsx +8 -8
- package/src/lib/util/cn.ts +6 -6
- package/src/lib/util/createVariantComponent.tsx +61 -61
- package/src/lib/util/ensureArray.ts +3 -3
- package/src/lib/util/flattenAllOf.ts +72 -72
- package/src/lib/util/flattenAllOfProcessor.ts +74 -74
- package/src/lib/util/hastToJsx.tsx +67 -67
- package/src/lib/util/humanFileSize.ts +15 -15
- package/src/lib/util/invariant.ts +52 -52
- package/src/lib/util/joinUrl.ts +60 -60
- package/src/lib/util/logInit.ts +9 -9
- package/src/lib/util/objectEntries.ts +5 -5
- package/src/lib/util/os.ts +18 -18
- package/src/lib/util/pastellize.ts +25 -25
- package/src/lib/util/problemJson.ts +63 -63
- package/src/lib/util/readFrontmatter.ts +14 -14
- package/src/lib/util/renderIf.ts +4 -4
- package/src/lib/util/scrollIntoViewIfNeeded.ts +18 -18
- package/src/lib/util/slugify.ts +21 -21
- package/src/lib/util/syncZustandState.ts +22 -22
- package/src/lib/util/traverse.ts +70 -70
- package/src/lib/util/types.ts +7 -7
- package/src/lib/util/url.ts +18 -18
- package/src/lib/util/useCopyToClipboard.ts +17 -17
- package/src/lib/util/useExposedProps.tsx +27 -27
- package/src/lib/util/useIsomorphicLayoutEffect.ts +6 -6
- package/src/lib/util/useLatest.ts +18 -18
- package/src/lib/util/useOnScreen.ts +34 -34
- package/src/lib/util/useScrollToAnchor.ts +70 -70
- package/src/lib/util/useScrollToTop.ts +18 -18
- package/src/types.d.ts +57 -53
- package/src/vite/api/SchemaManager.ts +371 -371
- package/src/vite/api/schema-codegen.ts +163 -163
- package/src/vite/config.ts +5 -1
- package/src/vite/css/collect.ts +42 -42
- package/src/vite/css/plugin.ts +105 -105
- package/src/vite/debug.ts +18 -18
- package/src/vite/dev-portal-env.ts +93 -0
- package/src/vite/html.ts +54 -54
- package/src/vite/index.ts +2 -2
- package/src/vite/mdx/rehype-extract-toc-with-jsx-export.ts +19 -19
- package/src/vite/mdx/rehype-extract-toc-with-jsx.ts +70 -70
- package/src/vite/mdx/remark-code-tabs.ts +47 -47
- package/src/vite/mdx/remark-inject-filepath.ts +13 -13
- package/src/vite/mdx/remark-last-modified.ts +103 -103
- package/src/vite/mdx/remark-link-rewrite.ts +23 -23
- package/src/vite/mdx/remark-normalize-image-url.ts +15 -15
- package/src/vite/mdx/remark-static-generation.ts +149 -149
- package/src/vite/mdx/utils.ts +32 -32
- package/src/vite/output.ts +261 -261
- package/src/vite/package-root.ts +12 -12
- package/src/vite/pagefind-dev-index.ts +100 -100
- package/src/vite/plugin-api-keys.ts +6 -1
- package/src/vite/plugin-api.ts +4 -1
- package/src/vite/plugin-auth.ts +5 -1
- package/src/vite/plugin-component.ts +1 -1
- package/src/vite/plugin-config-reload.ts +49 -49
- package/src/vite/plugin-doc-metadata.ts +45 -45
- package/src/vite/plugin-docs.ts +4 -1
- package/src/vite/plugin-metadata.ts +30 -30
- package/src/vite/plugin-modules.ts +144 -0
- package/src/vite/plugin-shiki-register.ts +97 -97
- package/src/vite/plugin-theme.ts +399 -399
- package/src/vite/plugin.ts +2 -0
- package/src/vite/prerender/utils.ts +76 -76
- package/src/vite/prerender/worker.ts +118 -118
- package/src/vite/reporter.ts +34 -34
- package/src/vite/shadcn-registry.ts +48 -48
- package/src/vite/sitemap.ts +78 -78
- package/src/vite/ssr-templates/cloudflare.ts +19 -19
- package/src/vite/ssr-templates/node.ts +25 -25
- package/src/vite/ssr-templates/vercel.ts +22 -22
- package/src/vite/zuplo.ts +30 -30
- package/src/vite-env.d.ts +6 -6
- package/src/zuplo/enrich-with-zuplo-mcp.ts +168 -168
- package/src/zuplo/enrich-with-zuplo.ts +256 -256
- package/src/zuplo/policy-types.ts +46 -46
- package/src/zuplo/with-zuplo-processors.ts +36 -36
- package/src/zuplo/with-zuplo.ts +14 -14
- package/LICENSE.md +0 -18
- package/dist/declarations/lib/components/navigation/ZudokuLogo.d.ts +0 -6
|
@@ -1,516 +1,516 @@
|
|
|
1
|
-
import * as oauth from "oauth4webapi";
|
|
2
|
-
import { ErrorBoundary } from "react-error-boundary";
|
|
3
|
-
import type { NavigateFunction } from "react-router";
|
|
4
|
-
import type { OpenIDAuthenticationConfig } from "../../../config/config.js";
|
|
5
|
-
import { ClientOnly } from "../../components/ClientOnly.js";
|
|
6
|
-
import { joinUrl } from "../../util/joinUrl.js";
|
|
7
|
-
import type {
|
|
8
|
-
AuthActionContext,
|
|
9
|
-
AuthActionOptions,
|
|
10
|
-
AuthenticationPlugin,
|
|
11
|
-
AuthenticationProviderInitializer,
|
|
12
|
-
} from "../authentication.js";
|
|
13
|
-
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
14
|
-
import { CallbackHandler } from "../components/CallbackHandler.js";
|
|
15
|
-
import { OAuthErrorPage } from "../components/OAuthErrorPage.js";
|
|
16
|
-
import { AuthorizationError } from "../errors.js";
|
|
17
|
-
import { type UserProfile, useAuthState } from "../state.js";
|
|
18
|
-
|
|
19
|
-
const CODE_VERIFIER_KEY = "code-verifier";
|
|
20
|
-
const STATE_KEY = "oauth-state";
|
|
21
|
-
|
|
22
|
-
export interface OpenIdProviderData {
|
|
23
|
-
// just for easy migration we also allow for undefined type. can be removed in the future.
|
|
24
|
-
type: "openid" | undefined;
|
|
25
|
-
accessToken: string;
|
|
26
|
-
idToken?: string;
|
|
27
|
-
refreshToken?: string;
|
|
28
|
-
expiresOn: Date;
|
|
29
|
-
tokenType: string;
|
|
30
|
-
claims: oauth.IDToken | undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
declare module "../state.js" {
|
|
34
|
-
interface ProviderDataRegistry {
|
|
35
|
-
openid: OpenIdProviderData;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const OPENID_CALLBACK_PATH = "/oauth/callback";
|
|
40
|
-
|
|
41
|
-
export class OpenIDAuthenticationProvider
|
|
42
|
-
extends CoreAuthenticationPlugin
|
|
43
|
-
implements AuthenticationPlugin
|
|
44
|
-
{
|
|
45
|
-
protected client: oauth.Client;
|
|
46
|
-
protected issuer: string;
|
|
47
|
-
protected authorizationServer: oauth.AuthorizationServer | undefined;
|
|
48
|
-
|
|
49
|
-
protected callbackUrlPath: string;
|
|
50
|
-
|
|
51
|
-
protected onAuthorizationUrl?: (
|
|
52
|
-
authorizationUrl: URL,
|
|
53
|
-
options: { isSignIn: boolean; isSignUp: boolean },
|
|
54
|
-
) => void;
|
|
55
|
-
|
|
56
|
-
protected readonly redirectToAfterSignUp: string | undefined;
|
|
57
|
-
protected readonly redirectToAfterSignIn: string | undefined;
|
|
58
|
-
protected readonly redirectToAfterSignOut: string;
|
|
59
|
-
private readonly audience?: string;
|
|
60
|
-
private readonly scopes: string[];
|
|
61
|
-
|
|
62
|
-
constructor({
|
|
63
|
-
issuer,
|
|
64
|
-
audience,
|
|
65
|
-
clientId,
|
|
66
|
-
redirectToAfterSignUp,
|
|
67
|
-
redirectToAfterSignIn,
|
|
68
|
-
redirectToAfterSignOut = "/",
|
|
69
|
-
basePath,
|
|
70
|
-
scopes,
|
|
71
|
-
}: OpenIDAuthenticationConfig) {
|
|
72
|
-
super();
|
|
73
|
-
this.client = {
|
|
74
|
-
client_id: clientId,
|
|
75
|
-
token_endpoint_auth_method: "none",
|
|
76
|
-
};
|
|
77
|
-
this.audience = audience;
|
|
78
|
-
this.issuer = issuer;
|
|
79
|
-
// This is the callback URL for the OAuth provider. So it needs the base path.
|
|
80
|
-
this.callbackUrlPath = joinUrl(basePath, OPENID_CALLBACK_PATH);
|
|
81
|
-
this.scopes = scopes ?? ["openid", "profile", "email"];
|
|
82
|
-
|
|
83
|
-
this.redirectToAfterSignUp = redirectToAfterSignUp;
|
|
84
|
-
this.redirectToAfterSignIn = redirectToAfterSignIn;
|
|
85
|
-
this.redirectToAfterSignOut = redirectToAfterSignOut;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
protected async getAuthServer() {
|
|
89
|
-
if (!this.authorizationServer) {
|
|
90
|
-
const issuerUrl = new URL(this.issuer);
|
|
91
|
-
const response = await oauth.discoveryRequest(issuerUrl);
|
|
92
|
-
this.authorizationServer = await oauth.processDiscoveryResponse(
|
|
93
|
-
issuerUrl,
|
|
94
|
-
response,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
return this.authorizationServer;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Sets the tokens from various OAuth responses
|
|
102
|
-
* @param response
|
|
103
|
-
*/
|
|
104
|
-
protected setTokensFromResponse(response: oauth.TokenEndpointResponse) {
|
|
105
|
-
if (!response.expires_in) {
|
|
106
|
-
throw new AuthorizationError("No expires_in in response");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const claims = response.id_token
|
|
110
|
-
? oauth.getValidatedIdTokenClaims(response)
|
|
111
|
-
: undefined;
|
|
112
|
-
|
|
113
|
-
const tokens: OpenIdProviderData = {
|
|
114
|
-
type: "openid",
|
|
115
|
-
accessToken: response.access_token,
|
|
116
|
-
refreshToken: response.refresh_token,
|
|
117
|
-
idToken: response.id_token,
|
|
118
|
-
expiresOn: new Date(Date.now() + response.expires_in * 1000),
|
|
119
|
-
tokenType: response.token_type,
|
|
120
|
-
claims,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const emailVerified =
|
|
124
|
-
claims?.email_verified === undefined
|
|
125
|
-
? undefined
|
|
126
|
-
: Boolean(claims?.email_verified);
|
|
127
|
-
|
|
128
|
-
useAuthState.setState((state) => {
|
|
129
|
-
const profile = state.profile
|
|
130
|
-
? {
|
|
131
|
-
...state.profile,
|
|
132
|
-
emailVerified:
|
|
133
|
-
emailVerified ?? state.profile.emailVerified ?? false,
|
|
134
|
-
}
|
|
135
|
-
: null;
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
profile,
|
|
139
|
-
providerData: tokens,
|
|
140
|
-
};
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async signUp(
|
|
145
|
-
_: { navigate: NavigateFunction },
|
|
146
|
-
{
|
|
147
|
-
redirectTo,
|
|
148
|
-
replace = false,
|
|
149
|
-
}: {
|
|
150
|
-
redirectTo?: string;
|
|
151
|
-
replace?: boolean;
|
|
152
|
-
} = {},
|
|
153
|
-
) {
|
|
154
|
-
return this.authorize({
|
|
155
|
-
redirectTo: this.redirectToAfterSignUp ?? redirectTo ?? "/",
|
|
156
|
-
replace,
|
|
157
|
-
isSignUp: true,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async signIn(
|
|
162
|
-
_: AuthActionContext,
|
|
163
|
-
{ redirectTo, replace = false }: AuthActionOptions,
|
|
164
|
-
) {
|
|
165
|
-
return this.authorize({
|
|
166
|
-
redirectTo: this.redirectToAfterSignIn ?? redirectTo ?? "/",
|
|
167
|
-
replace,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public async refreshUserProfile(): Promise<boolean> {
|
|
172
|
-
const accessToken = await this.getAccessToken();
|
|
173
|
-
const authServer = await this.getAuthServer();
|
|
174
|
-
|
|
175
|
-
const userInfoResponse = await oauth.userInfoRequest(
|
|
176
|
-
authServer,
|
|
177
|
-
this.client,
|
|
178
|
-
accessToken,
|
|
179
|
-
);
|
|
180
|
-
const userInfo = await userInfoResponse.json();
|
|
181
|
-
|
|
182
|
-
const { providerData } = useAuthState.getState();
|
|
183
|
-
const emailVerified =
|
|
184
|
-
providerData?.type === "openid"
|
|
185
|
-
? providerData.claims?.email_verified
|
|
186
|
-
: undefined;
|
|
187
|
-
|
|
188
|
-
const profile: UserProfile = {
|
|
189
|
-
sub: userInfo.sub,
|
|
190
|
-
email: userInfo.email,
|
|
191
|
-
name: userInfo.name,
|
|
192
|
-
emailVerified: userInfo.email_verified ?? emailVerified ?? false,
|
|
193
|
-
pictureUrl: userInfo.picture,
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
useAuthState.setState({
|
|
197
|
-
isAuthenticated: true,
|
|
198
|
-
isPending: false,
|
|
199
|
-
profile,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private async authorize({
|
|
206
|
-
redirectTo,
|
|
207
|
-
isSignUp = false,
|
|
208
|
-
replace = false,
|
|
209
|
-
}: {
|
|
210
|
-
redirectTo: string;
|
|
211
|
-
isSignUp?: boolean;
|
|
212
|
-
replace?: boolean;
|
|
213
|
-
}): Promise<void> {
|
|
214
|
-
const code_challenge_method = "S256";
|
|
215
|
-
const authorizationServer = await this.getAuthServer();
|
|
216
|
-
|
|
217
|
-
if (!authorizationServer.authorization_endpoint) {
|
|
218
|
-
throw new AuthorizationError("No authorization endpoint");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
|
|
223
|
-
* the codeVerifier and nonce in the end-user session such that it can be recovered as the user
|
|
224
|
-
* gets redirected from the authorization server back to your application.
|
|
225
|
-
*/
|
|
226
|
-
const codeVerifier = oauth.generateRandomCodeVerifier();
|
|
227
|
-
const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);
|
|
228
|
-
|
|
229
|
-
sessionStorage.setItem(CODE_VERIFIER_KEY, codeVerifier);
|
|
230
|
-
|
|
231
|
-
// redirect user to as.authorization_endpoint
|
|
232
|
-
const authorizationUrl = new URL(
|
|
233
|
-
authorizationServer.authorization_endpoint,
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
sessionStorage.setItem("redirect-to", redirectTo);
|
|
237
|
-
|
|
238
|
-
const redirectUrl = new URL(window.location.origin);
|
|
239
|
-
redirectUrl.pathname = this.callbackUrlPath;
|
|
240
|
-
redirectUrl.search = "";
|
|
241
|
-
redirectUrl.hash = "";
|
|
242
|
-
|
|
243
|
-
authorizationUrl.searchParams.set("client_id", this.client.client_id);
|
|
244
|
-
authorizationUrl.searchParams.set("redirect_uri", redirectUrl.toString());
|
|
245
|
-
authorizationUrl.searchParams.set("response_type", "code");
|
|
246
|
-
authorizationUrl.searchParams.set("scope", this.scopes.join(" "));
|
|
247
|
-
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
|
|
248
|
-
authorizationUrl.searchParams.set(
|
|
249
|
-
"code_challenge_method",
|
|
250
|
-
code_challenge_method,
|
|
251
|
-
);
|
|
252
|
-
if (this.audience) {
|
|
253
|
-
authorizationUrl.searchParams.set("audience", this.audience);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
this.onAuthorizationUrl?.(authorizationUrl, {
|
|
257
|
-
isSignIn: !isSignUp,
|
|
258
|
-
isSignUp,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* The state parameter is used to prevent CSRF attacks and should be used in all authorization requests.
|
|
263
|
-
* It is independent of PKCE and should be used regardless of PKCE support.
|
|
264
|
-
*/
|
|
265
|
-
const state = oauth.generateRandomState();
|
|
266
|
-
sessionStorage.setItem(STATE_KEY, state);
|
|
267
|
-
authorizationUrl.searchParams.set("state", state);
|
|
268
|
-
|
|
269
|
-
if (replace) {
|
|
270
|
-
location.replace(authorizationUrl.href);
|
|
271
|
-
} else {
|
|
272
|
-
location.href = authorizationUrl.href;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async getAccessToken(): Promise<string> {
|
|
277
|
-
const as = await this.getAuthServer();
|
|
278
|
-
const { providerData, setLoggedOut } = useAuthState.getState();
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
!providerData ||
|
|
282
|
-
(providerData?.type !== "openid" && providerData?.type !== undefined)
|
|
283
|
-
) {
|
|
284
|
-
useAuthState.getState().setLoggedOut();
|
|
285
|
-
throw new AuthorizationError("Invalid or incompatible provider data");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const tokenState = providerData;
|
|
289
|
-
|
|
290
|
-
if (new Date(tokenState.expiresOn) < new Date()) {
|
|
291
|
-
if (!tokenState.refreshToken) {
|
|
292
|
-
useAuthState.getState().setLoggedOut();
|
|
293
|
-
throw new AuthorizationError("No refresh token found");
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const response = await oauth.refreshTokenGrantRequest(
|
|
297
|
-
as,
|
|
298
|
-
this.client,
|
|
299
|
-
oauth.None(),
|
|
300
|
-
tokenState.refreshToken,
|
|
301
|
-
);
|
|
302
|
-
const result = await oauth.processRefreshTokenResponse(
|
|
303
|
-
as,
|
|
304
|
-
this.client,
|
|
305
|
-
response,
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
if (!result.access_token) {
|
|
309
|
-
setLoggedOut();
|
|
310
|
-
throw new AuthorizationError("No access token in response");
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
this.setTokensFromResponse(result);
|
|
314
|
-
|
|
315
|
-
return result.access_token.toString();
|
|
316
|
-
} else {
|
|
317
|
-
return tokenState.accessToken;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
signRequest = async (request: Request): Promise<Request> => {
|
|
322
|
-
const accessToken = await this.getAccessToken();
|
|
323
|
-
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
324
|
-
return request;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
signOut = async (_: AuthActionContext) => {
|
|
328
|
-
const { providerData } = useAuthState.getState();
|
|
329
|
-
const idToken =
|
|
330
|
-
providerData?.type === "openid" || providerData?.type === undefined
|
|
331
|
-
? providerData?.idToken
|
|
332
|
-
: undefined;
|
|
333
|
-
|
|
334
|
-
useAuthState.getState().setLoggedOut();
|
|
335
|
-
|
|
336
|
-
const as = await this.getAuthServer();
|
|
337
|
-
|
|
338
|
-
const redirectUrl = new URL(window.location.origin);
|
|
339
|
-
redirectUrl.pathname = joinUrl(
|
|
340
|
-
import.meta.env.BASE_URL,
|
|
341
|
-
this.redirectToAfterSignOut,
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
let logoutUrl: URL;
|
|
345
|
-
// The endSessionEndpoint is set, the IdP supports some form of logout,
|
|
346
|
-
// so we use the IdP logout. Otherwise, just redirect the user to home
|
|
347
|
-
if (as.end_session_endpoint) {
|
|
348
|
-
logoutUrl = new URL(as.end_session_endpoint);
|
|
349
|
-
if (idToken) {
|
|
350
|
-
logoutUrl.searchParams.set("id_token_hint", idToken);
|
|
351
|
-
}
|
|
352
|
-
logoutUrl.searchParams.set(
|
|
353
|
-
"post_logout_redirect_uri",
|
|
354
|
-
redirectUrl.toString(),
|
|
355
|
-
);
|
|
356
|
-
} else {
|
|
357
|
-
logoutUrl = redirectUrl;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
window.location.href = logoutUrl.toString();
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
onPageLoad = async () => {
|
|
364
|
-
const { providerData } = useAuthState.getState();
|
|
365
|
-
|
|
366
|
-
if (providerData?.type !== "openid") {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const tokenState = providerData;
|
|
370
|
-
|
|
371
|
-
if (new Date(tokenState.expiresOn) < new Date()) {
|
|
372
|
-
if (!tokenState.refreshToken) {
|
|
373
|
-
useAuthState.setState({
|
|
374
|
-
isAuthenticated: false,
|
|
375
|
-
isPending: false,
|
|
376
|
-
profile: null,
|
|
377
|
-
providerData: null,
|
|
378
|
-
});
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
const as = await this.getAuthServer();
|
|
384
|
-
const response = await oauth.refreshTokenGrantRequest(
|
|
385
|
-
as,
|
|
386
|
-
this.client,
|
|
387
|
-
oauth.None(),
|
|
388
|
-
tokenState.refreshToken,
|
|
389
|
-
);
|
|
390
|
-
const result = await oauth.processRefreshTokenResponse(
|
|
391
|
-
as,
|
|
392
|
-
this.client,
|
|
393
|
-
response,
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
if (!result.access_token) {
|
|
397
|
-
throw new AuthorizationError("No access token in response");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
this.setTokensFromResponse(result);
|
|
401
|
-
} catch {
|
|
402
|
-
useAuthState.getState().setLoggedOut();
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
useAuthState.setState({ isPending: false });
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
handleCallback = async () => {
|
|
411
|
-
const url = new URL(window.location.href);
|
|
412
|
-
const state = url.searchParams.get("state");
|
|
413
|
-
const storedState = sessionStorage.getItem(STATE_KEY);
|
|
414
|
-
sessionStorage.removeItem(STATE_KEY);
|
|
415
|
-
|
|
416
|
-
if (state !== storedState) {
|
|
417
|
-
throw new AuthorizationError("Invalid state parameter");
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// one eternity later, the user lands back on the redirect_uri
|
|
421
|
-
// Authorization Code Grant Request & Response
|
|
422
|
-
const codeVerifier = sessionStorage.getItem(CODE_VERIFIER_KEY);
|
|
423
|
-
sessionStorage.removeItem(CODE_VERIFIER_KEY);
|
|
424
|
-
if (!codeVerifier) {
|
|
425
|
-
throw new AuthorizationError("No code verifier found in state.");
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const authServer = await this.getAuthServer();
|
|
429
|
-
|
|
430
|
-
const params = oauth.validateAuthResponse(
|
|
431
|
-
authServer,
|
|
432
|
-
this.client,
|
|
433
|
-
url.searchParams,
|
|
434
|
-
state ?? undefined,
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
const redirectUrl = new URL(url);
|
|
438
|
-
redirectUrl.pathname = this.callbackUrlPath;
|
|
439
|
-
redirectUrl.search = "";
|
|
440
|
-
redirectUrl.hash = "";
|
|
441
|
-
|
|
442
|
-
const response = await oauth.authorizationCodeGrantRequest(
|
|
443
|
-
authServer,
|
|
444
|
-
this.client,
|
|
445
|
-
oauth.None(),
|
|
446
|
-
params,
|
|
447
|
-
redirectUrl.toString(),
|
|
448
|
-
codeVerifier,
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
const oauthResult = await oauth.processAuthorizationCodeResponse(
|
|
452
|
-
authServer,
|
|
453
|
-
this.client,
|
|
454
|
-
response,
|
|
455
|
-
);
|
|
456
|
-
|
|
457
|
-
this.setTokensFromResponse(oauthResult);
|
|
458
|
-
|
|
459
|
-
const accessToken = await this.getAccessToken();
|
|
460
|
-
|
|
461
|
-
const { providerData } = useAuthState.getState();
|
|
462
|
-
const claims =
|
|
463
|
-
providerData?.type === "openid" ? providerData.claims : undefined;
|
|
464
|
-
|
|
465
|
-
const userInfoResponse = await oauth.userInfoRequest(
|
|
466
|
-
authServer,
|
|
467
|
-
this.client,
|
|
468
|
-
accessToken,
|
|
469
|
-
);
|
|
470
|
-
const userInfo = await userInfoResponse.json();
|
|
471
|
-
|
|
472
|
-
const profile: UserProfile = {
|
|
473
|
-
...userInfo,
|
|
474
|
-
sub: userInfo.sub,
|
|
475
|
-
email: userInfo.email,
|
|
476
|
-
name: userInfo.name,
|
|
477
|
-
emailVerified: userInfo.email_verified ?? claims?.email_verified ?? false,
|
|
478
|
-
pictureUrl: userInfo.picture,
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
useAuthState.setState({
|
|
482
|
-
isAuthenticated: true,
|
|
483
|
-
isPending: false,
|
|
484
|
-
profile,
|
|
485
|
-
});
|
|
486
|
-
await this.refreshUserProfile();
|
|
487
|
-
|
|
488
|
-
const redirectTo = sessionStorage.getItem("redirect-to") ?? "/";
|
|
489
|
-
sessionStorage.removeItem("redirect-to");
|
|
490
|
-
return redirectTo;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
getRoutes() {
|
|
494
|
-
return [
|
|
495
|
-
...super.getRoutes(),
|
|
496
|
-
{
|
|
497
|
-
path: OPENID_CALLBACK_PATH,
|
|
498
|
-
element: (
|
|
499
|
-
<ClientOnly>
|
|
500
|
-
<ErrorBoundary
|
|
501
|
-
fallbackRender={({ error }) => <OAuthErrorPage error={error} />}
|
|
502
|
-
>
|
|
503
|
-
<CallbackHandler handleCallback={this.handleCallback} />
|
|
504
|
-
</ErrorBoundary>
|
|
505
|
-
</ClientOnly>
|
|
506
|
-
),
|
|
507
|
-
},
|
|
508
|
-
];
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const openIDAuth: AuthenticationProviderInitializer<
|
|
513
|
-
OpenIDAuthenticationConfig
|
|
514
|
-
> = (options) => new OpenIDAuthenticationProvider(options);
|
|
515
|
-
|
|
516
|
-
export default openIDAuth;
|
|
1
|
+
import * as oauth from "oauth4webapi";
|
|
2
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
3
|
+
import type { NavigateFunction } from "react-router";
|
|
4
|
+
import type { OpenIDAuthenticationConfig } from "../../../config/config.js";
|
|
5
|
+
import { ClientOnly } from "../../components/ClientOnly.js";
|
|
6
|
+
import { joinUrl } from "../../util/joinUrl.js";
|
|
7
|
+
import type {
|
|
8
|
+
AuthActionContext,
|
|
9
|
+
AuthActionOptions,
|
|
10
|
+
AuthenticationPlugin,
|
|
11
|
+
AuthenticationProviderInitializer,
|
|
12
|
+
} from "../authentication.js";
|
|
13
|
+
import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
|
|
14
|
+
import { CallbackHandler } from "../components/CallbackHandler.js";
|
|
15
|
+
import { OAuthErrorPage } from "../components/OAuthErrorPage.js";
|
|
16
|
+
import { AuthorizationError } from "../errors.js";
|
|
17
|
+
import { type UserProfile, useAuthState } from "../state.js";
|
|
18
|
+
|
|
19
|
+
const CODE_VERIFIER_KEY = "code-verifier";
|
|
20
|
+
const STATE_KEY = "oauth-state";
|
|
21
|
+
|
|
22
|
+
export interface OpenIdProviderData {
|
|
23
|
+
// just for easy migration we also allow for undefined type. can be removed in the future.
|
|
24
|
+
type: "openid" | undefined;
|
|
25
|
+
accessToken: string;
|
|
26
|
+
idToken?: string;
|
|
27
|
+
refreshToken?: string;
|
|
28
|
+
expiresOn: Date;
|
|
29
|
+
tokenType: string;
|
|
30
|
+
claims: oauth.IDToken | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare module "../state.js" {
|
|
34
|
+
interface ProviderDataRegistry {
|
|
35
|
+
openid: OpenIdProviderData;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const OPENID_CALLBACK_PATH = "/oauth/callback";
|
|
40
|
+
|
|
41
|
+
export class OpenIDAuthenticationProvider
|
|
42
|
+
extends CoreAuthenticationPlugin
|
|
43
|
+
implements AuthenticationPlugin
|
|
44
|
+
{
|
|
45
|
+
protected client: oauth.Client;
|
|
46
|
+
protected issuer: string;
|
|
47
|
+
protected authorizationServer: oauth.AuthorizationServer | undefined;
|
|
48
|
+
|
|
49
|
+
protected callbackUrlPath: string;
|
|
50
|
+
|
|
51
|
+
protected onAuthorizationUrl?: (
|
|
52
|
+
authorizationUrl: URL,
|
|
53
|
+
options: { isSignIn: boolean; isSignUp: boolean },
|
|
54
|
+
) => void;
|
|
55
|
+
|
|
56
|
+
protected readonly redirectToAfterSignUp: string | undefined;
|
|
57
|
+
protected readonly redirectToAfterSignIn: string | undefined;
|
|
58
|
+
protected readonly redirectToAfterSignOut: string;
|
|
59
|
+
private readonly audience?: string;
|
|
60
|
+
private readonly scopes: string[];
|
|
61
|
+
|
|
62
|
+
constructor({
|
|
63
|
+
issuer,
|
|
64
|
+
audience,
|
|
65
|
+
clientId,
|
|
66
|
+
redirectToAfterSignUp,
|
|
67
|
+
redirectToAfterSignIn,
|
|
68
|
+
redirectToAfterSignOut = "/",
|
|
69
|
+
basePath,
|
|
70
|
+
scopes,
|
|
71
|
+
}: OpenIDAuthenticationConfig) {
|
|
72
|
+
super();
|
|
73
|
+
this.client = {
|
|
74
|
+
client_id: clientId,
|
|
75
|
+
token_endpoint_auth_method: "none",
|
|
76
|
+
};
|
|
77
|
+
this.audience = audience;
|
|
78
|
+
this.issuer = issuer;
|
|
79
|
+
// This is the callback URL for the OAuth provider. So it needs the base path.
|
|
80
|
+
this.callbackUrlPath = joinUrl(basePath, OPENID_CALLBACK_PATH);
|
|
81
|
+
this.scopes = scopes ?? ["openid", "profile", "email"];
|
|
82
|
+
|
|
83
|
+
this.redirectToAfterSignUp = redirectToAfterSignUp;
|
|
84
|
+
this.redirectToAfterSignIn = redirectToAfterSignIn;
|
|
85
|
+
this.redirectToAfterSignOut = redirectToAfterSignOut;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected async getAuthServer() {
|
|
89
|
+
if (!this.authorizationServer) {
|
|
90
|
+
const issuerUrl = new URL(this.issuer);
|
|
91
|
+
const response = await oauth.discoveryRequest(issuerUrl);
|
|
92
|
+
this.authorizationServer = await oauth.processDiscoveryResponse(
|
|
93
|
+
issuerUrl,
|
|
94
|
+
response,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return this.authorizationServer;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Sets the tokens from various OAuth responses
|
|
102
|
+
* @param response
|
|
103
|
+
*/
|
|
104
|
+
protected setTokensFromResponse(response: oauth.TokenEndpointResponse) {
|
|
105
|
+
if (!response.expires_in) {
|
|
106
|
+
throw new AuthorizationError("No expires_in in response");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const claims = response.id_token
|
|
110
|
+
? oauth.getValidatedIdTokenClaims(response)
|
|
111
|
+
: undefined;
|
|
112
|
+
|
|
113
|
+
const tokens: OpenIdProviderData = {
|
|
114
|
+
type: "openid",
|
|
115
|
+
accessToken: response.access_token,
|
|
116
|
+
refreshToken: response.refresh_token,
|
|
117
|
+
idToken: response.id_token,
|
|
118
|
+
expiresOn: new Date(Date.now() + response.expires_in * 1000),
|
|
119
|
+
tokenType: response.token_type,
|
|
120
|
+
claims,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const emailVerified =
|
|
124
|
+
claims?.email_verified === undefined
|
|
125
|
+
? undefined
|
|
126
|
+
: Boolean(claims?.email_verified);
|
|
127
|
+
|
|
128
|
+
useAuthState.setState((state) => {
|
|
129
|
+
const profile = state.profile
|
|
130
|
+
? {
|
|
131
|
+
...state.profile,
|
|
132
|
+
emailVerified:
|
|
133
|
+
emailVerified ?? state.profile.emailVerified ?? false,
|
|
134
|
+
}
|
|
135
|
+
: null;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
profile,
|
|
139
|
+
providerData: tokens,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async signUp(
|
|
145
|
+
_: { navigate: NavigateFunction },
|
|
146
|
+
{
|
|
147
|
+
redirectTo,
|
|
148
|
+
replace = false,
|
|
149
|
+
}: {
|
|
150
|
+
redirectTo?: string;
|
|
151
|
+
replace?: boolean;
|
|
152
|
+
} = {},
|
|
153
|
+
) {
|
|
154
|
+
return this.authorize({
|
|
155
|
+
redirectTo: this.redirectToAfterSignUp ?? redirectTo ?? "/",
|
|
156
|
+
replace,
|
|
157
|
+
isSignUp: true,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async signIn(
|
|
162
|
+
_: AuthActionContext,
|
|
163
|
+
{ redirectTo, replace = false }: AuthActionOptions,
|
|
164
|
+
) {
|
|
165
|
+
return this.authorize({
|
|
166
|
+
redirectTo: this.redirectToAfterSignIn ?? redirectTo ?? "/",
|
|
167
|
+
replace,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public async refreshUserProfile(): Promise<boolean> {
|
|
172
|
+
const accessToken = await this.getAccessToken();
|
|
173
|
+
const authServer = await this.getAuthServer();
|
|
174
|
+
|
|
175
|
+
const userInfoResponse = await oauth.userInfoRequest(
|
|
176
|
+
authServer,
|
|
177
|
+
this.client,
|
|
178
|
+
accessToken,
|
|
179
|
+
);
|
|
180
|
+
const userInfo = await userInfoResponse.json();
|
|
181
|
+
|
|
182
|
+
const { providerData } = useAuthState.getState();
|
|
183
|
+
const emailVerified =
|
|
184
|
+
providerData?.type === "openid"
|
|
185
|
+
? providerData.claims?.email_verified
|
|
186
|
+
: undefined;
|
|
187
|
+
|
|
188
|
+
const profile: UserProfile = {
|
|
189
|
+
sub: userInfo.sub,
|
|
190
|
+
email: userInfo.email,
|
|
191
|
+
name: userInfo.name,
|
|
192
|
+
emailVerified: userInfo.email_verified ?? emailVerified ?? false,
|
|
193
|
+
pictureUrl: userInfo.picture,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
useAuthState.setState({
|
|
197
|
+
isAuthenticated: true,
|
|
198
|
+
isPending: false,
|
|
199
|
+
profile,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async authorize({
|
|
206
|
+
redirectTo,
|
|
207
|
+
isSignUp = false,
|
|
208
|
+
replace = false,
|
|
209
|
+
}: {
|
|
210
|
+
redirectTo: string;
|
|
211
|
+
isSignUp?: boolean;
|
|
212
|
+
replace?: boolean;
|
|
213
|
+
}): Promise<void> {
|
|
214
|
+
const code_challenge_method = "S256";
|
|
215
|
+
const authorizationServer = await this.getAuthServer();
|
|
216
|
+
|
|
217
|
+
if (!authorizationServer.authorization_endpoint) {
|
|
218
|
+
throw new AuthorizationError("No authorization endpoint");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
|
|
223
|
+
* the codeVerifier and nonce in the end-user session such that it can be recovered as the user
|
|
224
|
+
* gets redirected from the authorization server back to your application.
|
|
225
|
+
*/
|
|
226
|
+
const codeVerifier = oauth.generateRandomCodeVerifier();
|
|
227
|
+
const codeChallenge = await oauth.calculatePKCECodeChallenge(codeVerifier);
|
|
228
|
+
|
|
229
|
+
sessionStorage.setItem(CODE_VERIFIER_KEY, codeVerifier);
|
|
230
|
+
|
|
231
|
+
// redirect user to as.authorization_endpoint
|
|
232
|
+
const authorizationUrl = new URL(
|
|
233
|
+
authorizationServer.authorization_endpoint,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
sessionStorage.setItem("redirect-to", redirectTo);
|
|
237
|
+
|
|
238
|
+
const redirectUrl = new URL(window.location.origin);
|
|
239
|
+
redirectUrl.pathname = this.callbackUrlPath;
|
|
240
|
+
redirectUrl.search = "";
|
|
241
|
+
redirectUrl.hash = "";
|
|
242
|
+
|
|
243
|
+
authorizationUrl.searchParams.set("client_id", this.client.client_id);
|
|
244
|
+
authorizationUrl.searchParams.set("redirect_uri", redirectUrl.toString());
|
|
245
|
+
authorizationUrl.searchParams.set("response_type", "code");
|
|
246
|
+
authorizationUrl.searchParams.set("scope", this.scopes.join(" "));
|
|
247
|
+
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
|
|
248
|
+
authorizationUrl.searchParams.set(
|
|
249
|
+
"code_challenge_method",
|
|
250
|
+
code_challenge_method,
|
|
251
|
+
);
|
|
252
|
+
if (this.audience) {
|
|
253
|
+
authorizationUrl.searchParams.set("audience", this.audience);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.onAuthorizationUrl?.(authorizationUrl, {
|
|
257
|
+
isSignIn: !isSignUp,
|
|
258
|
+
isSignUp,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The state parameter is used to prevent CSRF attacks and should be used in all authorization requests.
|
|
263
|
+
* It is independent of PKCE and should be used regardless of PKCE support.
|
|
264
|
+
*/
|
|
265
|
+
const state = oauth.generateRandomState();
|
|
266
|
+
sessionStorage.setItem(STATE_KEY, state);
|
|
267
|
+
authorizationUrl.searchParams.set("state", state);
|
|
268
|
+
|
|
269
|
+
if (replace) {
|
|
270
|
+
location.replace(authorizationUrl.href);
|
|
271
|
+
} else {
|
|
272
|
+
location.href = authorizationUrl.href;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async getAccessToken(): Promise<string> {
|
|
277
|
+
const as = await this.getAuthServer();
|
|
278
|
+
const { providerData, setLoggedOut } = useAuthState.getState();
|
|
279
|
+
|
|
280
|
+
if (
|
|
281
|
+
!providerData ||
|
|
282
|
+
(providerData?.type !== "openid" && providerData?.type !== undefined)
|
|
283
|
+
) {
|
|
284
|
+
useAuthState.getState().setLoggedOut();
|
|
285
|
+
throw new AuthorizationError("Invalid or incompatible provider data");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const tokenState = providerData;
|
|
289
|
+
|
|
290
|
+
if (new Date(tokenState.expiresOn) < new Date()) {
|
|
291
|
+
if (!tokenState.refreshToken) {
|
|
292
|
+
useAuthState.getState().setLoggedOut();
|
|
293
|
+
throw new AuthorizationError("No refresh token found");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const response = await oauth.refreshTokenGrantRequest(
|
|
297
|
+
as,
|
|
298
|
+
this.client,
|
|
299
|
+
oauth.None(),
|
|
300
|
+
tokenState.refreshToken,
|
|
301
|
+
);
|
|
302
|
+
const result = await oauth.processRefreshTokenResponse(
|
|
303
|
+
as,
|
|
304
|
+
this.client,
|
|
305
|
+
response,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (!result.access_token) {
|
|
309
|
+
setLoggedOut();
|
|
310
|
+
throw new AuthorizationError("No access token in response");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.setTokensFromResponse(result);
|
|
314
|
+
|
|
315
|
+
return result.access_token.toString();
|
|
316
|
+
} else {
|
|
317
|
+
return tokenState.accessToken;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
signRequest = async (request: Request): Promise<Request> => {
|
|
322
|
+
const accessToken = await this.getAccessToken();
|
|
323
|
+
request.headers.set("Authorization", `Bearer ${accessToken}`);
|
|
324
|
+
return request;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
signOut = async (_: AuthActionContext) => {
|
|
328
|
+
const { providerData } = useAuthState.getState();
|
|
329
|
+
const idToken =
|
|
330
|
+
providerData?.type === "openid" || providerData?.type === undefined
|
|
331
|
+
? providerData?.idToken
|
|
332
|
+
: undefined;
|
|
333
|
+
|
|
334
|
+
useAuthState.getState().setLoggedOut();
|
|
335
|
+
|
|
336
|
+
const as = await this.getAuthServer();
|
|
337
|
+
|
|
338
|
+
const redirectUrl = new URL(window.location.origin);
|
|
339
|
+
redirectUrl.pathname = joinUrl(
|
|
340
|
+
import.meta.env.BASE_URL,
|
|
341
|
+
this.redirectToAfterSignOut,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
let logoutUrl: URL;
|
|
345
|
+
// The endSessionEndpoint is set, the IdP supports some form of logout,
|
|
346
|
+
// so we use the IdP logout. Otherwise, just redirect the user to home
|
|
347
|
+
if (as.end_session_endpoint) {
|
|
348
|
+
logoutUrl = new URL(as.end_session_endpoint);
|
|
349
|
+
if (idToken) {
|
|
350
|
+
logoutUrl.searchParams.set("id_token_hint", idToken);
|
|
351
|
+
}
|
|
352
|
+
logoutUrl.searchParams.set(
|
|
353
|
+
"post_logout_redirect_uri",
|
|
354
|
+
redirectUrl.toString(),
|
|
355
|
+
);
|
|
356
|
+
} else {
|
|
357
|
+
logoutUrl = redirectUrl;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
window.location.href = logoutUrl.toString();
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
onPageLoad = async () => {
|
|
364
|
+
const { providerData } = useAuthState.getState();
|
|
365
|
+
|
|
366
|
+
if (providerData?.type !== "openid") {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const tokenState = providerData;
|
|
370
|
+
|
|
371
|
+
if (new Date(tokenState.expiresOn) < new Date()) {
|
|
372
|
+
if (!tokenState.refreshToken) {
|
|
373
|
+
useAuthState.setState({
|
|
374
|
+
isAuthenticated: false,
|
|
375
|
+
isPending: false,
|
|
376
|
+
profile: null,
|
|
377
|
+
providerData: null,
|
|
378
|
+
});
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const as = await this.getAuthServer();
|
|
384
|
+
const response = await oauth.refreshTokenGrantRequest(
|
|
385
|
+
as,
|
|
386
|
+
this.client,
|
|
387
|
+
oauth.None(),
|
|
388
|
+
tokenState.refreshToken,
|
|
389
|
+
);
|
|
390
|
+
const result = await oauth.processRefreshTokenResponse(
|
|
391
|
+
as,
|
|
392
|
+
this.client,
|
|
393
|
+
response,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
if (!result.access_token) {
|
|
397
|
+
throw new AuthorizationError("No access token in response");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.setTokensFromResponse(result);
|
|
401
|
+
} catch {
|
|
402
|
+
useAuthState.getState().setLoggedOut();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
useAuthState.setState({ isPending: false });
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
handleCallback = async () => {
|
|
411
|
+
const url = new URL(window.location.href);
|
|
412
|
+
const state = url.searchParams.get("state");
|
|
413
|
+
const storedState = sessionStorage.getItem(STATE_KEY);
|
|
414
|
+
sessionStorage.removeItem(STATE_KEY);
|
|
415
|
+
|
|
416
|
+
if (state !== storedState) {
|
|
417
|
+
throw new AuthorizationError("Invalid state parameter");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// one eternity later, the user lands back on the redirect_uri
|
|
421
|
+
// Authorization Code Grant Request & Response
|
|
422
|
+
const codeVerifier = sessionStorage.getItem(CODE_VERIFIER_KEY);
|
|
423
|
+
sessionStorage.removeItem(CODE_VERIFIER_KEY);
|
|
424
|
+
if (!codeVerifier) {
|
|
425
|
+
throw new AuthorizationError("No code verifier found in state.");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const authServer = await this.getAuthServer();
|
|
429
|
+
|
|
430
|
+
const params = oauth.validateAuthResponse(
|
|
431
|
+
authServer,
|
|
432
|
+
this.client,
|
|
433
|
+
url.searchParams,
|
|
434
|
+
state ?? undefined,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const redirectUrl = new URL(url);
|
|
438
|
+
redirectUrl.pathname = this.callbackUrlPath;
|
|
439
|
+
redirectUrl.search = "";
|
|
440
|
+
redirectUrl.hash = "";
|
|
441
|
+
|
|
442
|
+
const response = await oauth.authorizationCodeGrantRequest(
|
|
443
|
+
authServer,
|
|
444
|
+
this.client,
|
|
445
|
+
oauth.None(),
|
|
446
|
+
params,
|
|
447
|
+
redirectUrl.toString(),
|
|
448
|
+
codeVerifier,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
const oauthResult = await oauth.processAuthorizationCodeResponse(
|
|
452
|
+
authServer,
|
|
453
|
+
this.client,
|
|
454
|
+
response,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
this.setTokensFromResponse(oauthResult);
|
|
458
|
+
|
|
459
|
+
const accessToken = await this.getAccessToken();
|
|
460
|
+
|
|
461
|
+
const { providerData } = useAuthState.getState();
|
|
462
|
+
const claims =
|
|
463
|
+
providerData?.type === "openid" ? providerData.claims : undefined;
|
|
464
|
+
|
|
465
|
+
const userInfoResponse = await oauth.userInfoRequest(
|
|
466
|
+
authServer,
|
|
467
|
+
this.client,
|
|
468
|
+
accessToken,
|
|
469
|
+
);
|
|
470
|
+
const userInfo = await userInfoResponse.json();
|
|
471
|
+
|
|
472
|
+
const profile: UserProfile = {
|
|
473
|
+
...userInfo,
|
|
474
|
+
sub: userInfo.sub,
|
|
475
|
+
email: userInfo.email,
|
|
476
|
+
name: userInfo.name,
|
|
477
|
+
emailVerified: userInfo.email_verified ?? claims?.email_verified ?? false,
|
|
478
|
+
pictureUrl: userInfo.picture,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
useAuthState.setState({
|
|
482
|
+
isAuthenticated: true,
|
|
483
|
+
isPending: false,
|
|
484
|
+
profile,
|
|
485
|
+
});
|
|
486
|
+
await this.refreshUserProfile();
|
|
487
|
+
|
|
488
|
+
const redirectTo = sessionStorage.getItem("redirect-to") ?? "/";
|
|
489
|
+
sessionStorage.removeItem("redirect-to");
|
|
490
|
+
return redirectTo;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
getRoutes() {
|
|
494
|
+
return [
|
|
495
|
+
...super.getRoutes(),
|
|
496
|
+
{
|
|
497
|
+
path: OPENID_CALLBACK_PATH,
|
|
498
|
+
element: (
|
|
499
|
+
<ClientOnly>
|
|
500
|
+
<ErrorBoundary
|
|
501
|
+
fallbackRender={({ error }) => <OAuthErrorPage error={error} />}
|
|
502
|
+
>
|
|
503
|
+
<CallbackHandler handleCallback={this.handleCallback} />
|
|
504
|
+
</ErrorBoundary>
|
|
505
|
+
</ClientOnly>
|
|
506
|
+
),
|
|
507
|
+
},
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const openIDAuth: AuthenticationProviderInitializer<
|
|
513
|
+
OpenIDAuthenticationConfig
|
|
514
|
+
> = (options) => new OpenIDAuthenticationProvider(options);
|
|
515
|
+
|
|
516
|
+
export default openIDAuth;
|