mirror of
https://github.com/caddyserver/caddy.git
synced 2025-10-19 15:53:17 +00:00
Merge dfee04c128
into f5f25d845a
This commit is contained in:
commit
83a0593043
7 changed files with 376 additions and 16 deletions
119
caddytest/integration/authenticate_test.go
Normal file
119
caddytest/integration/authenticate_test.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthentication(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
"admin": {
|
||||||
|
"listen": "localhost:2999"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities": {
|
||||||
|
"local": {
|
||||||
|
"install_trust": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"http_port": 9080,
|
||||||
|
"https_port": 9443,
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":9080"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/basic"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "authentication",
|
||||||
|
"providers": {
|
||||||
|
"http_basic": {
|
||||||
|
"hash_cache": {},
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"username": "Aladdin",
|
||||||
|
"password": "$2a$14$U5nG2p.Ac09gzn9oo5aRe.YnsXn30UdXA6pRUn45KFqADG636dRHa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/proxy"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "authentication",
|
||||||
|
"status_code": 407,
|
||||||
|
"providers": {
|
||||||
|
"http_basic": {
|
||||||
|
"hash_cache": {},
|
||||||
|
"authorization_header": "Proxy-Authorization",
|
||||||
|
"authenticate_header": "Proxy-Authenticate",
|
||||||
|
"realm": "HTTP proxy",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"username": "Aladdin",
|
||||||
|
"password": "$2a$14$U5nG2p.Ac09gzn9oo5aRe.YnsXn30UdXA6pRUn45KFqADG636dRHa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, "json")
|
||||||
|
|
||||||
|
assertHeader := func(tb testing.TB, resp *http.Response, header, want string) {
|
||||||
|
if actual := resp.Header.Get(header); actual != want {
|
||||||
|
tb.Errorf("expected %s header to be %s, but was %s", header, want, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := tester.AssertGetResponse("http://localhost:9080/basic", http.StatusUnauthorized, "")
|
||||||
|
assertHeader(t, resp, "WWW-Authenticate", `Basic realm="restricted"`)
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://Aladdin:open%20sesame@localhost:9080/basic", http.StatusOK, "")
|
||||||
|
|
||||||
|
tester.AssertGetResponse("http://localhost:9080/proxy", http.StatusProxyAuthRequired, "")
|
||||||
|
|
||||||
|
resp, _ = tester.AssertGetResponse("http://Aladdin:open%20sesame@localhost:9080/proxy", http.StatusProxyAuthRequired, "")
|
||||||
|
assertHeader(t, resp, "Proxy-Authenticate", `Basic realm="HTTP proxy"`)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/proxy", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create request %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")))
|
||||||
|
tester.AssertResponseCode(req, http.StatusOK)
|
||||||
|
}
|
|
@ -21,33 +21,33 @@
|
||||||
// Original source, copied because the package was marked internal:
|
// Original source, copied because the package was marked internal:
|
||||||
// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print.go
|
// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print.go
|
||||||
|
|
||||||
package reverseproxy
|
package ascii
|
||||||
|
|
||||||
// asciiEqualFold is strings.EqualFold, ASCII only. It reports whether s and t
|
// EqualFold is strings.EqualFold, ASCII only. It reports whether s and t
|
||||||
// are equal, ASCII-case-insensitively.
|
// are equal, ASCII-case-insensitively.
|
||||||
func asciiEqualFold(s, t string) bool {
|
func EqualFold(s, t string) bool {
|
||||||
if len(s) != len(t) {
|
if len(s) != len(t) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if asciiLower(s[i]) != asciiLower(t[i]) {
|
if lower(s[i]) != lower(t[i]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// asciiLower returns the ASCII lowercase version of b.
|
// lower returns the ASCII lowercase version of b.
|
||||||
func asciiLower(b byte) byte {
|
func lower(b byte) byte {
|
||||||
if 'A' <= b && b <= 'Z' {
|
if 'A' <= b && b <= 'Z' {
|
||||||
return b + ('a' - 'A')
|
return b + ('a' - 'A')
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// asciiIsPrint returns whether s is ASCII and printable according to
|
// IsPrint returns whether s is ASCII and printable according to
|
||||||
// https://tools.ietf.org/html/rfc20#section-4.2.
|
// https://tools.ietf.org/html/rfc20#section-4.2.
|
||||||
func asciiIsPrint(s string) bool {
|
func IsPrint(s string) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] < ' ' || s[i] > '~' {
|
if s[i] < ' ' || s[i] > '~' {
|
||||||
return false
|
return false
|
|
@ -21,7 +21,7 @@
|
||||||
// Original source, copied because the package was marked internal:
|
// Original source, copied because the package was marked internal:
|
||||||
// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print_test.go
|
// https://github.com/golang/go/blob/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a/src/net/http/internal/ascii/print_test.go
|
||||||
|
|
||||||
package reverseproxy
|
package ascii
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ func TestEqualFold(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := asciiEqualFold(tt.a, tt.b); got != tt.want {
|
if got := EqualFold(tt.a, tt.b); got != tt.want {
|
||||||
t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want)
|
t.Errorf("AsciiEqualFold(%q,%q): got %v want %v", tt.a, tt.b, got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -106,7 +106,7 @@ func TestIsPrint(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := asciiIsPrint(tt.in); got != tt.want {
|
if got := IsPrint(tt.in); got != tt.want {
|
||||||
t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want)
|
t.Errorf("IsASCIIPrint(%q): got %v want %v", tt.in, got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/internal/ascii"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -41,6 +42,12 @@ type HTTPBasicAuth struct {
|
||||||
// The list of accounts to authenticate.
|
// The list of accounts to authenticate.
|
||||||
AccountList []Account `json:"accounts,omitempty"`
|
AccountList []Account `json:"accounts,omitempty"`
|
||||||
|
|
||||||
|
// The name of the HTTP header to check. Default: Authorization
|
||||||
|
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||||
|
|
||||||
|
// The name of the HTTP header to check. Default: WWW-Authenticate
|
||||||
|
AuthenticateHeader string `json:"authenticate_header,omitempty"`
|
||||||
|
|
||||||
// The name of the realm. Default: restricted
|
// The name of the realm. Default: restricted
|
||||||
Realm string `json:"realm,omitempty"`
|
Realm string `json:"realm,omitempty"`
|
||||||
|
|
||||||
|
@ -141,7 +148,7 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||||
|
|
||||||
// Authenticate validates the user credentials in req and returns the user, if valid.
|
// Authenticate validates the user credentials in req and returns the user, if valid.
|
||||||
func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) {
|
func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) {
|
||||||
username, plaintextPasswordStr, ok := req.BasicAuth()
|
username, plaintextPasswordStr, ok := hba.credentials(req)
|
||||||
if !ok {
|
if !ok {
|
||||||
return hba.promptForCredentials(w, nil)
|
return hba.promptForCredentials(w, nil)
|
||||||
}
|
}
|
||||||
|
@ -162,6 +169,40 @@ func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request)
|
||||||
return User{ID: username}, true, nil
|
return User{ID: username}, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hba HTTPBasicAuth) credentials(r *http.Request) (username, password string, ok bool) {
|
||||||
|
header := hba.AuthorizationHeader
|
||||||
|
if header == "" {
|
||||||
|
header = "Authorization"
|
||||||
|
}
|
||||||
|
auth := r.Header.Get(header)
|
||||||
|
if auth == "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return parseBasicAuth(auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBasicAuth parses an HTTP Basic Authentication string.
|
||||||
|
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
|
||||||
|
//
|
||||||
|
// Copied from Go’s net/http.parseBasicAuth unexported function.
|
||||||
|
func parseBasicAuth(auth string) (username, password string, ok bool) {
|
||||||
|
const prefix = "Basic "
|
||||||
|
// Case insensitive prefix match. See https://go.dev/issue/22736.
|
||||||
|
if len(auth) < len(prefix) || !ascii.EqualFold(auth[:len(prefix)], prefix) {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
cs := string(c)
|
||||||
|
username, password, ok = strings.Cut(cs, ":")
|
||||||
|
if !ok {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return username, password, true
|
||||||
|
}
|
||||||
|
|
||||||
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
|
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
|
||||||
compare := func() (bool, error) {
|
compare := func() (bool, error) {
|
||||||
return hba.Hash.Compare(account.password, plaintextPassword)
|
return hba.Hash.Compare(account.password, plaintextPassword)
|
||||||
|
@ -212,7 +253,11 @@ func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error)
|
||||||
if realm == "" {
|
if realm == "" {
|
||||||
realm = "restricted"
|
realm = "restricted"
|
||||||
}
|
}
|
||||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
header := hba.AuthenticateHeader
|
||||||
|
if header == "" {
|
||||||
|
header = "WWW-Authenticate"
|
||||||
|
}
|
||||||
|
w.Header().Set(header, fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||||
return User{}, false, err
|
return User{}, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
178
modules/caddyhttp/caddyauth/basicauth_test.go
Normal file
178
modules/caddyhttp/caddyauth/basicauth_test.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package caddyauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseBasicAuth(t *testing.T) {
|
||||||
|
type basicAuthTest struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
ok bool
|
||||||
|
}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
header string
|
||||||
|
want basicAuthTest
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty header",
|
||||||
|
header: "",
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid header",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "Aladdin",
|
||||||
|
password: "open sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Upper case scheme",
|
||||||
|
header: "BASIC " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "Aladdin",
|
||||||
|
password: "open sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lower case scheme",
|
||||||
|
header: "basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "Aladdin",
|
||||||
|
password: "open sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mixed case scheme",
|
||||||
|
header: "BaSiC " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "Aladdin",
|
||||||
|
password: "open sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Password with colon",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open:sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "Aladdin",
|
||||||
|
password: "open:sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty username and password",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte(":")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing password",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty username",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte(":open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "open sesame",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing space between scheme and credentials",
|
||||||
|
header: "Basic" + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple spaces between scheme and credentials",
|
||||||
|
header: "Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing scheme",
|
||||||
|
header: base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")),
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing credentials",
|
||||||
|
header: "Basic ",
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Credentials are not base64-encoded",
|
||||||
|
header: "Basic Aladdin:open sesame",
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid scheme",
|
||||||
|
header: `Digest username="Aladdin"`,
|
||||||
|
want: basicAuthTest{
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(*testing.T) {
|
||||||
|
username, password, ok := parseBasicAuth(tt.header)
|
||||||
|
actual := basicAuthTest{username, password, ok}
|
||||||
|
if tt.want != actual {
|
||||||
|
t.Errorf("BasicAuth() = %#v, want %#v", actual, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,10 @@
|
||||||
package caddyauth
|
package caddyauth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
@ -29,6 +31,8 @@ func init() {
|
||||||
caddy.RegisterModule(Authentication{})
|
caddy.RegisterModule(Authentication{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errNotAuthenticated = errors.New("not authenticated")
|
||||||
|
|
||||||
// Authentication is a middleware which provides user authentication.
|
// Authentication is a middleware which provides user authentication.
|
||||||
// Rejects requests with HTTP 401 if the request is not authenticated.
|
// Rejects requests with HTTP 401 if the request is not authenticated.
|
||||||
//
|
//
|
||||||
|
@ -47,6 +51,11 @@ type Authentication struct {
|
||||||
// all requests will always be unauthenticated.
|
// all requests will always be unauthenticated.
|
||||||
ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"`
|
ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"`
|
||||||
|
|
||||||
|
// The HTTP status code to respind with for unauthenticated requests.
|
||||||
|
// Can be either an integer or a string if placeholders are needed.
|
||||||
|
// Optional. Default is 401.
|
||||||
|
StatusCode caddyhttp.WeakString `json:"status_code,omitempty"`
|
||||||
|
|
||||||
Providers map[string]Authenticator `json:"-"`
|
Providers map[string]Authenticator `json:"-"`
|
||||||
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
@ -96,7 +105,15 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !authed {
|
if !authed {
|
||||||
return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated"))
|
statusCode := http.StatusUnauthorized
|
||||||
|
if codeStr := a.StatusCode.String(); codeStr != "" {
|
||||||
|
intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
|
||||||
|
if err != nil {
|
||||||
|
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
statusCode = intVal
|
||||||
|
}
|
||||||
|
return caddyhttp.Error(statusCode, errNotAuthenticated)
|
||||||
}
|
}
|
||||||
|
|
||||||
repl.Set("http.auth.user.id", user.ID)
|
repl.Set("http.auth.user.id", user.ID)
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/internal/ascii"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,13 +64,13 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
||||||
|
|
||||||
// Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a
|
// Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a
|
||||||
// We know reqUpType is ASCII, it's checked by the caller.
|
// We know reqUpType is ASCII, it's checked by the caller.
|
||||||
if !asciiIsPrint(resUpType) {
|
if !ascii.IsPrint(resUpType) {
|
||||||
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil {
|
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil {
|
||||||
c.Write(zap.String("backend_upgrade", resUpType))
|
c.Write(zap.String("backend_upgrade", resUpType))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !asciiEqualFold(reqUpType, resUpType) {
|
if !ascii.EqualFold(reqUpType, resUpType) {
|
||||||
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil {
|
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil {
|
||||||
c.Write(
|
c.Write(
|
||||||
zap.String("backend_upgrade", resUpType),
|
zap.String("backend_upgrade", resUpType),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue