aboutsummaryrefslogtreecommitdiff
path: root/internal/authz/middleware.go
blob: f7e1728a5bd77080d78c857319708d22c6c26320 (plain)
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
// authentication and authorization module
package authz

import (
	"context"
	"encoding/hex"
	"fmt"
	"log/slog"
	"net/http"

	"git.ofmax.li/go-git-server/internal/admin"
	"golang.org/x/crypto/bcrypt"
)

// AuthzContextKey key used to store urn of user in context
type AuthzContextKey string

var (
	AuthzUrnKey AuthzContextKey = "goGitAuthzUrn"
)

func Authentication(authMap TokenMap, next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		slog.Info("access request recv")
		u, p, ok := req.BasicAuth()
		if !ok {
			rw.Header().Set("WWW-Authenticate", `Basic realm="git"`)
			http.Error(rw, "Authentication Required", http.StatusUnauthorized)
			return
		}
		urn := fmt.Sprintf("uid:%s", u)
		hash, ok := authMap[urn]
		if !ok {
			slog.Info("failed access", "urn", urn)
			http.Error(rw, "Bad Request", http.StatusForbidden)
			return
		}
		token, err := hex.DecodeString(p)
		if err != nil {
			http.Error(rw, "Bad Request", http.StatusBadRequest)
			return
		}
		if err := bcrypt.CompareHashAndPassword([]byte(hash), token); err != nil {
			slog.Info("bad token for user", "urn", urn)
			http.Error(rw, "Bad Request", http.StatusForbidden)
			return
		}
		ctx := context.WithValue(req.Context(), AuthzUrnKey, urn)
		slog.Info("access request granted", "urn", urn)
		next.ServeHTTP(rw, req.WithContext(ctx))
	})
}

// Authorization middleware to enforce authoirzation of all requests.
func Authorization(adminSvc *admin.Servicer, next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		ctx := req.Context()
		urn, ok := ctx.Value(AuthzUrnKey).(string)
		if !ok || urn == "" {
			http.Error(rw, "Bad Request", http.StatusBadRequest)
			return
		}
		repo := req.URL.Path
		action := req.Method
		ok, err := adminSvc.Enforce(urn, repo, action)
		if err != nil {
			slog.Info("error running enforce", "error", err)
			http.Error(rw, "Bad Request", http.StatusBadRequest)
			return
		}
		if !ok {
			slog.Info("Not Authorized", "urn", urn, "repo", repo)
			http.Error(rw, "Access denied", http.StatusForbidden)
			return
		}
		slog.Debug("Access Attempt", "action", action, "repo", repo)
		next.ServeHTTP(rw, req.WithContext(ctx))
	})
}