summaryrefslogtreecommitdiff
path: root/daemon/algod/api/server/lib/middlewares/auth.go
blob: 06a7573413725e5f6038b2d6c3f2c21f8db28162 (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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package middlewares

import (
	"crypto/subtle"
	"fmt"
	"net/http"
	"strings"

	"github.com/labstack/echo/v4"
)

// TokenPathParam is the name of the path parameter used by URLAuthPrefix
const TokenPathParam = "token"

// URLAuthPrefix is the echo formatted url/path param which can be used for supplying an API token.
const URLAuthPrefix = "/urlAuth/:" + TokenPathParam
const urlAuthFormatter = "/urlAuth/%s"

// InvalidTokenMessage is the message set when an invalid / missing token is found.
const InvalidTokenMessage = "Invalid API Token"

// AuthMiddleware provides some data to the handler.
type AuthMiddleware struct {
	// Header is the token header which needs to be provided. For example 'X-Algod-API-Token'.
	header string

	// Tokens is the set of tokens which can be set to allow access.
	tokens [][]byte
}

// MakeAuth constructs the auth middleware function
func MakeAuth(header string, tokens []string) echo.MiddlewareFunc {
	apiTokenBytes := make([][]byte, 0)
	for _, token := range tokens {
		apiTokenBytes = append(apiTokenBytes, []byte(token))
	}

	auth := AuthMiddleware{
		header: header,
		tokens: apiTokenBytes,
	}

	return auth.handler
}

// Auth takes a logger and an array of api token and return a middleware function
// that ensures one of the api tokens was provided.
func (auth *AuthMiddleware) handler(next echo.HandlerFunc) echo.HandlerFunc {
	return func(ctx echo.Context) error {
		// OPTIONS responses never require auth
		if ctx.Request().Method == "OPTIONS" {
			return next(ctx)
		}

		// Grab the apiToken from the HTTP header, or as a bearer token
		providedToken := []byte(ctx.Request().Header.Get(auth.header))
		if len(providedToken) == 0 {
			// Accept tokens provided in a bearer token format.
			bearer, token, found := strings.Cut(ctx.Request().Header.Get("Authorization"), " ")
			if found && strings.EqualFold("Bearer", bearer) {
				providedToken = []byte(token)
			}
		}

		// Handle debug routes with /urlAuth/:token prefix.
		if ctx.Param(TokenPathParam) != "" {
			// For debug routes, we place the apiToken in the path itself
			providedToken = []byte(ctx.Param("token"))

			// Internally, pprof matches exact routes and won't match our APIToken.
			// We need to rewrite the requested path to exclude the token prefix.
			// https://git.io/fp2NO
			authPrefix := fmt.Sprintf(urlAuthFormatter, providedToken)
			// /urlAuth/[token string]/debug/pprof/ => /debug/pprof/
			newPath := strings.TrimPrefix(ctx.Request().URL.Path, authPrefix)
			ctx.SetPath(newPath)
			ctx.Request().URL.Path = newPath
		}

		// Check the tokens in constant time
		for _, tokenBytes := range auth.tokens {
			if subtle.ConstantTimeCompare(providedToken, tokenBytes) == 1 {
				// Token was correct, keep serving request
				return next(ctx)
			}
		}

		return echo.NewHTTPError(http.StatusUnauthorized, InvalidTokenMessage)
	}
}