airlock

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 17, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package airlock provides a client SDK for the Airlock Gateway API.

The client covers all enforcer-side endpoints: artifact submission, exchange polling, pairing management, presence tracking, and gateway discovery.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AirlockAuthClient

type AirlockAuthClient struct {
	Options AirlockAuthOptions
	// contains filtered or unexported fields
}

AirlockAuthClient handles user authentication via Device Authorization Grant (RFC 8628) and Authorization Code + PKCE (RFC 7636).

func NewAirlockAuthClient

func NewAirlockAuthClient(opts AirlockAuthOptions) *AirlockAuthClient

NewAirlockAuthClient creates a new AirlockAuthClient.

func (*AirlockAuthClient) CurrentAccessToken

func (c *AirlockAuthClient) CurrentAccessToken() string

CurrentAccessToken returns the cached access token, or empty if none.

func (*AirlockAuthClient) Discover

Discover fetches the OIDC discovery document from Keycloak.

func (*AirlockAuthClient) ExchangeCode

func (c *AirlockAuthClient) ExchangeCode(ctx context.Context, code, redirectURI, codeVerifier string) (*TokenResponse, error)

ExchangeCode exchanges an authorization code for tokens (Auth Code + PKCE).

func (*AirlockAuthClient) GetAccessToken

func (c *AirlockAuthClient) GetAccessToken(ctx context.Context) (string, error)

GetAccessToken returns a valid access token, auto-refreshing if needed.

func (*AirlockAuthClient) GetAuthorizationURL

func (c *AirlockAuthClient) GetAuthorizationURL(ctx context.Context, redirectURI string) (*AuthCodeRequest, error)

GetAuthorizationURL builds the authorization URL for the Auth Code + PKCE flow. Use this when you manage the browser redirect yourself.

func (*AirlockAuthClient) GetTokenState

func (c *AirlockAuthClient) GetTokenState() (string, string, time.Time)

GetTokenState gets tokens for persistent storage.

func (*AirlockAuthClient) IsLoggedIn

func (c *AirlockAuthClient) IsLoggedIn() bool

IsLoggedIn returns true if we have a token.

func (*AirlockAuthClient) IsTokenExpired

func (c *AirlockAuthClient) IsTokenExpired() bool

IsTokenExpired returns true if the token is expired and needs refresh.

func (*AirlockAuthClient) Login

func (c *AirlockAuthClient) Login(ctx context.Context, onUserCode func(*DeviceCodeInfo)) (*TokenResponse, error)

Login starts the Device Authorization Grant flow. It calls onUserCode so the user can be prompted to open the verification URI and enter code. It polls the token endpoint until the user grants access or it times out.

func (*AirlockAuthClient) LoginWithAuthCode

func (c *AirlockAuthClient) LoginWithAuthCode(ctx context.Context, onBrowserURL func(string), redirectPort int) (*TokenResponse, error)

LoginWithAuthCode starts the Authorization Code + PKCE flow. It starts a local HTTP server to receive the callback and exchanges the code for tokens. Best for Web and Mobile enforcer apps.

func (*AirlockAuthClient) Logout

func (c *AirlockAuthClient) Logout(ctx context.Context) error

Logout revokes the refresh token and clears state.

func (*AirlockAuthClient) RefreshToken

func (c *AirlockAuthClient) RefreshToken(ctx context.Context) (*TokenResponse, error)

RefreshToken refreshes the access token.

func (*AirlockAuthClient) RestoreTokens

func (c *AirlockAuthClient) RestoreTokens(access, refresh string, expiresAt time.Time)

RestoreTokens restores tokens from persistent storage.

type AirlockAuthOptions

type AirlockAuthOptions struct {
	// KeycloakRealmURL is the base URL of the realm (e.g. "https://auth.airlocks.io/realms/airlock").
	KeycloakRealmURL string
	// OIDCClientID is the client ID for device authorization. Defaults to "airlock-integrations".
	OIDCClientID string
	// HTTPClient is an optional custom HTTP client.
	HTTPClient *http.Client
}

AirlockAuthOptions configures the AirlockAuthClient.

type ArtifactSubmitBody

type ArtifactSubmitBody struct {
	ArtifactType string            `json:"artifactType"`
	ArtifactHash string            `json:"artifactHash"`
	Ciphertext   CiphertextRef     `json:"ciphertext"`
	ExpiresAt    string            `json:"expiresAt"`
	Metadata     map[string]string `json:"metadata,omitempty"`
}

