Skip to content

Commit

Permalink
chore: 🤖 jwt and friendly errors
Browse files Browse the repository at this point in the history
  • Loading branch information
charlzyx committed Jan 31, 2024
1 parent 5d7c771 commit efb695d
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM oven/bun:alpine
FROM oven/bun:slim
# FROM oven/bun
WORKDIR /app
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"@elysiajs/cors": "^0.8.0",
"@elysiajs/html": "^0.8.0",
"@elysiajs/jwt": "^0.8.0",
"@elysiajs/static": "^0.8.1",
"elysia": "latest",
"gunzip-maybe": "^1.4.2",
Expand Down
43 changes: 30 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ export const BunPkgConfig = {
return process.env.HOST_NAME ?? "0.0.0.0";
},
get origin() {
return (
process.env.ORIGIN ||
`${process.env.HOST_NAME}:${process.env.PORT}` ||
"0.0.0.0"
);
return process.env.ORIGIN || `http://localhost${process.env.PORT}`;
},
cors: {
origin: /^\//.test(Bun.env.CORS_ORIGIN ?? "")
Expand All @@ -28,19 +24,40 @@ export const BunPkgConfig = {
const maybe = Number(Bun.env.CACHE_GIB);
return Number.isNaN(maybe) ? 4 : maybe;
},
set cacheDir(dir: string) {
Bun.env.CACHEW_DIR = dir;
},
get npmAuthToken() {
return Bun.env.NPM_AUTH_TOKEN;
},

get npmRegistryURL() {
return (Bun.env.NPM_REGISTRY_URL || "https://registry.npmjs.org").replace(
/\/$/,
"",
);
},
set npmRegistryURL(neo: string) {
Bun.env.NPM_REGISTRY_URL = neo;
get npmAuthToken() {
return Bun.env.NPM_AUTH_TOKEN;
},
get jwtSecret() {
return Bun.env.JWT_SECRET;
},
get jwtUserList() {
return (Bun.env.JWT_USERS || "").split(",").map((item) => item.trim());
},
// TODO: The list to ban some packages or scopes.
// banList: {
// packages: ["@some_scope/package_name"],
// scopes: [
// {
// name: "@your_scope",
// excludes: ["package_name"],
// },
// ],
// },

// // TODO: The list to only allow some packages or scopes.
// allowList: {
// packages: ["@some_scope/package_name"],
// scopes: [
// {
// name: "@your_scope",
// },
// ],
// },
};
89 changes: 74 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { cors } from "@elysiajs/cors";
import { Elysia } from "elysia";
import fs from "fs";
import { router } from "./routes/router";
import { cors } from "@elysiajs/cors";

import { BunPkgConfig } from "./config";
import path from "path";
import { jwt } from "@elysiajs/jwt";
import { BunPkgConfig } from "./config";
import { err } from "./templates";

// static file server
Expand All @@ -18,23 +17,70 @@ Bun.serve({
},
});

new Elysia()
const app = new Elysia();

app
.use(cors(BunPkgConfig.cors))
.use(
jwt({
name: "jwt",
secret: BunPkgConfig.jwtSecret || "NONE",
}),
)
.get("favicon.ico", () => {
return new Response("");
})
.get("", ({ set }) => {
set.redirect = "/";
})
.get("/browser/*", (ctx) => {
return "TODO: file browser";
})
.get("/", () => {
const resp = new Response(Bun.file("src/templates/BUNPKG.html"));
resp.headers.set("Content-Type", "text/html; charset=utf8");
return resp;
})
.use(router)
.guard(
{
async beforeHandle({ set, jwt, path, cookie: { auth } }) {
const hasJwt = BunPkgConfig.jwtSecret;
if (!hasJwt) return;
const isSign = /\/_sign\/\w+/.test(path);
if (isSign) return;

const profile = await jwt.verify(auth.value);
// console.log("🚀 ~ beforeHandle ~ profile:", profile);

if (!profile) {
set.status = 401;
return "Unauthorized";
}
},
},

(app) =>
app
.get("/_sign/:name", async ({ jwt, set, cookie: { auth }, params }) => {
const userlist = BunPkgConfig.jwtUserList;
// TODO: sqlite user.db
if (!userlist.includes(params.name)) {
set.status = 401;
return "Unauthorized";
} else {
auth.set({
value: await jwt.sign(params),
httpOnly: true,
maxAge: 7 * 86400,
path: "/",
});

return "welcome";
}
})

.get("/browser/*", (ctx) => {
return "TODO: file browser";
})
.get("/", () => {
const resp = new Response(Bun.file("src/templates/BUNPKG.html"));
resp.headers.set("Content-Type", "text/html; charset=utf8");
return resp;
})
.use(router),
)

