All files / estuary-rpc-server authentication.ts

100% Statements 35/35
80.85% Branches 76/94
100% Functions 3/3
100% Lines 35/35

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 81 82 83 84 85 86      1x             1x 4x   8x 8x 8x 2x   6x 6x 2x   4x 4x                                 1x         6x 6x 1x   5x   1x           1x 1x       1x 1x 1x     1x 1x 1x 1x     1x 1x 1x 1x     1x 1x 1x 1x      
import { Authentication } from "estuary-rpc";
 
import { IncomingMessage } from "http";
import { getUrl } from "./middleware";
 
/**
 * @param request IncomingMessage from the client
 * @returns a Record<string, string> containing all cookie name/value pairs passed in the request
 * @group Server
 */
export function getCookies(request: IncomingMessage): Record<string, string> {
  return (request.headers?.cookie?.split?.(`;`) ?? []).reduce(
    (cookies, cookie) => {
      let [name, ...rest] = cookie.split(`=`);
      name = name?.trim();
      if (!name) {
        return cookies;
      }
      const value = rest.join(`=`).trim();
      if (!value) {
        return cookies;
      }
      cookies[name] = decodeURIComponent(value);
      return cookies;
    },
    {}
  );
}
 
/**
 * Checks authentication of a message from the client
 * @param incoming IncomingMessage from the 'http' server
 * @param metaAuth Description of the authentication scheme created in your API Meta definition. See
 * {@linke estuary-rpc!Authentication} for specific types of authentication supported
 * @param authenticate Method defined in {@link ServerOpts} that should be implemented to actually check the
 * authentication against whatever database/backend/authorization scheme you have
 * @returns Pair of [boolean, Authentication | undefined], where the boolean is whether the user is authenticatd,
 * and the Authentication is the parsed authentication information if extractable
 * @group Server
 */
export function isAuthenticated(
  incoming: IncomingMessage,
  metaAuth?: Authentication,
  authenticate?: (authentication: Authentication) => boolean
): [boolean, Authentication | undefined] {
  let auth: Authentication | undefined = undefined;
  if (!metaAuth || !authenticate) {
    return [true, auth];
  }
  switch (metaAuth.type) {
    case "basic":
      const [username, password] = Buffer.from(
        incoming.headers["authorization"]?.split?.("Basic ")?.[1] ?? "",
        "base64"
      )
        .toString()
        .split(":");
      auth = { ...metaAuth, username, password: password ?? "" };
      return [authenticate(auth), auth];
 
    case "bearer":
      const token =
        incoming.headers["authorization"]?.split("Bearer ")?.[1] ?? "";
      auth = { ...metaAuth, token };
      return [authenticate(auth), auth];
 
    case "header":
      const header = metaAuth.keyPair[0];
      const headerValue = incoming.headers[header]?.[0] ?? "";
      auth = { ...metaAuth, keyPair: [header, headerValue] };
      return [authenticate(auth), auth];
 
    case "query":
      const query = metaAuth.keyPair[0];
      const queryValue = getUrl(incoming)?.searchParams?.get?.(query) ?? "";
      auth = { ...metaAuth, keyPair: [query, queryValue] };
      return [authenticate(auth), auth];
 
    case "cookie":
      const cookie = metaAuth.keyPair[0];
      const cookieValue = getCookies(incoming)?.[cookie] ?? "";
      auth = { ...metaAuth, keyPair: [cookie, cookieValue] };
      return [authenticate(auth), auth];
  }
}