UNPKG

fumadocs-openapi

Version:

Generate MDX docs for your OpenAPI spec

178 lines (177 loc) 10.2 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from '../../ui/components/dialog.js'; import { useForm } from 'react-hook-form'; import { Input, labelVariants } from '../../ui/components/input.js'; import { useQuery } from '../../utils/use-query.js'; import { useEffect, useState } from 'react'; import { cn } from 'fumadocs-ui/utils/cn'; import { buttonVariants } from 'fumadocs-ui/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../ui/components/select.js'; const FlowTypes = { password: { name: 'Resource Owner Password Flow', description: 'Authenticate using username and password.', }, clientCredentials: { name: 'Client Credentials', description: 'Intended for the server-to-server authentication.', }, authorizationCode: { name: 'Authorization code', description: 'Authenticate with 3rd party services', }, implicit: { name: 'Implicit', description: 'Retrieve the access token directly.', }, }; export function OauthDialog({ scheme, scopes, setToken, children, open, setOpen, }) { const [type, setType] = useState(() => { return Object.keys(scheme.flows)[0]; }); const form = useForm({ defaultValues: { clientId: '', clientSecret: '', username: '', password: '', }, }); const authCodeCallback = useQuery(async (code, state) => { const value = scheme.flows.authorizationCode; const res = await fetch(value.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', code, // note: `state` could be invalid, but server will check it redirect_uri: state.redirect_uri, client_id: state.client_id, client_secret: state.client_secret, }), }); if (!res.ok) throw new Error(await res.text()); const { access_token, token_type = 'Bearer' } = (await res.json()); setToken(`${token_type} ${access_token}`); setOpen(false); }); useEffect(() => { if (scheme.flows.authorizationCode) { const params = new URLSearchParams(window.location.search); const state = params.get('state'); const code = params.get('code'); if (state && code) { const parsedState = JSON.parse(state); setOpen(true); form.setValue('clientId', parsedState.client_id); form.setValue('clientSecret', parsedState.client_secret); authCodeCallback.start(code, parsedState); window.history.replaceState(null, '', window.location.pathname); return; } } if (scheme.flows.implicit && window.location.hash.length > 1) { const params = new URLSearchParams(window.location.hash.slice(1)); const state = params.get('state'); const token = params.get('access_token'); const type = params.get('token_type') ?? 'Bearer'; if (state && token) { const parsedState = JSON.parse(state); form.setValue('clientId', parsedState.client_id); setToken(`${type} ${token}`); window.history.replaceState(null, '', window.location.pathname); } } // eslint-disable-next-line react-hooks/exhaustive-deps -- first page load only }, []); const authorize = useQuery(async (values) => { if (type === 'implicit') { const value = scheme.flows[type]; const params = new URLSearchParams(); params.set('response_type', 'token'); params.set('client_id', values.clientId); params.set('redirect_uri', window.location.href); params.set('scope', scopes.join('+')); params.set('state', JSON.stringify({ client_id: values.clientId, redirect_uri: window.location.href, })); window.location.replace(`${value.authorizationUrl}?${params.toString()}`); return; } if (type === 'authorizationCode') { const value = scheme.flows[type]; const params = new URLSearchParams(); params.set('response_type', 'code'); params.set('client_id', values.clientId); params.set('redirect_uri', window.location.href); params.set('scope', scopes.join('+')); params.set('state', JSON.stringify({ client_id: values.clientId, client_secret: values.clientSecret, redirect_uri: window.location.href, })); window.location.replace(`${value.authorizationUrl}?${params.toString()}`); return; } let res; if (type === 'password') { const value = scheme.flows[type]; res = await fetch(value.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'password', username: values.username, password: values.password, scope: scopes.join('+'), }), }); } if (type === 'clientCredentials') { const value = scheme.flows[type]; res = await fetch(value.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: values.clientId, client_secret: values.clientSecret, scope: scopes.join('+'), }), }); } if (res) { if (!res.ok) throw new Error(await res.text()); const { access_token, token_type = 'Bearer' } = (await res.json()); setToken(`${token_type} ${access_token}`); setOpen(false); } }); const onSubmit = form.handleSubmit((values) => { return authorize.start(values); }); const isLoading = authorize.isLoading || authCodeCallback.isLoading; const error = authCodeCallback.error ?? authorize.error; return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [children, _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Authorization" }), _jsx(DialogDescription, { children: "Obtain the access token for API." })] }), _jsxs("form", { className: "flex flex-col gap-6", onSubmit: (e) => { void onSubmit(e); e.stopPropagation(); }, children: [_jsxs(Select, { value: type, onValueChange: setType, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(scheme.flows).map((key) => { const { name, description } = FlowTypes[key]; return (_jsxs(SelectItem, { value: key, children: [_jsx("p", { className: "font-medium", children: name }), _jsx("p", { className: "text-fd-muted-foreground", children: description })] }, key)); }) })] }), (type === 'authorizationCode' || type === 'clientCredentials' || type === 'implicit') && (_jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsx("label", { htmlFor: "client_id", className: cn(labelVariants()), children: "Client ID" }), _jsx("p", { className: "text-fd-muted-foreground text-sm", children: "The client ID of your OAuth application." }), _jsx(Input, { id: "client_id", placeholder: "Enter value", type: "text", autoComplete: "off", disabled: isLoading, ...form.register('clientId', { required: true }) })] })), (type === 'authorizationCode' || type === 'clientCredentials') && (_jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsx("label", { htmlFor: "client_secret", className: cn(labelVariants()), children: "Client Secret" }), _jsx("p", { className: "text-fd-muted-foreground text-sm", children: "The client secret of your OAuth application." }), _jsx(Input, { id: "client_secret", placeholder: "Enter value", type: "password", autoComplete: "off", disabled: isLoading, ...form.register('clientSecret', { required: true }) })] })), type === 'password' && (_jsxs(_Fragment, { children: [_jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsx("label", { htmlFor: "username", className: cn(labelVariants()), children: "Username" }), _jsx(Input, { id: "username", placeholder: "Enter value", type: "text", disabled: isLoading, autoComplete: "off", ...form.register('username', { required: true }) })] }), _jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsx("label", { htmlFor: "password", className: cn(labelVariants()), children: "Client Secret" }), _jsx(Input, { id: "password", placeholder: "Enter value", type: "password", autoComplete: "off", disabled: isLoading, ...form.register('password', { required: true }) })] })] })), error ? (_jsx("p", { className: "text-red-400 font-medium text-sm", children: String(error) })) : null, _jsx("button", { type: "submit", disabled: isLoading, className: cn(buttonVariants({ color: 'primary', })), children: authCodeCallback.isLoading ? 'Fetching token...' : 'Submit' })] })] })] })); } export const OauthDialogTrigger = DialogTrigger;