All files / src auth.ts

100% Statements 42/42
100% Branches 19/19
100% Functions 12/12
100% Lines 41/41
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191                                                                                                          2x             2x 2x 2x 2x             2x 2x   2x 2x         2x 4x 4x               2x 4x         2x             1x 1x             2x 4x         2x 3x   2x 2x   1x               2x 1x         2x 3x 2x   1x           2x   6x 6x             6x                   2x         8x   4x         4x         2x       4x   2x  
import { IncomingHttpHeaders } from 'http';
import { StringAnyMap } from './interfaces';
import AuthPayload from './payload';
import AuthScope from './scope';
 
export interface AuthToken<I> {
  new (Auth: AuthServer): I;
}
 
export interface IAccessToken {
  Auth: AuthServer;
  cookie: string;
  /**
   * Creates a payload based in some data
   */
  buildPayload(data: StringAnyMap): StringAnyMap;
  /**
   * Creates the accessToken
   */
  create(payload: StringAnyMap): string;
  /**
   * Verifies an accessToken and returns its payload
   */
  verify(accessToken: string): StringAnyMap;
  /**
   * Returns the expiration date of an accessToken
   */
  getExpDate(): Date;
}
 
export interface IRefreshToken {
  Auth: AuthServer;
  cookie: string;
  /**
   * Creates the refreshToken
   */
  create(data: StringAnyMap): Promise<string>;
  /**
   * Returns the payload in a refreshToken that can be used to create an
   * accessToken
   * @param reset Refresh the cookie of the refreshToken
   */
  getPayload(refreshToken: string, reset: () => void): Promise<StringAnyMap>;
  /**
   * Removes the refreshToken
   */
  remove(refreshToken: string): Promise<boolean> | boolean;
  /**
   * Returns the expiration date of a refreshToken
   */
  getExpDate(): Date;
}
 
export default class AuthServer {
  public accessToken: IAccessToken;
  public refreshToken: IRefreshToken;
  public payload: AuthPayload;
  public scope: AuthScope;
 
  constructor({
    AccessToken,
    RefreshToken,
    payload,
    scope
  }: {
    AccessToken: AuthToken<IAccessToken>;
    RefreshToken: AuthToken<IRefreshToken>;
    payload: AuthPayload;
    scope?: AuthScope;
  }) {
    this.accessToken = new AccessToken(this);
    this.refreshToken = new RefreshToken(this);
 
    this.payload = payload;
    this.scope = scope || new AuthScope();
  }
  /**
   * Creates a new accessToken
   */
  public createAccessToken(data: StringAnyMap) {
    const payload = this.accessToken.buildPayload(data);
    return {
      accessToken: this.accessToken.create(this.payload.create(payload)),
      payload
    };
  }
  /**
   * Creates a new refreshToken
   */
  public createRefreshToken(data: StringAnyMap) {
    return this.refreshToken.create(data);
  }
  /**
   * Creates both an accessToken and refreshToken
   */
  public async createTokens(
    data: StringAnyMap
  ): Promise<{
    refreshToken: string;
    accessToken: string;
    payload: StringAnyMap;
  }> {
    return {
      refreshToken: await this.createRefreshToken(data),
      ...this.createAccessToken(data)
    };
  }
  /**
   * Verifies an accessToken and returns its payload
   */
  public verify(accessToken: string) {
    return this.payload.parse(this.accessToken.verify(accessToken));
  }
  /**
   * Decodes and returns the payload of an accessToken
   */
  public decode(accessToken: string) {
    if (!accessToken) return null;
 
    try {
      return this.verify(accessToken);
    } catch (error) {
      return null;
    }
  }
  /**
   * Returns the payload in a refreshToken that can be used to create an
   * accessToken
   * @param reset Refresh the cookie of the refreshToken
   */
  public getPayload(refreshToken: string, reset: () => void) {
    return this.refreshToken.getPayload(refreshToken, reset);
  }
  /**
   * Removes an active refreshToken
   */
  public removeRefreshRoken(refreshToken: string): Promise<boolean> | boolean {
    if (refreshToken) {
      return this.refreshToken.remove(refreshToken);
    }
    return false;
  }
  /**
   * Returns a cookie, it searchs in this order:
   * signedCookies -> cookies -> null
   */
  public getCookie(
    {
      cookies,
      signedCookies
    }: {
      cookies?: StringAnyMap;
      signedCookies?: StringAnyMap;
    },
    cookie: string
  ): string | null {
    return (
      (signedCookies && signedCookies[cookie]) ||
      (cookies && cookies[cookie]) ||
      null
    );
  }
  /**
   * Returns the accessToken from a JWT Token using the headers or cookies of a
   * request, it will always look inside headers first
   */
  public getAccessToken(req: {
    headers?: IncomingHttpHeaders;
    cookies?: StringAnyMap;
    signedCookies?: StringAnyMap;
  }): string | null {
    const { authorization = '' } = req.headers || {};
    const accessToken =
      (authorization &&
        typeof authorization === 'string' &&
        authorization.split(' ')[1]) ||
      this.getCookie(req, this.accessToken.cookie);
 
    return accessToken;
  }
  /**
   * Returns the refreshToken from the cookies
   */
  public getRefreshToken(req: {
    cookies?: StringAnyMap;
    signedCookies?: StringAnyMap;
  }) {
    return this.getCookie(req, this.refreshToken.cookie);
  }
}