All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.contek.invoker.deribit.api.websocket.WebSocketAuthenticator Maven / Gradle / Ivy

package io.contek.invoker.deribit.api.websocket;

import com.google.common.io.BaseEncoding;
import io.contek.invoker.commons.websocket.AnyWebSocketMessage;
import io.contek.invoker.commons.websocket.IWebSocketAuthenticator;
import io.contek.invoker.commons.websocket.WebSocketAuthenticationException;
import io.contek.invoker.commons.websocket.WebSocketSession;
import io.contek.invoker.deribit.api.common._Error;
import io.contek.invoker.deribit.api.websocket.common.WebSocketRequest;
import io.contek.invoker.deribit.api.websocket.user.WebSocketAuthenticationConfirmation;
import io.contek.invoker.security.ICredential;
import org.slf4j.Logger;

import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
import java.time.Clock;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.google.common.io.BaseEncoding.base32Hex;
import static io.contek.invoker.deribit.api.websocket.common.constants.WebSocketAuthGrantTypeKeys._client_signature;
import static org.slf4j.LoggerFactory.getLogger;

@ThreadSafe
final class WebSocketAuthenticator implements IWebSocketAuthenticator {

  private static final Logger log = getLogger(WebSocketAuthenticator.class);
  private static final BaseEncoding ENCODING = base32Hex().lowerCase().omitPadding();
  private static final Random random = new Random();

  private final ICredential credential;
  private final WebSocketRequestIdGenerator requestIdGenerator;
  private final Clock clock;

  private final AtomicReference> pendingRequestHolder =
      new AtomicReference<>();
  private final AtomicBoolean authenticated = new AtomicBoolean();

  WebSocketAuthenticator(
      ICredential credential, WebSocketRequestIdGenerator requestIdGenerator, Clock clock) {
    this.credential = credential;
    this.requestIdGenerator = requestIdGenerator;
    this.clock = clock;
  }

  @Override
  public void handshake(WebSocketSession session) {
    if (credential.isAnonymous()) {
      return;
    }

    String clientId = credential.getApiKeyId();
    String timestamp = Long.toString(clock.instant().toEpochMilli());
    String nonce = getNonce();
    String data = "";

    String payload = timestamp + "\n" + nonce + "\n" + data;
    String signature = credential.sign(payload);

    AuthParams params = new AuthParams();
    params.grant_type = _client_signature;
    params.client_id = clientId;
    params.timestamp = timestamp;
    params.nonce = nonce;
    params.data = data;
    params.signature = signature;

    WebSocketRequest request = new WebSocketRequest<>();
    request.id = requestIdGenerator.getNextRequestId(WebSocketAuthenticationConfirmation.class);
    request.method = "public/auth";
    request.params = params;

    log.info("Requesting authentication for {}.", credential.getApiKeyId());
    session.send(request);
    pendingRequestHolder.set(request);
  }

  @Override
  public boolean isPending() {
    return pendingRequestHolder.get() != null;
  }

  @Override
  public boolean isCompleted() {
    return credential.isAnonymous() || authenticated.get();
  }

  @Override
  public void onMessage(AnyWebSocketMessage message, WebSocketSession session) {
    if (isCompleted()) {
      return;
    }

    if (!(message instanceof WebSocketAuthenticationConfirmation response)) {
      return;
    }

    WebSocketRequest request = pendingRequestHolder.get();
    if (request == null) {
      return;
    }

    if (!response.id.equals(request.id)) {
      return;
    }

    pendingRequestHolder.set(null);
    if (response.error != null) {
      _Error error = response.error;
      throw new WebSocketAuthenticationException(error.code + ": " + error.message);
    }

    authenticated.set(true);
    log.info("Authentication for {} completed.", credential.getApiKeyId());
  }

  @Override
  public void afterDisconnect() {
    pendingRequestHolder.set(null);
    authenticated.set(false);
  }

  private String getNonce() {
    byte[] bytes = new byte[8];
    while (true) {
      random.nextBytes(bytes);
      String encoded = ENCODING.encode(bytes);
      if (encoded.length() >= 8) {
        return encoded.substring(0, 8);
      }
    }
  }

  @NotThreadSafe
  public static final class AuthParams {

    public String grant_type;
    public String client_id;
    public String timestamp;
    public String nonce;
    public String data;
    public String signature;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy