Tango.Validation (tango v0.4.0)

Copy Markdown View Source

Comprehensive input validation for the Tango OAuth library.

This module provides validation functions for all user inputs to prevent security vulnerabilities and ensure data integrity.

Summary

Functions

Returns the exact web origin (scheme://host[:port]) of a URL, or nil when it has no scheme and host.

Splits a provider scope string on whitespace or commas.

Returns true when a key name looks like it carries a credential.

Rejects caller authorization parameters owned by the OAuth protocol flow.

Validates authorization code format.

Validates that a completion origin is an exact server-configured web origin.

Sanitizes and validates metadata map for storage.

Validates complete OAuth options map for security.

Validates OAuth provider configuration URLs.

Validates redirect URI when present, allows nil for session creation.

Validates provider slug format.

Validates redirect URI for security.

Validates OAuth scopes list.

Validates session token format.

Validates OAuth state parameter.

Validates tenant ID format and security.

Functions

origin(url)

@spec origin(String.t() | nil) :: String.t() | nil

Returns the exact web origin (scheme://host[:port]) of a URL, or nil when it has no scheme and host.

The default port for the scheme is omitted, matching how browsers report window.location.origin and MessageEvent.origin. Use it to derive a completion_origin from an endpoint URL.

Examples

iex> Tango.Validation.origin("https://app.example.com/api/oauth/callback")
"https://app.example.com"

iex> Tango.Validation.origin("http://localhost:4000/")
"http://localhost:4000"

parse_scopes(scopes)

Splits a provider scope string on whitespace or commas.

GitHub and some others return comma-separated scopes; OAuth2 commonly uses spaces. Empty segments are dropped.

sensitive_key?(key)

@spec sensitive_key?(term()) :: boolean()

Returns true when a key name looks like it carries a credential.

Matches the names Tango never stores or retains (access_token, client_secret, code_verifier, ...) and any name containing a credential-like term such as token, secret, password, authorization, cookie, header, credential, api_key, private_key, signing_key, encryption_key, signature, assertion, jwt, nonce, or a standalone code segment (code, auth_code). Accepts strings and atoms; any other term is not a key name and returns false.

Examples

iex> Tango.Validation.sensitive_key?("id_token")
true

iex> Tango.Validation.sensitive_key?(:client_secret)
true

iex> Tango.Validation.sensitive_key?("instance_url")
false

validate_auth_params(params)

Rejects caller authorization parameters owned by the OAuth protocol flow.

validate_authorization_code(code)

Validates authorization code format.

Examples

iex> Tango.Validation.validate_authorization_code("valid-auth-code-123")
:ok

iex> Tango.Validation.validate_authorization_code("")
{:error, :invalid_authorization_code}

validate_completion_origin(origin)

Validates that a completion origin is an exact server-configured web origin.

validate_metadata(metadata)

Sanitizes and validates metadata map for storage.

Credential keys (access_token, refresh_token, id_token, client_secret, password, api_key, session_token, code_verifier) are removed before validation. Other keys are kept, including ones sensitive_key?/1 matches.

Examples

iex> Tango.Validation.validate_metadata(%{"key" => "value"})
{:ok, %{"key" => "value"}}

iex> Tango.Validation.validate_metadata(%{client_secret: "secret"})
{:ok, %{}}

validate_oauth_options(opts)

Validates complete OAuth options map for security.

Examples

iex> opts = [redirect_uri: "https://app.com/callback", scopes: ["read"]]
iex> Tango.Validation.validate_oauth_options(opts)
{:ok, [redirect_uri: "https://app.com/callback", scopes: ["read"]]}

validate_oauth_url(url)

Validates OAuth provider configuration URLs.

Examples

iex> Tango.Validation.validate_oauth_url("https://github.com/login/oauth/authorize")
:ok

iex> Tango.Validation.validate_oauth_url("javascript:alert('xss')")
{:error, :unsafe_oauth_url}

validate_optional_redirect_uri(uri)

Validates redirect URI when present, allows nil for session creation.

validate_provider_slug(slug)

Validates provider slug format.

Examples

iex> Tango.Validation.validate_provider_slug("github")
:ok

iex> Tango.Validation.validate_provider_slug("invalid slug")
{:error, :invalid_provider_slug}

validate_redirect_uri(uri)

Validates redirect URI for security.

Examples

iex> Tango.Validation.validate_redirect_uri("https://app.com/callback")
:ok

iex> Tango.Validation.validate_redirect_uri("javascript:alert('xss')")
{:error, :unsafe_redirect_uri}

validate_scopes(scopes)

Validates OAuth scopes list.

Examples

iex> Tango.Validation.validate_scopes(["read", "write"])
:ok

iex> Tango.Validation.validate_scopes(["invalid scope with spaces"])
{:error, :invalid_scopes}

validate_session_token(token)

Validates session token format.

Examples

iex> valid_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
iex> Tango.Validation.validate_session_token(valid_token)
:ok

validate_state(state)

Validates OAuth state parameter.

Examples

iex> valid_state = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
iex> Tango.Validation.validate_state(valid_state)
:ok

iex> Tango.Validation.validate_state("short")
{:error, :invalid_state}

validate_tenant_id(tenant_id)

Validates tenant ID format and security.

Examples

iex> Tango.Validation.validate_tenant_id("tenant-123")
:ok

iex> Tango.Validation.validate_tenant_id("")
{:error, :invalid_tenant_id}

iex> Tango.Validation.validate_tenant_id("tenant'; DROP TABLE users; --")
{:error, :invalid_tenant_id}