mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
feat: add local auth support if api storage provided
This commit is contained in:
@@ -67,13 +67,14 @@ export const ClientProvider = ({
|
||||
|
||||
export const useApi = (host?: ApiOptions["host"]): Api => {
|
||||
const context = useContext(ClientContext);
|
||||
if (!context) {
|
||||
throw new Error("useApi must be used within a ClientProvider");
|
||||
}
|
||||
|
||||
if (!context?.api || (host && host.length > 0 && host !== context.baseUrl)) {
|
||||
console.info("creating new api", { host });
|
||||
return new Api({ host: host ?? "" });
|
||||
}
|
||||
if (!context) {
|
||||
throw new Error("useApi must be used within a ClientProvider");
|
||||
}
|
||||
|
||||
return context.api;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ type UseAuth = {
|
||||
logout: () => Promise<void>;
|
||||
verify: () => Promise<void>;
|
||||
setToken: (token: string) => void;
|
||||
local: boolean;
|
||||
};
|
||||
|
||||
export const useAuth = (options?: { baseUrl?: string }): UseAuth => {
|
||||
@@ -60,5 +61,6 @@ export const useAuth = (options?: { baseUrl?: string }): UseAuth => {
|
||||
logout,
|
||||
setToken,
|
||||
verify,
|
||||
local: !!api.options.storage,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type React from "react";
|
||||
import { Children } from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { Children, forwardRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Link } from "ui/components/wouter/Link";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tooltip } from "@mantine/core";
|
||||
import clsx from "clsx";
|
||||
import { getBrowser } from "core/utils";
|
||||
import { getBrowser } from "bknd/utils";
|
||||
import type { Field } from "data/fields";
|
||||
import { Switch as RadixSwitch } from "radix-ui";
|
||||
import {
|
||||
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
setPath,
|
||||
} from "./utils";
|
||||
|
||||
export type NativeFormProps = {
|
||||
export type NativeFormProps = Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
||||
hiddenSubmit?: boolean;
|
||||
validateOn?: "change" | "submit";
|
||||
errorFieldSelector?: <K extends keyof HTMLElementTagNameMap>(name: string) => any | null;
|
||||
errorFieldSelector?: (selector: string) => any | null;
|
||||
reportValidity?: boolean;
|
||||
onSubmit?: (data: any, ctx: { event: FormEvent<HTMLFormElement> }) => Promise<void> | void;
|
||||
onSubmitInvalid?: (
|
||||
@@ -33,7 +33,7 @@ export type NativeFormProps = {
|
||||
ctx: { event: ChangeEvent<HTMLFormElement>; key: string; value: any; errors: InputError[] },
|
||||
) => Promise<void> | void;
|
||||
clean?: CleanOptions | true;
|
||||
} & Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit">;
|
||||
};
|
||||
|
||||
export type InputError = {
|
||||
name: string;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useInsertionEffect, useRef } from "react";
|
||||
import { type LinkProps, Link as WouterLink, useRoute, useRouter } from "wouter";
|
||||
import { type LinkProps, Link as WouterLink, useRouter } from "wouter";
|
||||
import { useEvent } from "../../hooks/use-event";
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { AppAuthOAuthStrategy, AppAuthSchema } from "auth/auth-schema";
|
||||
import clsx from "clsx";
|
||||
import { NativeForm } from "ui/components/form/native-form/NativeForm";
|
||||
import { transform } from "lodash-es";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import { transformObject } from "bknd/utils";
|
||||
import { useEffect, useState, type ComponentPropsWithoutRef, type FormEvent } from "react";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import { Group, Input, Password, Label } from "ui/components/form/Formy/components";
|
||||
import { SocialLink } from "./SocialLink";
|
||||
import { useAuth } from "bknd/client";
|
||||
import { Alert } from "ui/components/display/Alert";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "onSubmit" | "action"> & {
|
||||
export type LoginFormProps = Omit<ComponentPropsWithoutRef<"form">, "action"> & {
|
||||
className?: string;
|
||||
formData?: any;
|
||||
action: "login" | "register";
|
||||
@@ -23,25 +26,47 @@ export function AuthForm({
|
||||
action,
|
||||
auth,
|
||||
buttonLabel = action === "login" ? "Sign in" : "Sign up",
|
||||
onSubmit: _onSubmit,
|
||||
...props
|
||||
}: LoginFormProps) {
|
||||
const $auth = useAuth();
|
||||
const basepath = auth?.basepath ?? "/api/auth";
|
||||
const [error, setError] = useState<string>();
|
||||
const [, navigate] = useLocation();
|
||||
const password = {
|
||||
action: `${basepath}/password/${action}`,
|
||||
strategy: auth?.strategies?.password ?? ({ type: "password" } as const),
|
||||
};
|
||||
|
||||
const oauth = transform(
|
||||
auth?.strategies ?? {},
|
||||
(result, value, key) => {
|
||||
if (value.type !== "password") {
|
||||
result[key] = value.config;
|
||||
}
|
||||
},
|
||||
{},
|
||||
) as Record<string, AppAuthOAuthStrategy>;
|
||||
const oauth = transformObject(auth?.strategies ?? {}, (value) => {
|
||||
return value.type !== "password" ? value.config : undefined;
|
||||
}) as Record<string, AppAuthOAuthStrategy>;
|
||||
const has_oauth = Object.keys(oauth).length > 0;
|
||||
|
||||
async function onSubmit(data: any, ctx: { event: FormEvent<HTMLFormElement> }) {
|
||||
if ($auth?.local) {
|
||||
ctx.event.preventDefault();
|
||||
|
||||
const res = await $auth.login(data);
|
||||
if ("token" in res) {
|
||||
navigate("/");
|
||||
} else {
|
||||
setError((res as any).error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _onSubmit?.(ctx.event);
|
||||
// submit form
|
||||
ctx.event.currentTarget.submit();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if ($auth.user) {
|
||||
navigate("/");
|
||||
}
|
||||
}, [$auth.user]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{has_oauth && (
|
||||
@@ -63,10 +88,12 @@ export function AuthForm({
|
||||
<NativeForm
|
||||
method={method}
|
||||
action={password.action}
|
||||
onSubmit={onSubmit}
|
||||
{...(props as any)}
|
||||
validateOn="change"
|
||||
className={clsx("flex flex-col gap-3 w-full", className)}
|
||||
>
|
||||
{error && <Alert.Exception message={error} className="justify-center" />}
|
||||
<Group>
|
||||
<Label htmlFor="email">Email address</Label>
|
||||
<Input type="email" name="email" required />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
||||
import { ucFirstAllSnakeToPascalWithSpaces } from "bknd/utils";
|
||||
import type { ReactNode } from "react";
|
||||
import { Button } from "ui/components/buttons/Button";
|
||||
import type { IconType } from "ui/components/buttons/IconButton";
|
||||
|
||||
@@ -154,8 +154,10 @@ function UserMenu() {
|
||||
|
||||
async function handleLogout() {
|
||||
await auth.logout();
|
||||
// @todo: grab from somewhere constant
|
||||
navigate(logout_route, { reload: true });
|
||||
|
||||
if (!auth.local) {
|
||||
navigate(logout_route, { reload: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { McpClient, type McpClientConfig } from "jsonv-ts/mcp";
|
||||
import { useApi } from "bknd/client";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
|
||||
const clients = new Map<string, McpClient>();
|
||||
@@ -12,5 +13,14 @@ export function getClient(opts: McpClientConfig) {
|
||||
|
||||
export function useMcpClient() {
|
||||
const { config } = useBknd();
|
||||
return getClient({ url: window.location.origin + config.server.mcp.path });
|
||||
const api = useApi();
|
||||
const token = api.getAuthState().token;
|
||||
const headers =
|
||||
api.token_transport === "header" && token ? { Authorization: `Bearer ${token}` } : undefined;
|
||||
|
||||
return getClient({
|
||||
url: window.location.origin + config.server.mcp.path,
|
||||
fetch: api.fetcher,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
||||
export default function ToolsMcp() {
|
||||
useBrowserTitle(["MCP UI"]);
|
||||
|
||||
const { config, options } = useBknd();
|
||||
const { config } = useBknd();
|
||||
const feature = useMcpStore((state) => state.feature);
|
||||
const setFeature = useMcpStore((state) => state.setFeature);
|
||||
const content = useMcpStore((state) => state.content);
|
||||
|
||||
Reference in New Issue
Block a user