#!/bin/bash # client-credentials - Obtain an OAuth2 access token via the client_credentials grant # # Usage: # client-credentials [options] # client-credentials -h # # Options: # -e, --endpoint Endpoint type: am or slas (default: am) # -c, --client-id Client ID (overrides J_CLIENT_ID) # -s, --client-secret Client secret (overrides J_CLIENT_SECRET) # -S, --scopes Scopes (optional; overrides J_SCOPES) # -C, --shortcode Shortcode (required for slas; overrides J_SHORTCODE) # -o, --org-id Organization ID (required for slas; overrides J_ORG_ID) # -h, --help Show help message _client_credentials() ( local SCRIPT_NAME; SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" case "${BASH_SOURCE[0]}" in /dev/*|/proc/*) SCRIPT_NAME="" ;; esac case "$SCRIPT_NAME" in ""|bash|sh|zsh|dash) SCRIPT_NAME="client-credentials" ;; esac _error() { echo "[ERR][$SCRIPT_NAME] $*" >&2; } _show_help() { local s; [ -t 1 ] && s=$'\033[4m' local r; [ -t 1 ] && r=$'\033[24m' echo "NAME" echo " $SCRIPT_NAME - obtain an OAuth2 access token via the client_credentials grant" echo "SYNOPSIS" echo " $SCRIPT_NAME [${s}options${r}]" echo " $SCRIPT_NAME -h" echo "DESCRIPTION" echo " Obtains an OAuth2 access token via the client_credentials grant." echo " Supports Salesforce Account Manager (AM) and Shopper Login API" echo " Service (SLAS) endpoints used by Salesforce B2C Commerce Cloud." echo "" echo " AM endpoint:" echo " https://account.demandware.com/dwsso/oauth2/access_token" echo " SLAS endpoint:" echo " https://{shortcode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/{org_id}/oauth2/token" echo "" echo " Prints the access token to stdout. Capture it with command substitution:" echo " TOKEN=\$($SCRIPT_NAME -c \"\$J_CLIENT_ID\" -s \"\$J_CLIENT_SECRET\")" echo "OPTIONS" echo " -e, --endpoint ${s}type${r} Endpoint type: am or slas (default: am)" echo " -c, --client-id ${s}id${r} Client ID (overrides J_CLIENT_ID)" echo " -s, --client-secret ${s}secret${r}" echo " Client secret (overrides J_CLIENT_SECRET)" echo " -S, --scopes ${s}scopes${r} Space-separated scopes (overrides J_SCOPES)" echo " -C, --shortcode ${s}shortcode${r} Shortcode (SLAS; overrides J_SHORTCODE)" echo " -o, --org-id ${s}org_id${r} Organization ID (SLAS; overrides J_ORG_ID)" echo " -h, --help Show this help message" echo "ENVIRONMENT" echo " Input (overridden by the matching flag):" echo " J_ENDPOINT am or slas" echo " J_CLIENT_ID Client ID" echo " J_CLIENT_SECRET Client secret" echo " J_SCOPES Scopes" echo " J_SHORTCODE Shortcode (SLAS)" echo " J_ORG_ID Organization ID (SLAS)" echo "EXIT STATUS" echo " 0 Success" echo " 1 Invalid endpoint or request failure" echo " 2 Usage / missing required parameter" echo "DEPENDENCIES" echo " curl, jq, base64, date" } _expand_short_opts() { # $1 = string of short-opt letters that take a value (e.g. "nXHd"); "" for flag-only scripts # $2..$N = "$@" # Populates _EXPANDED; caller does: set -- "${_EXPANDED[@]}"; unset _EXPANDED local value_opts="$1"; shift _EXPANDED=() local passthru="" local arg local rest local c for arg in "$@"; do if [ -n "$passthru" ]; then _EXPANDED+=("$arg"); continue; fi case "$arg" in --) passthru=1; _EXPANDED+=("$arg") ;; --*|-|"") _EXPANDED+=("$arg") ;; -[a-zA-Z]?*) rest="${arg#-}" while [ -n "$rest" ]; do c="${rest%"${rest#?}"}"; rest="${rest#?}" _EXPANDED+=("-$c") case "$value_opts" in *"$c"*) [ -n "$rest" ] && _EXPANDED+=("$rest") rest="" ;; esac done ;; *) _EXPANDED+=("$arg") ;; esac done } # Initialize from environment or defaults local endpoint="${J_ENDPOINT:-am}" local client_id="${J_CLIENT_ID:-}" local client_secret="${J_CLIENT_SECRET:-}" local scopes="${J_SCOPES:-}" local shortcode="${J_SHORTCODE:-}" local org_id="${J_ORG_ID:-}" _expand_short_opts "ecsSCo" "$@" set -- "${_EXPANDED[@]}"; unset _EXPANDED while [ "$#" -gt 0 ]; do case $1 in -e|--endpoint) [ -n "${2-}" ] || { _error "-e|--endpoint requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } endpoint="$2"; shift 2 ;; --endpoint=*) endpoint="${1#*=}" [ -n "$endpoint" ] || { _error "-e|--endpoint requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift ;; -c|--client|--client-id) [ -n "${2-}" ] || { _error "-c|--client-id requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } client_id="$2"; shift 2 ;; --client=*|--client-id=*) client_id="${1#*=}" [ -n "$client_id" ] || { _error "-c|--client-id requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift ;; -s|--secret|--client-secret) [ -n "${2-}" ] || { _error "-s|--client-secret requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } client_secret="$2"; shift 2 ;; --secret=*|--client-secret=*) client_secret="${1#*=}" [ -n "$client_secret" ] || { _error "-s|--client-secret requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift ;; -S|--scopes|--scope) # -S may be passed "" to explicitly clear J_SCOPES; no empty-value guard scopes="$2"; shift 2 ;; --scopes=*|--scope=*) # --scopes= sets scopes="" intentionally; no empty-value guard scopes="${1#*=}"; shift ;; -C|--shortcode) [ -n "${2-}" ] || { _error "-C|--shortcode requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shortcode="$2"; shift 2 ;; --shortcode=*) shortcode="${1#*=}" [ -n "$shortcode" ] || { _error "-C|--shortcode requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift ;; -o|--org-id) [ -n "${2-}" ] || { _error "-o|--org-id requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } org_id="$2"; shift 2 ;; --org-id=*) org_id="${1#*=}" [ -n "$org_id" ] || { _error "-o|--org-id requires a value. Run \`$SCRIPT_NAME -h\` for usage"; return 2; } shift ;; -h|--help) _show_help; return 0 ;; *) _error "Unknown argument '$1'. Run \`$SCRIPT_NAME -h\` for usage"; return 1 ;; esac done # Validate endpoint and required parameters before any network call # Exit code 2 is reserved for usage errors so callers can distinguish them # from runtime/request failures (exit 1) case "$endpoint" in am) if [ -z "$client_id" ]; then _error "client_id is required (use -c or J_CLIENT_ID). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi if [ -z "$client_secret" ]; then _error "client_secret is required (use -s or J_CLIENT_SECRET). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi ;; slas) if [ -z "$client_id" ]; then _error "client_id is required (use -c or J_CLIENT_ID). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi if [ -z "$client_secret" ]; then _error "client_secret is required (use -s or J_CLIENT_SECRET). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi if [ -z "$shortcode" ]; then _error "shortcode is required for slas (use -C or J_SHORTCODE). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi if [ -z "$org_id" ]; then _error "org_id is required for slas (use -o or J_ORG_ID). Run \`$SCRIPT_NAME -h\` for usage" return 2 fi ;; *) _error "Invalid endpoint type: '$endpoint'. Run \`$SCRIPT_NAME -h\` for usage" return 1 ;; esac # Build token URL local token_url if [ "$endpoint" = "am" ]; then token_url="https://account.demandware.com/dwsso/oauth2/access_token" else token_url="https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/${org_id}/oauth2/token" fi # Prepare auth header and request body local auth_header; auth_header="$(printf '%s:%s' "$client_id" "$client_secret" | base64)" local body="grant_type=client_credentials" if [ "$scopes" ]; then body="${body}&scope=${scopes}" fi # Request the token local response; response="$(curl -s -X POST "$token_url" \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Authorization: Basic $auth_header" -d "$body")" local access_token; access_token="$(jq -r .access_token <<< "$response")" printf '%s' "$access_token" ) _client_credentials "$@" __client_credentials_rc=$? unset -f _client_credentials if [ -n "${BASH_SOURCE[0]}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then eval "unset __client_credentials_rc; return $__client_credentials_rc" fi eval "unset __client_credentials_rc; exit $__client_credentials_rc"