ArtifactSubmitBody is the body of an artifact.submit envelope.

type ArtifactSubmitRequest

type ArtifactSubmitRequest struct {
	EnforcerID   string
	ArtifactType string
	ArtifactHash string
	Ciphertext   CiphertextRef
	ExpiresAt    *time.Time
	Metadata     map[string]string
	RequestID    string
}

ArtifactSubmitRequest holds options for building an artifact submission.

type AuthCodeRequest

type AuthCodeRequest struct {
	AuthorizationURL string
	State            string
	CodeVerifier     string
	RedirectURI      string
}

AuthCodeRequest holds the authorization URL and PKCE verifier for apps that manage the browser redirect themselves.

type CiphertextRef

type CiphertextRef struct {
	Alg   string `json:"alg"`
	Data  string `json:"data"`
	Nonce string `json:"nonce,omitempty"`
	Tag   string `json:"tag,omitempty"`
	Aad   string `json:"aad,omitempty"`
}

CiphertextRef is an encrypted payload reference.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an HTTP client for the Airlock Integrations Gateway API. Supports both Bearer token and enforcer app (ClientId/ClientSecret) auth.

func NewClient

func NewClient(baseURL, token string) *Client

NewClient creates a new Airlock Gateway client with Bearer token auth. The baseURL should be the gateway's root URL (e.g., "https://igw.example.com").

func NewClientWithCredentials

func NewClientWithCredentials(baseURL, clientID, clientSecret string) *Client

NewClientWithCredentials creates a new Airlock Gateway client with enforcer app (ClientId/ClientSecret) authentication.

func (*Client) CheckConsent

func (c *Client) CheckConsent() (string, error)

CheckConsent calls GET /v1/consent/status to check if the user has consented to this enforcer app. Returns the consent status string (e.g. "approved"). Throws GatewayError with error_code "app_consent_required", "app_consent_pending", or "app_consent_denied" if consent is not granted.

func (*Client) Echo

func (c *Client) Echo() (*EchoResponse, error)

Echo calls GET /echo for gateway discovery and health.

func (*Client) GetEffectiveDndPolicies

func (c *Client) GetEffectiveDndPolicies(enforcerID, workspaceID, sessionID string) (*DndEffectiveResponse, error)

GetEffectiveDndPolicies calls GET /v1/policy/dnd/effective and returns the effective policies for the given enforcer/workspace/session.

func (*Client) GetExchangeStatus

func (c *Client) GetExchangeStatus(requestID string) (*ExchangeStatusResponse, error)

GetExchangeStatus calls GET /v1/exchanges/{requestId}.

func (*Client) GetPairingStatus

func (c *Client) GetPairingStatus(nonce string) (*PairingStatusResponse, error)

GetPairingStatus calls GET /v1/pairing/{nonce}/status.

func (*Client) InitiatePairing

func (c *Client) InitiatePairing(req PairingInitiateRequest) (*PairingInitiateResponse, error)

InitiatePairing calls POST /v1/pairing/initiate.

func (*Client) RevokePairing

func (c *Client) RevokePairing(routingToken string) (*PairingRevokeResponse, error)

RevokePairing calls POST /v1/pairing/revoke.

func (*Client) SendHeartbeat

func (c *Client) SendHeartbeat(req PresenceHeartbeatRequest) error

SendHeartbeat calls POST /v1/presence/heartbeat.

func (*Client) SetBearerToken

func (c *Client) SetBearerToken(token string) *Client

SetBearerToken sets (or clears) the user Bearer token for dual-auth scenarios.

func (*Client) SubmitArtifact

func (c *Client) SubmitArtifact(req ArtifactSubmitRequest) (string, error)

SubmitArtifact posts an artifact for approval. Returns the request ID.

func (*Client) WaitForDecision

func (c *Client) WaitForDecision(requestID string, timeoutSec int) (*DecisionDeliverEnvelope, error)

WaitForDecision calls GET /v1/exchanges/{requestId}/wait with long-poll. Returns nil, nil if the server returns 204 (no decision yet).

func (*Client) WithHTTPClient

func (c *Client) WithHTTPClient(hc *http.Client) *Client

WithHTTPClient sets a custom http.Client (useful for testing).

func (*Client) WithdrawExchange

func (c *Client) WithdrawExchange(requestID string) error

WithdrawExchange calls POST /v1/exchanges/{requestId}/withdraw.

