Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically display iframe based on database query in angular fire, unsafe value, domsanitation

Currently using ionic/angular-fire to create a daily readings app that will dynamically display an iframe for embedded YT video depending on the date. Everything works fine until I attempt to use data bindings in the src url for the iframe instead of a test static url. I receive the error: Error: NG0904: unsafe value used in a resource URL context (see https://g.co/ng/security#xss)

I researched this site and learned about DomSanitizer and SafeResourceUrl. My problem is because I am using an observable to fetch the url data and all other data about the reading (title, description, ect) I am lost as to how to implement this strategy. All examples here seem to be working with static url's or using different method (vanilla js vs angular/typescript way). I am terrible at figuring out vanilla to angular as I come from a PHP background but really want to switch to FAng stack.

I tried to include the script for DomSanitizer as per angular specs and suggestions here on SO, but because of the observable I guess, nothing seems to work and I get errors and IDE complaints.

Here is what i did trying to implement the dynamic url since i couldn't figure out how to include in the in the getReadings function (i figure this is completely wrong strategy but like i said i'm lost): data-service.ts file:

    import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
    import { Injectable } from '@angular/core';
    // eslint-disable-next-line max-len
    import { Firestore, collection, collectionData, doc, docData, addDoc, deleteDoc, updateDoc, query, orderBy, limit } from '@angular/fire/firestore';
    import { Observable } from 'rxjs';

export interface Readings {
  id?: string;
  orderNo?: number;
  parshah?: string;
  date?: string;
  videoTorah?: string;
  torahPortion?: string;
  url?: string;
  safeUrl?: string;
}
@Injectable({
  providedIn: 'root'
})
export class DataService {
  url: string;
  urlSafe: SafeResourceUrl;

  constructor(
    private firestore: Firestore,
    private domSanitizer: DomSanitizer
    ) { }
  getReadings(): Observable<Readings[]> {
    const readingsRef = collection(this.firestore, 'readings');
    const q = query(readingsRef, orderBy('orderNo'), limit(3));
    return collectionData(q, { idField: 'id' }) as Observable<Readings[]>;
  }
  getUrl(readings: Readings): SafeResourceUrl {
    this.getReadings();
    this.url = 'https://www.youtube.com/embed/' + readings.videoTorah;
    this.urlSafe = this.domSanitizer.bypassSecurityTrustResourceUrl(this.url);
    return this.urlSafe;
  }
}

In my home.page.ts i added this line to my constructor:

 this.dataService.getReadings().subscribe(res => {
        this.readings = res;
        this.cd.detectChanges();
      });
        this.dataService.getUrl();

my home.page.html originally called the url like this:

   <iframe width="560" height="315" [src]="readings.videoTorah" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

now its:

<ion-list>
  <ion-item *ngFor="let reading of readings">
    <iframe width="560" height="315" src="getUrl()" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
    <ion-label>
      {{ reading.orderNo }}  {{ reading.parshah }} {{ reading.torahPortion }}}
    </ion-label>
  </ion-item>
</ion-list>

but now I am just unsure how to do the entire thing after src="getUrl()" didn't work. I am probably completely out in left field now but this is a hobby and I just need some guidance please? I just know I am missing something really simple due to my lack of formal education in coding. None of my udemy classes covered this sort of thing, and yes, i am going back and studying more on typescript and rxjs because I get stuck on these a lot.

like image 398
Alicia Taylor Avatar asked Aug 15 '26 16:08

Alicia Taylor


1 Answers

Angular is very particular in how it accepts SafeResources. These won't work:

<iframe src={{url}}>
<iframe [src]="method()">

The only way I got it to work was to bind to a property containing the SafeResource. But that's too restrictive. I want to properly use Angular with Observables and the async pipe and OnPush change detection.

To enable this I created a safe pipe. It's used like this:

  <iframe
    *ngIf="iFrameSrc$ | async as url"
    [src]="url | safe: scRESOURCE_URL"
  ></iframe>
  • The *ngIf is neccessary because the async pipe returns T | null even if the source observable only returns T.
  • scRESOURCE_URL is just public readonly scRESOURCE_URL = SecurityContext.RESOURCE_URL in the component class becaues Angular does not allow accessing things defined outside of the component.

Pipe implementation:

@Pipe({
  name: 'safe',
  pure: true,
})
export class SafePipe implements PipeTransform {
  constructor(private readonly sanitizer: DomSanitizer) {}

  public transform(value: string, type: SecurityContext.HTML): SafeHtml;
  public transform(value: string, type: SecurityContext.STYLE): SafeStyle;
  public transform(value: string, type: SecurityContext.SCRIPT): SafeScript;
  public transform(value: string, type: SecurityContext.URL): SafeUrl;
  public transform(
    value: string,
    type: SecurityContext.RESOURCE_URL
  ): SafeResourceUrl;
  public transform(
    input: string,
    type: SecurityContext
  ): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
    switch (type) {
      case SecurityContext.HTML:
        return this.sanitizer.bypassSecurityTrustHtml(input);
      case SecurityContext.STYLE:
        return this.sanitizer.bypassSecurityTrustStyle(input);
      case SecurityContext.SCRIPT:
        return this.sanitizer.bypassSecurityTrustScript(input);
      case SecurityContext.URL:
        return this.sanitizer.bypassSecurityTrustUrl(input);
      case SecurityContext.RESOURCE_URL:
        return this.sanitizer.bypassSecurityTrustResourceUrl(input);
    }
    throw 'Invalid SecurityContext';
  }
}
like image 80
Patrick Avatar answered Aug 18 '26 07:08

Patrick