NAME

VPNDetection::Oauth - sign a person in with the OAuth device flow

SYNOPSIS

my $client = VPNDetection->new;

my $device = $client->oauth->device_authorization('your-client-id',
    scope => 'account.read apikeys.read apikeys.reveal');
print "Open $device->{verification_uri} and enter $device->{user_code}\n";

my $token = $client->oauth->poll_device_token('your-client-id', $device);
my $keyed = VPNDetection->new(api_key => $token->{apikey});

DESCRIPTION

A program running on the person's own machine lets them sign in with a browser and pick one of their API keys, instead of asking them to paste it. Reached through "oauth" in VPNDetection.

Every method takes a client ID, issued on request from support@vpndetection.io. None of these requests carries the client's API key, and none needs one. Every method takes a per-call timeout in seconds, bounding each request it sends, and has a _p twin returning a Mojo::Promise.

Answers are hash references keyed by the wire names. A member the server did not send has no key at all, and an empty scope is present.

A refusal dies with a VPNDetection::OauthError; anything else, a transport failure or a server error, with the ordinary VPNDetection::Error.

METHODS

metadata(%options)

The authorization server's discovery document: issuer, authorization_endpoint and token_endpoint, plus whichever of device_authorization_endpoint, revocation_endpoint, scopes_supported, response_types_supported, grant_types_supported, code_challenge_methods_supported, token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported and service_documentation it carries.

device_authorization($client_id, %options)

Starts a device sign-in. scope is space-delimited and sent as given, and resource names the API the tokens are for; both are left out when not given. Returns device_code, user_code, verification_uri, expires_in and interval, and verification_uri_complete when the server sends it. Show the person user_code and verification_uri, then pass the hash to poll_device_token.

exchange_device_code($client_id, $device_code, %options)

Asks once whether the person approved. Until they do, it dies with an VPNDetection::OauthError coded authorization_pending. Never retried.

exchange_refresh_token($client_id, $refresh_token, %options)

Trades a refresh token for a new pair. The one presented is spent, so keep the refresh_token each call returns. A refresh names the key the person picked (apikey_id) but never reveals it again (apikey). Never retried.

Both exchanges return access_token, token_type and expires_in, and whichever of refresh_token, scope, apikey_id and apikey the server sent. apikey_id without apikey is normal: the key itself also needs a sign-in rather than a refresh, and a key whose secret can be shown again.

revoke($client_id, $token, %options)

Ends a token. A refresh token ends the whole sign-in and every token it issued, which is how a program signs the machine out; an access token ends only itself.

poll_device_token($client_id, $device, %options)

Waits for the person to approve, and returns the tokens. It waits interval seconds (5 when that is below 1) before EVERY request, the first included, and 5 more for the rest of the call each time the server answers slow_down. It ends at the first answer that is neither: a denial dies with VPNDetection::OauthAccessDeniedError, a code that ran out with VPNDetection::OauthExpiredTokenError - as does outliving expires_in, counted from this call, with no status - and any other failure as it came. The timeout bounds each request, never the poll.

There is no cancellation handle: the blocking form returns only at one of those outcomes. The waits are event-loop timers, so poll_device_token_p shares a running Mojo::IOLoop with everything else on it.