0

I am developing a web app using Angular and Firebase through angularfire. I have a service that handles the authentication process for the users called HandleAuthService. I would like to allow users to upload a profile picture of themselves. I am storing the profile picture within the Firebase storage under a unique file path. The file path where their image is stored is under: UID/displayPicture/.

When the user uploads a picture, it appears in the Firebase storage under their unique path. However, when I attempt to download the picture so that it may be displayed, I am running into a 403 error where it claims that the user does not have permission to read or write.

The Error

ERROR FirebaseError: Firebase Storage: User does not have permission to access 'USER_ID/displayPicture.jpg'. (storage/unauthorized)
{
  "error": {
    "code": 403,
    "message": "Permission denied. Please enable Firebase Storage for your bucket by visiting 
the Storage tab in the Firebase Console and ensure 
that you have sufficient permission to 
properly provision resources."
  }
}

Here are my Storage Rules (I have not changed them):

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

I tried removing the if request.auth !=null; but it still produced the same error.

Here are snippets of the HandleAuthService code:

constructor(private fireAuth: AngularFireAuth, private ngZone: NgZone, private router: Router) { 
    //I create an observable for other components to subscribe to in order to get
    //key information such as the uid 
    this.currentUser$ = this.fireAuth.authState.pipe(
      take(1),
      map(user => {
        if (user) {
          return {uid: user.uid, email:user.email, displayName:user.displayName}
        } else {
          return null;
        }
      })
    );
}

  //to login at the very beginning, the users may do so through google
  googleLogin(provider:any) {
    return this.fireAuth.signInWithPopup(provider)
    .then((result)=> {
      this.ngZone.run(() => {
        this.router.navigate(['data']);
      });
      this.setUser(result.user);
    })
    .catch((error) => {
      console.log(error);
    })
  }

Then, in each component, I subscribe to this.currentUser$ in order to access the user's authState data. This has been working for all other components.

In the component where I display the profile picture, picture.component.ts:

constructor(private handleAuth:HandleAuthService, private storage: AngularFireStorage, private crowboxService:CrowboxdbService) { }

//in this function I try to get the profile picture from the Firebase Storage 
ngOnInit(): void {
    //Subscribe to the user auth state observable and wait 
    //to get the UID to proceed - then get the user's profile picture to be displayed
    this.handleAuth.currentUser$
    .subscribe(user => {
      this.userId = user.uid;
      this.getProfilePicture();
    });  
  }

//the function to upload the profile picture - invoked by the click of a button
uploadFile(event:any) {
    const file = event.target.files[0];
    this.filePath = `${this.userId}/displayPicture`;
    const task = this.storage.upload(this.filePath, file);
  }

//the function to get the profile picture
getProfilePicture() {
    const ref = this.storage.ref(`${this.userId}/displayPicture.jpg`);
    this.profileUrl = ref.getDownloadURL();
  }

The HTML view of the picture.component.ts:

<div id="imageContainer">
    IMAGE GOES HERE
    <img alt="Profile Picture" [src]="profileUrl | async" />     
    <div>
        <input type="file" (change)="uploadFile($event)">
    </div>
</div>

If the user is able to upload the picture, why can I not retrieve the image? How can I ensure that the user is logged in?

I would also like to add that I have set up route guards as well:

const routes: Routes = [
  { path:'', redirectTo:'/home', pathMatch:'full' },
  { path:'home', component:HomeComponent},
  { path:'data', component:DataComponent, canActivate: [AngularFireAuthGuard] },
  { path:'troubleshoot', component:TroubleshootComponent, canActivate: [AngularFireAuthGuard] },
  { path:'profile', component:ProfileComponent, canActivate: [AngularFireAuthGuard] },  
];

1 Answers1

0

The problem was with how the URL was being retrieved. Here is the correct method according to AngularFire Documentation:

The picture.component.ts

uploadFile(event:any) {
    const file = event.target.files[0];
    this.filePath = `/Users/${this.userId}/profilePicture`;
    const fileRef = this.storage.ref(this.filePath);
    const task = this.storage.upload(this.filePath, file);


    this.$taskSub = task.snapshotChanges().pipe(
      finalize(() => {
        //Get the download url of the photo that has been stored
        //in the storage
        this.downloadURL = fileRef.getDownloadURL();
        
        this.$downloadURLSub = this.downloadURL.subscribe(result => {
          this.profileUrl = result;
        })
      }))
    .subscribe();
  }

In this code, I subscribe to the task's changes within the Firebase Project and retrieve the download URL from the task observable. This URL may now be used to retrieve the image, as shown below.

The picture.component.html

<img src="{{profileUrl}}" alt="Profile Picture" class="imageContainer">