All files / src/sso/schm SCHMWithSync.ts

28.85% Statements 15/52
0% Branches 0/20
9.09% Functions 1/11
27.08% Lines 13/48

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 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 1221x   1x       1x                   1x   1x                                                   1x                                         1x       1x       1x       1x       1x                                                   1x                         1x  
import dbg from 'debug';
 
import { ServerContextHandleManager } from './ServerContextHandleManager';
import { CtxtHandle } from '../../../lib/api';
import { SSOMethod } from '../interfaces';
 
const debug = dbg('node-expose-sspi:schManager');
 
type IPromiseFn = (value?: any) => void;
 
interface AuthItem {
  resolve?: IPromiseFn;
  reject?: IPromiseFn;
  timeout: NodeJS.Timeout;
}
 
const TOO_LATE_ERROR_MSG = 'too many concurrent connections.';
 
export class SCHMWithSync extends ServerContextHandleManager {
  /**
   * The authentication currently being processed.
   *
   * @private
   * @type {AuthItem}
   * @memberof SCHMWithSync
   */
  private authItem: AuthItem | undefined;
 
  /**
   * The queue of other authentication that are waiting.
   *
   * @private
   * @type {AuthItem[]}
   * @memberof SCHMWithSync
   */
  private queue: AuthItem[] = [];
 
  private serverContextHandle: CtxtHandle | undefined;
  private method!: SSOMethod;
 
  constructor(private delayMax = 20000) {
    super();
  }
 
  waitForReleased(): Promise<void> {
    return new Promise((resolve, reject) => {
      debug('waitForReleased: start promise');
      const timeout = setTimeout(() => {
        this.interrupt();
      }, this.delayMax);
      // if nobody else is currently authenticating then go now.
      if (this.authItem === undefined) {
        debug('waitForReleased: we can start now.');
        this.authItem = { timeout };
        return resolve();
      }
 
      debug(
        'someone is currently authenticating, go in the queue and wait for your turn.'
      );
      this.queue.push({ resolve, reject, timeout });
      debug('queue length', this.queue.length);
    });
  }
 
  getMethod(): SSOMethod {
    return this.method;
  }
 
  setMethod(ssoMethod: SSOMethod): void {
    this.method = ssoMethod;
  }
 
  getHandle(): CtxtHandle | undefined {
    return this.serverContextHandle;
  }
 
  setHandle(contextHandle: CtxtHandle): void {
    this.serverContextHandle = contextHandle;
  }
 
  release(): void {
    if (this.authItem) {
      clearTimeout(this.authItem.timeout);
    }
    this.serverContextHandle = undefined;
    this.authItem = undefined;
    if (this.queue.length > 0) {
      // it means another client B was waiting for authenticating.
      // so we start authenticating this client B.
      this.authItem = this.queue.shift();
      debug('releasing. queue length', this.queue.length);
      if (this.authItem?.resolve) {
        this.authItem.resolve();
      }
    }
  }
 
  /**
   * after timeout, all the queue is removed and rejected.
   * does not go to its final state before timeout.
   *
   *
   * @param {AuthItem} authItem
   * @returns
   * @memberof ServerContextHandleManager
   */
  interrupt(): void {
    while (this.queue.length > 0) {
      const ai = this.queue.pop();
      if (ai) {
        clearTimeout(ai.timeout);
        if (ai.reject) {
          ai.reject(TOO_LATE_ERROR_MSG);
        }
      }
    }
    this.authItem = undefined;
    this.serverContextHandle = undefined;
  }
}