Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider): imgproxy #385

Open
wants to merge 1 commit into
base: v0
Choose a base branch
from

Conversation

shadow81627
Copy link
Contributor

resolves #378

@shadow81627
Copy link
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
Copy link

codecov-commenter commented Aug 14, 2021

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 on lines +49 to +50
key: 'xxxxxxxxxxxxxx',
salt: 'xxxxxxxxxxxxxx'
Copy link
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
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
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
Copy link
Member

pi0 commented Oct 8, 2021

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
Copy link

vonec commented Nov 26, 2021

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

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

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"
6 participants