forked from jsgoecke/tesla
-
Notifications
You must be signed in to change notification settings - Fork 18
/
auth_option.go
117 lines (98 loc) · 2.83 KB
/
auth_option.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package tesla
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"golang.org/x/oauth2"
)
// github.com/uhthomas/tesla
func state() string {
var b [9]byte
if _, err := io.ReadFull(rand.Reader, b[:]); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b[:])
}
// https://www.oauth.com/oauth2-servers/pkce/
func pkce() (verifier, challenge string, err error) {
var p [87]byte
if _, err := io.ReadFull(rand.Reader, p[:]); err != nil {
return "", "", fmt.Errorf("rand read full: %w", err)
}
verifier = base64.RawURLEncoding.EncodeToString(p[:])
b := sha256.Sum256([]byte(verifier))
challenge = base64.RawURLEncoding.EncodeToString(b[:])
return verifier, challenge, nil
}
type authHandler struct {
auth *auth
username string
password string
}
func (c *authHandler) login(ctx context.Context, oc *oauth2.Config) (*oauth2.Token, error) {
verifier, challenge, err := pkce()
if err != nil {
return nil, err
}
c.auth.AuthURL = oc.AuthCodeURL(state(), oauth2.AccessTypeOffline,
oauth2.SetAuthURLParam("code_challenge", challenge),
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
)
code, err := c.auth.Do(ctx, c.username, c.password)
if err != nil {
return nil, err
}
token, err := oc.Exchange(ctx, code,
oauth2.SetAuthURLParam("code_verifier", verifier),
)
return token, err
}
func defaultHandler() *authHandler {
return &authHandler{
auth: &auth{
SelectDevice: mfaUnsupported,
SolveCaptcha: captchaUnsupported,
},
}
}
// WithMFAHandler allows a consumer to provide a different configuration from the default.
func WithMFAHandler(handler func(context.Context, []Device) (Device, string, error)) ClientOption {
return func(c *Client) error {
if c.authHandler == nil {
c.authHandler = defaultHandler()
}
c.authHandler.auth.SelectDevice = handler
return nil
}
}
func mfaUnsupported(_ context.Context, _ []Device) (Device, string, error) {
return Device{}, "", errors.New("multi factor authentication is not supported")
}
// WithCaptchaHandler allows a consumer to provide a different configuration from the default.
func WithCaptchaHandler(handler func(context.Context, io.Reader) (string, error)) ClientOption {
return func(c *Client) error {
if c.authHandler == nil {
c.authHandler = defaultHandler()
}
c.authHandler.auth.SolveCaptcha = handler
return nil
}
}
func captchaUnsupported(_ context.Context, _ io.Reader) (string, error) {
return "", errors.New("captcha solving is not supported")
}
// WithCredentials allows a consumer to provide a different configuration from the default.
func WithCredentials(username, password string) ClientOption {
return func(c *Client) error {
if c.authHandler == nil {
c.authHandler = defaultHandler()
}
c.authHandler.username = username
c.authHandler.password = password
return nil
}
}