type ConsentErrorInfo

type ConsentErrorInfo struct {
	Error      string
	Message    string
	ConsentURL string
	AppName    string
	AppID      string
}

ConsentErrorInfo contains details about a required/pending consent.

func ParseConsentError

func ParseConsentError(statusCode int, responseBody []byte) *ConsentErrorInfo

ParseConsentError parses a consent error from an HTTP response payload.

type DecisionDeliverBody

type DecisionDeliverBody struct {
	ArtifactHash string `json:"artifactHash"`
	Decision     string `json:"decision"`
	Reason       string `json:"reason,omitempty"`
	SignerKeyID  string `json:"signerKeyId,omitempty"`
	Nonce        string `json:"nonce,omitempty"`
	Signature    string `json:"signature,omitempty"`
	DecisionHash string `json:"decisionHash,omitempty"`
}

DecisionDeliverBody is the body of a decision.deliver envelope.

func (*DecisionDeliverBody) IsApproved

func (d *DecisionDeliverBody) IsApproved() bool

IsApproved returns true if the decision is "approve".

func (*DecisionDeliverBody) IsRejected

func (d *DecisionDeliverBody) IsRejected() bool

IsRejected returns true if the decision is "reject".

type DecisionDeliverEnvelope

type DecisionDeliverEnvelope struct {
	MsgID     string               `json:"msgId,omitempty"`
	MsgType   string               `json:"msgType"`
	RequestID string               `json:"requestId"`
	Body      *DecisionDeliverBody `json:"body,omitempty"`
}

DecisionDeliverEnvelope wraps a decision.deliver response.

type DeviceCodeInfo

type DeviceCodeInfo struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
	ExpiresIn               int    `json:"expires_in"`
	Interval                int    `json:"interval,omitempty"`
}

DeviceCodeInfo is the response from the device_authorization endpoint.

type DndEffectiveResponse

type DndEffectiveResponse struct {
	MsgType   string      `json:"msgType"`
	RequestID string      `json:"requestId"`
	Body      []DndPolicy `json:"body"`
}

DndEffectiveResponse is the response from GET /v1/policy/dnd/effective.

type DndPolicy

type DndPolicy struct {
	RequestID          string                 `json:"requestId"`
	ObjectType         string                 `json:"objectType"`
	WorkspaceID        string                 `json:"workspaceId"`
	SessionID          string                 `json:"sessionId,omitempty"`
	EnforcerID         string                 `json:"enforcerId"`
	PolicyMode         string                 `json:"policyMode"`
	TargetArtifactType string                 `json:"targetArtifactType,omitempty"`
	ActionSelector     map[string]interface{} `json:"actionSelector,omitempty"`
	SelectorHash       string                 `json:"selectorHash,omitempty"`
	CreatedAt          string                 `json:"createdAt,omitempty"`
	ExpiresAt          string                 `json:"expiresAt"`
}

DndPolicy represents a DND policy object as returned by the gateway.

type EchoResponse

type EchoResponse struct {
	UTC           string `json:"utc"`
	Local         string `json:"local"`
	Timezone      string `json:"timezone"`
	OffsetMinutes int    `json:"offsetMinutes"`
}

EchoResponse is the response from GET /echo.

type ExchangeStatusBody

type ExchangeStatusBody struct {
	RequestID    string      `json:"requestId"`
	State        string      `json:"state"`
	CreatedAt    string      `json:"createdAt,omitempty"`
	ExpiresAt    string      `json:"expiresAt,omitempty"`
	ArtifactHash string      `json:"artifactHash,omitempty"`
	Decision     interface{} `json:"decision,omitempty"`
}

ExchangeStatusBody holds exchange state information.

type ExchangeStatusResponse

type ExchangeStatusResponse struct {
	MsgType   string              `json:"msgType"`
	RequestID string              `json:"requestId"`
	Body      *ExchangeStatusBody `json:"body,omitempty"`
}

ExchangeStatusResponse wraps a GET /v1/exchanges/{id} response.

type GatewayError

type GatewayError struct {
	// HTTP status code.
	StatusCode int
	// Error code from the gateway (e.g., "no_approver", "quota_exceeded").
	ErrorCode string
	// Human-readable error message.
	Message string
	// Raw response body.
	ResponseBody string
	// Request ID associated with the failed operation.
	RequestID string
}

GatewayError represents an error response from the Airlock Gateway.

