MCPcopy Create free account
hub / github.com/TraderAlice/OpenAlice / createAuthMiddleware

Function createAuthMiddleware

src/webui/middleware/auth.ts:54–118  ·  view source on GitHub ↗
(opts: AuthMiddlewareOptions)

Source from the content-addressed store, hash-verified

52}
53
54export function createAuthMiddleware(opts: AuthMiddlewareOptions): MiddlewareHandler {
55 const trustedProxies = new Set(opts.trustedProxies)
56 const csrfTrustedOrigins = new Set(opts.csrfTrustedOrigins)
57
58 return async (c: Context, next) => {
59 if (opts.disabled) return next()
60
61 const path = c.req.path
62
63 if (PUBLIC_PATH_EXACT.has(path)) return next()
64 if (PUBLIC_PATH_PREFIX.some((p) => path.startsWith(p))) return next()
65
66 // SPA shell — any GET to a non-API path is public. The React bundle
67 // is the entity that decides "render the login page vs the app" by
68 // polling /api/auth/status; if we 401 the HTML itself, the user
69 // can't even reach the login UI. Mutations and any /api/* still
70 // require a session.
71 if (c.req.method === 'GET' && !path.startsWith('/api/')) {
72 return next()
73 }
74
75 // Localhost passthrough — only honored when no trusted proxy is
76 // configured. With a trusted proxy in front, the proxy IS at 127.0.0.1
77 // from Alice's view, so trusting "localhost requests" would let every
78 // public request through. See safe/playbooks/03-localhost-spoofing.md.
79 if (trustedProxies.size === 0) {
80 const clientIp = getSocketRemoteAddress(c)
81 if (clientIp && isLoopbackIp(clientIp)) {
82 return next()
83 }
84 }
85
86 // Session cookie check
87 const sid = readSessionCookie(c.req.header('cookie') ?? '')
88 if (!sid) {
89 return c.json({ error: 'Unauthorized', code: 'NO_SESSION' }, 401)
90 }
91 const session = await validateAndTouch(sid)
92 if (!session) {
93 return c.json({ error: 'Unauthorized', code: 'INVALID_SESSION' }, 401)
94 }
95
96 // CSRF — Origin check on state-changing methods. SameSite=Lax cookie
97 // catches most of these already, but a malicious page hosted same-site
98 // (e.g., XSS on a sibling subdomain) could still issue authenticated
99 // mutations. Explicit Origin enforcement is the second layer.
100 if (MUTATING_METHODS.has(c.req.method)) {
101 const origin = c.req.header('origin')
102 if (origin) {
103 if (!isAllowedOrigin(origin, c, csrfTrustedOrigins)) {
104 return c.json({ error: 'Forbidden: origin not allowed', code: 'CSRF_ORIGIN' }, 403)
105 }
106 }
107 // Origin header absent on POST is common from non-browser callers
108 // (curl, Telegram bot, server-to-server). We allow it — only reject
109 // when an Origin IS provided and is wrong. A future tightening could
110 // require Origin for browser-typical mutating requests, but it would
111 // break legitimate CLI use.

Callers 4

startMethod · 0.85
makeAppFunction · 0.85
makeSPAAppFunction · 0.85
auth.spec.tsFile · 0.85

Calls 8

nextFunction · 0.85
getSocketRemoteAddressFunction · 0.85
isLoopbackIpFunction · 0.85
validateAndTouchFunction · 0.85
isAllowedOriginFunction · 0.85
readSessionCookieFunction · 0.70
hasMethod · 0.65
setMethod · 0.65

Tested by 2

makeAppFunction · 0.68
makeSPAAppFunction · 0.68