46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
type AccessConfig = { origin: string } & (
|
|
| { mode: 'tailscale'; allowedLogin: string }
|
|
| { mode: 'none' }
|
|
)
|
|
|
|
export function readAccessConfig(): AccessConfig | null {
|
|
const origin = process.env.TWITTER_LITE_ORIGIN
|
|
const mode = process.env.TWITTER_LITE_AUTH_MODE ?? 'tailscale'
|
|
const allowedLogin = process.env.TWITTER_LITE_ALLOWED_LOGIN
|
|
if (!origin || (mode !== 'tailscale' && mode !== 'none')) return null
|
|
try {
|
|
const url = new URL(origin)
|
|
const secure = url.protocol === 'https:'
|
|
const local = url.protocol === 'http:' && url.hostname === '127.0.0.1'
|
|
if ((!secure && !local) || url.origin !== origin) return null
|
|
if (mode === 'none') return { origin, mode }
|
|
if (!allowedLogin?.trim()) return null
|
|
return { origin, mode, allowedLogin }
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Use loopback behind Serve for identity, or a private network for mode none. */
|
|
export function checkAccess(
|
|
request: Request,
|
|
config: AccessConfig | null,
|
|
): Response | null {
|
|
if (!config) {
|
|
return new Response('Access configuration is required.', { status: 503 })
|
|
}
|
|
if (
|
|
config.mode === 'tailscale' &&
|
|
request.headers.get('Tailscale-User-Login') !== config.allowedLogin
|
|
) {
|
|
return new Response('Forbidden', { status: 403 })
|
|
}
|
|
if (
|
|
!['GET', 'HEAD', 'OPTIONS'].includes(request.method) &&
|
|
request.headers.get('Origin') !== config.origin
|
|
) {
|
|
return new Response('Forbidden', { status: 403 })
|
|
}
|
|
return null
|
|
}
|