Open casualmatt opened 2 months ago
Some related resource about "secure the imgProxySalt and imgProxyKey"
https://github.com/nuxt/image/issues/276 https://github.com/nuxt/image/issues/963
@casualmatt I have implemented a version myself, you can use it as a reference
import type { ImageModifiers } from '@nuxt/image'
import { joinURL } from 'ufo'
import { defu } from 'defu'
import { urlSafeBase64 } from '../utils'
import { createOperationsGenerator } from '#image'
export interface ImgproxyModifiers extends ImageModifiers {
quality: string
background: string
rotate: 'auto_right' | 'auto_left' | 'ignore' | 'vflip' | 'hflip' | number
roundCorner: string
gravity: 'sm' | string
effect: string
color: string
flags: string
dpr: string
opacity: number
overlay: string
underlay: string
transformation: string
zoom: number
colorSpace: string
customFunc: string
density: number
aspectRatio: string
}
export interface ImgproxyOptions {
baseURL?: string
modifiers?: Partial<ImgproxyOptions>
key?: string
salt?: string
signatureSize?: number
srcPrefix?: string
[key: string]: any
}
const operationsGenerator = createOperationsGenerator({
keyMap: {
// standard
width: 'w',
height: 'h',
// format will act as a extension
// format: 'f',
quality: 'q',
fit: 'rs',
// imgporxy
formatQuality: 'fq', // fq:%format1:%quality1:%format2:%quality2:...:%formatN:%qualityN
resize: 'rs', // rs:%resizing_type:%width:%height:%enlarge:%extend
size: 's', // s:%width:%height:%enlarge:%extend
resizingType: 'rt', // rt:%resizing_type
enlarge: 'el', // el:%enlarge
extend: 'ex', // ex:%extend:%gravity
minWidth: 'mw', // mw:%width
minHeight: 'mh', // min-height
zoom: 'z', // z:%zoom_x_y | z:%zoom_x:%zoom_y
dpr: 'dpr', // dpr:%dpr
extendAspectRatio: 'exar', // exar:%extend:%gravity
gravity: 'g', // g:%type:%x_offset:%y_offset
crop: 'c', // c:%width:%height:%gravity
trim: 't', // t:%threshold:%color:%equal_hor:%equal_ver
padding: 'pd', // pd:%top:%right:%bottom:%left
autoRotate: 'ar', // ar:%auto_rotate
rotate: 'rot', // rot:%angle
background: 'bg', // bg:%R:%G:%B | bg:%hex_color
blur: 'bl', // bl:%sigma
sharpen: 'sh', // sh:%sigma
pixelate: 'pix', // pix:%size
watermark: 'wm', // wm:%opacity:%position:%x_offset:%y_offset:%scale
stripMetadata: 'sm', // sm:%strip_metadata
keepCopyright: 'kcr', // kcr:%keep_copyright
stripColorProfile: 'scp', // scp:%strip_color_profile
enforceThumbnail: 'eth', // eth:%enforce_thumbnail
max_bytes: 'mb', // mb:%bytes
skipProcessing: 'skp', // skp:%extension1:%extension2:...:%extensionN
raw: 'raw', // raw:%raw
cachebuster: 'cb', // cb:%string
expires: 'exp', // exp:%timestamp
filename: 'fn', // fn:%string
// pro features
resizingAlgorithm: 'ra', // * ra:%algorithm
unsharpening: 'ush', // * ush:%mode:%weight:%dividor
blurDetections: 'bd', // * bd:%sigma:%class_name1:%class_name2:...:%class_nameN
drawDetections: 'dd', // * dd:%draw:%class_name1:%class_name2:...:%class_nameN
gradient: 'gr', // * gr:%opacity:%color:%direction:%start%stop
watermarkURL: 'wmu', // * wmu:%url
watermarkText: 'wmt', // * wmt:%text
watermarkSize: 'wms', // * wms:%width:%height
watermarkShadow: 'wmsh', // * wmsh:%sigma
style: 'st', // * st:%style
backgroundAlpha: 'bga', // * bga:%alpha
adjust: 'a', // * a:%brightness:%contrast:%saturation
brightness: 'br', // * br:%brightness
contrast: 'co', // * co:%contrast
saturation: 'sa', // * sa:%saturation
autoquality: 'aq', // * aq:%method:%target:%min_quality:%max_quality:%allowed_error
jpegOptions: 'jpgo', // * jpgo:%progressive:%no_subsample:%trellis_quant:%overshoot_deringing:%optimize_scans:%quant_table
pngOptions: 'pngo', // * pngo:%interlaced:%quantize:%quantization_colors
webpOptions: 'pngo', // * webpo:%compression
page: 'pg', // * pg:%page
disableAnimation: 'da', // * da:%disable
videoThumbnailSecond: 'vts', // * vts:%second
fallbackImageUrl: 'fiu', // * fiu:%url
},
valueMap: {
fit: {
cover: 'fill:::1:0',
contain: 'fit:::0:1',
fill: 'force:::1:0',
inside: 'fit:::0:0', // inside use min dimensions
outside: 'fit:::0:0', // outside use max dimensions
},
},
joinWith: '/',
formatter: (key: string, val: string) => `${key}:${val}`,
})
/**
* 让修饰符兼容 nuxt image 默认选项值
*/
function makeModifiersCompatible(modifiers: Partial<ImgproxyModifiers> = {}): Partial<ImgproxyModifiers> {
const _modifiers: Partial<ImgproxyModifiers> = { ...modifiers }
if (_modifiers.fit === 'outside' && _modifiers.width && _modifiers.height) {
if (_modifiers.width > _modifiers.height)
delete _modifiers.height
else
delete _modifiers.width
}
// 这里采用 URL 后缀方式来设置 format,不使用 format 参数
if (_modifiers.format)
delete _modifiers.format
return _modifiers
}
const defaultModifiers = {
fit: 'cover',
}
export function getImage(
src: string,
{ modifiers = {}, baseURL = '/', srcPrefix = '' }: ImgproxyOptions = {}, // signatureSize = 32, key = '', salt = '',
) {
const mergedModifiers = defu(modifiers, defaultModifiers)
const compModifiers = makeModifiersCompatible(mergedModifiers)
const processingOptions = operationsGenerator(compModifiers)
const finalSrc = srcPrefix.length > 0 ? src.replace(new RegExp(srcPrefix), '') : src
const encodedURL = urlSafeBase64(finalSrc)
// const signature = await sign(salt, `/${processingOptions}/${encodedURL}`, key, signatureSize);
const signature = '_'
const extension = (typeof modifiers.format === 'string' && modifiers.format.length > 0)
? modifiers.format
: undefined
// https://docs.imgproxy.net/generating_the_url?id=example
return {
url: joinURL(
baseURL,
signature,
processingOptions,
extension ? `${encodedURL}.${extension}` : encodedURL,
),
}
}
export default getImage
WIP.
To start somewhere, I just imported my custom provider for imgproxy.
I used
hash.js
but that could probably be switch for ohash.And I'm open to suggestions on how to secure the
imgProxySalt
andimgProxyKey
.--> Add support provider "imgproxy"