mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge remote-tracking branch 'origin/release/0.7' into fix/auth-api-include-cookie
# Conflicts: # app/src/auth/authenticate/Authenticator.ts
This commit is contained in:
71
app/__test__/api/Api.spec.ts
Normal file
71
app/__test__/api/Api.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import { sign } from "hono/jwt";
|
||||||
|
import { Api } from "../../src/Api";
|
||||||
|
|
||||||
|
describe("Api", async () => {
|
||||||
|
it("should construct without options", () => {
|
||||||
|
const api = new Api();
|
||||||
|
expect(api.baseUrl).toBe("http://localhost");
|
||||||
|
expect(api.isAuthVerified()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore force verify if no claims given", () => {
|
||||||
|
const api = new Api({ verified: true });
|
||||||
|
expect(api.baseUrl).toBe("http://localhost");
|
||||||
|
expect(api.isAuthVerified()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should construct from request (token)", async () => {
|
||||||
|
const token = await sign({ foo: "bar" }, "1234");
|
||||||
|
const request = new Request("http://example.com/test", {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const api = new Api({ request });
|
||||||
|
expect(api.isAuthVerified()).toBe(false);
|
||||||
|
|
||||||
|
const params = api.getParams();
|
||||||
|
expect(params.token).toBe(token);
|
||||||
|
expect(params.token_transport).toBe("header");
|
||||||
|
expect(params.host).toBe("http://example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should construct from request (cookie)", async () => {
|
||||||
|
const token = await sign({ foo: "bar" }, "1234");
|
||||||
|
const request = new Request("http://example.com/test", {
|
||||||
|
headers: {
|
||||||
|
Cookie: `auth=${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const api = new Api({ request });
|
||||||
|
expect(api.isAuthVerified()).toBe(false);
|
||||||
|
|
||||||
|
const params = api.getParams();
|
||||||
|
console.log(params);
|
||||||
|
expect(params.token).toBe(token);
|
||||||
|
expect(params.token_transport).toBe("cookie");
|
||||||
|
expect(params.host).toBe("http://example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should construct from token", async () => {
|
||||||
|
const token = await sign({ foo: "bar" }, "1234");
|
||||||
|
const api = new Api({ token });
|
||||||
|
expect(api.isAuthVerified()).toBe(false);
|
||||||
|
|
||||||
|
const params = api.getParams();
|
||||||
|
expect(params.token).toBe(token);
|
||||||
|
expect(params.token_transport).toBe("header");
|
||||||
|
expect(params.host).toBe("http://localhost");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prefer host when request is given", async () => {
|
||||||
|
const request = new Request("http://example.com/test");
|
||||||
|
const api = new Api({ request, host: "http://another.com" });
|
||||||
|
|
||||||
|
const params = api.getParams();
|
||||||
|
expect(params.token).toBeUndefined();
|
||||||
|
expect(params.token_transport).toBe("header");
|
||||||
|
expect(params.host).toBe("http://another.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.7.0-rc.1",
|
"version": "0.7.0-rc.4",
|
||||||
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
||||||
"homepage": "https://bknd.io",
|
"homepage": "https://bknd.io",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -17,14 +17,21 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ApiOptions = {
|
export type ApiOptions = {
|
||||||
host: string;
|
host?: string;
|
||||||
user?: TApiUser;
|
|
||||||
token?: string;
|
|
||||||
headers?: Headers;
|
headers?: Headers;
|
||||||
key?: string;
|
key?: string;
|
||||||
localStorage?: boolean;
|
localStorage?: boolean;
|
||||||
fetcher?: typeof fetch;
|
fetcher?: typeof fetch;
|
||||||
};
|
verified?: boolean;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
token?: string;
|
||||||
|
user?: TApiUser;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
request: Request;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export type AuthState = {
|
export type AuthState = {
|
||||||
token?: string;
|
token?: string;
|
||||||
@@ -43,14 +50,26 @@ export class Api {
|
|||||||
public auth!: AuthApi;
|
public auth!: AuthApi;
|
||||||
public media!: MediaApi;
|
public media!: MediaApi;
|
||||||
|
|
||||||
constructor(private readonly options: ApiOptions) {
|
constructor(private options: ApiOptions = {}) {
|
||||||
if (options.user) {
|
// only mark verified if forced
|
||||||
this.user = options.user;
|
this.verified = options.verified === true;
|
||||||
this.token_transport = "none";
|
|
||||||
this.verified = true;
|
// prefer request if given
|
||||||
} else if (options.token) {
|
if ("request" in options) {
|
||||||
|
this.options.host = options.host ?? new URL(options.request.url).origin;
|
||||||
|
this.options.headers = options.headers ?? options.request.headers;
|
||||||
|
this.extractToken();
|
||||||
|
|
||||||
|
// then check for a token
|
||||||
|
} else if ("token" in options) {
|
||||||
this.token_transport = "header";
|
this.token_transport = "header";
|
||||||
this.updateToken(options.token);
|
this.updateToken(options.token);
|
||||||
|
|
||||||
|
// then check for an user object
|
||||||
|
} else if ("user" in options) {
|
||||||
|
this.token_transport = "none";
|
||||||
|
this.user = options.user;
|
||||||
|
this.verified = options.verified !== false;
|
||||||
} else {
|
} else {
|
||||||
this.extractToken();
|
this.extractToken();
|
||||||
}
|
}
|
||||||
@@ -59,7 +78,7 @@ export class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get baseUrl() {
|
get baseUrl() {
|
||||||
return this.options.host;
|
return this.options.host ?? "http://localhost";
|
||||||
}
|
}
|
||||||
|
|
||||||
get tokenKey() {
|
get tokenKey() {
|
||||||
@@ -67,13 +86,15 @@ export class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extractToken() {
|
private extractToken() {
|
||||||
|
// if token has to be extracted, it's never verified
|
||||||
|
this.verified = false;
|
||||||
|
|
||||||
if (this.options.headers) {
|
if (this.options.headers) {
|
||||||
// try cookies
|
// try cookies
|
||||||
const cookieToken = getCookieValue(this.options.headers.get("cookie"), "auth");
|
const cookieToken = getCookieValue(this.options.headers.get("cookie"), "auth");
|
||||||
if (cookieToken) {
|
if (cookieToken) {
|
||||||
this.updateToken(cookieToken);
|
|
||||||
this.token_transport = "cookie";
|
this.token_transport = "cookie";
|
||||||
this.verified = true;
|
this.updateToken(cookieToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +118,8 @@ export class Api {
|
|||||||
|
|
||||||
updateToken(token?: string, rebuild?: boolean) {
|
updateToken(token?: string, rebuild?: boolean) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
this.verified = false;
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
this.user = omit(decode(token).payload as any, ["iat", "iss", "exp"]) as any;
|
this.user = omit(decode(token).payload as any, ["iat", "iss", "exp"]) as any;
|
||||||
} else {
|
} else {
|
||||||
@@ -116,11 +139,15 @@ export class Api {
|
|||||||
if (rebuild) this.buildApis();
|
if (rebuild) this.buildApis();
|
||||||
}
|
}
|
||||||
|
|
||||||
markAuthVerified(verfied: boolean) {
|
private markAuthVerified(verfied: boolean) {
|
||||||
this.verified = verfied;
|
this.verified = verfied;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAuthVerified(): boolean {
|
||||||
|
return this.verified;
|
||||||
|
}
|
||||||
|
|
||||||
getAuthState(): AuthState {
|
getAuthState(): AuthState {
|
||||||
return {
|
return {
|
||||||
token: this.token,
|
token: this.token,
|
||||||
@@ -129,6 +156,11 @@ export class Api {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
const { token, user } = this.getAuthState();
|
||||||
|
return !!token && !!user;
|
||||||
|
}
|
||||||
|
|
||||||
async getVerifiedAuthState(): Promise<AuthState> {
|
async getVerifiedAuthState(): Promise<AuthState> {
|
||||||
await this.verifyAuth();
|
await this.verifyAuth();
|
||||||
return this.getAuthState();
|
return this.getAuthState();
|
||||||
@@ -141,11 +173,13 @@ export class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.auth.me();
|
const { ok, data } = await this.auth.me();
|
||||||
if (!res.ok || !res.body.user) {
|
const user = data?.user;
|
||||||
|
if (!ok || !user) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.user = user;
|
||||||
this.markAuthVerified(true);
|
this.markAuthVerified(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.markAuthVerified(false);
|
this.markAuthVerified(false);
|
||||||
@@ -157,13 +191,17 @@ export class Api {
|
|||||||
return this.user || null;
|
return this.user || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildApis() {
|
getParams() {
|
||||||
const baseParams = {
|
return Object.freeze({
|
||||||
host: this.options.host,
|
host: this.baseUrl,
|
||||||
token: this.token,
|
token: this.token,
|
||||||
headers: this.options.headers,
|
headers: this.options.headers,
|
||||||
token_transport: this.token_transport
|
token_transport: this.token_transport
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildApis() {
|
||||||
|
const baseParams = this.getParams();
|
||||||
const fetcher = this.options.fetcher;
|
const fetcher = this.options.fetcher;
|
||||||
|
|
||||||
this.system = new SystemApi(baseParams, fetcher);
|
this.system = new SystemApi(baseParams, fetcher);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Api, type ApiOptions, type App } from "bknd";
|
import type { App } from "bknd";
|
||||||
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
|
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
|
||||||
|
import { Api, type ApiOptions } from "bknd/client";
|
||||||
|
|
||||||
export type AstroBkndConfig<Args = TAstro> = FrameworkBkndConfig<Args>;
|
export type AstroBkndConfig<Args = TAstro> = FrameworkBkndConfig<Args>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import { Api, type App } from "bknd";
|
import type { App } from "bknd";
|
||||||
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
|
import { type FrameworkBkndConfig, createFrameworkApp } from "bknd/adapter";
|
||||||
import { nodeRequestToRequest } from "bknd/adapter/node";
|
import { nodeRequestToRequest } from "bknd/adapter/node";
|
||||||
|
import { Api } from "bknd/client";
|
||||||
|
|
||||||
export type NextjsBkndConfig = FrameworkBkndConfig & {
|
export type NextjsBkndConfig = FrameworkBkndConfig & {
|
||||||
cleanSearch?: string[];
|
cleanSearch?: string[];
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ export class AuthController extends Controller {
|
|||||||
return this.auth.ctx.guard;
|
return this.auth.ctx.guard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get em() {
|
||||||
|
return this.auth.ctx.em;
|
||||||
|
}
|
||||||
|
|
||||||
|
get userRepo() {
|
||||||
|
const entity_name = this.auth.config.entity_name;
|
||||||
|
return this.em.repo(entity_name as "users");
|
||||||
|
}
|
||||||
|
|
||||||
private registerStrategyActions(strategy: Strategy, mainHono: Hono<ServerEnv>) {
|
private registerStrategyActions(strategy: Strategy, mainHono: Hono<ServerEnv>) {
|
||||||
const actions = strategy.getActions?.();
|
const actions = strategy.getActions?.();
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
@@ -96,7 +105,10 @@ export class AuthController extends Controller {
|
|||||||
|
|
||||||
hono.get("/me", auth(), async (c) => {
|
hono.get("/me", auth(), async (c) => {
|
||||||
if (this.auth.authenticator.isUserLoggedIn()) {
|
if (this.auth.authenticator.isUserLoggedIn()) {
|
||||||
return c.json({ user: this.auth.authenticator.getUser() });
|
const claims = this.auth.authenticator.getUser()!;
|
||||||
|
const { data: user } = await this.userRepo.findId(claims.id);
|
||||||
|
|
||||||
|
return c.json({ user });
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.json({ user: null }, 403);
|
return c.json({ user: null }, 403);
|
||||||
|
|||||||
@@ -299,8 +299,8 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSuccessPath(c: Context) {
|
private getSafeUrl(c: Context, path: string) {
|
||||||
const p = (this.config.cookie.pathSuccess ?? "/").replace(/\/+$/, "/");
|
const p = path.replace(/\/+$/, "/");
|
||||||
|
|
||||||
// nextjs doesn't support non-fq urls
|
// nextjs doesn't support non-fq urls
|
||||||
// but env could be proxied (stackblitz), so we shouldn't fq every url
|
// but env could be proxied (stackblitz), so we shouldn't fq every url
|
||||||
@@ -312,7 +312,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async respond(c: Context, data: AuthResponse | Error | any, redirect?: string) {
|
async respond(c: Context, data: AuthResponse | Error | any, redirect?: string) {
|
||||||
const successUrl = this.getSuccessPath(c);
|
const successUrl = this.getSafeUrl(c, redirect ?? this.config.cookie.pathSuccess ?? "/");
|
||||||
const referer = redirect ?? c.req.header("Referer") ?? successUrl;
|
const referer = redirect ?? c.req.header("Referer") ?? successUrl;
|
||||||
//console.log("auth respond", { redirect, successUrl, successPath });
|
//console.log("auth respond", { redirect, successUrl, successPath });
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Authenticator, Strategy } from "auth";
|
import type { Authenticator, Strategy } from "auth";
|
||||||
|
import { isDebug, tbValidator as tb } from "core";
|
||||||
import { type Static, StringEnum, Type, parse } from "core/utils";
|
import { type Static, StringEnum, Type, parse } from "core/utils";
|
||||||
import { hash } from "core/utils";
|
import { hash } from "core/utils";
|
||||||
import { type Context, Hono } from "hono";
|
import { type Context, Hono } from "hono";
|
||||||
@@ -56,26 +57,56 @@ export class PasswordStrategy implements Strategy {
|
|||||||
const hono = new Hono();
|
const hono = new Hono();
|
||||||
|
|
||||||
return hono
|
return hono
|
||||||
.post("/login", async (c) => {
|
.post(
|
||||||
const body = await authenticator.getBody(c);
|
"/login",
|
||||||
|
tb(
|
||||||
|
"query",
|
||||||
|
Type.Object({
|
||||||
|
redirect: Type.Optional(Type.String())
|
||||||
|
})
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const body = await authenticator.getBody(c);
|
||||||
|
const { redirect } = c.req.valid("query");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await this.login(body);
|
const payload = await this.login(body);
|
||||||
const data = await authenticator.resolve("login", this, payload.password, payload);
|
const data = await authenticator.resolve(
|
||||||
|
"login",
|
||||||
|
this,
|
||||||
|
payload.password,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
return await authenticator.respond(c, data);
|
return await authenticator.respond(c, data, redirect);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return await authenticator.respond(c, e);
|
return await authenticator.respond(c, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
.post("/register", async (c) => {
|
.post(
|
||||||
const body = await authenticator.getBody(c);
|
"/register",
|
||||||
|
tb(
|
||||||
|
"query",
|
||||||
|
Type.Object({
|
||||||
|
redirect: Type.Optional(Type.String())
|
||||||
|
})
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const body = await authenticator.getBody(c);
|
||||||
|
const { redirect } = c.req.valid("query");
|
||||||
|
|
||||||
const payload = await this.register(body);
|
const payload = await this.register(body);
|
||||||
const data = await authenticator.resolve("register", this, payload.password, payload);
|
const data = await authenticator.resolve(
|
||||||
|
"register",
|
||||||
|
this,
|
||||||
|
payload.password,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
return await authenticator.respond(c, data);
|
return await authenticator.respond(c, data, redirect);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActions(): StrategyActions {
|
getActions(): StrategyActions {
|
||||||
|
|||||||
@@ -12,7 +12,5 @@ export {
|
|||||||
export * as middlewares from "modules/middlewares";
|
export * as middlewares from "modules/middlewares";
|
||||||
export { registries } from "modules/registries";
|
export { registries } from "modules/registries";
|
||||||
|
|
||||||
export { Api, type ApiOptions } from "./Api";
|
|
||||||
|
|
||||||
export type { MediaFieldSchema } from "media/AppMedia";
|
export type { MediaFieldSchema } from "media/AppMedia";
|
||||||
export type { UserFieldSchema } from "auth/AppAuth";
|
export type { UserFieldSchema } from "auth/AppAuth";
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ export type InitialModuleConfigs =
|
|||||||
} & ModuleConfigs)
|
} & ModuleConfigs)
|
||||||
| PartialRec<ModuleConfigs>;
|
| PartialRec<ModuleConfigs>;
|
||||||
|
|
||||||
|
enum Verbosity {
|
||||||
|
silent = 0,
|
||||||
|
error = 1,
|
||||||
|
log = 2
|
||||||
|
}
|
||||||
|
|
||||||
export type ModuleManagerOptions = {
|
export type ModuleManagerOptions = {
|
||||||
initial?: InitialModuleConfigs;
|
initial?: InitialModuleConfigs;
|
||||||
eventManager?: EventManager<any>;
|
eventManager?: EventManager<any>;
|
||||||
@@ -85,6 +91,8 @@ export type ModuleManagerOptions = {
|
|||||||
trustFetched?: boolean;
|
trustFetched?: boolean;
|
||||||
// runs when initial config provided on a fresh database
|
// runs when initial config provided on a fresh database
|
||||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
||||||
|
// wether
|
||||||
|
verbosity?: Verbosity;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConfigTable<Json = ModuleConfigs> = {
|
type ConfigTable<Json = ModuleConfigs> = {
|
||||||
@@ -135,7 +143,7 @@ export class ModuleManager {
|
|||||||
private _built = false;
|
private _built = false;
|
||||||
private readonly _booted_with?: "provided" | "partial";
|
private readonly _booted_with?: "provided" | "partial";
|
||||||
|
|
||||||
private logger = new DebugLogger(false);
|
private logger: DebugLogger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly connection: Connection,
|
private readonly connection: Connection,
|
||||||
@@ -144,6 +152,7 @@ export class ModuleManager {
|
|||||||
this.__em = new EntityManager([__bknd], this.connection);
|
this.__em = new EntityManager([__bknd], this.connection);
|
||||||
this.modules = {} as Modules;
|
this.modules = {} as Modules;
|
||||||
this.emgr = new EventManager();
|
this.emgr = new EventManager();
|
||||||
|
this.logger = new DebugLogger(this.verbosity === Verbosity.log);
|
||||||
const context = this.ctx(true);
|
const context = this.ctx(true);
|
||||||
let initial = {} as Partial<ModuleConfigs>;
|
let initial = {} as Partial<ModuleConfigs>;
|
||||||
|
|
||||||
@@ -171,6 +180,10 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get verbosity() {
|
||||||
|
return this.options?.verbosity ?? Verbosity.silent;
|
||||||
|
}
|
||||||
|
|
||||||
isBuilt(): boolean {
|
isBuilt(): boolean {
|
||||||
return this._built;
|
return this._built;
|
||||||
}
|
}
|
||||||
@@ -245,20 +258,23 @@ export class ModuleManager {
|
|||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
// disabling console log, because the table might not exist yet
|
// disabling console log, because the table might not exist yet
|
||||||
const result = await withDisabledConsole(async () => {
|
const result = await withDisabledConsole(
|
||||||
const { data: result } = await this.repo().findOne(
|
async () => {
|
||||||
{ type: "config" },
|
const { data: result } = await this.repo().findOne(
|
||||||
{
|
{ type: "config" },
|
||||||
sort: { by: "version", dir: "desc" }
|
{
|
||||||
|
sort: { by: "version", dir: "desc" }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw BkndError.with("no config");
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (!result) {
|
return result as unknown as ConfigTable;
|
||||||
throw BkndError.with("no config");
|
},
|
||||||
}
|
this.verbosity > Verbosity.silent ? [] : ["log", "error", "warn"]
|
||||||
|
);
|
||||||
return result as unknown as ConfigTable;
|
|
||||||
}, ["log", "error", "warn"]);
|
|
||||||
|
|
||||||
this.logger
|
this.logger
|
||||||
.log("took", performance.now() - startTime, "ms", {
|
.log("took", performance.now() - startTime, "ms", {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const ClientProvider = ({ children, baseUrl, user }: ClientProviderProps)
|
|||||||
console.error("error .....", e);
|
console.error("error .....", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("api init", { host: actualBaseUrl, user: user ?? winCtx.user });
|
//console.log("api init", { host: actualBaseUrl, user: user ?? winCtx.user });
|
||||||
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user });
|
const api = new Api({ host: actualBaseUrl, user: user ?? winCtx.user });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ export {
|
|||||||
export * from "./api/use-api";
|
export * from "./api/use-api";
|
||||||
export * from "./api/use-entity";
|
export * from "./api/use-entity";
|
||||||
export { useAuth } from "./schema/auth/use-auth";
|
export { useAuth } from "./schema/auth/use-auth";
|
||||||
export { Api } from "../../Api";
|
export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api";
|
||||||
|
|||||||
@@ -5,19 +5,74 @@ description: 'Use the bknd SDK in TypeScript'
|
|||||||
|
|
||||||
To start using the bknd API, start by creating a new API instance:
|
To start using the bknd API, start by creating a new API instance:
|
||||||
```ts
|
```ts
|
||||||
import { Api } from "bknd";
|
import { Api } from "bknd/client";
|
||||||
|
|
||||||
const api = new Api({
|
const api = new Api();
|
||||||
host: "..." // point to your bknd instance
|
|
||||||
});
|
|
||||||
|
|
||||||
// make sure to verify auth
|
// always make sure to verify auth
|
||||||
await api.verifyAuth();
|
await api.verifyAuth();
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Api` class is the main entry point for interacting with the bknd API. It provides methods
|
The `Api` class is the main entry point for interacting with the bknd API. It provides methods
|
||||||
for all available modules described below.
|
for all available modules described below.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
You can initialize an API instance by providing the `Request` object, or manually specifying the details such as `host` and `token`.
|
||||||
|
|
||||||
|
### Using the `Request` object
|
||||||
|
The recommended way to create an API instance is by passing the current `Request` object. This will automatically point the API to your current instance and extract the token from the headers (either from cookies or `Authorization` header):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Api } from "bknd/client";
|
||||||
|
|
||||||
|
// replace this with the actual request
|
||||||
|
let request: Request;
|
||||||
|
|
||||||
|
const api = new Api({ request });
|
||||||
|
```
|
||||||
|
|
||||||
|
If the authentication details are contained in the current request, but you're hosting your bknd instance somewhere else, you can specify a `host` option:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Api } from "bknd/client";
|
||||||
|
|
||||||
|
// replace this with the actual request
|
||||||
|
let request: Request;
|
||||||
|
|
||||||
|
const api = new Api({
|
||||||
|
host: "https://<your-endpoint>",
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the `token` option
|
||||||
|
If you want to have an API instance that is using a different token, e.g. an admin token, you can create it by specifying the `host` and `token` option:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Api } from "bknd/client";
|
||||||
|
const api = new Api({
|
||||||
|
host: "https://<your-endpoint>",
|
||||||
|
token: "<your-token>"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a local API
|
||||||
|
In case the place where you're using the API is the same as your bknd instance (e.g. when using it embedded in a React framework), you can specify a `fetcher` option to point to your bknd app. This way, requests won't travel over the network and instead processed locally:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { App } from "bknd";
|
||||||
|
import { Api } from "bknd/client";
|
||||||
|
|
||||||
|
// replace this with your actual `App` instance
|
||||||
|
let app: App;
|
||||||
|
|
||||||
|
const api = new Api({
|
||||||
|
fetcher: app.server.request as typeof fetch,
|
||||||
|
// specify `host` and `token` or `request`
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Data (`api.data`)
|
## Data (`api.data`)
|
||||||
Access the `Data` specific API methods at `api.data`.
|
Access the `Data` specific API methods at `api.data`.
|
||||||
|
|
||||||
@@ -79,19 +134,25 @@ const { data } = await api.data.deleteOne("posts", 1);
|
|||||||
Access the `Auth` specific API methods at `api.auth`. If there is successful authentication, the
|
Access the `Auth` specific API methods at `api.auth`. If there is successful authentication, the
|
||||||
API will automatically save the token and use it for subsequent requests.
|
API will automatically save the token and use it for subsequent requests.
|
||||||
|
|
||||||
### `auth.loginWithPassword([input])`
|
### `auth.strategies()`
|
||||||
To log in with a password, use the `loginWithPassword` method:
|
To retrieve the available authentication strategies, use the `strategies` method:
|
||||||
```ts
|
```ts
|
||||||
const { data } = await api.auth.loginWithPassword({
|
const { data } = await api.auth.strategies();
|
||||||
|
```
|
||||||
|
|
||||||
|
### `auth.login([strategy], [input])`
|
||||||
|
To log in with a password, use the `login` method:
|
||||||
|
```ts
|
||||||
|
const { data } = await api.auth.login("password", {
|
||||||
email: "...",
|
email: "...",
|
||||||
password: "..."
|
password: "..."
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### `auth.registerWithPassword([input])`
|
### `auth.register([strategy], [input])`
|
||||||
To register with a password, use the `registerWithPassword` method:
|
To register with a password, use the `register` method:
|
||||||
```ts
|
```ts
|
||||||
const { data } = await api.auth.registerWithPassword({
|
const { data } = await api.auth.register("password", {
|
||||||
email: "...",
|
email: "...",
|
||||||
password: "..."
|
password: "..."
|
||||||
});
|
});
|
||||||
@@ -103,8 +164,3 @@ To retrieve the current user, use the `me` method:
|
|||||||
const { data } = await api.auth.me();
|
const { data } = await api.auth.me();
|
||||||
```
|
```
|
||||||
|
|
||||||
### `auth.strategies()`
|
|
||||||
To retrieve the available authentication strategies, use the `strategies` method:
|
|
||||||
```ts
|
|
||||||
const { data } = await api.auth.strategies();
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user