• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gameap / gameap / 25960901307

16 May 2026 11:29AM UTC coverage: 76.887% (+0.2%) from 76.64%
25960901307

push

github

et-nik
audit logs tests

45395 of 59041 relevant lines covered (76.89%)

33895.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.18
/internal/api/daemonapi/gettoken/handler.go
1
package gettoken
2

3
import (
4
        "net/http"
5
        "strconv"
6
        "strings"
7
        "time"
8

9
        "github.com/gameap/gameap/internal/api/base"
10
        "github.com/gameap/gameap/internal/audit"
11
        "github.com/gameap/gameap/internal/filters"
12
        "github.com/gameap/gameap/internal/repositories"
13
        "github.com/gameap/gameap/pkg/api"
14
        pkgstrings "github.com/gameap/gameap/pkg/strings"
15
        "github.com/pkg/errors"
16
)
17

18
const tokenLength = 64
19

20
type Handler struct {
21
        nodeRepo    repositories.NodeRepository
22
        connChecker DaemonConnectionChecker
23
        responder   base.Responder
24
        audit       audit.Logger
25
}
26

27
func NewHandler(
28
        nodeRepo repositories.NodeRepository,
29
        connChecker DaemonConnectionChecker,
30
        responder base.Responder,
31
        auditLogger audit.Logger,
32
) *Handler {
11✔
33
        if auditLogger == nil {
20✔
34
                auditLogger = audit.NopLogger{}
9✔
35
        }
9✔
36

37
        return &Handler{
11✔
38
                nodeRepo:    nodeRepo,
11✔
39
                connChecker: connChecker,
11✔
40
                responder:   responder,
11✔
41
                audit:       auditLogger,
11✔
42
        }
11✔
43
}
44

45
func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
19✔
46
        ctx := r.Context()
19✔
47

19✔
48
        authHeader := r.Header.Get("Authorization")
19✔
49
        if authHeader == "" {
20✔
50
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
1✔
51
                        errors.New("invalid api key"),
1✔
52
                        http.StatusUnauthorized,
1✔
53
                ))
1✔
54

1✔
55
                return
1✔
56
        }
1✔
57

58
        apiKey := strings.TrimPrefix(strings.TrimSpace(authHeader), "Bearer ")
18✔
59
        if apiKey == "" {
18✔
60
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
61
                        errors.New("invalid api key"),
×
62
                        http.StatusUnauthorized,
×
63
                ))
×
64

×
65
                return
×
66
        }
×
67

68
        nodes, err := h.nodeRepo.Find(ctx, filters.FindNodeByGDaemonAPIKey(apiKey), nil, &filters.Pagination{
18✔
69
                Limit: 1,
18✔
70
        })
18✔
71
        if err != nil {
18✔
72
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
73
                        errors.WithMessage(err, "failed to find node by api key"),
×
74
                        http.StatusInternalServerError,
×
75
                ))
×
76

×
77
                return
×
78
        }
×
79

80
        if len(nodes) == 0 {
21✔
81
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
3✔
82
                        errors.New("invalid api key"),
3✔
83
                        http.StatusUnauthorized,
3✔
84
                ))
3✔
85

3✔
86
                return
3✔
87
        }
3✔
88

89
        node := &nodes[0]
15✔
90

15✔
91
        if h.connChecker.IsConnectedAnywhere(uint64(node.ID)) {
16✔
92
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
1✔
93
                        errors.New("daemon is connected via gRPC bidi stream, HTTP API is disabled for this node"),
1✔
94
                        http.StatusConflict,
1✔
95
                ))
1✔
96

1✔
97
                return
1✔
98
        }
1✔
99

100
        token, err := pkgstrings.CryptoRandomString(tokenLength)
14✔
101
        if err != nil {
14✔
102
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
103
                        errors.WithMessage(err, "failed to generate token"),
×
104
                        http.StatusInternalServerError,
×
105
                ))
×
106

×
107
                return
×
108
        }
×
109

110
        // Persist only the SHA-256 hash; the plaintext is returned to the daemon once
111
        // in the response below and must never be retrievable from the database.
112
        // Mirrors the Personal Access Token model in
113
        // internal/api/tokens/posttoken/handler.go and prevents a DB read from
114
        // yielding a usable daemon credential.
115
        node.GdaemonAPIToken = new(pkgstrings.SHA256(token))
14✔
116
        now := time.Now()
14✔
117
        node.UpdatedAt = &now
14✔
118

14✔
119
        err = h.nodeRepo.Save(ctx, node)
14✔
120
        if err != nil {
14✔
121
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
122
                        errors.WithMessage(err, "failed to update node"),
×
123
                        http.StatusInternalServerError,
×
124
                ))
×
125

×
126
                return
×
127
        }
×
128

129
        audit.SensitiveOp(ctx, h.audit, audit.EventDaemonTokenIssue, audit.CategoryTokenOp,
14✔
130
                "node", strconv.FormatUint(uint64(node.ID), 10), "issue")
14✔
131

14✔
132
        response := newTokenResponse(token, now.Unix())
14✔
133

14✔
134
        h.responder.Write(ctx, rw, response)
14✔
135
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc