This repository has been archived by the owner on Oct 28, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
weblogin.go
189 lines (160 loc) · 5.45 KB
/
weblogin.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package weblogin
import (
"crypto/aes"
"crypto/rand"
"encoding/hex"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/antonlindstrom/pgstore"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
"golang.org/x/oauth2"
"github.com/riking/marvin"
"github.com/riking/marvin/intra/cdnproxy"
"github.com/riking/marvin/slack"
)
type API interface {
marvin.Module
// GetCurrentUser gets the current User object for the request's cookies.
// If there is no logged in user, this method returns (nil, nil).
// An error is returned only in case of corrupt cookie data.
GetCurrentUser(w http.ResponseWriter, r *http.Request) (*User, error)
// GetUserBySlack gets the User object for the given Slack user.
// If no associated Slack account is found, ErrNoSuchUser is returned.
GetUserBySlack(slackID slack.UserID) (*User, error)
// GetUserByIntra gets the User object for the given Intra username.
// If no associated Intra account is found, ErrNoSuchUser is returned.
GetUserByIntra(login string) (*User, error)
// StartSlackURL returns the URL to redirect to to start Slack authentication.
StartSlackURL(returnURL string, extraScopes ...string) string
// StartSlackURL returns the URL to redirect to to start Intra authentication.
StartIntraURL(returnURL string, extraScopes ...string) string
// HTTPError renders a formatted error page.
HTTPError(w http.ResponseWriter, r *http.Request, err error)
}
var _ API = &WebLoginModule{}
// ---
func init() {
marvin.RegisterModule(NewWebLoginModule)
}
const Identifier = "weblogin"
type WebLoginModule struct {
team marvin.Team
slackOAuthConfig oauth2.Config
IntraOAuthConfig oauth2.Config
store sessions.Store
authTokenMap map[string]authNonceValue
authTokenLock sync.Mutex
}
func NewWebLoginModule(t marvin.Team) marvin.Module {
mod := &WebLoginModule{
team: t,
slackOAuthConfig: oauth2.Config{
ClientID: t.TeamConfig().ClientID,
ClientSecret: t.TeamConfig().ClientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: "https://slack.com/oauth/authorize",
TokenURL: "https://slack.com/api/oauth.access",
},
RedirectURL: t.AbsoluteURL("/oauth/slack/callback"),
Scopes: []string{"BOGUS_VALUE"},
},
IntraOAuthConfig: oauth2.Config{
ClientID: t.TeamConfig().IntraUID,
ClientSecret: t.TeamConfig().IntraSecret,
Endpoint: oauth2.Endpoint{
AuthURL: "https://api.intra.42.fr/oauth/authorize",
TokenURL: "https://api.intra.42.fr/oauth/token",
},
RedirectURL: t.AbsoluteURL("/oauth/intra/callback"),
Scopes: []string{},
},
store: nil,
authTokenMap: make(map[string]authNonceValue),
}
return mod
}
func (mod *WebLoginModule) Identifier() marvin.ModuleID {
return Identifier
}
func (mod *WebLoginModule) Load(t marvin.Team) {
var signKey [32]byte
var encKey [aes.BlockSize]byte
_, err := t.TeamConfig().GetSecretKey("session sign key", signKey[:])
if err != nil {
panic("could not read from kdf")
}
_, err = t.TeamConfig().GetSecretKey("session encrypt key", encKey[:])
if err != nil {
panic("could not read from kdf")
}
// load old style key - because it's second, won't be used for any new operations
var oldKey []byte
oldKey, err = hex.DecodeString(t.TeamConfig().CookieSecretKey)
if err != nil || len(oldKey) != aes.BlockSize {
// create phony alternate key
oldKey = make([]byte, aes.BlockSize)
rand.Read(oldKey)
}
store, err := pgstore.NewPGStoreFromPool(
t.DB().DB,
signKey[:], encKey[:],
oldKey, oldKey,
)
if err != nil {
panic(errors.Wrap(err, "Could not setup session store"))
}
uri, err := url.Parse(t.TeamConfig().HTTPURL)
if err != nil {
panic(err)
}
store.Options.HttpOnly = true
store.Options.Domain = uri.Host
if uri.Path != "" {
store.Options.Path = uri.Path
}
if strings.HasPrefix(t.TeamConfig().HTTPURL, "https") {
store.Options.Secure = true
}
mod.store = store
mod.team.DB().MustMigrate(Identifier, 1482049049, sqlMigrateUser1, sqlMigrateUser2, sqlMigrateUser3)
mod.team.DB().SyntaxCheck(
sqlLoadUser,
sqlNewUser,
sqlUpdateIntra,
sqlUpdateSlack,
sqlLookupUserBySlack,
sqlLookupUserByIntra,
)
}
func (mod *WebLoginModule) Enable(team marvin.Team) {
team.Router().HandleFunc("/oauth/slack/start", mod.OAuthSlackStart)
team.Router().HandleFunc("/oauth/slack/callback", mod.OAuthSlackCallback)
team.Router().HandleFunc("/oauth/altslack/start", mod.OAuthAltSlackStart)
team.Router().HandleFunc("/oauth/intra/start", mod.OAuthIntraStart)
team.Router().HandleFunc("/oauth/intra/callback", mod.OAuthIntraCallback)
team.Router().HandleFunc("/", mod.ServeRoot)
team.Router().PathPrefix("/assets/").HandlerFunc(mod.ServeAsset)
team.Router().PathPrefix("/cdn_proxy/").Handler(http.StripPrefix("/cdn_proxy", http.HandlerFunc(cdnproxy.ProxyIntraCDN)))
team.Router().PathPrefix("/cdnproxy/").Handler(http.StripPrefix("/cdnproxy", http.HandlerFunc(cdnproxy.ProxyIntraCDN)))
team.Router().HandleFunc("/session/csrf.json", mod.ServeCSRF)
team.Router().Methods(http.MethodDelete).Path("/session/destroy").HandlerFunc(mod.DestroySession)
team.Router().NotFoundHandler = http.HandlerFunc(mod.Serve404)
team.RegisterCommandFunc("web-authenticate", mod.CommandWebAuthenticate, "Used for assosciating a intra login with a slack name.")
go func() {
for {
time.Sleep(1 * time.Hour)
mod.janitorAuthToken()
}
}()
}
func (mod *WebLoginModule) Disable(team marvin.Team) {
}
// ---
// ---
func (mod *WebLoginModule) StartURL() string {
return mod.team.AbsoluteURL("/oauth/slack/start")
}