Reputation: 154
I need to upload from Angular 2 Quickstart base of angular.io (they do not use webpack)
This is the code in the component:
@Injectable()
export class S3Service {
private uploadSuccess = true;
private creds = {
"bucket": "nameOfBucket",
"access_key": "accessKey",
"secret_key": "secretKey",
"region": "us-east-1"
}
upload(file: File){
if (file){
console.log('verified with file');
}else{
console.log('without file');
}
console.log('filetype verified as images/png: ', file.type);
AWS.config.update({
accessKeyId: this.creds.access_key,
secretAccessKey: this.creds.secret_key,
});
AWS.config.region = this.creds.region;
AWS.config.sslEnabled = false;
console.log('aws.s3 is verified to be a function: ', AWS.S3);
let bucket = new AWS.S3({ params: { Bucket: this.creds.bucket }});
let key = `categories/${file.name}`;
console.log('verified key is : ', key);
let params = {Key: key, Body: file, ContentType: file.type, ServerSideEncryption: 'AES256'};
bucket.putObject(params, function (err: Response | any, data: Response) {
if (err){
console.log('there is an error: ', err);
}
else{
console.log('there is no error in s3 upload');
}
});
}
Here is the error log in Firefox web console:
there is an error: Object { __zone_symbol__error: Error, fileName: Getter, lineNumber: Getter, columnNumber: Getter, message: Getter, stack: Getter, originalStack: Getter, zoneAwareStack: Getter, toString: createMethodProperty/props[key].value(), toSource: createMethodProperty/props[key].value(), 7 more… }
This is for Chrome:
XMLHttpRequest cannot load http://bucketname.s3.amazonaws.com/categories/imagename.png. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'localhost:3000' is therefore not allowed access. The response had HTTP status code 400.
As I am just learning, I am trying with a permissive CORS:
<CORSConfiguration xmlns="removed this from being displayed">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedOrigin>http://localhost:3000</AllowedOrigin>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
<ExposeHeader>x-amz-request-id</ExposeHeader>
<ExposeHeader>x-amz-id-2</ExposeHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
The Angular 1 code successfully uploads the image.
Please help and advanced thanks.
Upvotes: 0
Views: 2584
Reputation: 611
Your CORS configuration on the S3 bucket looks fine for the scenario you describe.
I believe the problem is with the endpoint URL that is resulting from your code. I had a similar problem and it was confusing and rather difficult to track down.
For some reason the AWS SDK seems to generate the endpoint URL differently depending on the method you use to set the region information and/or the bucket name. When the resulting URL doesn't contain region information (which yours does not) it causes the pre-flight request to fail, which results in a misleading error message in the brower's console about CORS (which can sometimes be the source of a preflight failure).
'Problem' endpoint format: http(s)://<bucketname>.s3.amazonaws.com/<key>
'Desired' endpoint format: http(s)://s3-<region>.amazonaws.com/<bucketname>/<key>
Try using the component I've provided here to validate your AWS S3 configuration, access and CORS settings. Then, you can easily extract out the S3 specific stuff in to a service if you choose.
Here are the steps:
<s3-upload-test></s3-upload-test>
) in the template for AppComponent (see app.component.ts below).s3-upload-test.component.ts:
import { Component } from '@angular/core';
import { Credentials, S3 } from 'aws-sdk';
@Component({
selector: 's3-upload-test',
template: `
<div class="uploadSection">
<hr>
<h3>S3 File Upload Test</h3>
<div class="subsection">
<h4>Confirm Endpoint Format:</h4>
<div class="indent">
The endpoint should be in the following format <span class="monospace">s3-<region>.amazonaws.com</span>.
<pre>
Based on the configuration information you provided:
Expect Endpoint: {{expectEndpoint}}
Actual Endpoint: {{actualEndpoint}}
</pre>
</div>
</div>
<div class="subsection">
<h4>Select File:</h4>
<div class="indent">
<input type="file" (change)="fileEvent($event)" />
</div>
</div>
<div class="subsection">
<h4>Upload Status/Results:</h4>
<div class="indent">
<span class="monospace result">{{uploadStatus}}</span>
</div>
</div>
<hr>
</div>
`,
styles: [`
.uploadSection { font-family: sans-serif; }
.monospace { font-family: monospace; }
.subsection { margin-top: 35px;}
.indent { margin-left: 20px;}
.result { background-color: lightyellow }
`]
})
export class S3UploadTestComponent {
// Replace the values with your own
private readonly _awsConfig = {
accessKeyId: "<your keyId>",
secretAccessKey: "<your secret>",
s3BucketRegion: "<your region>", // example: "us-west-2"
s3BucketName: "<your bucket>" // example: "mycompany.testbucket"
}
private _awsCredentials: Credentials;
private _s3ClientConfig: S3.ClientConfiguration;
private _s3: S3;
uploadStatus: string = "(no upload yet)";
expectEndpoint: string;
actualEndpoint: string;
constructor() {
// Create an AWS S3 client
this._awsCredentials = new Credentials(this._awsConfig.accessKeyId, this._awsConfig.secretAccessKey);
this._s3ClientConfig = {
credentials: this._awsCredentials,
region: this._awsConfig.s3BucketRegion,
sslEnabled: true
};
this._s3 = new S3(this._s3ClientConfig);
// Set the expected and actual endpoints
var isRegionUSEast :boolean = (this._awsConfig.s3BucketRegion).toLowerCase() == "us-east-1";
var endpointHost :string = isRegionUSEast ? "s3" : "s3-" + this._awsConfig.s3BucketRegion
this.expectEndpoint = endpointHost + ".amazonaws.com";
this.actualEndpoint = this._s3.config.endpoint;
}
// Event triggered when a file has been specified
fileEvent(fileInput: any) {
this.uploadStatus = "starting upload...";
// get the file to upload
let file: File = fileInput.target.files[0];
console.log(file);
// upload file to S3
let putObjectRequest: S3.PutObjectRequest = {
Key: 'categories/' + file.name,
Body: file,
Bucket: this._awsConfig.s3BucketName,
ContentType: file.type,
ServerSideEncryption: "AES256"
};
// use "that" to be able to reach component properties within the then/catch callback functions
let that = this;
// upload to S3
this._s3.upload(putObjectRequest).promise()
.then(function (response: S3.ManagedUpload.SendData) {
that.uploadStatus = "Success!\n File URI: " + response.Location;
// alert("upload successful!");
})
.catch(function (err: Error) {
var errMsg = "";
errMsg += "upload failed.\n ";
errMsg += "Error Message: " + err.message + "\n ";
errMsg += "NOTE: an error message of 'Network Failure' may mean that you have the wrong region or the wrong bucket name.";
that.uploadStatus = errMsg;
// alert(errMsg);
});
}
}
systemjs.config.js additions:
(function (global) {
System.config({
...
map: {
...
'aws-sdk': 'npm:aws-sdk'
},
packages: {
...
'aws-sdk': {
defaultExtension: 'js',
main: 'dist/aws-sdk.js',
format: 'global'
}
}
});
})(this);
app.module.ts:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { S3UploadTestComponent } from './s3-upload-test.component';
@NgModule({
imports: [BrowserModule],
declarations: [
AppComponent,
S3UploadTestComponent,
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Hello {{name}}</h1>
<s3-upload-test></s3-upload-test>
`,
})
export class AppComponent { name = 'Angular'; }
AWS S3 bucket CORS Configuration:
NOTE: you may want to make your more restrictive, as appropriate for your security needs
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
AWS IAM Policy (attach to user or group):
NOTE: you will almost certainly want to make the allowed actions more restrictive, as appropriate for your security needs
NOTE: replace <your bucketname>
with appropriate value
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1485926968000",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::<your bucketname>/*"
]
}
]
}
If this doesn't solve your problem, use Chrome dev tools and look at the 'Network' tab to see the OPTIONS request to the S3 API and update your question with the entire response. When AWS S3 pre-flights fail, they usually provide good information in the response.
Upvotes: 3