Sheetshow.UserAccount (Sheetshow v0.1.0)

Copy Markdown View Source

A person's own Google account, as a value: the app you registered, and the refresh token they gave it.

The sibling of Sheetshow.ServiceAccount, and a client takes either. A service account is a robot with a private key and its own email address, which a spreadsheet has to be shared with; a user account acts as the person who signed in, and sees the spreadsheets they see. Both are credentials Sheetshow.connect/1 turns into a Sheetshow.Token, and nothing downstream of that knows the difference.

iex> account = Sheetshow.UserAccount.new("123.apps.googleusercontent.com", "secret")
iex> Sheetshow.UserAccount.authorized?(account)
false

It has two stages, which is why refresh_token may be nil. A new account is the app's own credentials and nothing else, which is enough to send somebody to a consent screen, which is Sheetshow.OAuth's half of the job. What comes back from Sheetshow.OAuth.authorize/3 is the same account with a refresh token on it, and that is the thing worth keeping.

That pair of stages is not an invention: it is exactly the shape of the authorized_user JSON that gcloud writes, so from_file/1 reads a file gcloud auth application-default login made, and to_json/1 writes one anything else that reads the format will take.

Inspecting an account shows neither the secret nor the refresh token.

Summary

Functions

Whether this account can get a token on its own, that is, whether somebody has been through the consent screen for it.

Reads an authorized_user credential file. The path is yours to supply, and nothing about it is remembered.

Same as from_file/1, raising on failure.

Reads the JSON of an authorized_user credential.

Same as from_json/1, raising on failure.

An account from the client id and secret of an app you registered in the Google Cloud console.

The account as authorized_user JSON, to write somewhere only you can read. Where that is, and how it is protected, is your application's business.

The token request as data, where to post and what to post, for whichever HTTP client is doing the talking. An account with no refresh token has nothing to ask with, and says so.

Types

t()

@type t() :: %Sheetshow.UserAccount{
  client_id: String.t(),
  client_secret: String.t(),
  refresh_token: String.t() | nil,
  token_uri: String.t()
}

Functions

authorized?(user_account)

@spec authorized?(t()) :: boolean()

Whether this account can get a token on its own, that is, whether somebody has been through the consent screen for it.

iex> Sheetshow.UserAccount.new("123", "s") |> Sheetshow.UserAccount.authorized?()
false

from_file(path)

@spec from_file(Path.t()) :: {:ok, t()} | {:error, Sheetshow.Error.t()}

Reads an authorized_user credential file. The path is yours to supply, and nothing about it is remembered.

from_file!(path)

@spec from_file!(Path.t()) :: t()

Same as from_file/1, raising on failure.

from_json(json)

@spec from_json(String.t()) :: {:ok, t()} | {:error, Sheetshow.Error.t()}

Reads the JSON of an authorized_user credential.

iex> json = ~s({"type":"authorized_user","client_id":"123","client_secret":"s","refresh_token":"1//r"})
iex> {:ok, account} = Sheetshow.UserAccount.from_json(json)
iex> account.client_id
"123"

iex> {:error, %Sheetshow.Error{reason: :invalid_credentials}} =
...>   Sheetshow.UserAccount.from_json(~s({"type": "service_account"}))

from_json!(json)

@spec from_json!(String.t()) :: t()

Same as from_json/1, raising on failure.

new(client_id, client_secret, opts \\ [])

@spec new(String.t(), String.t(), keyword()) :: t()

An account from the client id and secret of an app you registered in the Google Cloud console.

Options: :refresh_token, when you already hold one, and :token_uri.

iex> account = Sheetshow.UserAccount.new("123.apps.googleusercontent.com", "s", refresh_token: "1//r")
iex> Sheetshow.UserAccount.authorized?(account)
true

to_json(account)

@spec to_json(t()) :: String.t()

The account as authorized_user JSON, to write somewhere only you can read. Where that is, and how it is protected, is your application's business.

iex> Sheetshow.UserAccount.new("123", "s", refresh_token: "1//r")
...> |> Sheetshow.UserAccount.to_json()
...> |> JSON.decode!()
...> |> Map.get("type")
"authorized_user"

token_request(account)

@spec token_request(t()) ::
  {:ok, %{url: String.t(), form: map()}} | {:error, Sheetshow.Error.t()}

The token request as data, where to post and what to post, for whichever HTTP client is doing the talking. An account with no refresh token has nothing to ask with, and says so.

Sheetshow.UserAccount.token_request(account)
{:ok, %{
  url: "https://oauth2.googleapis.com/token",
  form: %{grant_type: "refresh_token", refresh_token: "1//r", client_id: "123", client_secret: "s"}
}}