mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
Merge pull request #81 from bknd-io/feat/app-auth-context
feat/app-auth-context
This commit is contained in:
@@ -16,10 +16,8 @@ describe("authorize", () => {
|
|||||||
role: "admin"
|
role: "admin"
|
||||||
};
|
};
|
||||||
|
|
||||||
guard.setUserContext(user);
|
expect(guard.granted("read", user)).toBe(true);
|
||||||
|
expect(guard.granted("write", user)).toBe(true);
|
||||||
expect(guard.granted("read")).toBe(true);
|
|
||||||
expect(guard.granted("write")).toBe(true);
|
|
||||||
|
|
||||||
expect(() => guard.granted("something")).toThrow();
|
expect(() => guard.granted("something")).toThrow();
|
||||||
});
|
});
|
||||||
@@ -46,10 +44,8 @@ describe("authorize", () => {
|
|||||||
role: "admin"
|
role: "admin"
|
||||||
};
|
};
|
||||||
|
|
||||||
guard.setUserContext(user);
|
expect(guard.granted("read", user)).toBe(true);
|
||||||
|
expect(guard.granted("write", user)).toBe(true);
|
||||||
expect(guard.granted("read")).toBe(true);
|
|
||||||
expect(guard.granted("write")).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("guard implicit allow", async () => {
|
test("guard implicit allow", async () => {
|
||||||
@@ -66,12 +62,12 @@ describe("authorize", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
guard.setUserContext({
|
const user = {
|
||||||
role: "admin"
|
role: "admin"
|
||||||
});
|
};
|
||||||
|
|
||||||
expect(guard.granted("read")).toBe(true);
|
expect(guard.granted("read", user)).toBe(true);
|
||||||
expect(guard.granted("write")).toBe(true);
|
expect(guard.granted("write", user)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("guard with guest role implicit allow", async () => {
|
test("guard with guest role implicit allow", async () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||||
import { App, createApp } from "../../src";
|
import { App, createApp } from "../../src";
|
||||||
import type { AuthResponse } from "../../src/auth";
|
import type { AuthResponse } from "../../src/auth";
|
||||||
|
import { auth } from "../../src/auth/middlewares";
|
||||||
import { randomString, secureRandomString, withDisabledConsole } from "../../src/core/utils";
|
import { randomString, secureRandomString, withDisabledConsole } from "../../src/core/utils";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
import { disableConsoleLog, enableConsoleLog } from "../helper";
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ const fns = <Mode extends "cookie" | "token" = "token">(app: App, mode?: Mode) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: token ? `Bearer ${token}` : "",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...additional
|
...additional
|
||||||
};
|
};
|
||||||
@@ -210,4 +211,36 @@ describe("integration auth", () => {
|
|||||||
expect(res.status).toBe(403);
|
expect(res.status).toBe(403);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("context is exclusive", async () => {
|
||||||
|
const app = createAuthApp();
|
||||||
|
await app.build();
|
||||||
|
const $fns = fns(app);
|
||||||
|
|
||||||
|
app.server.get("/get", auth(), async (c) => {
|
||||||
|
return c.json({
|
||||||
|
user: c.get("auth").user ?? null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.server.get("/wait", auth(), async (c) => {
|
||||||
|
await new Promise((r) => setTimeout(r, 20));
|
||||||
|
return c.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $fns.login(configs.users.normal);
|
||||||
|
const me = await $fns.me(data.token);
|
||||||
|
expect(me.user.email).toBe(configs.users.normal.email);
|
||||||
|
|
||||||
|
app.server.request("/wait", {
|
||||||
|
headers: { Authorization: `Bearer ${data.token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
const res = await app.server.request("/get");
|
||||||
|
const data = await res.json();
|
||||||
|
expect(data.user).toBe(null);
|
||||||
|
expect(await $fns.me()).toEqual({ user: null as any });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,10 +104,9 @@ export class AuthController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hono.get("/me", auth(), async (c) => {
|
hono.get("/me", auth(), async (c) => {
|
||||||
if (this.auth.authenticator.isUserLoggedIn()) {
|
const claims = c.get("auth")?.user;
|
||||||
const claims = this.auth.authenticator.getUser()!;
|
if (claims) {
|
||||||
const { data: user } = await this.userRepo.findId(claims.id);
|
const { data: user } = await this.userRepo.findId(claims.id);
|
||||||
|
|
||||||
return c.json({ user });
|
return c.json({ user });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,17 +106,15 @@ export type AuthUserResolver = (
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
profile: ProfileExchange
|
profile: ProfileExchange
|
||||||
) => Promise<SafeUser | undefined>;
|
) => Promise<SafeUser | undefined>;
|
||||||
|
type AuthClaims = SafeUser & {
|
||||||
|
iat: number;
|
||||||
|
iss?: string;
|
||||||
|
exp?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export class Authenticator<Strategies extends Record<string, Strategy> = Record<string, Strategy>> {
|
export class Authenticator<Strategies extends Record<string, Strategy> = Record<string, Strategy>> {
|
||||||
private readonly strategies: Strategies;
|
private readonly strategies: Strategies;
|
||||||
private readonly config: AuthConfig;
|
private readonly config: AuthConfig;
|
||||||
private _claims:
|
|
||||||
| undefined
|
|
||||||
| (SafeUser & {
|
|
||||||
iat: number;
|
|
||||||
iss?: string;
|
|
||||||
exp?: number;
|
|
||||||
});
|
|
||||||
private readonly userResolver: AuthUserResolver;
|
private readonly userResolver: AuthUserResolver;
|
||||||
|
|
||||||
constructor(strategies: Strategies, userResolver?: AuthUserResolver, config?: AuthConfig) {
|
constructor(strategies: Strategies, userResolver?: AuthUserResolver, config?: AuthConfig) {
|
||||||
@@ -148,21 +146,6 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
return this.strategies;
|
return this.strategies;
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserLoggedIn(): boolean {
|
|
||||||
return this._claims !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser(): SafeUser | undefined {
|
|
||||||
if (!this._claims) return;
|
|
||||||
|
|
||||||
const { iat, exp, iss, ...user } = this._claims;
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetUser() {
|
|
||||||
this._claims = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
strategy<
|
strategy<
|
||||||
StrategyName extends keyof Strategies,
|
StrategyName extends keyof Strategies,
|
||||||
Strat extends Strategy = Strategies[StrategyName]
|
Strat extends Strategy = Strategies[StrategyName]
|
||||||
@@ -206,7 +189,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
return sign(payload, secret, this.config.jwt?.alg ?? "HS256");
|
return sign(payload, secret, this.config.jwt?.alg ?? "HS256");
|
||||||
}
|
}
|
||||||
|
|
||||||
async verify(jwt: string): Promise<boolean> {
|
async verify(jwt: string): Promise<AuthClaims | undefined> {
|
||||||
try {
|
try {
|
||||||
const payload = await verify(
|
const payload = await verify(
|
||||||
jwt,
|
jwt,
|
||||||
@@ -221,14 +204,10 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._claims = payload as any;
|
return payload as any;
|
||||||
return true;
|
} catch (e) {}
|
||||||
} catch (e) {
|
|
||||||
this.resetUser();
|
|
||||||
//console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get cookieOptions(): CookieOptions {
|
private get cookieOptions(): CookieOptions {
|
||||||
@@ -258,8 +237,8 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestCookieRefresh(c: Context) {
|
async requestCookieRefresh(c: Context<ServerEnv>) {
|
||||||
if (this.config.cookie.renew && this.isUserLoggedIn()) {
|
if (this.config.cookie.renew && c.get("auth")?.user) {
|
||||||
const token = await this.getAuthCookie(c);
|
const token = await this.getAuthCookie(c);
|
||||||
if (token) {
|
if (token) {
|
||||||
await this.setAuthCookie(c, token);
|
await this.setAuthCookie(c, token);
|
||||||
@@ -276,13 +255,14 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
await deleteCookie(c, "auth", this.cookieOptions);
|
await deleteCookie(c, "auth", this.cookieOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(c: Context) {
|
async logout(c: Context<ServerEnv>) {
|
||||||
|
c.set("auth", undefined);
|
||||||
|
|
||||||
const cookie = await this.getAuthCookie(c);
|
const cookie = await this.getAuthCookie(c);
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
await this.deleteAuthCookie(c);
|
await this.deleteAuthCookie(c);
|
||||||
await addFlashMessage(c, "Signed out", "info");
|
await addFlashMessage(c, "Signed out", "info");
|
||||||
}
|
}
|
||||||
this.resetUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: move this to a server helper
|
// @todo: move this to a server helper
|
||||||
@@ -353,8 +333,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
await this.verify(token);
|
return await this.verify(token);
|
||||||
return this.getUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { Exception, Permission } from "core";
|
import { Exception, Permission } from "core";
|
||||||
import { objectTransform } from "core/utils";
|
import { objectTransform } from "core/utils";
|
||||||
|
import type { Context } from "hono";
|
||||||
|
import type { ServerEnv } from "modules/Module";
|
||||||
import { Role } from "./Role";
|
import { Role } from "./Role";
|
||||||
|
|
||||||
export type GuardUserContext = {
|
export type GuardUserContext = {
|
||||||
role: string | null | undefined;
|
role?: string | null;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GuardConfig = {
|
export type GuardConfig = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
export type GuardContext = Context<ServerEnv> | GuardUserContext;
|
||||||
|
|
||||||
const debug = false;
|
const debug = false;
|
||||||
|
|
||||||
export class Guard {
|
export class Guard {
|
||||||
permissions: Permission[];
|
permissions: Permission[];
|
||||||
user?: GuardUserContext;
|
|
||||||
roles?: Role[];
|
roles?: Role[];
|
||||||
config?: GuardConfig;
|
config?: GuardConfig;
|
||||||
|
|
||||||
@@ -89,24 +91,19 @@ export class Guard {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserContext(user: GuardUserContext | undefined) {
|
getUserRole(user?: GuardUserContext): Role | undefined {
|
||||||
this.user = user;
|
if (user && typeof user.role === "string") {
|
||||||
return this;
|
const role = this.roles?.find((role) => role.name === user?.role);
|
||||||
}
|
|
||||||
|
|
||||||
getUserRole(): Role | undefined {
|
|
||||||
if (this.user && typeof this.user.role === "string") {
|
|
||||||
const role = this.roles?.find((role) => role.name === this.user?.role);
|
|
||||||
if (role) {
|
if (role) {
|
||||||
debug && console.log("guard: role found", [this.user.role]);
|
debug && console.log("guard: role found", [user.role]);
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug &&
|
debug &&
|
||||||
console.log("guard: role not found", {
|
console.log("guard: role not found", {
|
||||||
user: this.user,
|
user: user,
|
||||||
role: this.user?.role
|
role: user?.role
|
||||||
});
|
});
|
||||||
return this.getDefaultRole();
|
return this.getDefaultRole();
|
||||||
}
|
}
|
||||||
@@ -119,9 +116,9 @@ export class Guard {
|
|||||||
return this.config?.enabled === true;
|
return this.config?.enabled === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission(permission: Permission): boolean;
|
hasPermission(permission: Permission, user?: GuardUserContext): boolean;
|
||||||
hasPermission(name: string): boolean;
|
hasPermission(name: string, user?: GuardUserContext): boolean;
|
||||||
hasPermission(permissionOrName: Permission | string): boolean {
|
hasPermission(permissionOrName: Permission | string, user?: GuardUserContext): boolean {
|
||||||
if (!this.isEnabled()) {
|
if (!this.isEnabled()) {
|
||||||
//console.log("guard not enabled, allowing");
|
//console.log("guard not enabled, allowing");
|
||||||
return true;
|
return true;
|
||||||
@@ -133,7 +130,7 @@ export class Guard {
|
|||||||
throw new Error(`Permission ${name} does not exist`);
|
throw new Error(`Permission ${name} does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const role = this.getUserRole();
|
const role = this.getUserRole(user);
|
||||||
|
|
||||||
if (!role) {
|
if (!role) {
|
||||||
debug && console.log("guard: role not found, denying");
|
debug && console.log("guard: role not found, denying");
|
||||||
@@ -156,12 +153,13 @@ export class Guard {
|
|||||||
return !!rolePermission;
|
return !!rolePermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
granted(permission: Permission | string): boolean {
|
granted(permission: Permission | string, c?: GuardContext): boolean {
|
||||||
return this.hasPermission(permission as any);
|
const user = c && "get" in c ? c.get("auth")?.user : c;
|
||||||
|
return this.hasPermission(permission as any, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
throwUnlessGranted(permission: Permission | string) {
|
throwUnlessGranted(permission: Permission | string, c: GuardContext) {
|
||||||
if (!this.granted(permission)) {
|
if (!this.granted(permission, c)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
`Permission "${typeof permission === "string" ? permission : permission.name}" not granted`,
|
`Permission "${typeof permission === "string" ? permission : permission.name}" not granted`,
|
||||||
403
|
403
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ function getPath(reqOrCtx: Request | Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function shouldSkip(c: Context<ServerEnv>, skip?: (string | RegExp)[]) {
|
export function shouldSkip(c: Context<ServerEnv>, skip?: (string | RegExp)[]) {
|
||||||
if (c.get("auth_skip")) return true;
|
const authCtx = c.get("auth");
|
||||||
|
if (!authCtx) {
|
||||||
|
throw new Error("auth ctx not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authCtx.skip) return true;
|
||||||
|
|
||||||
const req = c.req.raw;
|
const req = c.req.raw;
|
||||||
if (!skip) return false;
|
if (!skip) return false;
|
||||||
@@ -18,7 +23,7 @@ export function shouldSkip(c: Context<ServerEnv>, skip?: (string | RegExp)[]) {
|
|||||||
const path = getPath(req);
|
const path = getPath(req);
|
||||||
const result = skip.some((s) => patternMatch(path, s));
|
const result = skip.some((s) => patternMatch(path, s));
|
||||||
|
|
||||||
c.set("auth_skip", result);
|
authCtx.skip = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,29 +31,31 @@ export const auth = (options?: {
|
|||||||
skip?: (string | RegExp)[];
|
skip?: (string | RegExp)[];
|
||||||
}) =>
|
}) =>
|
||||||
createMiddleware<ServerEnv>(async (c, next) => {
|
createMiddleware<ServerEnv>(async (c, next) => {
|
||||||
|
if (!c.get("auth")) {
|
||||||
|
c.set("auth", {
|
||||||
|
registered: false,
|
||||||
|
resolved: false,
|
||||||
|
skip: false,
|
||||||
|
user: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const app = c.get("app");
|
const app = c.get("app");
|
||||||
const guard = app?.modules.ctx().guard;
|
const authCtx = c.get("auth")!;
|
||||||
const authenticator = app?.module.auth.authenticator;
|
const authenticator = app?.module.auth.authenticator;
|
||||||
|
|
||||||
let skipped = shouldSkip(c, options?.skip) || !app?.module.auth.enabled;
|
let skipped = shouldSkip(c, options?.skip) || !app?.module.auth.enabled;
|
||||||
|
|
||||||
// make sure to only register once
|
// make sure to only register once
|
||||||
if (c.get("auth_registered")) {
|
if (authCtx.registered) {
|
||||||
skipped = true;
|
skipped = true;
|
||||||
console.warn(`auth middleware already registered for ${getPath(c)}`);
|
console.warn(`auth middleware already registered for ${getPath(c)}`);
|
||||||
} else {
|
} else {
|
||||||
c.set("auth_registered", true);
|
authCtx.registered = true;
|
||||||
|
|
||||||
if (!skipped) {
|
if (!skipped && !authCtx.resolved && app?.module.auth.enabled) {
|
||||||
const resolved = c.get("auth_resolved");
|
authCtx.user = await authenticator?.resolveAuthFromRequest(c);
|
||||||
if (!resolved) {
|
authCtx.resolved = true;
|
||||||
if (!app?.module.auth.enabled) {
|
|
||||||
guard?.setUserContext(undefined);
|
|
||||||
} else {
|
|
||||||
guard?.setUserContext(await authenticator?.resolveAuthFromRequest(c));
|
|
||||||
c.set("auth_resolved", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +67,9 @@ export const auth = (options?: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// release
|
// release
|
||||||
guard?.setUserContext(undefined);
|
authCtx.skip = false;
|
||||||
authenticator?.resetUser();
|
authCtx.resolved = false;
|
||||||
c.set("auth_resolved", false);
|
authCtx.user = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const permission = (
|
export const permission = (
|
||||||
@@ -75,23 +82,26 @@ export const permission = (
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
createMiddleware<ServerEnv>(async (c, next) => {
|
createMiddleware<ServerEnv>(async (c, next) => {
|
||||||
const app = c.get("app");
|
const app = c.get("app");
|
||||||
//console.log("skip?", c.get("auth_skip"));
|
const authCtx = c.get("auth");
|
||||||
|
if (!authCtx) {
|
||||||
|
throw new Error("auth ctx not found");
|
||||||
|
}
|
||||||
|
|
||||||
// in tests, app is not defined
|
// in tests, app is not defined
|
||||||
if (!c.get("auth_registered") || !app) {
|
if (!authCtx.registered || !app) {
|
||||||
const msg = `auth middleware not registered, cannot check permissions for ${getPath(c)}`;
|
const msg = `auth middleware not registered, cannot check permissions for ${getPath(c)}`;
|
||||||
if (app?.module.auth.enabled) {
|
if (app?.module.auth.enabled) {
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
} else {
|
} else {
|
||||||
console.warn(msg);
|
console.warn(msg);
|
||||||
}
|
}
|
||||||
} else if (!c.get("auth_skip")) {
|
} else if (!authCtx.skip) {
|
||||||
const guard = app.modules.ctx().guard;
|
const guard = app.modules.ctx().guard;
|
||||||
const permissions = Array.isArray(permission) ? permission : [permission];
|
const permissions = Array.isArray(permission) ? permission : [permission];
|
||||||
|
|
||||||
if (options?.onGranted || options?.onDenied) {
|
if (options?.onGranted || options?.onDenied) {
|
||||||
let returned: undefined | void | Response;
|
let returned: undefined | void | Response;
|
||||||
if (permissions.every((p) => guard.granted(p))) {
|
if (permissions.every((p) => guard.granted(p, c))) {
|
||||||
returned = await options?.onGranted?.(c);
|
returned = await options?.onGranted?.(c);
|
||||||
} else {
|
} else {
|
||||||
returned = await options?.onDenied?.(c);
|
returned = await options?.onDenied?.(c);
|
||||||
@@ -100,7 +110,7 @@ export const permission = (
|
|||||||
return returned;
|
return returned;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
permissions.some((p) => guard.throwUnlessGranted(p));
|
permissions.some((p) => guard.throwUnlessGranted(p, c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,12 +327,9 @@ export class DataController extends Controller {
|
|||||||
// delete one
|
// delete one
|
||||||
.delete(
|
.delete(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
|
|
||||||
permission(DataPermissions.entityDelete),
|
permission(DataPermissions.entityDelete),
|
||||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
this.guard.throwUnlessGranted(DataPermissions.entityDelete);
|
|
||||||
|
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
@@ -350,14 +347,11 @@ export class DataController extends Controller {
|
|||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
tb("json", querySchema.properties.where),
|
tb("json", querySchema.properties.where),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
//console.log("request", c.req.raw);
|
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return c.notFound();
|
return c.notFound();
|
||||||
}
|
}
|
||||||
const where = c.req.valid("json") as RepoQuery["where"];
|
const where = c.req.valid("json") as RepoQuery["where"];
|
||||||
//console.log("where", where);
|
|
||||||
|
|
||||||
const result = await this.em.mutator(entity).deleteWhere(where);
|
const result = await this.em.mutator(entity).deleteWhere(where);
|
||||||
|
|
||||||
return c.json(this.mutatorResult(result));
|
return c.json(this.mutatorResult(result));
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ import { isEqual } from "lodash-es";
|
|||||||
|
|
||||||
export type ServerEnv = {
|
export type ServerEnv = {
|
||||||
Variables: {
|
Variables: {
|
||||||
app?: App;
|
app: App;
|
||||||
// to prevent resolving auth multiple times
|
// to prevent resolving auth multiple times
|
||||||
auth_resolved?: boolean;
|
auth?: {
|
||||||
// to only register once
|
resolved: boolean;
|
||||||
auth_registered?: boolean;
|
registered: boolean;
|
||||||
// whether or not to bypass auth
|
skip: boolean;
|
||||||
auth_skip?: boolean;
|
user?: { id: any; role?: string; [key: string]: any };
|
||||||
|
};
|
||||||
html?: string;
|
html?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class AdminController extends Controller {
|
|||||||
|
|
||||||
hono.use("*", async (c, next) => {
|
hono.use("*", async (c, next) => {
|
||||||
const obj = {
|
const obj = {
|
||||||
user: auth.authenticator?.getUser(),
|
user: c.get("auth")?.user,
|
||||||
logout_route: this.withBasePath(authRoutes.logout),
|
logout_route: this.withBasePath(authRoutes.logout),
|
||||||
color_scheme: configs.server.admin.color_scheme
|
color_scheme: configs.server.admin.color_scheme
|
||||||
};
|
};
|
||||||
@@ -91,7 +91,7 @@ export class AdminController extends Controller {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
onGranted: async (c) => {
|
onGranted: async (c) => {
|
||||||
// @todo: add strict test to permissions middleware?
|
// @todo: add strict test to permissions middleware?
|
||||||
if (auth.authenticator.isUserLoggedIn()) {
|
if (c.get("auth")?.user) {
|
||||||
console.log("redirecting to success");
|
console.log("redirecting to success");
|
||||||
return c.redirect(authRoutes.success);
|
return c.redirect(authRoutes.success);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export class SystemController extends Controller {
|
|||||||
|
|
||||||
private registerConfigController(client: Hono<any>): void {
|
private registerConfigController(client: Hono<any>): void {
|
||||||
const { permission } = this.middlewares;
|
const { permission } = this.middlewares;
|
||||||
|
// don't add auth again, it's already added in getController
|
||||||
const hono = this.create();
|
const hono = this.create();
|
||||||
|
|
||||||
hono.use(permission(SystemPermissions.configRead));
|
hono.use(permission(SystemPermissions.configRead));
|
||||||
@@ -63,7 +64,7 @@ export class SystemController extends Controller {
|
|||||||
const { secrets } = c.req.valid("query");
|
const { secrets } = c.req.valid("query");
|
||||||
const { module } = c.req.valid("param");
|
const { module } = c.req.valid("param");
|
||||||
|
|
||||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets, c);
|
||||||
|
|
||||||
const config = this.app.toJSON(secrets);
|
const config = this.app.toJSON(secrets);
|
||||||
|
|
||||||
@@ -227,8 +228,8 @@ export class SystemController extends Controller {
|
|||||||
const module = c.req.param("module") as ModuleKey | undefined;
|
const module = c.req.param("module") as ModuleKey | undefined;
|
||||||
const { config, secrets } = c.req.valid("query");
|
const { config, secrets } = c.req.valid("query");
|
||||||
|
|
||||||
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead);
|
config && this.ctx.guard.throwUnlessGranted(SystemPermissions.configRead, c);
|
||||||
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets);
|
secrets && this.ctx.guard.throwUnlessGranted(SystemPermissions.configReadSecrets, c);
|
||||||
|
|
||||||
const { version, ...schema } = this.app.getSchema();
|
const { version, ...schema } = this.app.getSchema();
|
||||||
|
|
||||||
@@ -261,73 +262,27 @@ export class SystemController extends Controller {
|
|||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { sync } = c.req.valid("query") as Record<string, boolean>;
|
const { sync } = c.req.valid("query") as Record<string, boolean>;
|
||||||
this.ctx.guard.throwUnlessGranted(SystemPermissions.build);
|
this.ctx.guard.throwUnlessGranted(SystemPermissions.build, c);
|
||||||
|
|
||||||
await this.app.build({ sync });
|
await this.app.build({ sync });
|
||||||
return c.json({ success: true, options: { sync } });
|
return c.json({ success: true, options: { sync } });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
hono.get("/ping", async (c) => {
|
hono.get("/ping", (c) => c.json({ pong: true }));
|
||||||
//console.log("c", c);
|
|
||||||
try {
|
|
||||||
// @ts-ignore @todo: fix with env
|
|
||||||
const context: any = c.req.raw.cf ? c.req.raw.cf : c.env.cf;
|
|
||||||
const cf = {
|
|
||||||
colo: context.colo,
|
|
||||||
city: context.city,
|
|
||||||
postal: context.postalCode,
|
|
||||||
region: context.region,
|
|
||||||
regionCode: context.regionCode,
|
|
||||||
continent: context.continent,
|
|
||||||
country: context.country,
|
|
||||||
eu: context.isEUCountry,
|
|
||||||
lat: context.latitude,
|
|
||||||
lng: context.longitude,
|
|
||||||
timezone: context.timezone
|
|
||||||
};
|
|
||||||
return c.json({ pong: true });
|
|
||||||
} catch (e) {
|
|
||||||
return c.json({ pong: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
hono.get("/info", async (c) => {
|
hono.get("/info", (c) =>
|
||||||
return c.json({
|
c.json({
|
||||||
version: this.app.version(),
|
version: c.get("app")?.version(),
|
||||||
test: 2,
|
|
||||||
app: c.get("app")?.version(),
|
|
||||||
runtime: getRuntimeKey()
|
runtime: getRuntimeKey()
|
||||||
});
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
hono.get("/openapi.json", async (c) => {
|
hono.get("/openapi.json", async (c) => {
|
||||||
//const config = this.app.toJSON();
|
|
||||||
const config = getDefaultConfig();
|
const config = getDefaultConfig();
|
||||||
return c.json(generateOpenAPI(config));
|
return c.json(generateOpenAPI(config));
|
||||||
});
|
});
|
||||||
|
|
||||||
/*hono.get("/test/sql", async (c) => {
|
|
||||||
// @ts-ignore
|
|
||||||
const ai = c.env?.AI as Ai;
|
|
||||||
const messages = [
|
|
||||||
{ role: "system", content: "You are a friendly assistant" },
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: "just say hello"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const stream = await ai.run("@cf/meta/llama-3.1-8b-instruct", {
|
|
||||||
messages,
|
|
||||||
stream: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(stream, {
|
|
||||||
headers: { "content-type": "text/event-stream" }
|
|
||||||
});
|
|
||||||
});*/
|
|
||||||
|
|
||||||
return hono;
|
return hono;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user