Skip to content

feat(provider): imgproxy#385

Closed
shadow81627 wants to merge 1 commit into
nuxt:0.xfrom
shadow81627:feat/provider/imgproxy
Closed

feat(provider): imgproxy#385
shadow81627 wants to merge 1 commit into
nuxt:0.xfrom
shadow81627:feat/provider/imgproxy

Conversation

@shadow81627

Copy link
Copy Markdown
Contributor

resolves #378

@shadow81627

Copy link
Copy Markdown
Contributor Author

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.

@codecov-commenter

codecov-commenter commented Aug 14, 2021

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 60.40%. Comparing base (026be95) to head (2cb8fc4).
Report is 89 commits behind head on v0.

Additional details and impacted files
@@            Coverage Diff             @@
##               v0     #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              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment thread playground/nuxt.config.ts
Comment on lines +49 to +50
key: 'xxxxxxxxxxxxxx',
salt: 'xxxxxxxxxxxxxx'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider options are also included in the pre-built JS that the web browser downloads to generate the image URL on the client side. Therefore, it is possible for a third party to steal the key and salt, but is it safe to leak these values?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider options are also included in the pre-built JS that the web browser downloads to generate the image URL on the client side. Therefore, it is possible for a third party to steal the key and salt, but is it safe to leak these values?

@misaon This PR is implementing #378 (comment)

Do you have any insight for the above?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to #276

@pi0 pi0 added the pending label Oct 8, 2021
@pi0

pi0 commented Oct 8, 2021

Copy link
Copy Markdown
Member

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.

@vonec

vonec commented Nov 26, 2021

Copy link
Copy Markdown

when will this be released :)

@pi0 pi0 added the provider label Jan 13, 2022
@pi0 pi0 added the v0 label Jul 7, 2022
@casualmatt

Copy link
Copy Markdown

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

@shadow81627

Copy link
Copy Markdown
Contributor Author

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),
  }
}

@danielroe

Copy link
Copy Markdown
Member

closing as v0 is no longer supported for some time.

if this is still needed, I'd welcome rebasing on main branch 🙏

@danielroe danielroe closed this Aug 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support provider "imgproxy"

7 participants