All files / rpc-server/src/http1 Http1Cors.ts

100% Statements 30/30
96.15% Branches 25/26
100% Functions 3/3
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80                                                            1x 1x   1x                   10x 10x   10x 10x 10x 10x 10x 10x 10x 10x         10x 10x 2x 2x 1x 1x 1x   9x 9x 9x 9x         3x 3x 3x      
import type * as http from 'http';
 
export interface CorsOpts {
  /**
   * Value of the `Access-Control-Allow-Origin` header, `'*'` by default. Set to
   * `true` to echo back the request `Origin` header instead, which is what
   * browsers require when credentials are enabled.
   */
  origin?: string | true;
 
  /** Value of `Access-Control-Allow-Methods`, sent on preflight responses. */
  methods?: string;
 
  /**
   * Value of `Access-Control-Allow-Headers`, sent on preflight responses. Must
   * list `Content-Type`: the `application/x.rpc.*` media types this server
   * negotiates are not CORS-safelisted, so the preflight fails without it.
   */
  headers?: string;
 
  /** Value of `Access-Control-Expose-Headers`. Omitted when empty. */
  expose?: string;
 
  /** Whether to send `Access-Control-Allow-Credentials: true`. */
  credentials?: boolean;
 
  /** Value of `Access-Control-Max-Age`, in seconds. Defaults to 86400. */
  maxAge?: number;
}
 
const DEFAULT_METHODS = 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
const DEFAULT_HEADERS = 'Content-Type, Authorization';
 
export class Http1Cors {
  public readonly origin: string;
  public readonly echo: boolean;
  public readonly credentials: boolean;
  public readonly methods: string;
  public readonly headers: string;
  public readonly expose: string;
  public readonly maxAge: string;
 
  constructor(opts: CorsOpts = {}) {
    const credentials = !!opts.credentials;
    const origin = opts.origin ?? '*';
    // Browsers reject a wildcard origin on a credentialed response.
    const echo = origin === true || (credentials && origin === '*');
    this.echo = echo;
    this.origin = echo ? '' : (origin as string);
    this.credentials = credentials;
    this.methods = opts.methods ?? DEFAULT_METHODS;
    this.headers = opts.headers ?? DEFAULT_HEADERS;
    this.expose = opts.expose ?? '';
    this.maxAge = String(opts.maxAge ?? 86400);
  }
 
  /** Headers every cross-origin response needs, preflight or not. */
  public apply(req: http.IncomingMessage, res: http.ServerResponse): void {
    let origin = this.origin;
    if (this.echo) {
      const header = req.headers.origin;
      if (typeof header !== 'string') return;
      origin = header;
      const vary = res.getHeader('Vary');
      res.setHeader('Vary', vary ? vary + ', Origin' : 'Origin');
    }
    res.setHeader('Access-Control-Allow-Origin', origin);
    if (this.credentials) res.setHeader('Access-Control-Allow-Credentials', 'true');
    const expose = this.expose;
    if (expose) res.setHeader('Access-Control-Expose-Headers', expose);
  }
 
  /** Headers only a preflight response needs, on top of {@link Http1Cors.apply}. */
  public preflight(res: http.ServerResponse): void {
    res.setHeader('Access-Control-Allow-Methods', this.methods);
    res.setHeader('Access-Control-Allow-Headers', this.headers);
    res.setHeader('Access-Control-Max-Age', this.maxAge);
  }
}