···11-package main
11+package services
2233import (
44 "context"
···1111 "strings"
1212 "time"
13131414+ "github.com/puregarlic/space/handlers"
1415 "github.com/puregarlic/space/pages"
1616+ "github.com/puregarlic/space/storage"
15171618 "github.com/a-h/templ"
1719 "github.com/aidarkhanov/nanoid"
···1921 "go.hacdias.com/indielib/indieauth"
2022)
21232424+type IndieAuth struct {
2525+ ProfileURL string
2626+ Server *indieauth.Server
2727+}
2828+2229// storeAuthorization stores the authorization request and returns a code for it.
2330// Something such as JWT tokens could be used in a production environment.
2424-func (s *server) storeAuthorization(req *indieauth.AuthenticationRequest) string {
3131+func (i *IndieAuth) storeAuthorization(req *indieauth.AuthenticationRequest) string {
2532 code := nanoid.New()
26332727- s.db.Authorization.Set(code, req, 0)
3434+ storage.AuthCache().Set(code, req, 0)
28352936 return code
3037}
···4047 scopesContextKey contextKey = "scopes"
4148)
42494343-// authorizationGetHandler handles the GET method for the authorization endpoint.
4444-func (s *server) authorizationGetHandler(w http.ResponseWriter, r *http.Request) {
5050+// HandleAuthGET handles the GET method for the authorization endpoint.
5151+func (i *IndieAuth) HandleAuthGET(w http.ResponseWriter, r *http.Request) {
4552 // In a production server, this page would usually be protected. In order for
4653 // the user to authorize this request, they must be authenticated. This could
4754 // be done in different ways: username/password, passkeys, etc.
48554956 // Parse the authorization request.
5050- req, err := s.ias.ParseAuthorization(r)
5757+ req, err := i.Server.ParseAuthorization(r)
5158 if err != nil {
5252- serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
5959+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
5360 return
5461 }
55625663 // Do a best effort attempt at fetching more information about the application
5764 // that we can show to the user. Not all applications provide this sort of
5865 // information.
5959- app, _ := s.ias.DiscoverApplicationMetadata(r.Context(), req.ClientID)
6666+ app, _ := i.Server.DiscoverApplicationMetadata(r.Context(), req.ClientID)
60676168 // Here, we just display a small HTML document where the user has to press
6269 // to authorize this request. Please note that this template contains a form
···6572 templ.Handler(pages.Auth(req, app)).ServeHTTP(w, r)
6673}
67746868-// authorizationPostHandler handles the POST method for the authorization endpoint.
6969-func (s *server) authorizationPostHandler(w http.ResponseWriter, r *http.Request) {
7070- s.authorizationCodeExchange(w, r, false)
7575+// HandleAuthPOST handles the POST method for the authorization endpoint.
7676+func (i *IndieAuth) HandleAuthPOST(w http.ResponseWriter, r *http.Request) {
7777+ i.authorizationCodeExchange(w, r, false)
7178}
72797373-// tokenHandler handles the token endpoint. In our case, we only accept the default
8080+// HandleToken handles the token endpoint. In our case, we only accept the default
7481// type which is exchanging an authorization code for a token.
7575-func (s *server) tokenHandler(w http.ResponseWriter, r *http.Request) {
8282+func (i *IndieAuth) HandleToken(w http.ResponseWriter, r *http.Request) {
7683 if r.Method != http.MethodPost {
7777- httpError(w, http.StatusMethodNotAllowed)
8484+ http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
7885 return
7986 }
8087···8592 return
8693 }
87948888- s.authorizationCodeExchange(w, r, true)
9595+ i.authorizationCodeExchange(w, r, true)
8996}
90979198type tokenResponse struct {
···99106// authorizationCodeExchange handles the authorization code exchange. It is used by
100107// both the authorization handler to exchange the code for the user's profile URL,
101108// and by the token endpoint, to exchange the code by a token.
102102-func (s *server) authorizationCodeExchange(w http.ResponseWriter, r *http.Request, withToken bool) {
109109+func (i *IndieAuth) authorizationCodeExchange(w http.ResponseWriter, r *http.Request, withToken bool) {
103110 if err := r.ParseForm(); err != nil {
104104- serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
111111+ handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
105112 return
106113 }
107114108115 // t := s.getAuthorization(r.Form.Get("code"))
109109- req, present := s.db.Authorization.GetAndDelete(r.Form.Get("code"))
116116+ req, present := storage.AuthCache().GetAndDelete(r.Form.Get("code"))
110117 if !present {
111111- serveErrorJSON(w, http.StatusBadRequest, "invalid_request", "invalid authorization")
118118+ handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", "invalid authorization")
112119 return
113120 }
114121 authRequest := req.Value()
115122116116- err := s.ias.ValidateTokenExchange(authRequest, r)
123123+ err := i.Server.ValidateTokenExchange(authRequest, r)
117124 if err != nil {
118118- serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
125125+ handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
119126 return
120127 }
121128122129 response := &tokenResponse{
123123- Me: s.profileURL,
130130+ Me: i.ProfileURL,
124131 }
125132126133 scopes := authRequest.Scopes
···152159153160 // An actual server may want to include the "profile" in the response if the
154161 // scope "profile" is included.
155155- serveJSON(w, http.StatusOK, response)
162162+ handlers.SendJSON(w, http.StatusOK, response)
156163}
157164158158-func (s *server) authorizationAcceptHandler(w http.ResponseWriter, r *http.Request) {
165165+func (i *IndieAuth) HandleAuthApproval(w http.ResponseWriter, r *http.Request) {
159166 // Parse authorization information. This only works because our authorization page
160167 // includes all the required information. This can be done in other ways: database,
161168 // whether temporary or not, cookies, etc.
162162- req, err := s.ias.ParseAuthorization(r)
169169+ req, err := i.Server.ParseAuthorization(r)
163170 if err != nil {
164164- serveErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
171171+ handlers.SendErrorJSON(w, http.StatusBadRequest, "invalid_request", err.Error())
165172 return
166173 }
167174168175 // Generate a random code and persist the information associated to that code.
169176 // You could do this in other ways: database, or JWT tokens, or both, for example.
170170- code := s.storeAuthorization(req)
177177+ code := i.storeAuthorization(req)
171178172179 // Redirect to client callback.
173180 query := url.Values{}
···176183 http.Redirect(w, r, req.RedirectURI+"?"+query.Encode(), http.StatusFound)
177184}
178185179179-// mustAuth is a middleware to ensure that the request is authorized. The way this
186186+// MustAuth is a middleware to ensure that the request is authorized. The way this
180187// works depends on the implementation. It then stores the scopes in the context.
181181-func (s *server) mustAuth(next http.Handler) http.Handler {
188188+func MustAuth(next http.Handler) http.Handler {
182189 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183190 tokenStr := r.Header.Get("Authorization")
184191 tokenStr = strings.TrimPrefix(tokenStr, "Bearer")
185192 tokenStr = strings.TrimSpace(tokenStr)
186193187194 if len(tokenStr) <= 0 {
188188- serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "no credentials")
195195+ handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "no credentials")
189196 return
190197 }
191198···194201 })
195202196203 if err != nil {
197197- serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "invalid token")
204204+ handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "invalid token")
198205 return
199206 } else if claims, ok := token.Claims.(*CustomTokenClaims); ok {
200207 ctx := context.WithValue(r.Context(), scopesContextKey, claims.Scopes)
201208 next.ServeHTTP(w, r.WithContext(ctx))
202209 return
203210 } else {
204204- serveErrorJSON(w, http.StatusUnauthorized, "invalid_request", "malformed claims")
211211+ handlers.SendErrorJSON(w, http.StatusUnauthorized, "invalid_request", "malformed claims")
205212 return
206213 }
207214 })
208215}
209216210210-func (s *server) mustBasicAuth(next http.Handler) http.Handler {
217217+func MustBasicAuth(next http.Handler) http.Handler {
211218 user, ok := os.LookupEnv("ADMIN_USERNAME")
212219 if !ok {
213220 panic(errors.New("ADMIN_USERNAME is not set, cannot start"))