mirror of
https://github.com/nestriness/nestri.git
synced 2026-08-01 17:34:15 +03:00
feat: Change logs a lil bit
This commit is contained in:
7
bun.lock
7
bun.lock
@@ -90,9 +90,11 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "^0.44.6",
|
"drizzle-orm": "^0.44.6",
|
||||||
|
"hono": "catalog:",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
"ulid": "^3.0.1",
|
"ulid": "^3.0.1",
|
||||||
|
"xdg-basedir": "^5.1.0",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
"zod-openapi": "^5.4.3",
|
"zod-openapi": "^5.4.3",
|
||||||
},
|
},
|
||||||
@@ -109,7 +111,7 @@
|
|||||||
"name": "@nestri/function",
|
"name": "@nestri/function",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
"hono": "^4.10.1",
|
"hono": "catalog:",
|
||||||
"hono-openapi": "^1.1.0",
|
"hono-openapi": "^1.1.0",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -249,6 +251,7 @@
|
|||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
"@types/bun": "1.3.0",
|
"@types/bun": "1.3.0",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
|
"hono": "^4.10.1",
|
||||||
"steam-session": "1.9.3",
|
"steam-session": "1.9.3",
|
||||||
"zod": "^4.1.12",
|
"zod": "^4.1.12",
|
||||||
},
|
},
|
||||||
@@ -2939,6 +2942,8 @@
|
|||||||
|
|
||||||
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||||
|
|
||||||
|
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||||
|
|
||||||
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
||||||
|
|
||||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"@types/bun": "1.3.0",
|
"@types/bun": "1.3.0",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"@tsconfig/node22": "22.0.2",
|
"@tsconfig/node22": "22.0.2",
|
||||||
|
"hono": "^4.10.1",
|
||||||
"steam-session": "1.9.3",
|
"steam-session": "1.9.3",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "^0.44.6",
|
"drizzle-orm": "^0.44.6",
|
||||||
|
"hono": "catalog:",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
"ulid": "^3.0.1",
|
"ulid": "^3.0.1",
|
||||||
|
"xdg-basedir": "^5.1.0",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
"zod-openapi": "^5.4.3"
|
"zod-openapi": "^5.4.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard error response schema used for OpenAPI documentation
|
* Standard error response schema used for OpenAPI documentation
|
||||||
@@ -108,7 +109,7 @@ export class VisibleError extends Error {
|
|||||||
/**
|
/**
|
||||||
* Convert this error to an HTTP status code
|
* Convert this error to an HTTP status code
|
||||||
*/
|
*/
|
||||||
public statusCode(): number {
|
public statusCode(): ContentfulStatusCode {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "validation":
|
case "validation":
|
||||||
return 400;
|
return 400;
|
||||||
|
|||||||
50
packages/core/src/global/index.ts
Normal file
50
packages/core/src/global/index.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir";
|
||||||
|
|
||||||
|
const app = "nestri";
|
||||||
|
|
||||||
|
const data = path.join(xdgData!, app);
|
||||||
|
const cache = path.join(xdgCache!, app);
|
||||||
|
const config = path.join(xdgConfig!, app);
|
||||||
|
const state = path.join(xdgState!, app);
|
||||||
|
|
||||||
|
export namespace Global {
|
||||||
|
export const Path = {
|
||||||
|
data,
|
||||||
|
bin: path.join(data, "bin"),
|
||||||
|
log: path.join(data, "log"),
|
||||||
|
cache,
|
||||||
|
config,
|
||||||
|
state,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fs.mkdir(Global.Path.data, { recursive: true }),
|
||||||
|
fs.mkdir(Global.Path.config, { recursive: true }),
|
||||||
|
fs.mkdir(Global.Path.state, { recursive: true }),
|
||||||
|
fs.mkdir(Global.Path.log, { recursive: true }),
|
||||||
|
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const CACHE_VERSION = "0";
|
||||||
|
|
||||||
|
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||||
|
.text()
|
||||||
|
.catch(() => "0");
|
||||||
|
|
||||||
|
if (version !== CACHE_VERSION) {
|
||||||
|
try {
|
||||||
|
const contents = await fs.readdir(Global.Path.cache);
|
||||||
|
await Promise.all(
|
||||||
|
contents.map((item) =>
|
||||||
|
fs.rm(path.join(Global.Path.cache, item), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION);
|
||||||
|
}
|
||||||
@@ -1,54 +1,191 @@
|
|||||||
|
import z from "zod/v4";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs/promises";
|
||||||
import { Context } from "../context.js";
|
import { Context } from "../context.js";
|
||||||
|
import { Global } from "../global/index.js";
|
||||||
|
|
||||||
export namespace Log {
|
export namespace Log {
|
||||||
const ctx = Context.create<{
|
const ctx = Context.create<{
|
||||||
tags: Record<string, any>;
|
tags: Record<string, any>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
export const Level = z
|
||||||
|
.enum(["DEBUG", "INFO", "WARN", "ERROR"])
|
||||||
|
.meta({ ref: "LogLevel", description: "Log level" });
|
||||||
|
export type Level = z.infer<typeof Level>;
|
||||||
|
|
||||||
|
const levelPriority: Record<Level, number> = {
|
||||||
|
DEBUG: 0,
|
||||||
|
INFO: 1,
|
||||||
|
WARN: 2,
|
||||||
|
ERROR: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let level: Level = "INFO";
|
||||||
|
|
||||||
|
function shouldLog(input: Level): boolean {
|
||||||
|
return levelPriority[input] >= levelPriority[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Logger = {
|
||||||
|
debug(message?: any, extra?: Record<string, any>): void;
|
||||||
|
info(message?: any, extra?: Record<string, any>): void;
|
||||||
|
error(message?: any, extra?: Record<string, any>): void;
|
||||||
|
warn(message?: any, extra?: Record<string, any>): void;
|
||||||
|
tag(key: string, value: string): Logger;
|
||||||
|
clone(): Logger;
|
||||||
|
time(
|
||||||
|
message: string,
|
||||||
|
extra?: Record<string, any>,
|
||||||
|
): {
|
||||||
|
stop(): void;
|
||||||
|
[Symbol.dispose](): void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const loggers = new Map<string, Logger>();
|
||||||
|
|
||||||
|
export const Default = create({ service: "default" });
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
print: boolean;
|
||||||
|
dev?: boolean;
|
||||||
|
level?: Level;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logpath = "";
|
||||||
|
export function file() {
|
||||||
|
return logpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function init(options: Options) {
|
||||||
|
if (options.level) level = options.level;
|
||||||
|
cleanup(Global.Path.log);
|
||||||
|
if (options.print) return;
|
||||||
|
logpath = path.join(
|
||||||
|
Global.Path.log,
|
||||||
|
options.dev
|
||||||
|
? "dev.log"
|
||||||
|
: `${new Date().toISOString().split(".")[0]?.replace(/:/g, "")}.log`,
|
||||||
|
);
|
||||||
|
const logfile = Bun.file(logpath);
|
||||||
|
await fs.truncate(logpath).catch(() => {});
|
||||||
|
const writer = logfile.writer();
|
||||||
|
process.stderr.write = (msg) => {
|
||||||
|
writer.write(msg);
|
||||||
|
writer.flush();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanup(dir: string) {
|
||||||
|
const glob = new Bun.Glob("????-??-??T??????.log");
|
||||||
|
const files = await Array.fromAsync(
|
||||||
|
glob.scan({
|
||||||
|
cwd: dir,
|
||||||
|
absolute: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (files.length <= 5) return;
|
||||||
|
|
||||||
|
const filesToDelete = files.slice(0, -10);
|
||||||
|
await Promise.all(
|
||||||
|
filesToDelete.map((file) => fs.unlink(file).catch(() => {})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatError(error: Error, depth = 0): string {
|
||||||
|
const result = error.message;
|
||||||
|
return error.cause instanceof Error && depth < 10
|
||||||
|
? result + " Caused by: " + formatError(error.cause, depth + 1)
|
||||||
|
: result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last = Date.now();
|
||||||
export function create(tags?: Record<string, any>) {
|
export function create(tags?: Record<string, any>) {
|
||||||
tags = tags || {};
|
tags = tags || {};
|
||||||
|
|
||||||
const result = {
|
const service = tags["service"];
|
||||||
info(msg: string, extra?: Record<string, any>) {
|
if (service && typeof service === "string") {
|
||||||
|
const cached = loggers.get(service);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function build(message: any, extra?: Record<string, any>) {
|
||||||
const prefix = Object.entries({
|
const prefix = Object.entries({
|
||||||
...use().tags,
|
...use().tags,
|
||||||
...tags,
|
...tags,
|
||||||
...extra,
|
...extra,
|
||||||
})
|
})
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
.filter(([_, value]) => value !== undefined && value !== null)
|
||||||
.join(" ");
|
.map(([key, value]) => {
|
||||||
console.log(prefix, msg);
|
const prefix = `${key}=`;
|
||||||
return result;
|
if (value instanceof Error) return prefix + formatError(value);
|
||||||
},
|
if (typeof value === "object") return prefix + JSON.stringify(value);
|
||||||
warn(msg: string, extra?: Record<string, any>) {
|
return prefix + value;
|
||||||
const prefix = Object.entries({
|
|
||||||
...use().tags,
|
|
||||||
...tags,
|
|
||||||
...extra,
|
|
||||||
})
|
})
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
|
||||||
.join(" ");
|
.join(" ");
|
||||||
console.warn(prefix, msg);
|
const next = new Date();
|
||||||
return result;
|
const diff = next.getTime() - last;
|
||||||
|
last = next.getTime();
|
||||||
|
return (
|
||||||
|
[next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ") + "\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result: Logger = {
|
||||||
|
debug(message?: any, extra?: Record<string, any>) {
|
||||||
|
if (shouldLog("DEBUG")) {
|
||||||
|
process.stderr.write("DEBUG " + build(message, extra));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error(error: Error) {
|
info(message?: any, extra?: Record<string, any>) {
|
||||||
const prefix = Object.entries({
|
if (shouldLog("INFO")) {
|
||||||
...use().tags,
|
process.stderr.write("INFO " + build(message, extra));
|
||||||
...tags,
|
}
|
||||||
})
|
},
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
error(message?: any, extra?: Record<string, any>) {
|
||||||
.join(" ");
|
if (shouldLog("ERROR")) {
|
||||||
console.error(prefix, error);
|
process.stderr.write("ERROR " + build(message, extra));
|
||||||
return result;
|
}
|
||||||
|
},
|
||||||
|
warn(message?: any, extra?: Record<string, any>) {
|
||||||
|
if (shouldLog("WARN")) {
|
||||||
|
process.stderr.write("WARN " + build(message, extra));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tag(key: string, value: string) {
|
tag(key: string, value: string) {
|
||||||
tags[key] = value;
|
if (tags) tags[key] = value;
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
clone() {
|
clone() {
|
||||||
return Log.create({ ...tags });
|
return Log.create({ ...tags });
|
||||||
},
|
},
|
||||||
|
time(message: string, extra?: Record<string, any>) {
|
||||||
|
const now = Date.now();
|
||||||
|
result.info(message, { status: "started", ...extra });
|
||||||
|
function stop() {
|
||||||
|
result.info(message, {
|
||||||
|
status: "completed",
|
||||||
|
duration: Date.now() - now,
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
stop,
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
stop();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (service && typeof service === "string") {
|
||||||
|
loggers.set(service, result);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
"hono": "^4.10.1",
|
"hono": "catalog:",
|
||||||
"hono-openapi": "^1.1.0",
|
"hono-openapi": "^1.1.0",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +1,48 @@
|
|||||||
import { subjects } from "../subjects";
|
import { Hono } from "hono";
|
||||||
import { MiddlewareHandler } from "hono";
|
import { auth } from "./utils/auth";
|
||||||
import { Actor } from "@nestri/core/actor";
|
import { logger } from "hono/logger";
|
||||||
import { Api } from "@nestri/core/api/index";
|
|
||||||
import { Log } from "@nestri/core/utils/log";
|
import { Log } from "@nestri/core/utils/log";
|
||||||
import { createClient } from "@openauthjs/openauth/client";
|
import { HTTPException } from "hono/http-exception";
|
||||||
import { VisibleError, ErrorCodes } from "@nestri/core/error";
|
import { VisibleError, ErrorCodes } from "@nestri/core/error";
|
||||||
|
|
||||||
const client = createClient({
|
const log = Log.create({ service: "api" });
|
||||||
clientID: "api",
|
|
||||||
issuer: process.env.AUTH_URL,
|
export const app = new Hono();
|
||||||
subjects,
|
app
|
||||||
|
.use(logger())
|
||||||
|
.use(async (c, next) => {
|
||||||
|
c.header("Cache-Control", "no-store");
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.use(auth);
|
||||||
|
|
||||||
|
app.onError((error, c) => {
|
||||||
|
// Handle our custom VisibleError
|
||||||
|
if (error instanceof VisibleError) {
|
||||||
|
return c.json(error.toResponse(), error.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle HTTP exceptions
|
||||||
|
if (error instanceof HTTPException) {
|
||||||
|
console.error("http error:", error);
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
type: "validation",
|
||||||
|
code: ErrorCodes.Validation.INVALID_PARAMETER,
|
||||||
|
message: "Invalid request",
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any other errors as internal server errors
|
||||||
|
log.error("Unhandled API error:", { error });
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
type: "internal",
|
||||||
|
code: ErrorCodes.Server.INTERNAL_ERROR,
|
||||||
|
message: "Internal server error",
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const log = Log.create({ namespace: "api" });
|
|
||||||
|
|
||||||
const auth: MiddlewareHandler = async (c, next) => {
|
|
||||||
const authHeader =
|
|
||||||
c.req.query("authorization") ?? c.req.header("authorization");
|
|
||||||
if (authHeader) {
|
|
||||||
const match = authHeader.match(/^Bearer (.+)$/);
|
|
||||||
if (!match || !match[1]) {
|
|
||||||
throw new VisibleError(
|
|
||||||
"authentication",
|
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
|
||||||
"Bearer token not found or improperly formatted",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const bearerToken = match[1];
|
|
||||||
|
|
||||||
if (bearerToken?.startsWith("nst_")) {
|
|
||||||
const token = await Api.Personal.fromToken(bearerToken);
|
|
||||||
if (!token)
|
|
||||||
throw new VisibleError(
|
|
||||||
"authentication",
|
|
||||||
ErrorCodes.Authentication.INVALID_TOKEN,
|
|
||||||
"Invalid personal access token",
|
|
||||||
);
|
|
||||||
return Actor.provide(
|
|
||||||
"token",
|
|
||||||
{
|
|
||||||
teamID: token.teamID,
|
|
||||||
tokenID: token.id,
|
|
||||||
},
|
|
||||||
next,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await client.verify(bearerToken!);
|
|
||||||
if (result.err)
|
|
||||||
throw new VisibleError(
|
|
||||||
"authentication",
|
|
||||||
ErrorCodes.Authentication.INVALID_TOKEN,
|
|
||||||
"Invalid bearer token",
|
|
||||||
);
|
|
||||||
if (result.subject.type === "team") {
|
|
||||||
return Actor.provide(
|
|
||||||
"team",
|
|
||||||
{
|
|
||||||
email: result.subject.properties.email,
|
|
||||||
teamID: result.subject.properties.teamID,
|
|
||||||
},
|
|
||||||
next,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Actor.provide("public", {}, next);
|
|
||||||
};
|
|
||||||
|
|||||||
67
packages/function/src/api/utils/auth.ts
Normal file
67
packages/function/src/api/utils/auth.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { subjects } from "@/subjects";
|
||||||
|
import { MiddlewareHandler } from "hono";
|
||||||
|
import { Actor } from "@nestri/core/actor";
|
||||||
|
import { Api } from "@nestri/core/api/index";
|
||||||
|
import { createClient } from "@openauthjs/openauth/client";
|
||||||
|
import { VisibleError, ErrorCodes } from "@nestri/core/error";
|
||||||
|
|
||||||
|
const client = createClient({
|
||||||
|
clientID: "api",
|
||||||
|
issuer: process.env.AUTH_URL,
|
||||||
|
subjects,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const auth: MiddlewareHandler = async (c, next) => {
|
||||||
|
const authHeader =
|
||||||
|
c.req.query("authorization") ?? c.req.header("authorization");
|
||||||
|
if (authHeader) {
|
||||||
|
const match = authHeader.match(/^Bearer (.+)$/);
|
||||||
|
if (!match || !match[1]) {
|
||||||
|
throw new VisibleError(
|
||||||
|
"authentication",
|
||||||
|
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||||
|
"Bearer token not found or improperly formatted",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bearerToken = match[1];
|
||||||
|
|
||||||
|
if (bearerToken?.startsWith("nst_")) {
|
||||||
|
const token = await Api.Personal.fromToken(bearerToken);
|
||||||
|
if (!token)
|
||||||
|
throw new VisibleError(
|
||||||
|
"authentication",
|
||||||
|
ErrorCodes.Authentication.INVALID_TOKEN,
|
||||||
|
"Invalid personal access token",
|
||||||
|
);
|
||||||
|
return Actor.provide(
|
||||||
|
"token",
|
||||||
|
{
|
||||||
|
teamID: token.teamID,
|
||||||
|
tokenID: token.id,
|
||||||
|
},
|
||||||
|
next,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await client.verify(bearerToken!);
|
||||||
|
if (result.err)
|
||||||
|
throw new VisibleError(
|
||||||
|
"authentication",
|
||||||
|
ErrorCodes.Authentication.INVALID_TOKEN,
|
||||||
|
"Invalid bearer token",
|
||||||
|
);
|
||||||
|
if (result.subject.type === "team") {
|
||||||
|
return Actor.provide(
|
||||||
|
"team",
|
||||||
|
{
|
||||||
|
email: result.subject.properties.email,
|
||||||
|
teamID: result.subject.properties.teamID,
|
||||||
|
},
|
||||||
|
next,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Actor.provide("public", {}, next);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { RedisClient } from "bun";
|
import { RedisClient } from "bun";
|
||||||
import { subjects } from "../subjects.js";
|
import { logger } from "hono/logger";
|
||||||
|
import { subjects } from "@/subjects.js";
|
||||||
import { issuer } from "@openauthjs/openauth";
|
import { issuer } from "@openauthjs/openauth";
|
||||||
import { Team } from "@nestri/core/team/index";
|
import { Team } from "@nestri/core/team/index";
|
||||||
import { RedisStorage } from "./src/storage.js";
|
import { RedisStorage } from "./src/storage.js";
|
||||||
@@ -62,12 +63,10 @@ const app = issuer({
|
|||||||
async allow(input) {
|
async allow(input) {
|
||||||
const url = new URL(input.redirectURI);
|
const url = new URL(input.redirectURI);
|
||||||
return (
|
return (
|
||||||
url.hostname.endsWith("localhost") ||
|
url.hostname.endsWith("localhost") || url.hostname.endsWith("nestri.io")
|
||||||
url.hostname.endsWith("nestri.io") ||
|
|
||||||
url.hostname.endsWith("console.nestri.io")
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
}).use(logger());
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
|
|||||||
Reference in New Issue
Block a user