updated adapters to automatically verify auth

This commit is contained in:
dswbx
2025-01-25 09:09:09 +01:00
parent f64e5dac03
commit b0c5f6307a
11 changed files with 60 additions and 57 deletions

View File

@@ -13,11 +13,13 @@ export type Options = {
host?: string; host?: string;
}; };
export function getApi(Astro: TAstro, options: Options = { mode: "static" }) { export async function getApi(Astro: TAstro, options: Options = { mode: "static" }) {
return new Api({ const api = new Api({
host: new URL(Astro.request.url).origin, host: new URL(Astro.request.url).origin,
headers: options.mode === "dynamic" ? Astro.request.headers : undefined headers: options.mode === "dynamic" ? Astro.request.headers : undefined
}); });
await api.verifyAuth();
return api;
} }
let app: App; let app: App;

View File

@@ -29,8 +29,10 @@ export function createApi({ req }: GetServerSidePropsContext) {
} }
export function withApi<T>(handler: (ctx: GetServerSidePropsContext & { api: Api }) => T) { export function withApi<T>(handler: (ctx: GetServerSidePropsContext & { api: Api }) => T) {
return (ctx: GetServerSidePropsContext & { api: Api }) => { return async (ctx: GetServerSidePropsContext & { api: Api }) => {
return handler({ ...ctx, api: createApi(ctx) }); const api = createApi(ctx);
await api.verifyAuth();
return handler({ ...ctx, api });
}; };
} }

View File

@@ -1,9 +1,11 @@
import { useAuth } from "bknd/client";
import type { BkndAdminProps } from "bknd/ui"; import type { BkndAdminProps } from "bknd/ui";
import { Suspense, lazy, useEffect, useState } from "react"; import { Suspense, lazy, useEffect, useState } from "react";
export function adminPage(props?: BkndAdminProps) { export function adminPage(props?: BkndAdminProps) {
const Admin = lazy(() => import("bknd/ui").then((mod) => ({ default: mod.Admin }))); const Admin = lazy(() => import("bknd/ui").then((mod) => ({ default: mod.Admin })));
return () => { return () => {
const auth = useAuth();
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
@@ -13,7 +15,7 @@ export function adminPage(props?: BkndAdminProps) {
return ( return (
<Suspense> <Suspense>
<Admin {...props} /> <Admin withProvider={{ user: auth.user }} {...props} />
</Suspense> </Suspense>
); );
}; };

View File

@@ -1,5 +1,6 @@
import { type FrameworkBkndConfig, createFrameworkApp } from "adapter"; import { type FrameworkBkndConfig, createFrameworkApp } from "adapter";
import type { App } from "bknd"; import type { App } from "bknd";
import { Api } from "bknd/client";
export type RemixBkndConfig = FrameworkBkndConfig; export type RemixBkndConfig = FrameworkBkndConfig;
@@ -12,3 +13,19 @@ export function serve(config: RemixBkndConfig = {}) {
return app.fetch(args.request); return app.fetch(args.request);
}; };
} }
export function withApi<Args extends { request: Request; context: { api: Api } }, R>(
handler: (args: Args, api: Api) => Promise<R>
) {
return async (args: Args) => {
if (!args.context.api) {
args.context.api = new Api({
host: new URL(args.request.url).origin,
headers: args.request.headers
});
await args.context.api.verifyAuth();
}
return handler(args, args.context.api);
};
}

View File

@@ -65,7 +65,7 @@ import "bknd/dist/styles.css";
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
const user = api.getUser(); const user = api.getUser();
export const prerender = false; export const prerender = false;
@@ -94,7 +94,7 @@ Here is an example of using the API in static context:
```jsx ```jsx
--- ---
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro); const api = await getApi(Astro);
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
--- ---
@@ -109,7 +109,7 @@ On SSR pages, you can also access the authenticated user:
```jsx ```jsx
--- ---
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
const user = api.getUser(); const user = api.getUser();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");

View File

