Skip to content

Commit

Permalink
feat(server): validate username
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed May 2, 2023
1 parent 94ae6c8 commit 2366f90
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 13 deletions.
6 changes: 6 additions & 0 deletions server/backend/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"text/template"

"github.com/charmbracelet/log"
Expand Down Expand Up @@ -410,6 +411,11 @@ func (d *SqliteBackend) SetProjectName(repo string, name string) error {
//
// It implements backend.Backend.
func (d *SqliteBackend) AddCollaborator(repo string, username string) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return err
}

repo = utils.SanitizeRepo(repo)
return wrapDbErr(wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
_, err := tx.Exec(`INSERT INTO collab (user_id, repo_id, updated_at)
Expand Down
48 changes: 40 additions & 8 deletions server/backend/sqlite/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package sqlite

import (
"context"
"strings"

"github.com/charmbracelet/soft-serve/server/backend"
"github.com/charmbracelet/soft-serve/server/utils"
"github.com/jmoiron/sqlx"
"golang.org/x/crypto/ssh"
)
Expand Down Expand Up @@ -136,6 +138,11 @@ func (d *SqliteBackend) AccessLevelByPublicKey(repo string, pk ssh.PublicKey) ba
//
// It implements backend.Backend.
func (d *SqliteBackend) AddPublicKey(username string, pk ssh.PublicKey) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return err
}

return wrapDbErr(
wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
var userID int
Expand All @@ -154,16 +161,16 @@ func (d *SqliteBackend) AddPublicKey(username string, pk ssh.PublicKey) error {
//
// It implements backend.Backend.
func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (backend.User, error) {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return nil, err
}

var user *User
if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
into := "INSERT INTO user (username"
values := "VALUES (?"
args := []interface{}{username}
if opts.Admin {
into += ", admin"
values += ", ?"
args = append(args, opts.Admin)
}
into := "INSERT INTO user (username, admin"
values := "VALUES (?, ?"
args := []interface{}{username, opts.Admin}
into += ", updated_at)"
values += ", CURRENT_TIMESTAMP)"

Expand Down Expand Up @@ -202,6 +209,11 @@ func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (b
//
// It implements backend.Backend.
func (d *SqliteBackend) DeleteUser(username string) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return err
}

return wrapDbErr(
wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
_, err := tx.Exec("DELETE FROM user WHERE username = ?", username)
Expand All @@ -226,6 +238,11 @@ func (d *SqliteBackend) RemovePublicKey(username string, pk ssh.PublicKey) error

// ListPublicKeys lists the public keys of a user.
func (d *SqliteBackend) ListPublicKeys(username string) ([]ssh.PublicKey, error) {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return nil, err
}

keys := make([]ssh.PublicKey, 0)
if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
var keyStrings []string
Expand Down Expand Up @@ -256,6 +273,11 @@ func (d *SqliteBackend) ListPublicKeys(username string) ([]ssh.PublicKey, error)
//
// It implements backend.Backend.
func (d *SqliteBackend) SetUsername(username string, newUsername string) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return err
}

return wrapDbErr(
wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
_, err := tx.Exec("UPDATE user SET username = ? WHERE username = ?", newUsername, username)
Expand All @@ -268,6 +290,11 @@ func (d *SqliteBackend) SetUsername(username string, newUsername string) error {
//
// It implements backend.Backend.
func (d *SqliteBackend) SetAdmin(username string, admin bool) error {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return err
}

return wrapDbErr(
wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
_, err := tx.Exec("UPDATE user SET admin = ? WHERE username = ?", admin, username)
Expand All @@ -280,6 +307,11 @@ func (d *SqliteBackend) SetAdmin(username string, admin bool) error {
//
// It implements backend.Backend.
func (d *SqliteBackend) User(username string) (backend.User, error) {
username = strings.ToLower(username)
if err := utils.ValidateUsername(username); err != nil {
return nil, err
}

if err := wrapTx(d.db, context.Background(), func(tx *sqlx.Tx) error {
return tx.Get(&username, "SELECT username FROM user WHERE username = ?", username)
}); err != nil {
Expand Down
15 changes: 10 additions & 5 deletions server/cmd/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,24 @@ func userCommand() *cobra.Command {
Args: cobra.ExactArgs(1),
PersistentPreRunE: checkIfAdmin,
RunE: func(cmd *cobra.Command, args []string) error {
var pubkeys []ssh.PublicKey
cfg, _ := fromContext(cmd)
username := args[0]
pk, _, err := backend.ParseAuthorizedKey(key)
if err != nil {
return err
if key != "" {
pk, _, err := backend.ParseAuthorizedKey(key)
if err != nil {
return err
}

pubkeys = []ssh.PublicKey{pk}
}

opts := backend.UserOptions{
Admin: admin,
PublicKeys: []ssh.PublicKey{pk},
PublicKeys: pubkeys,
}

_, err = cfg.Backend.CreateUser(username, opts)
_, err := cfg.Backend.CreateUser(username, opts)
return err
},
}
Expand Down
5 changes: 5 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func ParseConfig(path string) (*Config, error) {
return cfg, nil
}

// WriteConfig writes the configuration to the given file.
func WriteConfig(path string, cfg *Config) error {
return os.WriteFile(path, []byte(newConfigFile(cfg)), 0o600) // nolint: errcheck
}

// DefaultConfig returns a Config with the values populated with the defaults
// or specified environment variables.
func DefaultConfig() *Config {
Expand Down
21 changes: 21 additions & 0 deletions server/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package utils

import (
"fmt"
"path/filepath"
"strings"
"unicode"
)

// SanitizeRepo returns a sanitized version of the given repository name.
Expand All @@ -12,3 +14,22 @@ func SanitizeRepo(repo string) string {
repo = strings.TrimSuffix(repo, ".git")
return repo
}

// ValidateUsername returns an error if any of the given usernames are invalid.
func ValidateUsername(username string) error {
if username == "" {
return fmt.Errorf("username cannot be empty")
}

if !unicode.IsLetter(rune(username[0])) {
return fmt.Errorf("username must start with a letter")
}

for _, r := range username {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '-' {
return fmt.Errorf("username can only contain letters, numbers, and hyphens")
}
}

return nil
}

0 comments on commit 2366f90

Please sign in to comment.