+6
-17
legit/routes/auth.go
+6
-17
legit/routes/auth.go
···
7
7
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
"github.com/bluesky-social/indigo/xrpc"
10
+
rauth "github.com/icyphox/bild/legit/routes/auth"
10
11
)
11
12
12
13
const (
···
19
20
auth, ok := session.Values["authenticated"].(bool)
20
21
21
22
if !ok || !auth {
22
-
http.Error(w, "Forbidden: You are not logged in", http.StatusForbidden)
23
+
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
23
24
return
24
25
}
25
26
···
43
44
},
44
45
}
45
46
atSession, err := comatproto.ServerRefreshSession(r.Context(), &client)
46
-
47
47
if err != nil {
48
48
log.Println(err)
49
-
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
49
+
h.Write500(w)
50
50
return
51
51
}
52
52
53
-
clientSession, _ := h.s.Get(r, "bild-session")
54
-
clientSession.Values["handle"] = atSession.Handle
55
-
clientSession.Values["did"] = atSession.Did
56
-
clientSession.Values["accessJwt"] = atSession.AccessJwt
57
-
clientSession.Values["refreshJwt"] = atSession.RefreshJwt
58
-
clientSession.Values["expiry"] = time.Now().Add(time.Hour).String()
59
-
clientSession.Values["pds"] = pdsUrl
60
-
clientSession.Values["authenticated"] = true
61
-
62
-
err = clientSession.Save(r, w)
63
-
53
+
err = h.auth.StoreSession(r, w, nil, &rauth.AtSessionRefresh{ServerRefreshSession_Output: *atSession, PDSEndpoint: pdsUrl})
64
54
if err != nil {
65
-
log.Printf("failed to store session for did: %s\n", atSession.Did)
66
-
log.Println(err)
67
-
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
55
+
log.Printf("failed to store session for did: %s\n: %s", atSession.Did, err)
56
+
h.Write500(w)
68
57
return
69
58
}
70
59
+98
legit/routes/auth/auth.go
+98
legit/routes/auth/auth.go
···
1
+
package auth
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"net/http"
7
+
"time"
8
+
9
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
10
+
"github.com/bluesky-social/indigo/atproto/identity"
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
"github.com/bluesky-social/indigo/xrpc"
13
+
"github.com/gorilla/sessions"
14
+
)
15
+
16
+
type Auth struct {
17
+
s sessions.Store
18
+
}
19
+
20
+
func NewAuth(store sessions.Store) *Auth {
21
+
return &Auth{store}
22
+
}
23
+
24
+
func resolveIdent(ctx context.Context, arg string) (*identity.Identity, error) {
25
+
id, err := syntax.ParseAtIdentifier(arg)
26
+
if err != nil {
27
+
return nil, err
28
+
}
29
+
30
+
dir := identity.DefaultDirectory()
31
+
return dir.Lookup(ctx, *id)
32
+
}
33
+
34
+
func (a *Auth) CreateInitialSession(w http.ResponseWriter, r *http.Request, username, appPassword string) (AtSessionCreate, error) {
35
+
ctx := r.Context()
36
+
resolved, err := resolveIdent(ctx, username)
37
+
if err != nil {
38
+
return AtSessionCreate{}, fmt.Errorf("invalid handle: %s", err)
39
+
}
40
+
41
+
pdsUrl := resolved.PDSEndpoint()
42
+
client := xrpc.Client{
43
+
Host: pdsUrl,
44
+
}
45
+
46
+
atSession, err := comatproto.ServerCreateSession(ctx, &client, &comatproto.ServerCreateSession_Input{
47
+
Identifier: resolved.DID.String(),
48
+
Password: appPassword,
49
+
})
50
+
if err != nil {
51
+
return AtSessionCreate{}, fmt.Errorf("invalid app password")
52
+
}
53
+
54
+
return AtSessionCreate{
55
+
ServerCreateSession_Output: *atSession,
56
+
PDSEndpoint: pdsUrl,
57
+
}, nil
58
+
}
59
+
60
+
func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionCreate *AtSessionCreate, atSessionRefresh *AtSessionRefresh) error {
61
+
if atSessionCreate != nil {
62
+
atSession := atSessionCreate
63
+
64
+
clientSession, _ := a.s.Get(r, "bild-session")
65
+
clientSession.Values["handle"] = atSession.Handle
66
+
clientSession.Values["did"] = atSession.Did
67
+
clientSession.Values["accessJwt"] = atSession.AccessJwt
68
+
clientSession.Values["refreshJwt"] = atSession.RefreshJwt
69
+
clientSession.Values["expiry"] = time.Now().Add(time.Hour).String()
70
+
clientSession.Values["pds"] = atSession.PDSEndpoint
71
+
clientSession.Values["authenticated"] = true
72
+
73
+
return clientSession.Save(r, w)
74
+
} else {
75
+
atSession := atSessionRefresh
76
+
77
+
clientSession, _ := a.s.Get(r, "bild-session")
78
+
clientSession.Values["handle"] = atSession.Handle
79
+
clientSession.Values["did"] = atSession.Did
80
+
clientSession.Values["accessJwt"] = atSession.AccessJwt
81
+
clientSession.Values["refreshJwt"] = atSession.RefreshJwt
82
+
clientSession.Values["expiry"] = time.Now().Add(time.Hour).String()
83
+
clientSession.Values["pds"] = atSession.PDSEndpoint
84
+
clientSession.Values["authenticated"] = true
85
+
86
+
return clientSession.Save(r, w)
87
+
}
88
+
}
89
+
90
+
func (a *Auth) GetSessionUser(r *http.Request) (*identity.Identity, error) {
91
+
session, _ := a.s.Get(r, "bild-session")
92
+
did, ok := session.Values["did"].(string)
93
+
if !ok {
94
+
return nil, fmt.Errorf("user is not authenticated")
95
+
}
96
+
97
+
return resolveIdent(r.Context(), did)
98
+
}
+15
legit/routes/auth/types.go
+15
legit/routes/auth/types.go
···
1
+
package auth
2
+
3
+
import (
4
+
comatproto "github.com/bluesky-social/indigo/api/atproto"
5
+
)
6
+
7
+
type AtSessionCreate struct {
8
+
comatproto.ServerCreateSession_Output
9
+
PDSEndpoint string
10
+
}
11
+
12
+
type AtSessionRefresh struct {
13
+
comatproto.ServerRefreshSession_Output
14
+
PDSEndpoint string
15
+
}
+8
-4
legit/routes/handler.go
+8
-4
legit/routes/handler.go
···
11
11
"github.com/gorilla/sessions"
12
12
"github.com/icyphox/bild/legit/config"
13
13
"github.com/icyphox/bild/legit/db"
14
+
"github.com/icyphox/bild/legit/routes/auth"
14
15
"github.com/icyphox/bild/legit/routes/tmpl"
15
16
)
16
17
···
44
45
return nil, fmt.Errorf("failed to load templates: %w", err)
45
46
}
46
47
48
+
auth := auth.NewAuth(s)
49
+
47
50
db, err := db.Setup(c.Server.DBPath)
48
51
if err != nil {
49
52
return nil, fmt.Errorf("failed to setup db: %w", err)
50
53
}
51
54
52
55
h := Handle{
53
-
c: c,
54
-
t: t,
55
-
s: s,
56
-
db: db,
56
+
c: c,
57
+
t: t,
58
+
s: s,
59
+
db: db,
60
+
auth: auth,
57
61
}
58
62
59
63
r.Get("/login", h.Login)
+15
-47
legit/routes/routes.go
+15
-47
legit/routes/routes.go
···
2
2
3
3
import (
4
4
"compress/gzip"
5
-
"context"
6
5
"errors"
7
6
"fmt"
8
7
"html/template"
···
15
14
"strings"
16
15
"time"
17
16
18
-
comatproto "github.com/bluesky-social/indigo/api/atproto"
19
-
"github.com/bluesky-social/indigo/atproto/identity"
20
-
"github.com/bluesky-social/indigo/atproto/syntax"
21
-
"github.com/bluesky-social/indigo/xrpc"
22
17
"github.com/dustin/go-humanize"
23
18
"github.com/go-chi/chi/v5"
24
19
"github.com/go-git/go-git/v5/plumbing"
···
26
21
"github.com/icyphox/bild/legit/config"
27
22
"github.com/icyphox/bild/legit/db"
28
23
"github.com/icyphox/bild/legit/git"
24
+
"github.com/icyphox/bild/legit/routes/auth"
29
25
"github.com/russross/blackfriday/v2"
30
26
"golang.org/x/crypto/ssh"
31
27
)
32
28
33
29
type Handle struct {
34
-
c *config.Config
35
-
t *template.Template
36
-
s *sessions.CookieStore
37
-
db *db.DB
30
+
c *config.Config
31
+
t *template.Template
32
+
s *sessions.CookieStore
33
+
db *db.DB
34
+
auth *auth.Auth
38
35
}
39
36
40
37
func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
···
440
437
http.ServeFile(w, r, f)
441
438
}
442
439
443
-
func resolveIdent(ctx context.Context, arg string) (*identity.Identity, error) {
444
-
id, err := syntax.ParseAtIdentifier(arg)
445
-
if err != nil {
446
-
return nil, err
447
-
}
448
-
449
-
dir := identity.DefaultDirectory()
450
-
return dir.Lookup(ctx, *id)
451
-
}
452
-
453
440
func (h *Handle) Login(w http.ResponseWriter, r *http.Request) {
454
441
switch r.Method {
455
442
case http.MethodGet:
···
458
445
return
459
446
}
460
447
case http.MethodPost:
461
-
ctx := r.Context()
462
448
username := r.FormValue("username")
463
449
appPassword := r.FormValue("app_password")
464
450
465
-
resolved, err := resolveIdent(ctx, username)
451
+
atSession, err := h.auth.CreateInitialSession(w, r, username, appPassword)
466
452
if err != nil {
467
-
http.Error(w, "invalid `handle`", http.StatusBadRequest)
453
+
h.WriteOOBNotice(w, "login", "Invalid username or app password.")
454
+
log.Printf("creating initial session: %s", err)
468
455
return
469
456
}
470
457
471
-
pdsUrl := resolved.PDSEndpoint()
472
-
client := xrpc.Client{
473
-
Host: pdsUrl,
474
-
}
475
-
476
-
atSession, err := comatproto.ServerCreateSession(ctx, &client, &comatproto.ServerCreateSession_Input{
477
-
Identifier: resolved.DID.String(),
478
-
Password: appPassword,
479
-
})
480
-
481
-
clientSession, _ := h.s.Get(r, "bild-session")
482
-
clientSession.Values["handle"] = atSession.Handle
483
-
clientSession.Values["did"] = atSession.Did
484
-
clientSession.Values["accessJwt"] = atSession.AccessJwt
485
-
clientSession.Values["refreshJwt"] = atSession.RefreshJwt
486
-
clientSession.Values["expiry"] = time.Now().Add(time.Hour).String()
487
-
clientSession.Values["pds"] = pdsUrl
488
-
clientSession.Values["authenticated"] = true
489
-
490
-
err = clientSession.Save(r, w)
491
-
458
+
err = h.auth.StoreSession(r, w, &atSession, nil)
492
459
if err != nil {
493
-
log.Printf("failed to store session for did: %s\n", atSession.Did)
494
-
log.Println(err)
460
+
h.WriteOOBNotice(w, "login", "Failed to store session.")
461
+
log.Printf("storing session: %s", err)
495
462
return
496
463
}
497
464
498
465
log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did)
499
-
http.Redirect(w, r, "/@"+atSession.Handle, 302)
466
+
w.Header().Set("HX-Redirect", "/")
467
+
w.WriteHeader(http.StatusOK)
500
468
}
501
469
}
502
470
···
508
476
case http.MethodGet:
509
477
keys, err := h.db.GetPublicKeys(did)
510
478
if err != nil {
479
+
h.WriteOOBNotice(w, "keys", "Failed to list keys. Try again later.")
511
480
log.Println(err)
512
-
http.Error(w, "invalid `did`", http.StatusBadRequest)
513
481
return
514
482
}
515
483