Auth
This commit is contained in:
parent
527ecf6146
commit
d6b845e6d2
|
|
@ -1,47 +1,123 @@
|
|||
import { startSubdomainServers } from "@/orchestrator"
|
||||
import { nanoRemix } from "@workshop/nano-remix"
|
||||
import { serve } from "bun"
|
||||
import { join } from "node:path"
|
||||
|
||||
const portMap = await startSubdomainServers()
|
||||
/**
|
||||
* Password-protected subdomain proxy server
|
||||
*
|
||||
* How it works:
|
||||
* 1. Starts multiple subdomain servers on different ports (3001+)
|
||||
* 2. Main server (port 3000) acts as a proxy that routes requests based on subdomain
|
||||
* 3. All requests are protected with Basic HTTP auth + persistent cookies
|
||||
* 4. Once authenticated, cookies work across all subdomains (*.yourdomain.com)
|
||||
*
|
||||
* This server is designed to be run in production with NODE_ENV=production. On development,
|
||||
* it allows unauthenticated access because YOLO.
|
||||
*/
|
||||
|
||||
const server = serve({
|
||||
port: 3000,
|
||||
fetch: async (req) => {
|
||||
const url = new URL(req.url)
|
||||
const hostname = url.hostname
|
||||
const serve = async () => {
|
||||
const portMap = await startSubdomainServers()
|
||||
const server = startServer(portMap)
|
||||
logServerInfo(server, portMap)
|
||||
}
|
||||
|
||||
const subdomain = hostname.split(".")[0]
|
||||
serve()
|
||||
|
||||
const targetPort = subdomain && portMap[subdomain]
|
||||
if (!targetPort) {
|
||||
const routePath = join(import.meta.dir, "routes")
|
||||
return nanoRemix(req, { routePath })
|
||||
const startServer = (portMap: Record<string, number>) => {
|
||||
const server = Bun.serve({
|
||||
port: 3000,
|
||||
routes: {
|
||||
// Handle main site routes through nano-remix
|
||||
"/*": requireAuth(async (req) => {
|
||||
const url = new URL(req.url)
|
||||
const hostname = url.hostname
|
||||
const subdomain = hostname.split(".")[0]
|
||||
|
||||
const targetPort = subdomain && portMap[subdomain]
|
||||
if (!targetPort) {
|
||||
const routePath = join(import.meta.dir, "routes")
|
||||
return nanoRemix(req, { routePath })
|
||||
}
|
||||
|
||||
try {
|
||||
const target = `http://127.0.0.1:${targetPort}${url.pathname}${url.search}`
|
||||
|
||||
// Forward the request with auth headers preserved
|
||||
const forwardedReq = new Request(target, {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body: req.body,
|
||||
})
|
||||
|
||||
const upstream = await fetch(forwardedReq)
|
||||
|
||||
return new Response(upstream.body, {
|
||||
status: upstream.status,
|
||||
headers: new Headers(upstream.headers),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Error forwarding request to subdomain "${subdomain}":`, error)
|
||||
return new Response(`Failed to forward request to "${subdomain}"`, { status: 500 })
|
||||
}
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
const logServerInfo = (server: Bun.Server, portMap: Record<string, number>) => {
|
||||
console.log(`${server.url}`)
|
||||
const subdomainEntries = Object.entries(portMap)
|
||||
subdomainEntries.forEach(([subdomain, port], index) => {
|
||||
const subdomainUrl = new URL(server.url)
|
||||
subdomainUrl.hostname = `${subdomain}.${subdomainUrl.hostname}`
|
||||
subdomainUrl.port = port.toString()
|
||||
const isLast = index === subdomainEntries.length - 1
|
||||
const prefix = isLast ? "└─" : "├─"
|
||||
console.log(`${prefix} ${subdomainUrl}`)
|
||||
})
|
||||
console.log("")
|
||||
}
|
||||
|
||||
const requireAuth = (handler: (req: Request) => Response | Promise<Response>) => {
|
||||
return async (req: Request) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
try {
|
||||
const target = `http://127.0.0.1:${targetPort}${url.pathname}${url.search}`
|
||||
const upstream = await fetch(target, req)
|
||||
const cookies = new Bun.CookieMap(req.headers.get("cookie") ?? "")
|
||||
if (cookies.get("auth") === "authenticated") {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
return new Response(upstream.body, {
|
||||
status: upstream.status,
|
||||
headers: new Headers(upstream.headers),
|
||||
const auth = req.headers.get("authorization")
|
||||
const correctAuth = `Basic ${btoa("spike:888")}`
|
||||
|
||||
if (auth === correctAuth) {
|
||||
const response = await handler(req)
|
||||
const newHeaders = new Headers(response.headers)
|
||||
|
||||
const url = new URL(req.url)
|
||||
const cookieDomain = `.${url.hostname.split(".").slice(-2).join(".")}`
|
||||
|
||||
const authCookie = new Bun.Cookie({
|
||||
name: "auth",
|
||||
value: "authenticated",
|
||||
domain: cookieDomain,
|
||||
path: "/",
|
||||
maxAge: 86400,
|
||||
httpOnly: true,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Error forwarding request to subdomain "${subdomain}":`, error)
|
||||
return new Response(`Failed to forward request to "${subdomain}"`, { status: 500 })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`${server.url}`)
|
||||
const subdomainEntries = Object.entries(portMap)
|
||||
subdomainEntries.forEach(([subdomain, port], index) => {
|
||||
const subdomainUrl = new URL(server.url)
|
||||
subdomainUrl.hostname = `${subdomain}.${subdomainUrl.hostname}`
|
||||
subdomainUrl.port = port.toString()
|
||||
const isLast = index === subdomainEntries.length - 1
|
||||
const prefix = isLast ? "└─" : "├─"
|
||||
console.log(`${prefix} ${subdomainUrl}`)
|
||||
})
|
||||
console.log("")
|
||||
newHeaders.set("Set-Cookie", authCookie.toString())
|
||||
|
||||
return new Response(response.body, { status: response.status, headers: newHeaders })
|
||||
}
|
||||
|
||||
return new Response("Unauthorized", {
|
||||
status: 401,
|
||||
headers: { "WWW-Authenticate": 'Basic realm="Workshop Access"' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user