mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	Consume hcaptcha and pwn deps (#22610)
This PR just consumes the [hcaptcha](https://gitea.com/jolheiser/hcaptcha) and [haveibeenpwned](https://gitea.com/jolheiser/pwn) modules directly into Gitea. Also let this serve as a notice that I'm fine with transferring my license (which was already MIT) from my own name to "The Gitea Authors". Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
		
							
								
								
									
										47
									
								
								modules/hcaptcha/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								modules/hcaptcha/error.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package hcaptcha
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ErrMissingInputSecret           ErrorCode = "missing-input-secret"
 | 
			
		||||
	ErrInvalidInputSecret           ErrorCode = "invalid-input-secret"
 | 
			
		||||
	ErrMissingInputResponse         ErrorCode = "missing-input-response"
 | 
			
		||||
	ErrInvalidInputResponse         ErrorCode = "invalid-input-response"
 | 
			
		||||
	ErrBadRequest                   ErrorCode = "bad-request"
 | 
			
		||||
	ErrInvalidOrAlreadySeenResponse ErrorCode = "invalid-or-already-seen-response"
 | 
			
		||||
	ErrNotUsingDummyPasscode        ErrorCode = "not-using-dummy-passcode"
 | 
			
		||||
	ErrSitekeySecretMismatch        ErrorCode = "sitekey-secret-mismatch"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrorCode is any possible error from hCaptcha
 | 
			
		||||
type ErrorCode string
 | 
			
		||||
 | 
			
		||||
// String fulfills the Stringer interface
 | 
			
		||||
func (err ErrorCode) String() string {
 | 
			
		||||
	switch err {
 | 
			
		||||
	case ErrMissingInputSecret:
 | 
			
		||||
		return "Your secret key is missing."
 | 
			
		||||
	case ErrInvalidInputSecret:
 | 
			
		||||
		return "Your secret key is invalid or malformed."
 | 
			
		||||
	case ErrMissingInputResponse:
 | 
			
		||||
		return "The response parameter (verification token) is missing."
 | 
			
		||||
	case ErrInvalidInputResponse:
 | 
			
		||||
		return "The response parameter (verification token) is invalid or malformed."
 | 
			
		||||
	case ErrBadRequest:
 | 
			
		||||
		return "The request is invalid or malformed."
 | 
			
		||||
	case ErrInvalidOrAlreadySeenResponse:
 | 
			
		||||
		return "The response parameter has already been checked, or has another issue."
 | 
			
		||||
	case ErrNotUsingDummyPasscode:
 | 
			
		||||
		return "You have used a testing sitekey but have not used its matching secret."
 | 
			
		||||
	case ErrSitekeySecretMismatch:
 | 
			
		||||
		return "The sitekey is not registered with the provided secret."
 | 
			
		||||
	default:
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Error fulfills the error interface
 | 
			
		||||
func (err ErrorCode) Error() string {
 | 
			
		||||
	return err.String()
 | 
			
		||||
}
 | 
			
		||||
@@ -5,20 +5,127 @@ package hcaptcha
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"go.jolheiser.com/hcaptcha"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const verifyURL = "https://hcaptcha.com/siteverify"
 | 
			
		||||
 | 
			
		||||
// Client is an hCaptcha client
 | 
			
		||||
type Client struct {
 | 
			
		||||
	ctx  context.Context
 | 
			
		||||
	http *http.Client
 | 
			
		||||
 | 
			
		||||
	secret string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PostOptions are optional post form values
 | 
			
		||||
type PostOptions struct {
 | 
			
		||||
	RemoteIP string
 | 
			
		||||
	Sitekey  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientOption is a func to modify a new Client
 | 
			
		||||
type ClientOption func(*Client)
 | 
			
		||||
 | 
			
		||||
// WithHTTP sets the http.Client of a Client
 | 
			
		||||
func WithHTTP(httpClient *http.Client) func(*Client) {
 | 
			
		||||
	return func(hClient *Client) {
 | 
			
		||||
		hClient.http = httpClient
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithContext sets the context.Context of a Client
 | 
			
		||||
func WithContext(ctx context.Context) func(*Client) {
 | 
			
		||||
	return func(hClient *Client) {
 | 
			
		||||
		hClient.ctx = ctx
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New returns a new hCaptcha Client
 | 
			
		||||
func New(secret string, options ...ClientOption) (*Client, error) {
 | 
			
		||||
	if strings.TrimSpace(secret) == "" {
 | 
			
		||||
		return nil, ErrMissingInputSecret
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := &Client{
 | 
			
		||||
		ctx:    context.Background(),
 | 
			
		||||
		http:   http.DefaultClient,
 | 
			
		||||
		secret: secret,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, opt := range options {
 | 
			
		||||
		opt(client)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Response is an hCaptcha response
 | 
			
		||||
type Response struct {
 | 
			
		||||
	Success     bool        `json:"success"`
 | 
			
		||||
	ChallengeTS string      `json:"challenge_ts"`
 | 
			
		||||
	Hostname    string      `json:"hostname"`
 | 
			
		||||
	Credit      bool        `json:"credit,omitempty"`
 | 
			
		||||
	ErrorCodes  []ErrorCode `json:"error-codes"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify checks the response against the hCaptcha API
 | 
			
		||||
func (c *Client) Verify(token string, opts PostOptions) (*Response, error) {
 | 
			
		||||
	if strings.TrimSpace(token) == "" {
 | 
			
		||||
		return nil, ErrMissingInputResponse
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	post := url.Values{
 | 
			
		||||
		"secret":   []string{c.secret},
 | 
			
		||||
		"response": []string{token},
 | 
			
		||||
	}
 | 
			
		||||
	if strings.TrimSpace(opts.RemoteIP) != "" {
 | 
			
		||||
		post.Add("remoteip", opts.RemoteIP)
 | 
			
		||||
	}
 | 
			
		||||
	if strings.TrimSpace(opts.Sitekey) != "" {
 | 
			
		||||
		post.Add("sitekey", opts.Sitekey)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Basically a copy of http.PostForm, but with a context
 | 
			
		||||
	req, err := http.NewRequestWithContext(c.ctx, http.MethodPost, verifyURL, strings.NewReader(post.Encode()))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
	resp, err := c.http.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	var response *Response
 | 
			
		||||
	if err := json.Unmarshal(body, &response); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify calls hCaptcha API to verify token
 | 
			
		||||
func Verify(ctx context.Context, response string) (bool, error) {
 | 
			
		||||
	client, err := hcaptcha.New(setting.Service.HcaptchaSecret, hcaptcha.WithContext(ctx))
 | 
			
		||||
	client, err := New(setting.Service.HcaptchaSecret, WithContext(ctx))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Verify(response, hcaptcha.PostOptions{
 | 
			
		||||
	resp, err := client.Verify(response, PostOptions{
 | 
			
		||||
		Sitekey: setting.Service.HcaptchaSitekey,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								modules/hcaptcha/hcaptcha_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								modules/hcaptcha/hcaptcha_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package hcaptcha
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	dummySiteKey = "10000000-ffff-ffff-ffff-000000000001"
 | 
			
		||||
	dummySecret  = "0x0000000000000000000000000000000000000000"
 | 
			
		||||
	dummyToken   = "10000000-aaaa-bbbb-cccc-000000000001"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	os.Exit(m.Run())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCaptcha(t *testing.T) {
 | 
			
		||||
	tt := []struct {
 | 
			
		||||
		Name   string
 | 
			
		||||
		Secret string
 | 
			
		||||
		Token  string
 | 
			
		||||
		Error  ErrorCode
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			Name:   "Success",
 | 
			
		||||
			Secret: dummySecret,
 | 
			
		||||
			Token:  dummyToken,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:  "Missing Secret",
 | 
			
		||||
			Token: dummyToken,
 | 
			
		||||
			Error: ErrMissingInputSecret,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:   "Missing Token",
 | 
			
		||||
			Secret: dummySecret,
 | 
			
		||||
			Error:  ErrMissingInputResponse,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:   "Invalid Token",
 | 
			
		||||
			Secret: dummySecret,
 | 
			
		||||
			Token:  "test",
 | 
			
		||||
			Error:  ErrInvalidInputResponse,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range tt {
 | 
			
		||||
		t.Run(tc.Name, func(t *testing.T) {
 | 
			
		||||
			client, err := New(tc.Secret, WithHTTP(&http.Client{
 | 
			
		||||
				Timeout: time.Second * 5,
 | 
			
		||||
			}))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// The only error that can be returned from creating a client
 | 
			
		||||
				if tc.Error == ErrMissingInputSecret && err == ErrMissingInputSecret {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				t.Log(err)
 | 
			
		||||
				t.FailNow()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			resp, err := client.Verify(tc.Token, PostOptions{
 | 
			
		||||
				Sitekey: dummySiteKey,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// The only error that can be returned prior to the request
 | 
			
		||||
				if tc.Error == ErrMissingInputResponse && err == ErrMissingInputResponse {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				t.Log(err)
 | 
			
		||||
				t.FailNow()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if tc.Error.String() != "" {
 | 
			
		||||
				if resp.Success {
 | 
			
		||||
					t.Log("Verification should fail.")
 | 
			
		||||
					t.Fail()
 | 
			
		||||
				}
 | 
			
		||||
				if len(resp.ErrorCodes) == 0 {
 | 
			
		||||
					t.Log("hCaptcha should have returned an error.")
 | 
			
		||||
					t.Fail()
 | 
			
		||||
				}
 | 
			
		||||
				var hasErr bool
 | 
			
		||||
				for _, err := range resp.ErrorCodes {
 | 
			
		||||
					if strings.EqualFold(err.String(), tc.Error.String()) {
 | 
			
		||||
						hasErr = true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if !hasErr {
 | 
			
		||||
					t.Log("hCaptcha did not return the error being tested")
 | 
			
		||||
					t.Fail()
 | 
			
		||||
				}
 | 
			
		||||
			} else if !resp.Success {
 | 
			
		||||
				t.Log("Verification should succeed.")
 | 
			
		||||
				t.Fail()
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,9 +6,8 @@ package password
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/password/pwn"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"go.jolheiser.com/pwn"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// IsPwned checks whether a password has been pwned
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								modules/password/pwn/pwn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								modules/password/pwn/pwn.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package pwn
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/sha1"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const passwordURL = "https://api.pwnedpasswords.com/range/"
 | 
			
		||||
 | 
			
		||||
// ErrEmptyPassword is an empty password error
 | 
			
		||||
var ErrEmptyPassword = errors.New("password cannot be empty")
 | 
			
		||||
 | 
			
		||||
// Client is a HaveIBeenPwned client
 | 
			
		||||
type Client struct {
 | 
			
		||||
	ctx  context.Context
 | 
			
		||||
	http *http.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New returns a new HaveIBeenPwned Client
 | 
			
		||||
func New(options ...ClientOption) *Client {
 | 
			
		||||
	client := &Client{
 | 
			
		||||
		ctx:  context.Background(),
 | 
			
		||||
		http: http.DefaultClient,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, opt := range options {
 | 
			
		||||
		opt(client)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientOption is a way to modify a new Client
 | 
			
		||||
type ClientOption func(*Client)
 | 
			
		||||
 | 
			
		||||
// WithHTTP will set the http.Client of a Client
 | 
			
		||||
func WithHTTP(httpClient *http.Client) func(pwnClient *Client) {
 | 
			
		||||
	return func(pwnClient *Client) {
 | 
			
		||||
		pwnClient.http = httpClient
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithContext will set the context.Context of a Client
 | 
			
		||||
func WithContext(ctx context.Context) func(pwnClient *Client) {
 | 
			
		||||
	return func(pwnClient *Client) {
 | 
			
		||||
		pwnClient.ctx = ctx
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newRequest(ctx context.Context, method, url string, body io.ReadCloser) (*http.Request, error) {
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, method, url, body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Add("User-Agent", "Gitea "+setting.AppVer)
 | 
			
		||||
	return req, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckPassword returns the number of times a password has been compromised
 | 
			
		||||
// Adding padding will make requests more secure, however is also slower
 | 
			
		||||
// because artificial responses will be added to the response
 | 
			
		||||
// For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
 | 
			
		||||
func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
 | 
			
		||||
	if strings.TrimSpace(pw) == "" {
 | 
			
		||||
		return -1, ErrEmptyPassword
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sha := sha1.New()
 | 
			
		||||
	sha.Write([]byte(pw))
 | 
			
		||||
	enc := hex.EncodeToString(sha.Sum(nil))
 | 
			
		||||
	prefix, suffix := enc[:5], enc[5:]
 | 
			
		||||
 | 
			
		||||
	req, err := newRequest(c.ctx, http.MethodGet, fmt.Sprintf("%s%s", passwordURL, prefix), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, nil
 | 
			
		||||
	}
 | 
			
		||||
	if padding {
 | 
			
		||||
		req.Header.Add("Add-Padding", "true")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := c.http.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	for _, pair := range strings.Split(string(body), "\n") {
 | 
			
		||||
		parts := strings.Split(pair, ":")
 | 
			
		||||
		if len(parts) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if strings.EqualFold(suffix, parts[0]) {
 | 
			
		||||
			count, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return -1, err
 | 
			
		||||
			}
 | 
			
		||||
			return int(count), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								modules/password/pwn/pwn_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								modules/password/pwn/pwn_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package pwn
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var client = New(WithHTTP(&http.Client{
 | 
			
		||||
	Timeout: time.Second * 2,
 | 
			
		||||
}))
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	rand.Seed(time.Now().Unix())
 | 
			
		||||
	os.Exit(m.Run())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPassword(t *testing.T) {
 | 
			
		||||
	// Check input error
 | 
			
		||||
	_, err := client.CheckPassword("", false)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Log("blank input should return an error")
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if !errors.Is(err, ErrEmptyPassword) {
 | 
			
		||||
		t.Log("blank input should return ErrEmptyPassword")
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Should fail
 | 
			
		||||
	fail := "password1234"
 | 
			
		||||
	count, err := client.CheckPassword(fail, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Log(err)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		t.Logf("%s should fail as a password\n", fail)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Should fail (with padding)
 | 
			
		||||
	failPad := "administrator"
 | 
			
		||||
	count, err = client.CheckPassword(failPad, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Log(err)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		t.Logf("%s should fail as a password\n", failPad)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Checking for a "good" password isn't going to be perfect, but we can give it a good try
 | 
			
		||||
	// with hopefully minimal error. Try five times?
 | 
			
		||||
	var good bool
 | 
			
		||||
	var pw string
 | 
			
		||||
	for idx := 0; idx <= 5; idx++ {
 | 
			
		||||
		pw = testPassword()
 | 
			
		||||
		count, err = client.CheckPassword(pw, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Log(err)
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if count == 0 {
 | 
			
		||||
			good = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !good {
 | 
			
		||||
		t.Log("no generated passwords passed. there is a chance this is a fluke")
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Again, but with padded responses
 | 
			
		||||
	good = false
 | 
			
		||||
	for idx := 0; idx <= 5; idx++ {
 | 
			
		||||
		pw = testPassword()
 | 
			
		||||
		count, err = client.CheckPassword(pw, true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Log(err)
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if count == 0 {
 | 
			
		||||
			good = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !good {
 | 
			
		||||
		t.Log("no generated passwords passed. there is a chance this is a fluke")
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Credit to https://golangbyexample.com/generate-random-password-golang/
 | 
			
		||||
// DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR
 | 
			
		||||
var (
 | 
			
		||||
	lowerCharSet   = "abcdedfghijklmnopqrst"
 | 
			
		||||
	upperCharSet   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | 
			
		||||
	specialCharSet = "!@#$%&*"
 | 
			
		||||
	numberSet      = "0123456789"
 | 
			
		||||
	allCharSet     = lowerCharSet + upperCharSet + specialCharSet + numberSet
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testPassword() string {
 | 
			
		||||
	var password strings.Builder
 | 
			
		||||
 | 
			
		||||
	// Set special character
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.Intn(len(specialCharSet))
 | 
			
		||||
		password.WriteString(string(specialCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set numeric
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.Intn(len(numberSet))
 | 
			
		||||
		password.WriteString(string(numberSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set uppercase
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.Intn(len(upperCharSet))
 | 
			
		||||
		password.WriteString(string(upperCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.Intn(len(allCharSet))
 | 
			
		||||
		password.WriteString(string(allCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
	inRune := []rune(password.String())
 | 
			
		||||
	rand.Shuffle(len(inRune), func(i, j int) {
 | 
			
		||||
		inRune[i], inRune[j] = inRune[j], inRune[i]
 | 
			
		||||
	})
 | 
			
		||||
	return string(inRune)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user