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
package pds
import (
"net/http"
"strconv"
"time"
"github.com/bluesky-social/indigo/atproto/auth"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/johnandersen777/socialweb-computer-kcp/internal/atproto/serviceauth"
"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/httpjson"
)
type getServiceAuthRequest struct {
Aud string `json:"aud"`
Lxm string `json:"lxm"`
Exp int64 `json:"exp"`
}
type getServiceAuthOutput struct {
Token string `json:"token"`
}
func (s *Server) handleGetServiceAuth(w http.ResponseWriter, r *http.Request, info *authInfo) {
if info.Service {
writeXRPCError(w, http.StatusForbidden, "Forbidden", "service auth tokens cannot mint service auth tokens")
return
}
if s.keys == nil {
writeXRPCError(w, http.StatusInternalServerError, "InternalError", "repo key store is not configured")
return
}
var req getServiceAuthRequest
query := r.URL.Query()
req.Aud = query.Get("aud")
req.Lxm = query.Get("lxm")
if rawExp := query.Get("exp"); rawExp != "" {
exp, err := strconv.ParseInt(rawExp, 10, 64)
if err != nil {
writeXRPCError(w, http.StatusBadRequest, "InvalidRequest", "exp must be a unix timestamp")
return
}
req.Exp = exp
}
if r.Method == http.MethodPost && r.ContentLength != 0 {
if err := s.decodeJSON(w, r, &req); err != nil {
writeXRPCError(w, http.StatusBadRequest, "InvalidRequest", "invalid JSON")
return
}
}
if req.Aud == "" {
writeXRPCError(w, http.StatusBadRequest, "InvalidRequest", "aud is required")
return
}
var lexMethod *syntax.NSID
if req.Lxm != "" {
parsed, err := syntax.ParseNSID(req.Lxm)
if err != nil {
writeXRPCError(w, http.StatusBadRequest, "InvalidRequest", "invalid lxm")
return
}
lexMethod = &parsed
}
ttl := serviceauth.MaxTokenLifetime
if req.Exp != 0 {
remaining := time.Until(time.Unix(req.Exp, 0))
if remaining <= 0 {
writeXRPCError(w, http.StatusBadRequest, "InvalidRequest", "exp is in the past")
return
}
if remaining < ttl {
ttl = remaining
}
}
key, err := s.keys.GetRepoKey(r.Context(), info.DID)
if err != nil {
writeXRPCError(w, http.StatusInternalServerError, "InternalError", "failed to load signing key")
return
}
token, err := auth.SignServiceAuth(info.DID, req.Aud, ttl, lexMethod, key)
if err != nil {
writeXRPCError(w, http.StatusInternalServerError, "InternalError", "failed to sign service auth token")
return
}
httpjson.Write(w, http.StatusOK, getServiceAuthOutput{Token: token})
}