~/bend-docscommunity

oauth2.bend relies on unsafe/foreign

raw source on the hub · import bend-kit-oauth2@0.3.0.0/oauth2.bend as Oauth2

OAuth 2.0 client credentials, refresh, and authorization code with PKCE over Hairpin. Source: https://github.com/paymog/bend-kit/tree/main/oauth2

9 imports
import Base
import bend-kit-crypto@0.1.1.0/crypto.bend as Crypto
import bend-kit-hairpin@0.3.0.0/hairpin.bend as Hairpin
import bend-kit-http@0.32.0.0/http.bend as Http
import bend-kit-time@0.1.2.1/time.bend as Time
import 0x1f2d80f53f971b16c6de6a65cb1918ae/url.bend as Url
import 0x584fc27920487ceab242392391418d7f/json.bend as Json
import 0xcfc8be7b076f41f95c8e118383892d55/encoding.bend as Enc
import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes

Types

type Config source · line 16 · raw

Data

RFC 6749 and RFC 7636. Text is a String of code points; form values go out as UTF-8. token: the token endpoint URL. secret: "" for a public client, which sends client_id in the body; a confidential client authenticates with HTTP Basic (RFC 6749 §2.3.1).

type Token source · line 21 · raw

Data

kind: token_type, as "Bearer". refresh: "" for none. scope: "" when the server did not say. expires: when the access token lapses, counted from just before the request; None without expires_in.

type Err source · line 28 · raw

Data

ErrNet: the request did not complete. ErrOAuth: an RFC 6749 §5.2 error with its status, or a §4.1.2.1 error in a callback, status 0. ErrReply: not a Bearer token response, with its status and text. ErrState: the callback's state is not ours (§10.12). ErrCrypto: the random source or SHA-256 failed. ErrExpired: the token lapsed and has no refresh token.

type Got source · line 91 · raw

Data

Definitions

def b64url.ch source · line 38 · raw

@+c:U32 -> @t:String -> String

def b64url.go source · line 49 · raw

@s:String -> String

def b64url source · line 57 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> String

Base64url without padding (RFC 4648 §5, RFC 7636 Appendix A).

def utf8 source · line 62 · raw

@+s:String -> String

def field source · line 65 · raw

@m:Map<&2, String> -> @k:String -> @+v:String -> Map<&2, String>

def scoped source · line 68 · raw

@+scope:String -> @+m:Map<&2, String> -> Map<&2, String>

def form source · line 71 · raw

@+k:String -> @+v:String -> Map<&2, String>

def basic source · line 75 · raw

@+id:String -> @+secret:String -> String

RFC 6749 §2.3.1: id and secret are form-encoded before base64.

def auth.headers source · line 78 · raw

@+id:String -> @+secret:String -> Map<&2, List<&2, String>>

def auth.body source · line 82 · raw

@+id:String -> @+secret:String -> @+m:Map<&2, String> -> Map<&2, String>

def body source · line 86 · raw

@+id:String -> @+secret:String -> @m:Map<&2, String> -> String

The form body of a token request from cfg's client.

def got.put source · line 94 · raw

@g:Got -> @+k:String -> @+v:String -> Got

def got.text source · line 102 · raw

@v:0x584fc27920487ceab242392391418d7f/json.Val -> String

A string's text, or a number's, as some servers send expires_in as "3600".

def got.go source · line 111 · raw

@kvs:List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)> -> @g:Got -> Got

def got.obj source · line 118 · raw

@v:0x584fc27920487ceab242392391418d7f/json.Val -> Maybe<&2, Got>

def got source · line 125 · raw

@m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Maybe<&2, Got>

def expiry source · line 132 · raw

@now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @m:Maybe<&2, U32> -> Maybe<&2, 0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant>

def good source · line 140 · raw

@+status:U32 -> @g:Got -> Bool

A 2xx with an access token of type Bearer (RFC 6750 §4, case-insensitive), the only kind request sends.

def token.pick source · line 145 · raw

@ok:Bool -> @+status:U32 -> @+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @g:Got -> Result<&1, &1, Err, Token>

§6: a refresh reply without refresh_token keeps the old one. b: the body, decoded only for an error.

def token.of source · line 154 · raw

@+status:U32 -> @+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @m:Maybe<&2, Got> -> Result<&1, &1, Err, Token>

def parse.copy source · line 161 · raw

@+status:U32 -> @+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> Result<&1, &1, Err, Token>

def parse source · line 166 · raw

