+1
-1
appview/consts.go
appview/oauth/consts.go
+1
-1
appview/consts.go
appview/oauth/consts.go
+68
-46
appview/oauth/handler/handler.go
+68
-46
appview/oauth/handler/handler.go
···
29
29
)
30
30
31
31
type OAuthHandler struct {
32
-
Config *config.Config
33
-
Pages *pages.Pages
34
-
Idresolver *idresolver.Resolver
35
-
Db *db.DB
36
-
Store *sessions.CookieStore
37
-
OAuth *oauth.OAuth
38
-
Enforcer *rbac.Enforcer
39
-
Posthog posthog.Client
32
+
config *config.Config
33
+
pages *pages.Pages
34
+
idResolver *idresolver.Resolver
35
+
db *db.DB
36
+
store *sessions.CookieStore
37
+
oauth *oauth.OAuth
38
+
enforcer *rbac.Enforcer
39
+
posthog posthog.Client
40
+
}
41
+
42
+
func New(
43
+
config *config.Config,
44
+
pages *pages.Pages,
45
+
idResolver *idresolver.Resolver,
46
+
db *db.DB,
47
+
store *sessions.CookieStore,
48
+
oauth *oauth.OAuth,
49
+
enforcer *rbac.Enforcer,
50
+
posthog posthog.Client,
51
+
) *OAuthHandler {
52
+
return &OAuthHandler{
53
+
config: config,
54
+
pages: pages,
55
+
idResolver: idResolver,
56
+
db: db,
57
+
store: store,
58
+
oauth: oauth,
59
+
enforcer: enforcer,
60
+
posthog: posthog,
61
+
}
40
62
}
41
63
42
64
func (o *OAuthHandler) Router() http.Handler {
···
45
67
r.Get("/login", o.login)
46
68
r.Post("/login", o.login)
47
69
48
-
r.With(middleware.AuthMiddleware(o.OAuth)).Post("/logout", o.logout)
70
+
r.With(middleware.AuthMiddleware(o.oauth)).Post("/logout", o.logout)
49
71
50
72
r.Get("/oauth/client-metadata.json", o.clientMetadata)
51
73
r.Get("/oauth/jwks.json", o.jwks)
···
56
78
func (o *OAuthHandler) clientMetadata(w http.ResponseWriter, r *http.Request) {
57
79
w.Header().Set("Content-Type", "application/json")
58
80
w.WriteHeader(http.StatusOK)
59
-
json.NewEncoder(w).Encode(o.OAuth.ClientMetadata())
81
+
json.NewEncoder(w).Encode(o.oauth.ClientMetadata())
60
82
}
61
83
62
84
func (o *OAuthHandler) jwks(w http.ResponseWriter, r *http.Request) {
63
-
jwks := o.Config.OAuth.Jwks
85
+
jwks := o.config.OAuth.Jwks
64
86
pubKey, err := pubKeyFromJwk(jwks)
65
87
if err != nil {
66
88
log.Printf("error parsing public key: %v", err)
···
78
100
func (o *OAuthHandler) login(w http.ResponseWriter, r *http.Request) {
79
101
switch r.Method {
80
102
case http.MethodGet:
81
-
o.Pages.Login(w, pages.LoginParams{})
103
+
o.pages.Login(w, pages.LoginParams{})
82
104
case http.MethodPost:
83
105
handle := strings.TrimPrefix(r.FormValue("handle"), "@")
84
106
85
-
resolved, err := o.Idresolver.ResolveIdent(r.Context(), handle)
107
+
resolved, err := o.idResolver.ResolveIdent(r.Context(), handle)
86
108
if err != nil {
87
109
log.Println("failed to resolve handle:", err)
88
-
o.Pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle))
110
+
o.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle))
89
111
return
90
112
}
91
-
self := o.OAuth.ClientMetadata()
113
+
self := o.oauth.ClientMetadata()
92
114
oauthClient, err := client.NewClient(
93
115
self.ClientID,
94
-
o.Config.OAuth.Jwks,
116
+
o.config.OAuth.Jwks,
95
117
self.RedirectURIs[0],
96
118
)
97
119
98
120
if err != nil {
99
121
log.Println("failed to create oauth client:", err)
100
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
122
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
101
123
return
102
124
}
103
125
104
126
authServer, err := oauthClient.ResolvePdsAuthServer(r.Context(), resolved.PDSEndpoint())
105
127
if err != nil {
106
128
log.Println("failed to resolve auth server:", err)
107
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
129
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
108
130
return
109
131
}
110
132
111
133
authMeta, err := oauthClient.FetchAuthServerMetadata(r.Context(), authServer)
112
134
if err != nil {
113
135
log.Println("failed to fetch auth server metadata:", err)
114
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
136
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
115
137
return
116
138
}
117
139
118
140
dpopKey, err := helpers.GenerateKey(nil)
119
141
if err != nil {
120
142
log.Println("failed to generate dpop key:", err)
121
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
143
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
122
144
return
123
145
}
124
146
125
147
dpopKeyJson, err := json.Marshal(dpopKey)
126
148
if err != nil {
127
149
log.Println("failed to marshal dpop key:", err)
128
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
150
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
129
151
return
130
152
}
131
153
132
154
parResp, err := oauthClient.SendParAuthRequest(r.Context(), authServer, authMeta, handle, oauthScope, dpopKey)
133
155
if err != nil {
134
156
log.Println("failed to send par auth request:", err)
135
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
157
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
136
158
return
137
159
}
138
160
139
-
err = db.SaveOAuthRequest(o.Db, db.OAuthRequest{
161
+
err = db.SaveOAuthRequest(o.db, db.OAuthRequest{
140
162
Did: resolved.DID.String(),
141
163
PdsUrl: resolved.PDSEndpoint(),
142
164
Handle: handle,
···
148
170
})
149
171
if err != nil {
150
172
log.Println("failed to save oauth request:", err)
151
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
173
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
152
174
return
153
175
}
154
176
···
157
179
query.Add("client_id", self.ClientID)
158
180
query.Add("request_uri", parResp.RequestUri)
159
181
u.RawQuery = query.Encode()
160
-
o.Pages.HxRedirect(w, u.String())
182
+
o.pages.HxRedirect(w, u.String())
161
183
}
162
184
}
163
185
164
186
func (o *OAuthHandler) callback(w http.ResponseWriter, r *http.Request) {
165
187
state := r.FormValue("state")
166
188
167
-
oauthRequest, err := db.GetOAuthRequestByState(o.Db, state)
189
+
oauthRequest, err := db.GetOAuthRequestByState(o.db, state)
168
190
if err != nil {
169
191
log.Println("failed to get oauth request:", err)
170
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
192
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
171
193
return
172
194
}
173
195
174
196
defer func() {
175
-
err := db.DeleteOAuthRequestByState(o.Db, state)
197
+
err := db.DeleteOAuthRequestByState(o.db, state)
176
198
if err != nil {
177
199
log.Println("failed to delete oauth request for state:", state, err)
178
200
}
···
182
204
errorDescription := r.FormValue("error_description")
183
205
if error != "" || errorDescription != "" {
184
206
log.Printf("error: %s, %s", error, errorDescription)
185
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
207
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
186
208
return
187
209
}
188
210
189
211
code := r.FormValue("code")
190
212
if code == "" {
191
213
log.Println("missing code for state: ", state)
192
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
214
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
193
215
return
194
216
}
195
217
196
218
iss := r.FormValue("iss")
197
219
if iss == "" {
198
220
log.Println("missing iss for state: ", state)
199
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
221
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
200
222
return
201
223
}
202
224
203
-
self := o.OAuth.ClientMetadata()
225
+
self := o.oauth.ClientMetadata()
204
226
205
227
oauthClient, err := client.NewClient(
206
228
self.ClientID,
207
-
o.Config.OAuth.Jwks,
229
+
o.config.OAuth.Jwks,
208
230
self.RedirectURIs[0],
209
231
)
210
232
211
233
if err != nil {
212
234
log.Println("failed to create oauth client:", err)
213
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
235
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
214
236
return
215
237
}
216
238
217
239
jwk, err := helpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk))
218
240
if err != nil {
219
241
log.Println("failed to parse jwk:", err)
220
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
242
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
221
243
return
222
244
}
223
245
···
231
253
)
232
254
if err != nil {
233
255
log.Println("failed to get token:", err)
234
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
256
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
235
257
return
236
258
}
237
259
238
260
if tokenResp.Scope != oauthScope {
239
261
log.Println("scope doesn't match:", tokenResp.Scope)
240
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
262
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
241
263
return
242
264
}
243
265
244
-
err = o.OAuth.SaveSession(w, r, oauthRequest, tokenResp)
266
+
err = o.oauth.SaveSession(w, r, oauthRequest, tokenResp)
245
267
if err != nil {
246
268
log.Println("failed to save session:", err)
247
-
o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
269
+
o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.")
248
270
return
249
271
}
250
272
251
273
log.Println("session saved successfully")
252
274
go o.addToDefaultKnot(oauthRequest.Did)
253
275
254
-
if !o.Config.Core.Dev {
255
-
err = o.Posthog.Enqueue(posthog.Capture{
276
+
if !o.config.Core.Dev {
277
+
err = o.posthog.Enqueue(posthog.Capture{
256
278
DistinctId: oauthRequest.Did,
257
279
Event: "signin",
258
280
})
···
265
287
}
266
288
267
289
func (o *OAuthHandler) logout(w http.ResponseWriter, r *http.Request) {
268
-
err := o.OAuth.ClearSession(r, w)
290
+
err := o.oauth.ClearSession(r, w)
269
291
if err != nil {
270
292
log.Println("failed to clear session:", err)
271
293
http.Redirect(w, r, "/", http.StatusFound)
···
292
314
defaultKnot := "knot1.tangled.sh"
293
315
294
316
log.Printf("adding %s to default knot", did)
295
-
err := o.Enforcer.AddMember(defaultKnot, did)
317
+
err := o.enforcer.AddMember(defaultKnot, did)
296
318
if err != nil {
297
319
log.Println("failed to add user to knot1.tangled.sh: ", err)
298
320
return
299
321
}
300
-
err = o.Enforcer.E.SavePolicy()
322
+
err = o.enforcer.E.SavePolicy()
301
323
if err != nil {
302
324
log.Println("failed to add user to knot1.tangled.sh: ", err)
303
325
return
304
326
}
305
327
306
-
secret, err := db.GetRegistrationKey(o.Db, defaultKnot)
328
+
secret, err := db.GetRegistrationKey(o.db, defaultKnot)
307
329
if err != nil {
308
330
log.Println("failed to get registration key for knot1.tangled.sh")
309
331
return
310
332
}
311
-
signedClient, err := knotclient.NewSignedClient(defaultKnot, secret, o.Config.Core.Dev)
333
+
signedClient, err := knotclient.NewSignedClient(defaultKnot, secret, o.config.Core.Dev)
312
334
resp, err := signedClient.AddMember(did)
313
335
if err != nil {
314
336
log.Println("failed to add user to knot1.tangled.sh: ", err)
+16
-17
appview/oauth/oauth.go
+16
-17
appview/oauth/oauth.go
···
10
10
"github.com/gorilla/sessions"
11
11
oauth "tangled.sh/icyphox.sh/atproto-oauth"
12
12
"tangled.sh/icyphox.sh/atproto-oauth/helpers"
13
-
"tangled.sh/tangled.sh/core/appview"
14
13
"tangled.sh/tangled.sh/core/appview/config"
15
14
"tangled.sh/tangled.sh/core/appview/db"
16
15
"tangled.sh/tangled.sh/core/appview/oauth/client"
···
44
43
45
44
func (o *OAuth) SaveSession(w http.ResponseWriter, r *http.Request, oreq db.OAuthRequest, oresp *oauth.TokenResponse) error {
46
45
// first we save the did in the user session
47
-
userSession, err := o.Store.Get(r, appview.SessionName)
46
+
userSession, err := o.Store.Get(r, SessionName)
48
47
if err != nil {
49
48
return err
50
49
}
51
50
52
-
userSession.Values[appview.SessionDid] = oreq.Did
53
-
userSession.Values[appview.SessionHandle] = oreq.Handle
54
-
userSession.Values[appview.SessionPds] = oreq.PdsUrl
55
-
userSession.Values[appview.SessionAuthenticated] = true
51
+
userSession.Values[SessionDid] = oreq.Did
52
+
userSession.Values[SessionHandle] = oreq.Handle
53
+
userSession.Values[SessionPds] = oreq.PdsUrl
54
+
userSession.Values[SessionAuthenticated] = true
56
55
err = userSession.Save(r, w)
57
56
if err != nil {
58
57
return fmt.Errorf("error saving user session: %w", err)
···
75
74
}
76
75
77
76
func (o *OAuth) ClearSession(r *http.Request, w http.ResponseWriter) error {
78
-
userSession, err := o.Store.Get(r, appview.SessionName)
77
+
userSession, err := o.Store.Get(r, SessionName)
79
78
if err != nil || userSession.IsNew {
80
79
return fmt.Errorf("error getting user session (or new session?): %w", err)
81
80
}
82
81
83
-
did := userSession.Values[appview.SessionDid].(string)
82
+
did := userSession.Values[SessionDid].(string)
84
83
85
84
err = db.DeleteOAuthSessionByDid(o.Db, did)
86
85
if err != nil {
···
93
92
}
94
93
95
94
func (o *OAuth) GetSession(r *http.Request) (*db.OAuthSession, bool, error) {
96
-
userSession, err := o.Store.Get(r, appview.SessionName)
95
+
userSession, err := o.Store.Get(r, SessionName)
97
96
if err != nil || userSession.IsNew {
98
97
return nil, false, fmt.Errorf("error getting user session (or new session?): %w", err)
99
98
}
100
99
101
-
did := userSession.Values[appview.SessionDid].(string)
102
-
auth := userSession.Values[appview.SessionAuthenticated].(bool)
100
+
did := userSession.Values[SessionDid].(string)
101
+
auth := userSession.Values[SessionAuthenticated].(bool)
103
102
104
103
session, err := db.GetOAuthSessionByDid(o.Db, did)
105
104
if err != nil {
···
156
155
}
157
156
158
157
func (a *OAuth) GetUser(r *http.Request) *User {
159
-
clientSession, err := a.Store.Get(r, appview.SessionName)
158
+
clientSession, err := a.Store.Get(r, SessionName)
160
159
161
160
if err != nil || clientSession.IsNew {
162
161
return nil
163
162
}
164
163
165
164
return &User{
166
-
Handle: clientSession.Values[appview.SessionHandle].(string),
167
-
Did: clientSession.Values[appview.SessionDid].(string),
168
-
Pds: clientSession.Values[appview.SessionPds].(string),
165
+
Handle: clientSession.Values[SessionHandle].(string),
166
+
Did: clientSession.Values[SessionDid].(string),
167
+
Pds: clientSession.Values[SessionPds].(string),
169
168
}
170
169
}
171
170
172
171
func (a *OAuth) GetDid(r *http.Request) string {
173
-
clientSession, err := a.Store.Get(r, appview.SessionName)
172
+
clientSession, err := a.Store.Get(r, SessionName)
174
173
175
174
if err != nil || clientSession.IsNew {
176
175
return ""
177
176
}
178
177
179
-
return clientSession.Values[appview.SessionDid].(string)
178
+
return clientSession.Values[SessionDid].(string)
180
179
}
181
180
182
181
func (o *OAuth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
+3
-12
appview/state/router.go
+3
-12
appview/state/router.go
···
7
7
"github.com/go-chi/chi/v5"
8
8
"github.com/gorilla/sessions"
9
9
"tangled.sh/tangled.sh/core/appview/middleware"
10
-
oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler"
10
+
oauth "tangled.sh/tangled.sh/core/appview/oauth/handler"
11
11
"tangled.sh/tangled.sh/core/appview/pulls"
12
12
"tangled.sh/tangled.sh/core/appview/repo"
13
13
"tangled.sh/tangled.sh/core/appview/settings"
···
154
154
}
155
155
156
156
func (s *State) OAuthRouter() http.Handler {
157
-
oauth := &oauthhandler.OAuthHandler{
158
-
Config: s.config,
159
-
Pages: s.pages,
160
-
Idresolver: s.idResolver,
161
-
Db: s.db,
162
-
Store: sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)),
163
-
OAuth: s.oauth,
164
-
Enforcer: s.enforcer,
165
-
Posthog: s.posthog,
166
-
}
167
-
157
+
store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret))
158
+
oauth := oauth.New(s.config, s.pages, s.idResolver, s.db, store, s.oauth, s.enforcer, s.posthog)
168
159
return oauth.Router()
169
160
}
170
161
+2
-2
appview/state/state.go
+2
-2
appview/state/state.go
···
176
176
177
177
return
178
178
case http.MethodPost:
179
-
session, err := s.oauth.Store.Get(r, appview.SessionName)
179
+
session, err := s.oauth.Store.Get(r, oauth.SessionName)
180
180
if err != nil || session.IsNew {
181
181
log.Println("unauthorized attempt to generate registration key")
182
182
http.Error(w, "Forbidden", http.StatusUnauthorized)
183
183
return
184
184
}
185
185
186
-
did := session.Values[appview.SessionDid].(string)
186
+
did := session.Values[oauth.SessionDid].(string)
187
187
188
188
// check if domain is valid url, and strip extra bits down to just host
189
189
domain := r.FormValue("domain")