145 lines
3.9 KiB
Go
145 lines
3.9 KiB
Go
|
package auth
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"html/template"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"path/filepath"
|
||
|
|
||
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/helpers"
|
||
|
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/internal/db"
|
||
|
"github.com/google/uuid"
|
||
|
"go.uber.org/zap"
|
||
|
)
|
||
|
|
||
|
const ApprovalRoute = "/approval"
|
||
|
|
||
|
var scopeDescriptions = map[string]string{
|
||
|
"offline_access": "Have offline access",
|
||
|
"profile": "View basic profile information",
|
||
|
"email": "View your email address",
|
||
|
"groups": "View your groups",
|
||
|
}
|
||
|
|
||
|
func scopeDescription(rawScope string) string {
|
||
|
if desc, ok := scopeDescriptions[rawScope]; ok {
|
||
|
return desc
|
||
|
}
|
||
|
return rawScope
|
||
|
}
|
||
|
|
||
|
type approvalData struct {
|
||
|
Scopes []string
|
||
|
Client string
|
||
|
AuthReqID string
|
||
|
}
|
||
|
|
||
|
type ApprovalController struct {
|
||
|
l *zap.SugaredLogger
|
||
|
st db.Storage
|
||
|
baseDir string
|
||
|
}
|
||
|
|
||
|
func NewApprovalController(l *zap.SugaredLogger, st db.Storage, baseDir string) *ApprovalController {
|
||
|
return &ApprovalController{
|
||
|
l: l,
|
||
|
st: st,
|
||
|
baseDir: baseDir,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *ApprovalController) handleFormResponse(w http.ResponseWriter, r *http.Request) {
|
||
|
reqID, err := uuid.Parse(r.Form.Get("req"))
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Invalid request ID: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid query format"), c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if r.Form.Get("approval") != "approve" {
|
||
|
c.l.Debug("Approval rejected")
|
||
|
helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("approval rejected"), c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := c.st.AuthRequestStorage().GiveConsent(r.Context(), reqID); err != nil {
|
||
|
c.l.Errorf("Failed to approve request: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
http.Redirect(w, r, fmt.Sprintf("/callback?code=%s&state=%s", r.Form.Get("code"), reqID.String()), http.StatusSeeOther)
|
||
|
}
|
||
|
|
||
|
func (c *ApprovalController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
if err := r.ParseForm(); err != nil {
|
||
|
c.l.Errorf("Failed to parse query: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("invalid query format"), c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if r.Method == http.MethodPost {
|
||
|
c.handleFormResponse(w, r)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
state := r.Form.Get("state")
|
||
|
reqID, err := uuid.Parse(state)
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Invalid state %q: %s", state, err)
|
||
|
helpers.HandleResponse(w, r, http.StatusBadRequest, []byte("unexpected state"), c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
req, err := c.st.AuthRequestStorage().GetAuthRequestByID(r.Context(), reqID)
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Failed to get auth request from DB: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
app, err := c.st.ClientStorage().GetClientByID(r.Context(), req.ClientID)
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Failed to get client details from DB: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
data := approvalData{
|
||
|
Scopes: []string{},
|
||
|
Client: app.Name,
|
||
|
AuthReqID: reqID.String(),
|
||
|
}
|
||
|
for _, s := range req.Scopes {
|
||
|
if s == "openid" { // it's implied we want that, no consent is really important there
|
||
|
continue
|
||
|
}
|
||
|
data.Scopes = append(data.Scopes, scopeDescription(s))
|
||
|
}
|
||
|
|
||
|
lp := filepath.Join(c.baseDir, "templates", "approval.html")
|
||
|
hdrTpl := filepath.Join(c.baseDir, "templates", "header.html")
|
||
|
footTpl := filepath.Join(c.baseDir, "templates", "footer.html")
|
||
|
tmpl, err := template.New("approval.html").ParseFiles(hdrTpl, footTpl, lp)
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Failed to parse templates: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
buf := new(bytes.Buffer)
|
||
|
|
||
|
if err := tmpl.Execute(buf, data); err != nil {
|
||
|
c.l.Errorf("Failed to execute template: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
_, err = io.Copy(w, buf)
|
||
|
if err != nil {
|
||
|
c.l.Errorf("Failed to write response: %s", err)
|
||
|
helpers.HandleResponse(w, r, http.StatusInternalServerError, nil, c.l)
|
||
|
return
|
||
|
}
|
||
|
}
|