-
Notifications
You must be signed in to change notification settings - Fork 229
/
login.go
138 lines (120 loc) · 3.37 KB
/
login.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Package login handles login and authentication with Encore's platform.
package login
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"net"
"net/http"
"encr.dev/cli/internal/platform"
"encr.dev/internal/conf"
)
// Flow keeps the state of an ongoing login flow.
type Flow struct {
URL string // Local URL the flow is listening on
LoginCh chan *conf.Config // Successful logins are sent on this
state string
challenge string
pubKey, privKey string
srv *http.Server
ln net.Listener
}
// Begin begins a new login attempt.
func Begin() (f *Flow, err error) {
// Generate initial request state
state, err1 := genRandData()
challenge, err2 := genRandData()
if err1 != nil || err2 != nil {
return nil, fmt.Errorf("could not generate random data: %v/%v", err1, err2)
}
challengeHash := sha256.Sum256([]byte(challenge))
encodedChallenge := base64.RawURLEncoding.EncodeToString(challengeHash[:])
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
defer func() {
if err != nil {
ln.Close()
}
}()
addr := ln.Addr().(*net.TCPAddr)
url := fmt.Sprintf("http://localhost:%d/oauth", addr.Port)
req := &platform.CreateOAuthSessionParams{
Challenge: encodedChallenge,
State: state,
RedirectURL: url,
}
authURL, err := platform.CreateOAuthSession(context.Background(), req)
if err != nil {
return nil, err
}
flow := &Flow{
URL: authURL,
LoginCh: make(chan *conf.Config),
state: state,
challenge: challenge,
}
flow.srv = &http.Server{Handler: http.HandlerFunc(flow.oauthHandler)}
go flow.srv.Serve(ln)
return flow, nil
}
// Close closes the login flow.
func (f *Flow) Close() {
f.srv.Close()
}
func (f *Flow) oauthHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/oauth" {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
code := req.FormValue("code")
reqState := req.FormValue("state")
if code == "" || reqState != f.state {
http.Error(w, "Bad Request (bad code or state)", http.StatusBadRequest)
return
}
params := &platform.ExchangeOAuthTokenParams{
Challenge: f.challenge,
Code: code,
}
resp, err := platform.ExchangeOAuthToken(req.Context(), params)
if err != nil {
http.Error(w, "Could not exchange token: "+err.Error(), http.StatusBadGateway)
return
} else if resp.Token == nil {
http.Error(w, "Invalid response: missing token", http.StatusBadGateway)
return
}
conf := &conf.Config{Token: *resp.Token, Actor: resp.Actor, Email: resp.Email, AppSlug: resp.AppSlug}
select {
case f.LoginCh <- conf:
http.Redirect(w, req, "https://www.encore.dev/auth/success", http.StatusFound)
default:
http.Error(w, "Unexpected request", http.StatusBadRequest)
}
}
func WithAuthKey(authKey string) (*conf.Config, error) {
params := &platform.ExchangeAuthKeyParams{
AuthKey: authKey,
}
resp, err := platform.ExchangeAuthKey(context.Background(), params)
if err != nil {
return nil, err
} else if resp.Token == nil {
return nil, fmt.Errorf("invalid response: missing token")
}
tok := resp.Token
conf := &conf.Config{Token: *tok, Actor: resp.Actor, AppSlug: resp.AppSlug}
return conf, nil
}
func genRandData() (string, error) {
data := make([]byte, 32)
_, err := rand.Read(data[:])
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(data), nil
}