image icon indicating copy to clipboard operation
image copied to clipboard

feat(provider): imgproxy

Open shadow81627 opened this issue 4 years ago • 6 comments

resolves #378

shadow81627 avatar Aug 14 '21 02:08 shadow81627

I ran yarn install with the latest version of yarn 1 but it has removed all " from the lock file. I also had to set the test src for imgproxy to a static string of /test.png I think there was something changing the signature.

shadow81627 avatar Aug 14 '21 02:08 shadow81627

Codecov Report

Merging #385 (2cb8fc4) into main (026be95) will increase coverage by 1.08%. The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #385      +/-   ##
==========================================
+ Coverage   59.32%   60.40%   +1.08%     
==========================================
  Files          27       28       +1     
  Lines         622      639      +17     
  Branches      196      156      -40     
==========================================
+ Hits          369      386      +17     
  Misses        253      253              
Impacted Files Coverage Δ
src/provider.ts 74.28% <ø> (ø)
src/runtime/providers/imgproxy.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 026be95...2cb8fc4. Read the comment docs.

codecov-commenter avatar Aug 14 '21 02:08 codecov-commenter

Hi! I'm pending this issue for #276. In order to being able generate hashes without exposing secret to the client we need possibilities form nuxt 3 / nitro.

pi0 avatar Oct 08 '21 14:10 pi0

when will this be released :)

vonec avatar Nov 26 '21 10:11 vonec

Is this MR still block because there is no way to sign request?

Is possible to see it in the new V1 module, because imgproxy is quite faster then IPX

casualmatt avatar Jan 14 '23 22:01 casualmatt

I've made my own custom provider for imgproxy to use with Nuxt 3 and Cloudflare. I haven't worried about the imgproxy keys being public, it would be nice to see how to handle the provider being server-side only and how it work at runtime.

Here is the code for my custom nuxt 3 imgproxy provider.

import { encodeURI } from 'js-base64'
import { Buffer } from 'buffer'
import { OperationGeneratorConfig, OperationMapper } from '~/types/image'
import { joinURL, withBase } from 'ufo'
import hmacSHA256 from 'crypto-js/hmac-sha256.js'
import Base64url from 'crypto-js/enc-base64url.js'
import hex from 'crypto-js/enc-hex.js'

const hexDecode = (hex: string) => Buffer.from(hex, 'hex')

function createMapper(map: Record<string, string>): OperationMapper {
  return (key?: string) => {
    return key ? map[key] ?? key : map.missingValue
  }
}

function createOperationsGenerator({
  formatter,
  keyMap,
  joinWith = '/',
  valueMap,
}: OperationGeneratorConfig = {}) {
  if (!formatter) {
    formatter = (key, value: string) => `${key}=${value}`
  }
  if (keyMap && typeof keyMap !== 'function') {
    keyMap = createMapper(keyMap)
  }
  const map = valueMap ?? {}
  Object.keys(map).forEach((valueKey) => {
    if (typeof map[valueKey] !== 'function') {
      map[valueKey] = createMapper(map[valueKey])
    }
  })

  return (modifiers: { [key: string]: string } = {}) => {
    const operations = Object.entries(modifiers)
      .filter(([_, value]) => typeof value !== 'undefined')
      .map(([key, value]) => {
        const mapper = map[key]
        if (typeof mapper === 'function') {
          value = mapper(modifiers[key])
        }

        key = typeof keyMap === 'function' ? keyMap(key) : key

        return formatter?.(key, value)
      })

    return operations.join(joinWith)
  }
}

const sign = (salt: string, target: string, secret: string) => {
  const msg = hexDecode(salt + Buffer.from(target).toString('hex')) // Uint8Array of arbitrary length
  const hmac = hmacSHA256(hex.parse(msg.toString('hex')), hex.parse(secret))
  const digest = hmac.toString(Base64url)
  return digest
}

const operationsGenerator = createOperationsGenerator({
  keyMap: {
    resize: 'rs',
    size: 's',
    fit: 'rt',
    width: 'w',
    height: 'h',
    dpr: 'dpr',
    enlarge: 'el',
    extend: 'ex',
    gravity: 'g',
    crop: 'c',
    padding: 'pd',
    trim: 't',
    rotate: 'rot',
    quality: 'q',
    maxBytes: 'mb',
    background: 'bg',
    backgroundAlpha: 'bga',
    blur: 'bl',
    sharpen: 'sh',
    watermark: 'wm',
    preset: 'pr',
    cacheBuster: 'cb',
    stripMetadata: 'sm',
    stripColorProfile: 'scp',
    autoRotate: 'ar',
    filename: 'fn',
    format: 'f',
  },
  formatter: (key, value) => `${key}:${value}`,
})

export const getImage: ProviderGetImage = (
  src,
  { modifiers = {}, baseURL = '', key, salt } = {},
) => {
  const encodedUrl = encodeURI(src)
  const path = joinURL('/', operationsGenerator(modifiers), encodedUrl)
  const signature = sign(salt, path, key)

  return {
    url: withBase(joinURL(signature, path), baseURL),
  }
}

shadow81627 avatar Feb 18 '23 00:02 shadow81627