Auth.java

package com.shengzhiai.yugu.internal;

import com.shengzhiai.yugu.Signer;

import java.util.LinkedHashMap;
import java.util.Map;

/** Applies the configured credentials to REST attempts and WebSocket handshakes. */
public final class Auth {
    private final String appKey;
    private final String secretKey;
    private final String token;

    /**
     * @param appKey    app key, null for token authentication
     * @param secretKey secret key, null for token authentication
     * @param token     JWT, null for key authentication
     */
    public Auth(String appKey, String secretKey, String token) {
        this.appKey = appKey;
        this.secretKey = secretKey;
        this.token = token;
    }

    /** @return true for token authentication */
    public boolean isToken() {
        return token != null;
    }

    /** @return app key or null */
    public String appKey() {
        return appKey;
    }

    private static String nowSeconds() {
        return String.valueOf(System.currentTimeMillis() / 1000L);
    }

    /**
     * Headers of one REST attempt. Each attempt gets a fresh timestamp and nonce; the signature covers
     * the business parameters only.
     *
     * @param signParams business parameters of the call
     * @return headers
     */
    public Map<String, String> restHeaders(Map<String, String> signParams) {
        Map<String, String> h = new LinkedHashMap<>();
        if (token != null) {
            h.put("Authorization", "Bearer " + token);
            return h;
        }
        h.put("X-App-Key", appKey);
        h.put("X-Timestamp", nowSeconds());
        h.put("X-Nonce", Ids.nonce());
        h.put("X-Signature", Signer.sign(signParams, secretKey));
        return h;
    }

    /**
     * Query of one WebSocket handshake. Key mode signs every parameter except {@code signature}.
     *
     * @param business business parameters such as {@code idempotencyKey}
     * @return ordered query parameters
     */
    public Map<String, String> wsQuery(Map<String, String> business) {
        Map<String, String> q = new LinkedHashMap<>();
        if (token != null) {
            q.put("token", token);
            q.putAll(business);
            return q;
        }
        q.put("appKey", appKey);
        q.put("timestamp", nowSeconds());
        q.put("nonce", Ids.nonce());
        q.putAll(business);
        q.put("signature", Signer.sign(q, secretKey));
        return q;
    }

    /**
     * @param params query parameters
     * @return {@code ?a=b&c=d} with RFC 3986 encoding, empty for no parameters
     */
    public static String encodeQuery(Map<String, String> params) {
        if (params.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder("?");
        for (Map.Entry<String, String> e : params.entrySet()) {
            if (sb.length() > 1) {
                sb.append('&');
            }
            sb.append(PercentEncoding.encode(e.getKey())).append('=').append(PercentEncoding.encode(e.getValue()));
        }
        return sb.toString();
    }
}