Go Server SDK


Dodgeball Server Trust SDK for Go

GoDoc reference GoReportCard

Table of Contents

Purpose

Dodgeball enables developers to decouple security logic from their application code. This has several benefits including:

  • The ability to toggle and compare security services like fraud engines, MFA, KYC, and bot prevention.
  • Faster responses to new attacks. When threats evolve and new vulnerabilities are identified, your application's security logic can be updated without changing a single line of code.
  • The ability to put in placeholders for future security improvements while focussing on product development.
  • A way to visualize all application security logic in one place.

The Dodgeball Server Trust SDK for Go makes integration with the Dodgeball API easy and is maintained by the Dodgeball team.

Prerequisites

You will need to obtain an API key for your application from the Developer Center > API Keys page.

Related

Check out the Dodgeball Trust Client SDK for how to integrate Dodgeball into your frontend applications.

Installation

Make sure your project is using Go Modules (it will have a go.mod file in its root if it already is):

go mod init

Then, reference dodgeball-trust-sdk-go in a Go program with import:

import (
  "github.com/dodgeballhq/dodgeball-trust-sdk-go"
)

Usage

package main

import (
  "encoding/json"
  "fmt"
  "log"
  "net"
  "net/http"
  "os"
  "strings"

  "github.com/dodgeballhq/dodgeball-trust-sdk-go"
)

// Initialize the SDK with your secret API key
var dodgeballClient = dodgeball.New(os.Getenv("DODGEBALL_SECRET_KEY"), dodgeball.NewConfig())

func orders(w http.ResponseWriter, req *http.Request) {
  userIP, err := getIP(req)
  if err != nil {
    fmt.Fprintf(w, "error reading IP\n")
    return
  }
  checkpointRequest := dodgeball.CheckpointRequest{
    CheckpointName: "PLACE_ORDER",
    Event: dodgeball.CheckpointEvent{
      IP: userIP,
      Data: map[string]interface{}{
        "order": "order456",
      },
    },
    SourceToken:       req.Header.Get("x-dodgeball-source-token"), // Obtained from the Dodgeball Client SDK
    SessionID:         "session_def456",
    UserID:            "user123",
    UseVerificationID: req.Header.Get("x-dodgeball-verification-id"),
  }

  checkpointResponse, err := dodgeballClient.Checkpoint(*checkpointRequest)
  if err != nil {
    fmt.Fprintf(w, "error checking checkpoint\n")
    return
  }

  w.Header().Set("Content-Type", "application/json")
  resp := make(map[string]interface{})

  if checkpointResponse.IsAllowed() {
    w.WriteHeader(http.StatusOK) // 200
    resp["order"] = "order details"
  } else if checkpointResponse.IsRunning() {
    w.WriteHeader(http.StatusAccepted) // 202
    resp["verification"] = checkpointResponse.Verification
  } else if checkpointResponse.IsDenied() {
    w.WriteHeader(http.StatusForbidden) // 403
    resp["verification"] = checkpointResponse.Verification
  } else {
    w.WriteHeader(http.StatusInternalServerError) // 500
    resp["message"] = checkpointResponse.Errors
  }

  jsonResp, err := json.Marshal(resp)
  if err != nil {
    log.Fatalf("error marshalling response: %s", err)
  }
  w.Write(jsonResp)
}

func main() {
  http.HandleFunc("/api/orders", orders)
  http.ListenAndServe(os.Getenv("APP_PORT"), nil)
}

func getIP(r *http.Request) (string, error) {
  ip := r.Header.Get("X-REAL-IP")
  netIP := net.ParseIP(ip)
  if netIP != nil {
    return ip, nil
  }

  ips := r.Header.Get("X-FORWARDED-FOR")
  splitIps := strings.Split(ips, ",")
  for _, ip := range splitIps {
    netIP := net.ParseIP(ip)
    if netIP != nil {
      return ip, nil
    }
  }

  ip, _, err := net.SplitHostPort(r.RemoteAddr)
  if err != nil {
    return "", err
  }
  netIP = net.ParseIP(ip)
  if netIP != nil {
    return ip, nil
  }
  return "", fmt.Errorf("no valid IP found")
}

API

Configuration

The package requires a secret API key as the first argument to the constructor.

var dodgeballClient = dodgeball.New(os.Getenv("DODGEBALL_SECRET_KEY"), dodgeball.NewConfig())

Optionally, you can pass in configuration options:

var dodgeballConfig = dodgeball.NewConfig()
dodgeballConfig.APIURL = "https://api.dodgeball.com"
dodgeballConfig.APIVersion = "v1"

var dodgeballClient = dodgeball.New(os.Getenv("DODGEBALL_SECRET_KEY"), dodgeballConfig)
OptionDefaultDescription
APIVersionv1The Dodgeball API version to use.
APIURLhttps://api.dodgeballhq.comThe base URL of the Dodgeball API. Useful for sending requests to different environments such as https://api.sandbox.dodgeballhq.com.

Call a Checkpoint

