Files
puregym-unofficial-dotnet/PureGym/Auth.fs
Smaug123 77ceafde0b
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/all-checks-complete Pipeline was successful
Add visits info
2023-11-01 16:43:33 +00:00

103 lines
3.7 KiB
Forth

namespace PureGym
open System
open System.Collections.Generic
open System.Net.Http
open System.Text.Json
open System.Text.Json.Serialization
open System.Threading.Tasks
// System.Text.Json does not support internal F# records as of .NET 8, presumably because it can't find the constructor.
// So we end up with this gruesome soup of C#.
// TODO(net8): make this internal
/// An internal type. Don't use it.
type AuthResponseRaw [<JsonConstructor>] (access_token : string, expires_in : int, token_type : string, scope : string)
=
/// Don't use this internal type.
member _.access_token = access_token
/// Don't use this internal type.
member _.expires_in = expires_in
/// Don't use this internal type.
member _.token_type = token_type
/// Don't use this internal type.
member _.scope = scope
/// A token which can be used to authenticate with the PureGym API.
type AuthToken =
{
/// A string which can be passed as the "Bearer" in an Authorization header
AccessToken : string
/// Time that this token expires, if known
ExpiryTime : DateTime option
}
static member internal Parse (response : AuthResponseRaw) : AuthToken =
if response.scope <> "pgcapi" then
failwithf $"unexpected scope: %s{response.scope}"
if response.token_type <> "Bearer" then
failwithf $"unexpected type: %s{response.token_type}"
{
AccessToken = response.access_token
ExpiryTime = Some (DateTime.Now + TimeSpan.FromSeconds response.expires_in)
}
/// Authentication credentials as known to a human user
type UsernamePin =
{
/// The user's email address
Username : string
/// Eight-digit PIN
Pin : string
}
/// Any way to authenticate with the PureGym API.
type Auth =
/// Authenticate with credentials which are known to the user
| User of UsernamePin
/// An AuthToken (that is, a bearer token)
| Token of AuthToken
/// Methods for constructing PureGym authentication tokens.
[<RequireQualifiedAccess>]
module AuthToken =
/// Construct an AuthToken given that you already have a bearer token.
let ofBearerToken (token : string) : AuthToken =
{
AccessToken = token
ExpiryTime = None
}
let private options = JsonSerializerOptions (IncludeFields = true)
/// Get an AuthToken for the given user email address with the given eight-digit PureGym PIN.
let get (creds : UsernamePin) : Task<AuthToken> =
task {
use client = new HttpClient ()
client.BaseAddress <- Uri "https://auth.puregym.com"
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
let request =
[
"grant_type", "password"
"username", creds.Username
"password", creds.Pin
"scope", "pgcapi"
"client_id", "ro.client"
]
|> List.map KeyValuePair
use content = new FormUrlEncodedContent (request)
let! response = client.PostAsync (Uri "https://auth.puregym.com/connect/token", content)
if response.IsSuccessStatusCode then
let! content = response.Content.ReadAsStreamAsync ()
let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
// let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
return AuthToken.Parse response
else
let! content = response.Content.ReadAsStringAsync ()
return failwithf $"bad status code: %+A{response.StatusCode}\n%s{content}"
}