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

Add support provider "imgproxy" #378

Open
adinvadim opened this issue Aug 2, 2021 · 13 comments · May be fixed by #385
Open

Add support provider "imgproxy" #378

adinvadim opened this issue Aug 2, 2021 · 13 comments · May be fixed by #385
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers provider

Comments

@adinvadim
Copy link

It's free, fast and opensource stadealone server for resizising images.

https://imgproxy.net/

@pi0 pi0 added the provider label Aug 3, 2021
@misaon
Copy link

misaon commented Aug 13, 2021

I'm currently testing my custom version of the provider, maybe it will help the developers.
@pi0

$ yarn add create-hmac

nuxt.config.js

export default {
  // ....
  image: {
    providers: {
      customProvider: {
        name: 'imgproxy',
        provider: './src/providers/imgproxy.js',
        options: {
          baseURL: 'http://localhost:4000',
          key: 'xxxxxxxxxxxxxx',
          salt: 'xxxxxxxxxxxxxx',
        }
      }
    },
    provider: 'imgproxy',
  },
 // ....
}

imgproxy.js

import {joinURL} from 'ufo';
import createHmac from 'create-hmac';
import {createOperationsGenerator} from '~image'

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

function urlSafeBase64(string) {
  return Buffer.from(string)
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
}

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

function sign(salt, target, secret) {
  const hmac = createHmac('sha256', hexDecode(secret))

  hmac.update(hexDecode(salt))
  hmac.update(target)

  return urlSafeBase64(hmac.digest())
}

const defaultModifiers = {
  fit: 'fill',
  width: 0,
  height: 0,
  gravity: 'no',
  enlarge: 1,
  format: 'webp',
}

export function getImage(src, {modifiers, baseURL, key, salt} = {}) {
  const mergeModifiers = {...defaultModifiers, ...modifiers}
  const encodedUrl = urlSafeBase64(src)
  const path = joinURL('/', operationsGenerator(mergeModifiers), encodedUrl)
  const signature = sign(salt, path, key)
  
  return {
    url: joinURL(baseURL, signature, path)
  }
}

Example of usage

<template>
  <div class="md:container md:mx-auto md:max-w-screen-lg">
    <nuxt-img src="https://cc.cz/wp-content/uploads/2021/08/messi-psg.jpg" />
  </div>
</template>

<script>
export default {
  name: 'Homepage'
}
</script>

Cheers! 🍷 ⚽

shadow81627 added a commit to shadow81627/image that referenced this issue Aug 14, 2021
@shadow81627 shadow81627 linked a pull request Aug 14, 2021 that will close this issue
@adinvadim
Copy link
Author

Thanks, but what is createOperationsGenerator? How does this function look like?

@adinvadim
Copy link
Author

I found

import { createOperationsGenerator } from '@nuxt/image/dist/runtime/utils/index';

@adinvadim
Copy link
Author

Thanks a lot!

@adinvadim
Copy link
Author

Update for nuxt 3:

create-hmac doesnt work fine with vite, thats why easiest way it is create-hmac → hash.js

function sign(salt: string, target: string, secret: string) {
  const hmac = hash.hmac(hash.sha256, hexDecode(secret));
  hmac.update(hexDecode(salt));
  hmac.update(target);

  return urlSafeBase64(hmac.digest());
}

And you should yarn add buffer and import { Buffer } from 'buffer' for isomorph Buffer

@adinvadim
Copy link
Author

Full version of imageproxy provider for nuxt3 and nuxt/image v1

import { joinURL } from "ufo";
import { createOperationsGenerator } from "@nuxt/image-edge/dist/runtime/utils/index";
import { ProviderGetImage } from "@nuxt/image-edge";
import { Buffer } from "buffer";
import * as hash from "hash.js";

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

function urlSafeBase64(string) {
  return Buffer.from(string, "utf8")
    .toString("base64")
    .replace(/=/g, "")
    .replace(/\+/g, "-")
    .replace(/\//g, "_");
}

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

function sign(salt: string, target: string, secret: string) {
  const hmac = hash.hmac(hash.sha256, hexDecode(secret));
  hmac.update(hexDecode(salt));
  hmac.update(target);

  return urlSafeBase64(hmac.digest());
}

const defaultModifiers = {
  fit: "fill",
  width: 0,
  height: 0,
  gravity: "no",
  enlarge: 1,
  format: "webp",
};

export const getImage: ProviderGetImage = (src, options) => {
  const { modifiers, imgProxyUrl, imgProxySalt, imgProxyKey } = options;
  const mergeModifiers = { ...defaultModifiers, ...modifiers };
  const encodedUrl = urlSafeBase64(src);
  const path = joinURL("/", operationsGenerator(mergeModifiers), encodedUrl);
  const signature = sign(imgProxySalt, path, imgProxyKey);

  return {
    url: joinURL(imgProxyUrl, signature, path),
  };
};

@casualmatt
Copy link

Hello @adinvadim,
Where should I add the config that you have shared ?
Is it still working?

@adinvadim
Copy link
Author

Hi! @casualmatt

Add this file to any directory and add this configuration to nuxt.config.ts

  image: {
    providers: {
      imgproxy: {
        name: "imgproxy",
        provider: "~~/utils/nuxt-image/imgproxy.provider",
        options: {
          imgProxyUrl: ...,
          imgProxyKey: ...,
          imgProxySalt: ...,
        },
      },
    },
  },

image

@Sevochka
Copy link

Sevochka commented Jul 9, 2023

Hi @adinvadim. How does getSafeConfigEnv look like?

@adinvadim
Copy link
Author

@Sevochka it is just my simple utils for working with env variables. May be is not great, because I am using this code from my nuxt 2 codebase.

export const getSafeEnv = (key: string, defaultValue?: string) => {
  const result = process.env[key];

  if (result != null) {
    return result;
  } else if (defaultValue !== undefined) {
    return defaultValue;
  } else {
    throw new Error(`Env variable "${key}" is required`);
  }
};

export const getSafeConfigEnv = (key: string, defaultValue?: string) => {
  try {
    return getSafeEnv(key, defaultValue);
  } catch (e) {
    if (isBuild) {
      return null;
    } else {
      throw e;
    }
  }
};

@danielroe danielroe added the enhancement New feature or request label Apr 22, 2024
@danielroe
Copy link
Member

I would welcome a PR to add an imgproxy provider. 🙏

Given the code provided above, I imagine it would be a straightforward implementation...

@danielroe danielroe added the good first issue Good for newcomers label Apr 22, 2024
@casualmatt
Copy link

@danielroe
I have mine working as the custom provider; I can open a PR; the only question I have is how to sign the URL without publishing the key so that imgproxy can't be exploited, right now I allow on imgproxy just my domain as source but I don't think is a clean way to do it.

Copy link
Member

Perhaps we can run a docker container within the github actions workflow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers provider
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants