* refactor(auth): replace dashboard sessions with stateless tokens * feat(auth): harden session issuance and distributed enforcement * fix(proxy): preserve trusted proxy compatibility defaults * refactor: address dashboard auth review feedback * refactor: remove classic frontend and flatten web app
243 lines
6.1 KiB
TypeScript
243 lines
6.1 KiB
TypeScript
/*
|
|
Copyright (C) 2023-2026 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
export type HeaderNavAccessConfig = {
|
|
enabled: boolean
|
|
requireAuth: boolean
|
|
}
|
|
|
|
export type HeaderNavModulesConfig = {
|
|
home: boolean
|
|
console: boolean
|
|
pricing: HeaderNavAccessConfig
|
|
rankings: HeaderNavAccessConfig
|
|
docs: boolean
|
|
about: boolean
|
|
[key: string]: boolean | HeaderNavAccessConfig
|
|
}
|
|
|
|
export type SidebarSectionConfig = {
|
|
enabled: boolean
|
|
[key: string]: boolean
|
|
}
|
|
|
|
export type SidebarModulesAdminConfig = Record<string, SidebarSectionConfig>
|
|
|
|
export const HEADER_NAV_DEFAULT: HeaderNavModulesConfig = {
|
|
home: true,
|
|
console: true,
|
|
pricing: {
|
|
enabled: true,
|
|
requireAuth: false,
|
|
},
|
|
rankings: {
|
|
enabled: true,
|
|
requireAuth: false,
|
|
},
|
|
docs: true,
|
|
about: true,
|
|
}
|
|
|
|
export const SIDEBAR_MODULES_DEFAULT: SidebarModulesAdminConfig = {
|
|
chat: {
|
|
enabled: true,
|
|
playground: true,
|
|
chat: true,
|
|
},
|
|
console: {
|
|
enabled: true,
|
|
detail: true,
|
|
token: true,
|
|
log: true,
|
|
midjourney: true,
|
|
task: true,
|
|
},
|
|
personal: {
|
|
enabled: true,
|
|
topup: true,
|
|
personal: true,
|
|
},
|
|
admin: {
|
|
enabled: true,
|
|
channel: true,
|
|
models: true,
|
|
redemption: true,
|
|
user: true,
|
|
setting: true,
|
|
subscription: true,
|
|
},
|
|
}
|
|
|
|
const toBoolean = (value: unknown, fallback: boolean): boolean => {
|
|
if (typeof value === 'boolean') return value
|
|
if (typeof value === 'number') return value === 1
|
|
if (typeof value === 'string') {
|
|
const normalized = value.trim().toLowerCase()
|
|
if (normalized === 'true' || normalized === '1') return true
|
|
if (normalized === 'false' || normalized === '0') return false
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
const cloneHeaderNavDefault = (): HeaderNavModulesConfig => ({
|
|
...HEADER_NAV_DEFAULT,
|
|
pricing: { ...HEADER_NAV_DEFAULT.pricing },
|
|
rankings: { ...HEADER_NAV_DEFAULT.rankings },
|
|
})
|
|
|
|
const parseAccessModule = (
|
|
raw: unknown,
|
|
fallback: HeaderNavAccessConfig
|
|
): HeaderNavAccessConfig => {
|
|
if (
|
|
typeof raw === 'boolean' ||
|
|
typeof raw === 'string' ||
|
|
typeof raw === 'number'
|
|
) {
|
|
return {
|
|
enabled: toBoolean(raw, fallback.enabled),
|
|
requireAuth: fallback.requireAuth,
|
|
}
|
|
}
|
|
if (raw && typeof raw === 'object') {
|
|
const record = raw as Record<string, unknown>
|
|
return {
|
|
enabled: toBoolean(record.enabled, fallback.enabled),
|
|
requireAuth: toBoolean(record.requireAuth, fallback.requireAuth),
|
|
}
|
|
}
|
|
return { ...fallback }
|
|
}
|
|
|
|
const cloneSidebarDefault = (): SidebarModulesAdminConfig =>
|
|
Object.entries(SIDEBAR_MODULES_DEFAULT).reduce<SidebarModulesAdminConfig>(
|
|
(acc, [section, config]) => {
|
|
acc[section] = { ...config }
|
|
return acc
|
|
},
|
|
{}
|
|
)
|
|
|
|
export function parseHeaderNavModules(
|
|
value: string | null | undefined
|
|
): HeaderNavModulesConfig {
|
|
const base = cloneHeaderNavDefault()
|
|
if (!value) {
|
|
return base
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(value) as Record<string, unknown>
|
|
const result: HeaderNavModulesConfig = {
|
|
...base,
|
|
pricing: { ...base.pricing },
|
|
rankings: { ...base.rankings },
|
|
}
|
|
|
|
Object.entries(parsed).forEach(([key, raw]) => {
|
|
if (key === 'pricing') {
|
|
result.pricing = parseAccessModule(raw, base.pricing)
|
|
return
|
|
}
|
|
if (key === 'rankings') {
|
|
result.rankings = parseAccessModule(raw, base.rankings)
|
|
return
|
|
}
|
|
|
|
if (typeof raw === 'boolean') {
|
|
result[key] = raw
|
|
return
|
|
}
|
|
if (typeof raw === 'string' || typeof raw === 'number') {
|
|
result[key] = toBoolean(raw, Boolean(base[key]))
|
|
return
|
|
}
|
|
})
|
|
|
|
return result
|
|
} catch {
|
|
return base
|
|
}
|
|
}
|
|
|
|
export function serializeHeaderNavModules(
|
|
config: HeaderNavModulesConfig
|
|
): string {
|
|
return JSON.stringify(config)
|
|
}
|
|
|
|
export function parseSidebarModulesAdmin(
|
|
value: string | null | undefined
|
|
): SidebarModulesAdminConfig {
|
|
const defaults = cloneSidebarDefault()
|
|
// If empty string, null, or undefined, use default config
|
|
if (!value || value.trim() === '') return defaults
|
|
|
|
try {
|
|
const parsed = JSON.parse(value) as Record<string, unknown>
|
|
const result: SidebarModulesAdminConfig = {}
|
|
|
|
Object.entries(parsed).forEach(([sectionKey, raw]) => {
|
|
if (!raw || typeof raw !== 'object') return
|
|
|
|
const defaultSection = defaults[sectionKey] ?? { enabled: true }
|
|
const sectionConfig: SidebarSectionConfig = {
|
|
enabled: toBoolean(
|
|
(raw as Record<string, unknown>).enabled,
|
|
defaultSection.enabled ?? true
|
|
),
|
|
}
|
|
|
|
Object.entries(raw as Record<string, unknown>).forEach(
|
|
([moduleKey, moduleValue]) => {
|
|
if (moduleKey === 'enabled') return
|
|
sectionConfig[moduleKey] = toBoolean(
|
|
moduleValue,
|
|
defaultSection[moduleKey] ?? true
|
|
)
|
|
}
|
|
)
|
|
|
|
result[sectionKey] = sectionConfig
|
|
})
|
|
|
|
// Merge defaults to ensure expected sections exist
|
|
Object.entries(defaults).forEach(([sectionKey, config]) => {
|
|
if (!result[sectionKey]) {
|
|
result[sectionKey] = { ...config }
|
|
return
|
|
}
|
|
|
|
Object.entries(config).forEach(([moduleKey, moduleValue]) => {
|
|
if (!(moduleKey in result[sectionKey])) {
|
|
result[sectionKey][moduleKey] = moduleValue
|
|
}
|
|
})
|
|
})
|
|
|
|
return result
|
|
} catch {
|
|
return defaults
|
|
}
|
|
}
|
|
|
|
export function serializeSidebarModulesAdmin(
|
|
config: SidebarModulesAdminConfig
|
|
): string {
|
|
return JSON.stringify(config)
|
|
}
|