mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-28 15:51:29 +08:00
fdb61c2a14
* feat: add captcha protection for authentication Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * test: guard captcha option key literal Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * test: avoid captcha placeholder literal Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * fix: pin captcha secret option literal Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * fix: block oauth when captcha is enabled Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * test: cover captcha social pass-through Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98 * test: cover settings identity and registration saves Agent-Profile: https://agent-kanban.dev/agents/a318237412dd8b98
168 lines
7.2 KiB
TypeScript
168 lines
7.2 KiB
TypeScript
import { Hono } from 'hono'
|
|
import { cors } from 'hono/cors'
|
|
import type { Auth } from './auth'
|
|
import { authMiddleware } from './middleware/auth'
|
|
import { imageHostingDomain } from './middleware/image-hosting-domain'
|
|
import { accessLog } from './middleware/logger'
|
|
import type { Env } from './middleware/platform'
|
|
import { platformMiddleware } from './middleware/platform'
|
|
import type { Platform } from './platform/interface'
|
|
import { adminAnnouncements, announcements } from './routes/announcements'
|
|
import { adminAudit } from './routes/audit'
|
|
import { adminAuthProviders, publicAuthProviders } from './routes/auth-providers'
|
|
import backgroundJobs from './routes/background-jobs'
|
|
import { brandingAdmin, publicBranding } from './routes/branding'
|
|
import { adminCloudStore, cloudStore, cloudStoreWebhooks } from './routes/cloud-store'
|
|
import emailConfig from './routes/email-config'
|
|
import ihost from './routes/ihost'
|
|
import ihostConfig from './routes/ihost-config'
|
|
import { adminInviteCodes, publicInviteCodes } from './routes/invite-codes'
|
|
import licensing from './routes/licensing'
|
|
import licensingAdmin from './routes/licensing-admin'
|
|
import { me } from './routes/me'
|
|
import { notifications } from './routes/notifications'
|
|
import objects from './routes/objects'
|
|
import profile from './routes/profile'
|
|
import { adminQuotas, userQuotas } from './routes/quotas'
|
|
import redirect from './routes/redirect'
|
|
import { authedShares, publicShares } from './routes/shares'
|
|
import { adminSiteInvitations, publicSiteInvitations } from './routes/site-invitations'
|
|
import storages from './routes/storages'
|
|
import system from './routes/system'
|
|
import { publicTeams, teams } from './routes/teams'
|
|
import trash from './routes/trash'
|
|
import users from './routes/users'
|
|
import webdav from './routes/webdav'
|
|
import { isCaptchaEnabled, verifyCaptchaToken } from './services/captcha'
|
|
|
|
export function createApp(platform: Platform, auth: Auth) {
|
|
const app = new Hono<Env>()
|
|
|
|
app.use('/*', platformMiddleware(platform, auth))
|
|
app.use('/*', imageHostingDomain)
|
|
app.use('/api/*', accessLog)
|
|
app.use('/dav', accessLog)
|
|
app.use('/dav/*', accessLog)
|
|
|
|
app.use(
|
|
'/api/*',
|
|
cors({
|
|
origin: (origin) => origin || '*',
|
|
allowHeaders: ['Content-Type', 'Authorization'],
|
|
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
credentials: true,
|
|
}),
|
|
)
|
|
|
|
app.on(['POST', 'GET'], '/api/auth/*', async (c) => {
|
|
const captchaAuthPaths = ['/api/auth/sign-in/email', '/api/auth/sign-in/username', '/api/auth/sign-up/email']
|
|
const captchaBlockedAuthPaths = ['/api/auth/sign-in/social']
|
|
if (c.req.method === 'POST' && captchaAuthPaths.includes(c.req.path)) {
|
|
const body = (await c.req.raw
|
|
.clone()
|
|
.json()
|
|
.catch(() => ({}))) as { captchaToken?: string }
|
|
const valid = await verifyCaptchaToken(c.get('platform').db, body.captchaToken, c.req.header('CF-Connecting-IP'))
|
|
if (!valid) return c.json({ error: 'Invalid captcha token' }, 400)
|
|
}
|
|
if (c.req.method === 'POST' && captchaBlockedAuthPaths.includes(c.req.path)) {
|
|
if (await isCaptchaEnabled(c.get('platform').db)) {
|
|
return c.json({ error: 'Captcha is required for social authentication' }, 400)
|
|
}
|
|
}
|
|
const a = c.get('auth')
|
|
return a.handler(c.req.raw)
|
|
})
|
|
|
|
app.all('/dav', (c) => c.redirect('/dav/', 308))
|
|
app.route('/dav', webdav)
|
|
|
|
// Public routes — no auth required; mount before authMiddleware.
|
|
// /api/shares/:token endpoints are covered by run_worker_first=["/api/*"] in wrangler.toml.
|
|
// /r/* is listed separately in run_worker_first.
|
|
// /s/:token is intentionally left for the SPA landing page.
|
|
app.route('/api/shares', publicShares)
|
|
app.route('/r', redirect)
|
|
app.route('/api/profiles', profile)
|
|
app.route('/api/teams', publicTeams)
|
|
app.route('/api/auth-providers', publicAuthProviders)
|
|
app.route('/api/licensing', licensing)
|
|
app.route('/api/branding', publicBranding)
|
|
app.route('/api/site-invitations', publicSiteInvitations)
|
|
app.route('/api/store', cloudStoreWebhooks)
|
|
|
|
app.use('/api/*', authMiddleware)
|
|
|
|
app.route('/api/me', me)
|
|
app.route('/api/announcements', announcements)
|
|
|
|
// Mount routes separately to avoid deep type chain accumulation.
|
|
// Each .route() call is independent — TypeScript doesn't stack types.
|
|
app.route('/api/objects', objects)
|
|
app.route('/api/shares', authedShares)
|
|
app.route('/api/trash', trash)
|
|
app.route('/api/teams', teams)
|
|
app.route('/api/admin/storages', storages)
|
|
app.route('/api/admin/users', users)
|
|
app.route('/api/admin/email-config', emailConfig)
|
|
app.route('/api/admin/invite-codes', adminInviteCodes)
|
|
app.route('/api/invite-codes', publicInviteCodes)
|
|
app.route('/api/admin/site-invitations', adminSiteInvitations)
|
|
app.route('/api/admin/quotas', adminQuotas)
|
|
app.route('/api/quotas', userQuotas)
|
|
app.route('/api/store', cloudStore)
|
|
app.route('/api/admin/store', adminCloudStore)
|
|
app.route('/api/system', system)
|
|
app.route('/api/admin/auth-providers', adminAuthProviders)
|
|
app.route('/api/notifications', notifications)
|
|
app.route('/api/background-jobs', backgroundJobs)
|
|
app.route('/api/ihost', ihost)
|
|
app.route('/api/ihost/config', ihostConfig)
|
|
app.route('/api/licensing', licensingAdmin)
|
|
app.route('/api/admin/branding', brandingAdmin)
|
|
app.route('/api/admin/announcements', adminAnnouncements)
|
|
app.route('/api/admin/audit', adminAudit)
|
|
|
|
app.get('/api/health', (c) => c.json({ status: 'ok' }))
|
|
|
|
return app
|
|
}
|
|
|
|
export type AppType = ReturnType<typeof createApp>
|
|
|
|
// Sub-router types for RPC clients — avoids combined AppType OOM
|
|
export type ObjectsRoute = typeof objects
|
|
export type PublicSharesRoute = typeof publicShares
|
|
export type AuthedSharesRoute = typeof authedShares
|
|
export type TrashRoute = typeof trash
|
|
export type StoragesRoute = typeof storages
|
|
export type UsersRoute = typeof users
|
|
export type AdminQuotasRoute = typeof adminQuotas
|
|
export type UserQuotasRoute = typeof userQuotas
|
|
export type SystemRoute = typeof system
|
|
export type EmailConfigRoute = typeof emailConfig
|
|
export type AdminInviteCodesRoute = typeof adminInviteCodes
|
|
export type PublicInviteCodesRoute = typeof publicInviteCodes
|
|
export type AdminSiteInvitationsRoute = typeof adminSiteInvitations
|
|
export type PublicSiteInvitationsRoute = typeof publicSiteInvitations
|
|
export type AuthProvidersRoute = typeof publicAuthProviders
|
|
export type AdminAuthProvidersRoute = typeof adminAuthProviders
|
|
export type ProfileRoute = typeof profile
|
|
export type CloudStoreRoute = typeof cloudStore
|
|
export type CloudStoreWebhooksRoute = typeof cloudStoreWebhooks
|
|
export type AdminCloudStoreRoute = typeof adminCloudStore
|
|
export type TeamsRoute = typeof teams
|
|
export type PublicTeamsRoute = typeof publicTeams
|
|
export type NotificationsRoute = typeof notifications
|
|
export type BackgroundJobsRoute = typeof backgroundJobs
|
|
export type IhostRoute = typeof ihost
|
|
export type IhostConfigRoute = typeof ihostConfig
|
|
export type MeRoute = typeof me
|
|
export type AnnouncementsRoute = typeof announcements
|
|
export type AdminAnnouncementsRoute = typeof adminAnnouncements
|
|
export type LicensingRoute = typeof licensing
|
|
export type LicensingAdminRoute = typeof licensingAdmin
|
|
export type PublicBrandingRoute = typeof publicBranding
|
|
export type BrandingAdminRoute = typeof brandingAdmin
|
|
export type AdminAuditRoute = typeof adminAudit
|