Add token creation methods
Signed-off-by: Nikolai Rodionov <allanger@badhouseplants.net>
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.25.9
|
||||
require (
|
||||
github.com/alecthomas/assert/v2 v2.11.0
|
||||
github.com/alecthomas/kong v1.15.0
|
||||
github.com/db-operator/can-haz-password v0.1.1
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
@@ -24,6 +25,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/geozelot/intree v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -33,6 +33,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/db-operator/can-haz-password v0.1.1 h1:g0OC+9e4L751aWS9h02p/G4WrAaWgbR9LFjKLLC/hnA=
|
||||
github.com/db-operator/can-haz-password v0.1.1/go.mod h1:0iO+taMqfWqNF3ltVhDWfIghD4VkXnnaQf06xiEIX8M=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||
@@ -51,6 +53,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/geozelot/intree v1.0.0 h1:xUyiXMt0wD9zbPMOjy2rVShiUc3PGMPddPuTmi+Jy2s=
|
||||
github.com/geozelot/intree v1.0.0/go.mod h1:JrqfsNwe17AgzOM023tCXPyUB89NhaZAb8o5rzfZQ7Q=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
|
||||
@@ -3,15 +3,28 @@ package controllers
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/hash"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/token"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type TokenController struct {
|
||||
DB *sql.DB
|
||||
DB *sql.DB
|
||||
HashCost int16
|
||||
}
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrServerError = errors.New("internal server error")
|
||||
)
|
||||
|
||||
type TokenData struct {
|
||||
UUID string
|
||||
Name string
|
||||
UserID string
|
||||
CreatedAt time.Time
|
||||
LastUserAt time.Time
|
||||
@@ -23,7 +36,29 @@ type TokenData struct {
|
||||
type Scopes struct{}
|
||||
|
||||
// Create a new token, store its hash in the database and return the token value
|
||||
func (ctrl *TokenController) Create(ctx context.Context) (string, error) {
|
||||
func (ctrl *TokenController) Create(ctx context.Context, data *TokenData) (string, error) {
|
||||
id := uuid.NewString()
|
||||
log := logger.FromContext(ctx).WithValues("uuid", id)
|
||||
log.V(2).Info("Creating a new token")
|
||||
|
||||
tokenValue, err := token.GenerateToken()
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't create a token")
|
||||
return "", ErrServerError
|
||||
}
|
||||
|
||||
tokenHash, err := hash.HashPassword(tokenValue, int(ctrl.HashCost))
|
||||
if err != nil {
|
||||
log.Error(err, "Couldn't calculate token hash")
|
||||
return "", ErrServerError
|
||||
}
|
||||
|
||||
query := "INSERT INTO tokens (uuid, token_hash, user_id, scopes, created_at, expires_at) VALUES ($1, $2, $3, $4, $5, $6)"
|
||||
if _, err := ctrl.DB.Query(query, id, tokenHash, "dummy", time.Now(), data.ExpiredAt); err != nil {
|
||||
log.Error(err, "Couldn't insert a token in the database")
|
||||
return "", ErrServerError
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
||||
45
internal/helpers/token/token.go
Normal file
45
internal/helpers/token/token.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Package token should be used to generate secure tokens
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/db-operator/can-haz-password/password"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenPrefix = "sft"
|
||||
)
|
||||
|
||||
// GenerateToken generates secure password string
|
||||
func GenerateToken() (string, error) {
|
||||
generator := password.NewGenerator(newTokenRule())
|
||||
password, err := generator.Generate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", TokenPrefix, password), nil
|
||||
}
|
||||
|
||||
// Minimum length of 20 characters, maximum length of 30 characters.
|
||||
// Varied composition including special characters and uppercase and lowercase letters.
|
||||
// Excludes consecutive dashes (for hybris compatibility) and uses only url safe special characters.
|
||||
type tokenRule struct{}
|
||||
|
||||
func newTokenRule() *tokenRule {
|
||||
return &tokenRule{}
|
||||
}
|
||||
|
||||
func (r *tokenRule) Config() *password.Configuration {
|
||||
return &password.Configuration{
|
||||
Length: 40,
|
||||
CharacterClasses: []password.CharacterClassConfiguration{ // codespell:ignore
|
||||
{Characters: password.LowercaseCharacters + password.UppercaseCharacters, Minimum: 10},
|
||||
{Characters: password.DigitCharacters, Minimum: 8},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tokenRule) Valid(password []rune) bool {
|
||||
return true
|
||||
}
|
||||
16
internal/helpers/token/token_test.go
Normal file
16
internal/helpers/token/token_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package token_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/token"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUnitTokenGeneration(t *testing.T) {
|
||||
token, err := token.GenerateToken()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, token, 44)
|
||||
assert.True(t, strings.HasPrefix(token, "sft_"))
|
||||
}
|
||||
Reference in New Issue
Block a user