Tengo un depósito S3 llamado BUCKET
en la región BUCKET_REGION
. Estoy tratando de permitir que los usuarios de mis aplicaciones web y móviles carguen archivos de imagen en estos cubos, siempre que cumplan con ciertas restricciones basadas en Content-Type
Content-Length
(es decir, solo quiero permitir que se carguen jpegs de menos de 3mbs). subido). Una vez cargados, los archivos deben ser de acceso público.
Basado en una investigación bastante extensa a través de los documentos de AWS, asumo que el proceso debería verse así en mis aplicaciones de interfaz:
const a = await axios.post('my-api.com/get_s3_id'); const b = await axios.put(`https://{BUCKET}.amazonaws.com/{a.id}`, { // ?? headersForAuth: a.headersFromAuth, file: myFileFromSomewhere // ie HTML5 File() object }); // now can do things like <img src={`https://{BUCKET}.amazonaws.com/{a.id}`} /> // UNLESS the file is over 3mb or not an image/jpeg, in which case I want it to be throwing errors
donde en mi API back-end estaría haciendo algo como
import aws from 'aws-sdk'; import uuid from 'uuid'; app.post('/get_s3_id', (req, res, next) => { // do some validation of request (ie checking user Ids) const s3 = new aws.S3({region: BUCKET_REGION}); const id = uuid.v4(); // TODO do something with s3 to make it possible for anyone to upload pictures under 3mbs that have the s3 key === id res.json({id, additionalAWSHeaders}); });
Lo que no estoy seguro es qué métodos exactos de S3 debería estar buscando.
Aquí hay algunas cosas que no funcionan:
He visto muchas menciones de (una muy antigua) API accesible con s3.getSignedUrl('putObject', ...)
. Sin embargo, esto no parece admitir la configuración confiable de ContentLength
, al menos más. (Consulte https://stackoverflow.com/a/28699269/251162 ).
También he visto un ejemplo más cercano al trabajo usando un HTTP POST
con API form-data
que también es muy antiguo. Supongo que esto podría hacerlo si no hay alternativas, pero me preocupa que ya no sea la forma "correcta" de hacer las cosas; además, parece hacer mucho cifrado manual, etc. y no usar el nodo oficial. SDK. (Consulte https://stackoverflow.com/a/28638155/251162 ).
Creo que lo que podría ser mejor para este caso es publicar directamente en S3, omitiendo su servidor backend.
Lo que puede hacer es definir una política que especifique explícitamente qué se puede cargar y dónde, esta política se firma luego con una clave de acceso secreta de AWS (usando AWS sig v4, puede generar una política usando this ).
Un ejemplo de uso de la política y la firma si se puede ver en los documentos de AWS
Para sus usos puede especificar condiciones como:
conditions: [ ['content-length-range, 0, '3000000'], ['starts-with', '$Content-Type', 'image/'] ]
Esto limitará las cargas a 3 Mb y Content-Type
solo a elementos que comiencen con image/
Además, solo tiene que generar su firma para la política una vez (o cada vez que cambie), lo que significa que no necesita una solicitud a su servidor para obtener una política válida, simplemente la codifica en su JS. Cuando / si necesita actualizar, simplemente regenere la política y la firma y luego actualice el archivo JS.
editar: no hay un método a través del SDK para hacer esto, ya que está pensado como una forma de publicar directamente desde un formulario en una página web, es decir, puede funcionar sin javascript.
edición 2: ejemplo completo de cómo firmar una política utilizando paquetes estándar de NodeJS:
import crypto from 'crypto'; const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; const ISO_DATE = '20190728T000000Z'; const DATE = '20161201'; const REGION = process.env.AWS_DEFAULT_REGION || 'eu-west-1'; const SERVICE = 's3'; const BUCKET = 'your_bucket'; if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) { throw new Error('AWS credentials are incorrect'); } const hmac = (key, string, encoding) => { return crypto.createHmac("sha256", key).update(string, "utf8").digest(encoding); }; const policy = { expiration: '2022-01-01T00:00:00Z', conditions: [ { bucket: BUCKET, }, ['starts-with', '$key', 'logs'], ['content-length-range', '0', '10485760'], { 'x-amz-date': ISO_DATE, }, { 'x-amz-algorithm': 'AWS4-HMAC-SHA256' }, { 'x-amz-credential': `${AWS_ACCESS_KEY_ID}/${DATE}/${REGION}/${SERVICE}/aws4_request` }, { 'acl': 'private' } ] }; function aws4_sign(secret, date, region, service, string_to_sign) { const date_key = hmac("AWS4" + secret, date); const region_key = hmac(date_key, region); const service_key = hmac(region_key, service); const signing_key = hmac(service_key, "aws4_request"); const signature = hmac(signing_key, string_to_sign, "hex"); return signature; } const b64 = new Buffer(JSON.stringify(policy)).toString('base64').toString(); console.log(`b64 policy: \n${b64}`); const signature = aws4_sign(AWS_SECRET_ACCESS_KEY, DATE, REGION, SERVICE, b64); console.log(`signature: \n${signature}\n`);
Debe familiarizarse con Amazon Cognito y especialmente con el conjunto de identidades.
Con Amazon Cognito Sync, puede recuperar los datos en las plataformas, dispositivos y sistemas operativos de los clientes, de modo que si un usuario comienza a usar su aplicación en un teléfono y luego cambia a una tableta, la información de la aplicación persistente todavía está disponible para ese usuario.
Obtenga más información aquí: Grupos de identidades de Cognito
Una vez que cree un nuevo grupo de identificación, puede hacer referencia a él mientras usa S3 JavaScript SDK, lo que le permitirá cargar contenido sin exponer ninguna credencial al cliente.
Ejemplo aquí: Subiendo a S3
Lea todo, especialmente la sección "Configuración del SDK".
La segunda parte de su rompecabezas: validaciones.
Me gustaría implementar una validación del lado del cliente (si es posible) para evitar la latencia de la red antes de dar un error. Si elige implementar la validación en S3 o AWS Lambda, está buscando un tiempo de espera hasta que el archivo llegue a AWS: latencia de red.
Esto es algo que sé que tenemos en nuestro proyecto, así que les mostraré parte de los códigos:
primero debe publicar en su propio servidor para obtener los créditos para la carga, a partir de ahí devolverá los parámetros de la carga del cliente a S3.
estos son parámetros que envía al servicio aws s3, necesitará el depósito, la ruta de carga y el archivo
let params = { Bucket: s3_bucket, Key: upload_path, Body: file_itself };
este es el código que tengo para la carga real a s3
config.credentials = new AWS.Credentials(credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken); let s3 = new S3(config); return s3.upload(params, options).on("httpUploadProgress", handleProgress);
todos esos elementos de credenciales que obtiene de su backend, por supuesto.