package authz import ( "crypto/rand" "encoding/csv" "encoding/hex" "fmt" "log/slog" "os" "golang.org/x/crypto/bcrypt" ) // TokenSize is the number of random bytes used for token generation const TokenSize = 32 // NewTokenMap create a new token map func NewTokenMap() TokenMap { return TokenMap{} } // TokenMap a map of username,hash type TokenMap map[string]string // LoadTokensFromFile load tokens from a csv into a map func (tm TokenMap) LoadTokensFromFile(path string) error { // TODO this should be configurable contents, err := os.Open(path) if err != nil { slog.Error("File reading error", slog.Any("error", err)) return err } defer contents.Close() r := csv.NewReader(contents) tokens, err := r.ReadAll() if err != nil { fmt.Println("File reading error", err) return err } for _, acctToken := range tokens { acct, hash := acctToken[0], acctToken[1] tm[acct] = hash } return err } // GenerateNewToken generates a new secure random token and its bcrypt hash // The token is 32 bytes (256 bits) of cryptographically secure random data // encoded as a 64-character hex string. The hash is a bcrypt hash of the // random bytes using default cost parameters. func GenerateNewToken() (string, string, error) { tokenBytes := make([]byte, TokenSize) if _, err := rand.Read(tokenBytes); err != nil { return "", "", fmt.Errorf("failed to generate random token: %w", err) } hashBytes, err := bcrypt.GenerateFromPassword(tokenBytes, bcrypt.DefaultCost) if err != nil { return "", "", err } token := hex.EncodeToString(tokenBytes) hash := string(hashBytes) return token, hash, nil }