ekyc

Electronic KYC process with uploading ID document using OAuth 2.1 (experimental)
Log | Files | Refs | README | LICENSE

code_challenge.ts (2339B)


      1 import { Code } from "#core/domain/code.ts";
      2 import { isSafeEqual, nonceCode } from "#core/domain/crypto.ts";
      3 import { Ephemeral } from "#core/domain/ephemeral.ts";
      4 import { DomainError } from "#core/domain/error.ts";
      5 import { Limiter } from "#core/domain/limiter.ts";
      6 
      7 export class CodeChallengeError extends DomainError {
      8 }
      9 
     10 export class AlreadyVerifiedCodeChallenge extends CodeChallengeError {
     11   constructor(options?: ErrorOptions) {
     12     super("Code challenge already verified", options);
     13   }
     14 }
     15 
     16 export class InvalidCodeChallenge extends CodeChallengeError {
     17   constructor(readonly delay: number, options?: ErrorOptions) {
     18     super("Invalid code challenge", options);
     19   }
     20 }
     21 
     22 export class CodeChallenge {
     23   constructor(
     24     readonly TTL: number,
     25     readonly REQUEST_LIMIT: number,
     26     readonly ATTEMPT_LIMIT: number,
     27     private _verified: boolean = false,
     28     private _code: Ephemeral<Code> = Ephemeral.empty(),
     29     private _request: Limiter = new Limiter(REQUEST_LIMIT, TTL),
     30     private _attempt: Limiter = new Limiter(ATTEMPT_LIMIT, TTL),
     31   ) {}
     32 
     33   get verified() {
     34     return this._verified;
     35   }
     36 
     37   get code() {
     38     return this._code.valueOf();
     39   }
     40 
     41   get request() {
     42     return this._request.count;
     43   }
     44 
     45   get requestDelay() {
     46     return this._request.delay;
     47   }
     48 
     49   get attempt() {
     50     return this._attempt.count;
     51   }
     52 
     53   get attemptDelay() {
     54     return this._attempt.delay;
     55   }
     56 
     57   get codeExpire() {
     58     return this._code.expire;
     59   }
     60 
     61   get requestExpire() {
     62     return this._request.expire;
     63   }
     64 
     65   get attemptExpire() {
     66     return this._attempt.expire;
     67   }
     68 
     69   requestChallenge(): Code {
     70     if (this.verified) {
     71       throw new AlreadyVerifiedCodeChallenge();
     72     }
     73 
     74     this._request.increment();
     75     const code = nonceCode();
     76     this._code = Ephemeral.of(code, this.TTL);
     77     this._attempt = new Limiter(this.ATTEMPT_LIMIT, this.TTL);
     78     return code;
     79   }
     80 
     81   attemptChallenge(candidate: Code): void {
     82     if (this.verified) {
     83       throw new AlreadyVerifiedCodeChallenge();
     84     }
     85     this._attempt.increment();
     86     this._verified = isSafeEqual(this.code, candidate);
     87     if (!this.verified) {
     88       throw new InvalidCodeChallenge(this.attemptDelay);
     89     }
     90     this._code = Ephemeral.empty();
     91     this._request = new Limiter(this.REQUEST_LIMIT, this.TTL);
     92     this._attempt = new Limiter(this.ATTEMPT_LIMIT, this.TTL);
     93   }
     94 }