@+status:U32 -> @+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Token>

A token endpoint's reply at status, requested at now. old: the refresh token to keep when none comes back.

def reply source · line 169 · raw

@+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @r:Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Result<&1, &1, Err, Token>

def grant.done source · line 177 · raw

@+old:String -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>)

def grant source · line 183 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @m:Map<&2, String> -> @+old:String -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

POST m to the token endpoint. Hairpin sends a POST once.

def client_credentials source · line 194 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+scope:String -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

RFC 6749 §4.4. scope: "" for the server's default.

def code source · line 198 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+code:String -> @+redirect:String -> @+verifier:String -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

RFC 6749 §4.1.3 with RFC 7636 §4.5: the code from callback, the same redirect_uri, and the verifier.

def refresh.go source · line 202 · raw

@none:Bool -> @h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+r:String -> @+scope:String -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

def refresh source · line 210 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @tok:Token -> @+scope:String -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

RFC 6749 §6. scope: "" keeps the granted scope. ErrExpired when tok has no refresh token.

def expiring.at source · line 214 · raw

@e:Maybe<&2, 0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant> -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @+skew:U32 -> Bool

def expiring source · line 222 · raw

@tok:Token -> @now:0xe00bbf72c3ff9dc6d7b077524522b3a8/time.Instant -> @+skew:U32 -> Bool

Does tok lapse within skew seconds of now? A token without expires_in never does.

def fresh.go source · line 226 · raw

@due:Bool -> @h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+tok:Token -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

def fresh source · line 234 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+tok:Token -> @+skew:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

tok, or a refreshed one when it lapses within skew seconds.

def fresh.cc.go source · line 239 · raw

@due:Bool -> @h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+scope:String -> @+tok:Token -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

def fresh.cc source · line 247 · raw

@h:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @cfg:Config -> @+scope:String -> @+tok:Token -> @+skew:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, Err, Token>))

tok, or a new client-credentials token when it lapses within skew seconds (§4.4.3: no refresh token).

def random.of source · line 254 · raw

@r:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> Result<&1, &1, Err, String>

def random source · line 262 · raw

@+n:U32 -> IO(Result<&1, &1, Err, String>)

n octets from the OS secure random source, as base64url.

def verifier source · line 268 · raw

IO(Result<&1, &1, Err, String>)

§4.1: 32 random octets, 43 unreserved chars.

def state source · line 272 · raw

IO(Result<&1, &1, Err, String>)

An unguessable state for the authorization request (RFC 6749 §10.12).

def challenge.words source · line 275 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, String>)

def challenge source · line 282 · raw

@+verifier:String -> IO(Result<&1, &1, Err, String>)

§4.2 S256: BASE64URL(SHA256(ASCII(verifier))).

def qmark source · line 285 · raw

@s:String -> Bool

def authorize.url source · line 293 · raw

@cfg:Config -> @+endpoint:String -> @+redirect:String -> @+scope:String -> @+state:String -> @+challenge:String -> String

RFC 6749 §4.1.1 with RFC 7636 §4.3: the URL to send the user to. endpoint may carry its own query.

def param.of source · line 299 · raw

@r:Pair(Map<&2, String>, String) -> String

def param source · line 303 · raw

@+m:Map<&2, String> -> @k:String -> String

def callback.pick source · line 306 · raw

@+err:String -> @desc:String -> @+got:String -> @+code:String -> @+state:String -> Result<&1, &1, Err, String>

def callback.m source · line 311 · raw

@+state:String -> @m:Maybe<&2, 0x1f2d80f53f971b16c6de6a65cb1918ae/url.Url> -> Result<&1, &1, Err, String>

def callback.frag source · line 318 · raw

@+state:String -> @r:Pair(String, String) -> Result<&1, &1, Err, String>

def callback.q source · line 322 · raw

@+state:String -> @r:Pair(String, String) -> Result<&1, &1, Err, String>

def callback source · line 327 · raw

@url:String -> @+state:String -> Result<&1, &1, Err, String>

§4.1.2: the code in the redirect to redirect_uri, once its state matches ours; the server's error if it sent one.

def bearer source · line 332 · raw

@tok:Token -> @h:Map<&2, List<&2, String>> -> Map<&2, List<&2, String>>

def request source · line 337 · raw

@c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @tok:Token -> @+method:String -> @url:String -> @headers:Map<&2, List<&2, String>> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>))

Hairpin.request with Authorization: Bearer <access>, over any authorization header in headers.