@@ -32,6 +32,9 @@ Now make sure that you wrap your root layout with the `ClientProvider` so that a
share the same context: share the same context:
```tsx ```tsx
// app/root.tsx // app/root.tsx
import { withApi } from "bknd/adapter/remix"
import { type Api, ClientProvider } from "bknd/client";
export function Layout(props) { export function Layout(props) {
// nothing to change here, just for orientation // nothing to change here, just for orientation
return ( return (
@@ -48,21 +51,12 @@ declare module "@remix-run/server-runtime" {
} }
// export a loader that initiates the API // export a loader that initiates the API
// and pass it through the context // and passes it down to args.context.api
export const loader = async (args: LoaderFunctionArgs) => { export const loader = withApi(async (args: LoaderFunctionArgs, api: Api) => {
const api = new Api({ return {
host: new URL(args.request.url).origin, user: api.getUser()
headers: args.request.headers };
}); });
// get the user from the API
const user = api.getUser();
// add api to the context
args.context.api = api;
return { user };
};
export default function App() { export default function App() {
const { user } = useLoaderData<typeof loader>(); const { user } = useLoaderData<typeof loader>();
@@ -93,15 +87,9 @@ Since the API has already been constructed in the root layout, you can now use i
import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useLoaderData } from "@remix-run/react"; import { useLoaderData } from "@remix-run/react";
export const loader = async (args: LoaderFunctionArgs) => { export const loader = async ({ context: { api } }: LoaderFunctionArgs) => {
const { api } = args.context;
// get the authenticated user
const user = api.getAuthState().user;
// get the data from the API
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
return { data, user }; return { data, user: api.getUser() };
}; };
export default function Index() { export default function Index() {

View File

@@ -4,8 +4,7 @@ import "bknd/dist/styles.css";
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
await api.verifyAuth();
const user = api.getUser(); const user = api.getUser();
export const prerender = false; export const prerender = false;

View File

@@ -2,7 +2,8 @@
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
const api = getApi(Astro);
const api = await getApi(Astro);
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
--- ---

View File

@@ -2,8 +2,7 @@
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
await api.verifyAuth();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
const user = api.getUser(); const user = api.getUser();

View File

@@ -1,12 +1,7 @@
import type { LoaderFunctionArgs } from "@remix-run/node"; import type { LoaderFunctionArgs } from "@remix-run/node";
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from "@remix-run/react"; import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from "@remix-run/react";
import { Api, ClientProvider } from "bknd/client"; import { withApi } from "bknd/adapter/remix";
import { type Api, ClientProvider } from "bknd/client";
declare module "@remix-run/server-runtime" {
export interface AppLoadContext {
api: Api;
}
}
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
@@ -26,20 +21,17 @@ export function Layout({ children }: { children: React.ReactNode }) {
); );
} }
export const loader = async (args: LoaderFunctionArgs) => { declare module "@remix-run/server-runtime" {
const api = new Api({ export interface AppLoadContext {
host: new URL(args.request.url).origin, api: Api;
headers: args.request.headers }
}); }
// add api to the context export const loader = withApi(async (args: LoaderFunctionArgs, api: Api) => {
args.context.api = api;
await api.verifyAuth();
return { return {
user: api.getAuthState()?.user user: api.getUser()
}; };
}; });
export default function App() { export default function App() {
const data = useLoaderData<typeof loader>(); const data = useLoaderData<typeof loader>();

View File

@@ -1,19 +1,20 @@
import { type MetaFunction, useLoaderData } from "@remix-run/react"; import { type MetaFunction, useLoaderData } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useAuth } from "bknd/client";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [{ title: "Remix & bknd" }, { name: "description", content: "Welcome to Remix & bknd!" }]; return [{ title: "Remix & bknd" }, { name: "description", content: "Welcome to Remix & bknd!" }];
}; };
export const loader = async (args: LoaderFunctionArgs) => { export const loader = async ({ context: { api } }: LoaderFunctionArgs) => {
const api = args.context.api;
await api.verifyAuth();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
return { data, user: api.getUser() }; return { data, user: api.getUser() };
}; };
export default function Index() { export default function Index() {
const { data, user } = useLoaderData<typeof loader>(); const { data, user } = useLoaderData<typeof loader>();
const auth = useAuth();
console.log("auth", auth);
return ( return (
<div> <div>