import { ABC, Base64 } from "./deps.ts"; import config from "./config.ts"; const packageNameRegex = /^[@]?[a-zA-Z][\d\w\-\_]*$/g.compile(); const packageVersionRegex = /^\d(\.\d)?(\.\d)?$/g.compile(); const packageFullVersionRegex = /^\d(\.\d)(\.\d)$/g.compile(); export const isValidPackageName = (name: string) => packageNameRegex.test(name); export const isValidVersion = (version: string) => packageVersionRegex.test(version); export const isValidFullVersion = (version: string) => packageFullVersionRegex.test(version); const ALg = 1; const ASm = -ALg; const Equ = 0; export const sortVersions = (a: string, b: string) => { const [a1, a2, a3] = a.split(".").map(Number); const [b1, b2, b3] = b.split(".").map(Number); if (a1 > b1) return ALg; if (a1 < b1) return ASm; if (a2 > b2) return ALg; if (a2 < b2) return ASm; if (a3 > b3) return ALg; if (a3 < b3) return ASm; return Equ; }; export const basicauth = (realm: string) => (next: ABC.HandlerFunc) => ( ctx: ABC.Context ) => { const value = ctx.request.headers.get("authorization"); console.log("Header:", value); if (value && value.toLowerCase().startsWith("basic ")) { const credentials = value.slice(6); const [username, passwd] = new TextDecoder() .decode(Base64.decode(credentials)) .split(":", 2); if (config?.user[username]?.password === passwd) { console.log("User authenticated!"); if (!ctx.customContext) ctx.customContext = {}; ctx.customContext.user = username; return next(ctx); } } console.log("Authentication required"); ctx.response.status = 401; ctx.response.headers.set("WWW-Authenticate", "Basic realm=" + realm); return { statusCode: 401, error: "Authentication required", }; }; export function extractPackagePath(path: string): [string, string | undefined] { let packageName = ""; path = path.toLowerCase(); if (path.startsWith("@")) { packageName = "@"; path = path.slice(1); } let parts = path.split("@"); if (parts.length > 2) throw new Error("Invalid package name!"); packageName += parts[0]; let packageVersion: string | undefined = parts[1]; if (!isValidPackageName(packageName)) throw new Error("Invalid package name!"); if (packageVersion !== "") { if (!isValidVersion(packageVersion)) throw new Error("Invalid package version!"); } return [packageName, packageVersion]; } import type { IPackage } from "./db.ts"; import bucket from "./s3.ts"; export function getAbsolutePackageVersion( pkg?: IPackage | null, version?: string ) { if (!pkg || pkg.versions.length < 1) return undefined; const versions = pkg.versions.sort(sortVersions).reverse(); if (!version) { version = versions[0]; } else { const v = versions.filter((e) => e.startsWith(version as string)); if (v.length < 1) return undefined; version = v[0]; } return version; } export async function getBucketFilePath( pkgName: string, version: string, file: string ) { if (file.startsWith("/")) { file = file.substr(1); } const bucketPath = ( "packages/" + pkgName + "/" + version + "/" + file ).replace(/@/g, "ยง"); return bucketPath; } export async function getFile( pkgName: string, version: string | undefined, file: string ): Promise<{ etag: string; data: Uint8Array } | null | undefined> { if (!version) return undefined; const bucketPath = await getBucketFilePath(pkgName, version, file); if (!bucketPath) return null; console.log("Getting file from:", bucketPath); try { const res = await bucket.getObject(bucketPath); if (!res || res.body.byteLength === 0) return undefined; return { etag: res.etag, data: res.body, }; } catch (err) { const msg = err.message as string; if (msg.indexOf("404") >= 0) return null; throw err; } }