.onError(({ code, error }) => {
const resp = new Response(
Expand All @@ -53,8 +99,21 @@ new Elysia()
port: BunPkgConfig.PORT,
});

const black = /npmAuthToken|jwtSecret/;

console.log(
`BUNPKG is Running at http://${BunPkgConfig.HOST_NAME}:${BunPkgConfig.PORT}`,
"with BunConfig :\n",
JSON.stringify(BunPkgConfig, null, 2),
JSON.stringify(
Object.keys(BunPkgConfig).reduce(
(ok: any, key) => {
if (black.test(key)) return ok;
ok[key] = BunPkgConfig[key as keyof typeof BunPkgConfig];
return ok;
},
{} as typeof BunPkgConfig,
),
null,
2,
),
);
3 changes: 1 addition & 2 deletions src/routes/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export const meta: WithPkgInfo<Response> = async (
} catch (error: any) {
set.status = 404;
throw new Error(
error.toString() ||
`Cannot find an meta of ${filename} in ${packageName}@${packageVersion}`,
`Cannot find meta of ${filename} in ${packageName}@${packageVersion}. Case by ${error.toString()}`,
);
}
};
8 changes: 5 additions & 3 deletions src/utils/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,17 @@ export const searchPackageEntry = async (

const result = await search(tarball!, filename);

const { foundEntry: entry, matchingEntries: entries } = result;
const { foundEntry: entry, matchingEntries: entries, tried } = result;

if (meta) {
return { entry, entries };
}

if (!entry) {
throw new Error(
`Cannot find entry ${filename} in ${packageName}@${packageVersion}`,
`Cannot find entry ${filename} in ${packageName}@${packageVersion}. tried ${tried.join(
"\n",
)}`,
);
}
if (entry.type === "file" && entry.path !== filename) {
Expand All @@ -208,7 +210,7 @@ export const searchPackageEntry = async (
filename = indexEntry.path!;
} else {
throw new Error(
`Cannot find an index in ${filename} in ${packageName}@${packageVersion}`,
`Cannot find an index in ${filename} in ${packageName}@${packageVersion}, tried ${indexEntry}.`,
);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/sqlite-lru-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class SqliteLRUCache<Meta> {
maxByteSize: maxSize = 1 * Math.pow(2, 30),
onRemove = () => {},
}: SqlCacheOptions<Meta>) {
console.log("🚀 ~ SqliteLRUCache<Meta> ~ database:", database);
this.db = new Database(database ?? ":memory:", { create: true });
this.maxLen = maxLen;
this.maxByteSize = maxSize;
Expand Down
4 changes: 4 additions & 0 deletions src/utils/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const search = async (tarball: IncomingMessage, filename: string) => {
type SearchResult = {
foundEntry: IFileMeta;
matchingEntries: Record<string, IFileMeta>;
tried: string[];
};
return new Promise<SearchResult>((accept, reject) => {
const jsEntryFilename = `${filename}.js`;
Expand All @@ -52,6 +53,7 @@ export const search = async (tarball: IncomingMessage, filename: string) => {
if (filename === "/") {
foundEntry = matchingEntries["/"] = { name: "/", type: "directory" };
}
const tried: string[] = [];

tarball
.pipe(gunzip())
Expand All @@ -68,6 +70,7 @@ export const search = async (tarball: IncomingMessage, filename: string) => {
type: header.type,
};

tried.push(`[${entry.type ?? ""}] ${entry.path}`);
// Skip non-files and files that don't match the entryName.
if (entry.type !== "file" || !entry?.path?.startsWith(filename)) {
stream.resume();
Expand Down Expand Up @@ -137,6 +140,7 @@ export const search = async (tarball: IncomingMessage, filename: string) => {
// try a directory entry with the same name.
foundEntry: foundEntry || matchingEntries[filename] || null,
matchingEntries: matchingEntries,
tried,
});
});
});
Expand Down
29 changes: 29 additions & 0 deletions src/utils/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Database, Statement } from "bun:sqlite";
import { serialize, deserialize } from "v8";

export class UserDB {
db: Database;

constructor() {
this.db = new Database("user.db", { create: true });
this._initdb(this.db);
}

private _initdb(db: Database) {
db.exec("PRAGMA journal_mode = WAL;");
db.transaction(() => {
// 初始化
db.prepare(
`CREATE TABLE IF NOT EXISTS user (
username TEXT PRIMARY KEY,
passwd BLOB,
ban INT
)`,
).run();
// 创建索引
db.prepare(
"CREATE INDEX IF NOT EXISTS username ON cache (username)",
).run();
})();
}
}

0 comments on commit efb695d

Please sign in to comment.