release: v1.1.3 — dual-domain separation and URL options
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Add admin/image domain separation with host isolation middleware, optional hide-folder public URLs, GitHub release update check, and Lucky/Nginx deployment docs. Switch license to GPL-3.0-only.
This commit is contained in:
1 parent
55effa204e
commit
d8c1303c32
41 files changed
+2170
-299
No files matched your search
@@ -1,9 +1,12 @@
|
||||
import { requireAdminAuth } from '../../utils/access'
|
||||
import { createApiError } from '../../utils/api-error'
|
||||
import {
|
||||
getSetting,
|
||||
setSetting,
|
||||
SETTINGS_ALLOWED_REFERER_HOSTS,
|
||||
SETTINGS_HIDE_FOLDER_IN_URL,
|
||||
SETTINGS_IMAGE_BASE_URL,
|
||||
SETTINGS_SITE_BASE_URL,
|
||||
SETTINGS_WEBP_QUALITY,
|
||||
setAllowRegistration
|
||||
} from '../../utils/db'
|
||||
@@ -11,18 +14,23 @@ import {
|
||||
getSettingsPayload,
|
||||
isValidImageBaseUrl,
|
||||
isValidRefererHost,
|
||||
isValidSiteBaseUrl,
|
||||
MAX_AUTO_DELETE_DAYS,
|
||||
normalizeImageBaseUrl,
|
||||
normalizeRefererHosts,
|
||||
normalizeSiteBaseUrl,
|
||||
parseAutoDeleteDays,
|
||||
parseWebpQuality,
|
||||
setGlobalAutoDeletePolicy
|
||||
setGlobalAutoDeletePolicy,
|
||||
validateDomainSeparationPair
|
||||
} from '../../utils/env'
|
||||
|
||||
interface SettingsPatchBody {
|
||||
webpQuality?: unknown
|
||||
allowedRefererHosts?: unknown
|
||||
siteBaseUrl?: unknown
|
||||
imageBaseUrl?: unknown
|
||||
hideFolderInUrl?: unknown
|
||||
autoDeleteDays?: unknown
|
||||
allowRegistration?: unknown
|
||||
}
|
||||
@@ -61,6 +69,21 @@ export default defineEventHandler(async (event) => {
|
||||
setSetting(SETTINGS_ALLOWED_REFERER_HOSTS, normalized)
|
||||
}
|
||||
|
||||
if (body.siteBaseUrl !== undefined) {
|
||||
const configured = normalizeSiteBaseUrl(
|
||||
typeof body.siteBaseUrl === 'string' ? body.siteBaseUrl : ''
|
||||
)
|
||||
if (configured && !isValidSiteBaseUrl(configured)) {
|
||||
createApiError(
|
||||
event,
|
||||
'INVALID_REQUEST',
|
||||
'SITE_BASE_URL 需为 http(s) 地址,或留空使用当前请求域名',
|
||||
400
|
||||
)
|
||||
}
|
||||
setSetting(SETTINGS_SITE_BASE_URL, configured)
|
||||
}
|
||||
|
||||
if (body.imageBaseUrl !== undefined) {
|
||||
const configured = normalizeImageBaseUrl(
|
||||
typeof body.imageBaseUrl === 'string' ? body.imageBaseUrl : ''
|
||||
@@ -76,6 +99,21 @@ export default defineEventHandler(async (event) => {
|
||||
setSetting(SETTINGS_IMAGE_BASE_URL, configured)
|
||||
}
|
||||
|
||||
if (body.siteBaseUrl !== undefined || body.imageBaseUrl !== undefined) {
|
||||
const siteConfigured = normalizeSiteBaseUrl(
|
||||
getSetting(SETTINGS_SITE_BASE_URL) ?? ''
|
||||
)
|
||||
const imageConfigured = normalizeImageBaseUrl(
|
||||
getSetting(SETTINGS_IMAGE_BASE_URL) ?? ''
|
||||
)
|
||||
if (siteConfigured && imageConfigured) {
|
||||
const pairError = validateDomainSeparationPair(siteConfigured, imageConfigured)
|
||||
if (pairError) {
|
||||
createApiError(event, 'INVALID_REQUEST', pairError, 400)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (body.autoDeleteDays !== undefined) {
|
||||
const days = parseAutoDeleteDays(String(body.autoDeleteDays))
|
||||
if (days === null) {
|
||||
@@ -93,5 +131,9 @@ export default defineEventHandler(async (event) => {
|
||||
setAllowRegistration(Boolean(body.allowRegistration))
|
||||
}
|
||||
|
||||
if (body.hideFolderInUrl !== undefined) {
|
||||
setSetting(SETTINGS_HIDE_FOLDER_IN_URL, body.hideFolderInUrl ? 'true' : 'false')
|
||||
}
|
||||
|
||||
return getSettingsPayload(event)
|
||||
})
|
||||
@@ -4,13 +4,21 @@ import {
|
||||
createUserAccount,
|
||||
isInitialized
|
||||
} from '../utils/auth'
|
||||
import { setAllowRegistration } from '../utils/db'
|
||||
import { setAllowRegistration, setSetting, SETTINGS_IMAGE_BASE_URL, SETTINGS_SITE_BASE_URL } from '../utils/db'
|
||||
import {
|
||||
normalizeImageBaseUrl,
|
||||
normalizeSiteBaseUrl,
|
||||
validateDomainSeparationPair
|
||||
} from '../utils/env'
|
||||
import { clientIp, logInfo } from '../utils/logger'
|
||||
|
||||
interface SetupBody {
|
||||
username?: string
|
||||
password?: string
|
||||
allowRegistration?: boolean
|
||||
domainSeparation?: boolean
|
||||
siteBaseUrl?: string
|
||||
imageBaseUrl?: string
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
@@ -30,6 +38,18 @@ export default defineEventHandler(async (event) => {
|
||||
})
|
||||
|
||||
setAllowRegistration(Boolean(body?.allowRegistration))
|
||||
|
||||
if (body?.domainSeparation) {
|
||||
const siteBaseUrl = normalizeSiteBaseUrl(body.siteBaseUrl ?? '')
|
||||
const imageBaseUrl = normalizeImageBaseUrl(body.imageBaseUrl ?? '')
|
||||
const pairError = validateDomainSeparationPair(siteBaseUrl, imageBaseUrl)
|
||||
if (pairError) {
|
||||
createApiError(event, 'INVALID_REQUEST', pairError, 400)
|
||||
}
|
||||
setSetting(SETTINGS_SITE_BASE_URL, siteBaseUrl)
|
||||
setSetting(SETTINGS_IMAGE_BASE_URL, imageBaseUrl)
|
||||
}
|
||||
|
||||
await createSession(event, user.id)
|
||||
|
||||
logInfo('setup complete', { ip: clientIp(event), username: user.username })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { requireUserAuth } from '../../utils/access'
|
||||
import { ensureUserApiToken } from '../../utils/db'
|
||||
import { getImageBaseUrl } from '../../utils/env'
|
||||
import { getImageBaseUrl, getSiteBaseUrl, isHideFolderInUrl } from '../../utils/env'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireUserAuth(event)
|
||||
@@ -13,7 +13,9 @@ export default defineEventHandler(async (event) => {
|
||||
env: {
|
||||
webpQuality: 80,
|
||||
refererConfigured: false,
|
||||
siteBaseUrl: getSiteBaseUrl(event),
|
||||
imageBaseUrl: getImageBaseUrl(event),
|
||||
hideFolderInUrl: isHideFolderInUrl(event),
|
||||
appVersion: config.appVersion as string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { requireAdminAuth } from '../../utils/access'
|
||||
import {
|
||||
fetchLatestGitHubRelease,
|
||||
isVersionNewer
|
||||
} from '../../utils/app-version'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await requireAdminAuth(event)
|
||||
|
||||
const config = useRuntimeConfig(event)
|
||||
const currentVersion = String(config.appVersion ?? '')
|
||||
const latest = await fetchLatestGitHubRelease()
|
||||
|
||||
if (!latest) {
|
||||
return {
|
||||
currentVersion,
|
||||
latestVersion: null,
|
||||
updateAvailable: false,
|
||||
releaseUrl: null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentVersion,
|
||||
latestVersion: latest.latestVersion,
|
||||
updateAvailable: isVersionNewer(latest.latestVersion, currentVersion),
|
||||
releaseUrl: latest.releaseUrl
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user