func (*GatewayError) Error

func (e *GatewayError) Error() string

func (*GatewayError) IsConflict

func (e *GatewayError) IsConflict() bool

IsConflict returns true if the error is an idempotency conflict (409).

func (*GatewayError) IsExpired

func (e *GatewayError) IsExpired() bool

IsExpired returns true if the error indicates an expired resource.

func (*GatewayError) IsPairingRevoked

func (e *GatewayError) IsPairingRevoked() bool

IsPairingRevoked returns true if the pairing was revoked.

func (*GatewayError) IsQuotaExceeded

func (e *GatewayError) IsQuotaExceeded() bool

IsQuotaExceeded returns true if the error is a rate-limit (429) or quota-exceeded response.

type HarpEnvelope

type HarpEnvelope struct {
	MsgID     string      `json:"msgId,omitempty"`
	MsgType   string      `json:"msgType"`
	RequestID string      `json:"requestId"`
	CreatedAt string      `json:"createdAt,omitempty"`
	ExpiresAt string      `json:"expiresAt,omitempty"`
	Sender    *SenderInfo `json:"sender,omitempty"`
	Recipient *RecipInfo  `json:"recipient,omitempty"`
	Body      interface{} `json:"body,omitempty"`
}

HarpEnvelope is the HARP Gateway Wire Envelope.

type OidcDiscoveryResult

type OidcDiscoveryResult struct {
	TokenEndpoint               string `json:"token_endpoint"`
	DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
	RevocationEndpoint          string `json:"revocation_endpoint"`
	AuthorizationEndpoint       string `json:"authorization_endpoint"`
}

OidcDiscoveryResult is the subset of OIDC config we need.

type PairingInitiateRequest

type PairingInitiateRequest struct {
	DeviceID        string `json:"deviceId"`
	EnforcerID      string `json:"enforcerId"`
	GatewayURL      string `json:"gatewayUrl,omitempty"`
	X25519PublicKey string `json:"x25519PublicKey,omitempty"`
	EnforcerLabel   string `json:"enforcerLabel,omitempty"`
	WorkspaceName   string `json:"workspaceName,omitempty"`
}

PairingInitiateRequest is the request body for POST /v1/pairing/initiate.

type PairingInitiateResponse

type PairingInitiateResponse struct {
	PairingNonce string `json:"pairingNonce"`
	PairingCode  string `json:"pairingCode"`
	DeviceID     string `json:"deviceId"`
	GatewayURL   string `json:"gatewayUrl,omitempty"`
	ExpiresAt    string `json:"expiresAt,omitempty"`
}

PairingInitiateResponse is the response from POST /v1/pairing/initiate.

type PairingRevokeResponse

type PairingRevokeResponse struct {
	Status     string `json:"status"`
	EnforcerID string `json:"enforcerId,omitempty"`
}

PairingRevokeResponse is the response from POST /v1/pairing/revoke.

type PairingStatusResponse

type PairingStatusResponse struct {
	PairingNonce string `json:"pairingNonce"`
	State        string `json:"state"`
	ResponseJSON string `json:"responseJson,omitempty"`
	RoutingToken string `json:"routingToken,omitempty"`
	ExpiresAt    string `json:"expiresAt,omitempty"`
}

PairingStatusResponse is the response from GET /v1/pairing/{nonce}/status.

type PresenceHeartbeatRequest

type PresenceHeartbeatRequest struct {
	EnforcerID    string `json:"enforcerId"`
	WorkspaceName string `json:"workspaceName,omitempty"`
	EnforcerLabel string `json:"enforcerLabel,omitempty"`
}

PresenceHeartbeatRequest is the request body for POST /v1/presence/heartbeat.

type RecipInfo

type RecipInfo struct {
	EnforcerID string `json:"enforcerId,omitempty"`
	ApproverID string `json:"approverId,omitempty"`
}

RecipInfo identifies the recipient of a HARP message.

type SenderInfo

type SenderInfo struct {
	EnforcerID string `json:"enforcerId,omitempty"`
	ApproverID string `json:"approverId,omitempty"`
	GatewayID  string `json:"gatewayId,omitempty"`
}

SenderInfo identifies the sender of a HARP message.

type TokenErrorResponse

type TokenErrorResponse struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

TokenErrorResponse is the standard OAuth2 error response.

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	Scope        string `json:"scope"`
}

TokenResponse is the standard OAuth2 token response.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL