2

I am new to Nest.JS and apparently don't understand how to use observables so hopefully ya'll can help.

Basically I have a method that needs to: first: login to hashicorp vault and return a client_token via an http call. second: if we got a token back from vault, we then check that the request contained a certification-id, if not we have to request a new certification to be generated. Which requires the client_token from vault.

The problem I am having is that when I call vault to get the client_token, it does not get returned in time for me to be able to use it to generate a new cert via a second api call.

What can I do in order to be able to use the client_token in the next step?

Here is the code for my latest attempt:

Controller:

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('getUserCert')
  async getUserCert(@Body() loginDto: vaultLoginReqDto) {
    return this.userService.getCertificate(loginDto);
  }
}

Controller calls the getCertificate method:

  getCertificate(loginDto: vaultLoginReqDto) {
    this.loginToVault(loginDto);

    if (this.vault_token) {
      if (loginDto.cert_id) {
        this.checkForExistingCert(loginDto);
      } else {
        this.generateNewCert(this.vault_token);
      }
    } else {
      throw new Error('User is not authorized to access Vault.');
    }
  }

The logon method:

  loginToVault(loginDto: vaultLoginReqDto) {
    const url = 'http://vault:8200/v1/auth/jwt/login';
    const payload: vaultLoginReqDto = {
      jwt: loginDto.jwt,
      role: loginDto.role,
    };
    try {
      this.httpService
        .post(url, payload)
        .subscribe((res: AxiosResponse<vaultLoginResDto>) => {
          this.vault_token = res.data.auth.client_token;
        });
    } catch (e) {
      this.throwError(e, url, 'Unable to login to vault');
    }
  }

the problem method is the generateNewCert method. It is not getting the vault_token in time.

  generateNewCert(vault_token: string): Observable<string> {
    const url = `http://127.0.0.1:8200/v1/xxxx/xxxx/issue/reader`;
    const payload = {
      common_name: 'id.xxxx.com',
    };

    const headers = {
      'X-Vault-Token': vault_token,
    };

    try {
      return this.httpService.post(url, payload, { headers: headers }).pipe(
        map((res: AxiosResponse<vaultGetCertResDto>) => {
          return res.data.data.certificate;
        }),
      );
    } catch (e) {
      this.throwError(e, url);
    }
  }

I appreciate the help!

Taylor Belk
  • 162
  • 1
  • 16

2 Answers2

3

The easiest way to make it work is the convert to a Promise so you can wait for the result.

loginToVault(loginDto: vaultLoginReqDto) {
  const url = 'http://vault:8200/v1/auth/jwt/login';
  const payload = {
    jwt: loginDto.jwt,
    role: loginDto.role,
  };

  return this.httpService
    .post(url, payload)
    .pipe(
      catchError(() => {/** ...handleError **/}),
      map((res) => {
        this.vault_token = res.data.auth.client_token;
        return this.vault_token;
      }),
    )
    .toPromise()
}

Now, you can use async / await at getCertificate

async getCertificate(loginDto: vaultLoginReqDto) {
  await this.loginToVault(loginDto);
  // or const vault_token = await this.loginToVault(loginDto)

  if (this.vault_token) {
    if (loginDto.cert_id) {
      this.checkForExistingCert(loginDto);
    } else {
      this.generateNewCert(this.vault_token);
    }
  } else {
    throw new Error('User is not authorized to access Vault.');
  }
}
1

If you decide to stick with the observables, you can return an observable from the loginToVault method as opposed to subscribing to it

loginToVault(loginDto: vaultLoginReqDto): Observable<string> {
    const url = 'http://vault:8200/v1/auth/jwt/login';
    const payload = {
      jwt: loginDto.jwt,
      role: loginDto.role,
    };

    return this.httpService
      .post(url, payload)
      .pipe(
        catchError(() => { /* handle errors */ }),
        map((res) => res.data.auth.client_token)
      )
  }

Then in getCertificate method, you subscribe to loginToVault and handle the logic

  getCertificate(loginDto: vaultLoginReqDto) {
    this.loginToVault(loginDto)
      .pipe(
        tap(vault_token => {
          if (!vault_token) {
            throw new Error('User is not authorized to access Vault.');
          }
        })
      )
      .subscribe(vault_token => loginDto.cert_id ?
        this.checkForExistingCert(loginDto) :
        this.generateNewCert(vault_token)
      )
  }

The vault_token is passed from one service to another and thus will be accessible in the generateNewCert method. You do not need to declare it globally

Mirza Leka
  • 651
  • 7
  • 11
  • 1
    Can you explain the advantages of HttpService (Observables) over pure axios calls (Promise) ? Or do you have some posts explaining it and how to use it properly ? – E-jarod Aug 24 '22 at 16:35
  • 1
    I guess it comes down to whether you want to use observables or promises. HttpService from @nestjs/axios package allows you to do both: httpService: HttpService a) ```httpService.get('...') // observable``` b) ```httpService.axiosRef.get('...')``` // promise. I prefer observables as they give more ways to manage data and handle the response. But there isn't really a right or wrong answer. It's one programming paradigm vs another. Here is a tutorial series on Rx.js (observables). Hope it comes in handy https://www.youtube.com/playlist?list=PL_euSNU_eLbc0HclFbirIaMXaXzQJ_K4n – Mirza Leka Aug 28 '22 at 01:31
  • 1
    Thank you very much ! I thought there was a benefit that isn't possible with promises, but I guess it's "just" a way of coding – E-jarod Aug 28 '22 at 08:46
  • 1
    for HTTP requests there isn't much difference but for streams and real-time communication, observables all the way. E.g. of that: https://docs.nestjs.com/techniques/server-sent-events Promises have a downside that they can only fire once and are either resolved or rejected. Observable can execute any number of times and will continue to execute until completed(unsubscribed) or it fails. Here is a good comparison between the two https://www.youtube.com/watch?v=GSI7iyK_ju4 – Mirza Leka Aug 28 '22 at 11:28