furaiev / amazon-cognito-identity-dart-2

Unofficial Amazon Cognito Identity Provider Dart SDK, to easily add user sign-up and sign-in to your mobile and web apps with AWS.
MIT License
187 stars 114 forks source link

s3 upload not working #60

Closed Faiyyaz closed 3 years ago

Faiyyaz commented 4 years ago

Hi,

I am unable to upload images using the library.

I m getting the below response

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>3881BA8E9D4EAD4B</RequestId><HostId>EIi9mLd/oUOTw1+pOzGnC9fbrdbTiSLGJAwVJUjJLByglccc2yfPti2LKkkISq71eWx+QvqOjuM=</HostId></Error>

The code for upload is as follows :

static Future<String> uploadFile({File file}) async {
    CognitoUserPool userPool = CognitoUserPool(
      kUserPoolID,
      kClientID,
    );

    CognitoCredentials credentials = CognitoCredentials(
      kIdentityPoolID,
      userPool,
    );

    String response;
    try {
      final length = await file.length();
      final stream = file.openRead();
      final String fileName = path.basename(file.path);

      final uri = Uri.parse(
        ks3Endpoint,
      );

      final req = http.MultipartRequest(
        kPOST,
        uri,
      );

      String mimeType = mime(fileName);
      List<String> mimeTypes = mimeType.split('/');

      final multipartFile = http.MultipartFile(
        'file',
        stream,
        length,
        filename: fileName,
        contentType: MediaType(
          mimeTypes[0],
          mimeTypes[1],
        ),
      );

      final token = await AppSyncConfig.getIdToken();
      await credentials.getAwsCredentials(
        token,
      );

      final String usrIdentityId = credentials.userIdentityId;

      final String bucketKey = '$kBucketKey/$usrIdentityId/$fileName';

      final policy = Policy.fromS3PreSignedPost(
        bucketKey,
        kBucketID,
        15,
        credentials.accessKeyId,
        length,
        credentials.sessionToken,
        region: kRegion,
      );

      final key = SigV4.calculateSigningKey(
        credentials.secretAccessKey,
        policy.datetime,
        kRegion,
        's3',
      );

      final signature = SigV4.calculateSignature(
        key,
        policy.encode(),
      );

      req.fields["key"] = policy.key;
      req.fields["acl"] = "public-read";
      req.fields["X-Amz-Credential"] = policy.credential;
      req.fields["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256";
      req.fields["X-Amz-Date"] = policy.datetime;
      req.fields["Policy"] = policy.encode();
      req.fields["X-Amz-Signature"] = signature;
      req.fields["x-amz-security-token"] = credentials.sessionToken;
      req.files.add(multipartFile);

      final res = await req.send();
      if (res.statusCode == 204) {
        response = kSuccess;
      } else {
        response = res.statusCode.toString();
      }
    } catch (e) {
      response = e.toString();
    }

    print(response);
    return response;
  }

Policy class

import 'dart:convert';

import 'package:amazon_cognito_identity_dart_2/sig_v4.dart';

class Policy {
  String expiration;
  String region;
  String bucket;
  String key;
  String credential;
  String datetime;
  String sessionToken;
  int maxFileSize;

  Policy(this.key, this.bucket, this.datetime, this.expiration, this.credential,
      this.maxFileSize, this.sessionToken,
      {this.region = 'us-west-2'});

  factory Policy.fromS3PreSignedPost(
      String key,
      String bucket,
      int expiryMinutes,
      String accessKeyId,
      int maxFileSize,
      String sessionToken,
      {String region}) {
    final datetime = SigV4.generateDatetime();
    final expiration = (DateTime.now())
        .add(Duration(minutes: expiryMinutes))
        .toUtc()
        .toString()
        .split(' ')
        .join('T');
    final cred =
        '$accessKeyId/${SigV4.buildCredentialScope(datetime, region, 's3')}';
    final p = Policy(
        key, bucket, datetime, expiration, cred, maxFileSize, sessionToken,
        region: region);
    return p;
  }

  String encode() {
    final bytes = utf8.encode(toString());
    return base64.encode(bytes);
  }

  @override
  String toString() {
    // Safe to remove the "acl" line if your bucket has no ACL permissions
    return '''
    { "expiration": "${this.expiration}",
      "conditions": [
        {"bucket": "${this.bucket}"},
        ["starts-with", "\$key", "${this.key}"],
        {"acl": "public-read"},
        ["content-length-range", 1, ${this.maxFileSize}],
        {"x-amz-credential": "${this.credential}"},
        {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
        {"x-amz-date": "${this.datetime}" },
        {"x-amz-security-token": "${this.sessionToken}" }
      ]
    }
    ''';
  }
}
furaiev commented 4 years ago

Hi, Please check this working code https://gist.github.com/furaiev/e0fff973b31babd36f5c5d7b53d85349

nitdrive commented 4 years ago

@Faiyyaz It happened in my case too, the reason is when it's calculating the signature it assumes acl: "public-read" as per the toString() method and if your bucket has permissions "Block all public access" on, then it means your bucket is private which is also the default. So that's why it isn't working. To fix it you can either remove the acl property from the conditions in toString() or set it as "private". Make sure to remove or set the same in the uploadFile() method

https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#sample-acl

BartusZak commented 4 years ago

I still can not access my Bucket with POST/PUT method. GET works fine.