Checkpoints represent key moments of risk in an application. A checkpoint can represent any activity deemed to be a risk — login, placing an order, redeeming a coupon, posting a review, changing bank account information, making a donation, transferring funds, creating a listing.

checkpointRequest := &dodgeball.CheckpointRequest{
  CheckpointName: "CHECKPOINT_NAME",
  Event: dodgeball.CheckpointEvent{
    IP: "127.0.0.1", // The IP address of the device where the request originated
    Data: map[string]interface{}{
      "amount":   100,
      "currency": "USD",
    },
  },
  SourceToken:       req.Header.Get("x-dodgeball-source-token"), // Obtained from the Dodgeball Client SDK
  SessionID:         "session_def456",
  UserID:            "user123",
  UseVerificationID: req.Header.Get("x-dodgeball-verification-id"),
}

checkpointResponse, err := dodgeballClient.Checkpoint(checkpointRequest)
ParameterRequiredDescription
CheckpointNametrueThe name of the checkpoint to call.
EventtrueThe event to send to the checkpoint.
Event.IPtrueThe IP address of the device where the request originated.
Event.DatafalseArbitrary data to send to the checkpoint.
SourceTokentrueA Dodgeball generated token representing the device making the request. Obtained from the Dodgeball Trust Client SDK.
UserIDfalseThe ID representing the user in your database (after registration). Leave blank if unknown.
UseVerificationIDfalseIf a previous verification was performed on this request, pass it in here. See useVerification for details.

Interpreting the Checkpoint Response

Calling a checkpoint creates a verification in Dodgeball. The status and outcome of a verification determine how your application should proceed.

type CheckpointResponse struct {
  Success bool `json:"success"`
  Errors  []struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
  } `json:"errors"`
  Version      string `json:"version"`
  Verification struct {
    ID      string `json:"id"`
    Status  string `json:"status"`
    Outcome string `json:"outcome"`
  } `json:"verification"`
}
PropertyDescription
SuccessWhether the request was successful or failed.
ErrorsIf Success is false, contains an array of error objects with code and message.
VersionThe Dodgeball API version used. Default is v1.
VerificationObject representing the verification performed at this checkpoint.
Verification.IDThe ID of the verification created.
Verification.StatusThe current status of the verification. See Verification Statuses.
Verification.OutcomeThe outcome of the verification. See Verification Outcomes.

Verification Statuses

StatusDescription
COMPLETEThe verification was completed successfully.
PENDINGThe verification is currently processing.
BLOCKEDThe verification is waiting for input from the user.
FAILEDThe verification encountered an error and was unable to proceed.

Verification Outcomes

OutcomeDescription
APPROVEDThe request should be allowed to proceed.
DENIEDThe request should be denied.
PENDINGA determination has not been reached yet.
ERRORThe verification encountered an error and was unable to make a determination.

Possible Checkpoint Responses

Approvedstatus: COMPLETE, outcome: APPROVED

Deniedstatus: COMPLETE, outcome: DENIED

Pendingstatus: PENDING, outcome: PENDING (still processing)

Blockedstatus: BLOCKED, outcome: PENDING (awaiting user input, e.g. MFA)

Undecidedstatus: COMPLETE, outcome: PENDING (finished but no determination reached)

Errorsuccess: false, status: FAILED, outcome: ERROR


Utility Methods

It is strongly advised to use these methods rather than directly interpreting the checkpoint response.

checkpointResponse.IsAllowed()

Returns true if the request is allowed to proceed.

checkpointResponse.IsDenied()

Returns true if the request is denied and should not proceed.

checkpointResponse.IsRunning()

Returns true if no determination has been reached. Return the verification to the frontend to gather additional user input. See useVerification.

checkpointResponse.IsUndecided()

Returns true if the verification has finished with no determination reached.

checkpointResponse.HasError()

Returns true if the response contains an error.

checkpointResponse.IsTimeout()

Returns true if the verification has timed out. The application decides how to proceed.


useVerification

Sometimes additional input is required from the user before making a determination. For example, if 2FA is required, the checkpoint response will have status: BLOCKED and outcome: PENDING. Return the verification to your frontend and pass it to dodgeball.handleVerification() to prompt the user. Once the user completes the step, pass the resulting verification ID back to your API via useVerification.

Important: To prevent replay attacks, each verification ID can only be passed to useVerification once.

End-to-End Example

// In your frontend application...
const placeOrder = async (order, previousVerificationId = null) => {
  const sourceToken = await dodgeball.getSourceToken();

  const endpointResponse = await axios.post(
    "/api/orders",
    { order },
    {
      headers: {
        "x-dodgeball-source-token": sourceToken,
        "x-dodgeball-verification-id": previousVerificationId,
      },
    }
  );

  dodgeball.handleVerification(endpointResponse.data.verification, {
    onVerified: async (verification) => {
      await placeOrder(order, verification.id);
    },
    onApproved: async () => {
      setIsOrderPlaced(true);
    },
    onDenied: async (verification) => {
      setIsOrderDenied(true);
    },
    onError: async (error) => {
      setError(error);
      setIsPlacingOrder(false);
    },
  });
};