Skip to content
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ config.certify.yml
/.deepsec

# jetbrains
/.idea/
/.idea/
16 changes: 11 additions & 5 deletions cmd/tinyauth/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package main

import (
"encoding/json"
"fmt"
"strings"

"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model"
Expand All @@ -11,15 +11,21 @@ import (
func configCmd(tconfig *model.Config, loaders []cli.ResourceLoader) *cli.Command {
return &cli.Command{
Name: "config",
Description: "Print the configuration of Tinyauth",
Description: "Dump the current configuration in YAML format, useful for debugging",
Configuration: tconfig,
Resources: loaders,
Run: func(_ []string) error {
jsonBytes, err := json.MarshalIndent(tconfig, "", " ")
buf := strings.Builder{}

fmt.Fprint(&buf, "Your current configuration in YAML is:\n\n")

err := renderYamlToBuf(&buf, tconfig)

if err != nil {
return fmt.Errorf("failed to marshal configuration: %w", err)
return fmt.Errorf("failed to render yaml config: %w", err)
}
fmt.Println(string(jsonBytes))

fmt.Print(buf.String())
return nil
},
}
Expand Down
83 changes: 64 additions & 19 deletions cmd/tinyauth/create_oidc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"strings"

"github.com/google/uuid"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils"
)

func createOidcClientCmd() *cli.Command {
Expand All @@ -31,40 +32,84 @@ func createOidcClientCmd() *cli.Command {
return errors.New("client name can only contain alphanumeric characters and hyphens")
}

uuid := uuid.New()
clientId := uuid.String()
u := uuid.New()
clientId := u.String()
clientSecret := "ta-" + utils.GenerateString(61)

uclientName := strings.ToUpper(clientName)
lclientName := strings.ToLower(clientName)

builder := strings.Builder{}
buf := strings.Builder{}

// header
fmt.Fprintf(&builder, "Created credentials for client %s\n\n", clientName)
fmt.Fprintf(&buf, "Created '%s' OIDC client.\n\n", clientName)

// credentials
fmt.Fprintf(&builder, "Client Name: %s\n", clientName)
fmt.Fprintf(&builder, "Client ID: %s\n", clientId)
fmt.Fprintf(&builder, "Client Secret: %s\n\n", clientSecret)
fmt.Fprintf(&buf, "Credentials:\n\n")
fmt.Fprintf(&buf, "Client Name: %s\n", clientName)
fmt.Fprintf(&buf, "Client ID: %s\n", clientId)
fmt.Fprintf(&buf, "Client Secret: %s\n\n", clientSecret)

// env variables
fmt.Fprint(&builder, "Environment variables:\n\n")
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTID=%s\n", uclientName, clientId)
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET=%s\n", uclientName, clientSecret)
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_NAME=%s\n\n", uclientName, utils.Capitalize(lclientName))
// end variables
fmt.Fprintf(&buf, "Environment variables:\n\n")
renderToBuf(&buf, []kv{
{
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTID", uclientName),
v: clientId,
},
{
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET", uclientName),
v: clientSecret,
},
{
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_NAME", uclientName),
v: utils.Capitalize(lclientName),
},
}, "=")
fmt.Fprintf(&buf, "\n")

// cli flags
fmt.Fprint(&builder, "CLI flags:\n\n")
fmt.Fprintf(&builder, "--oidc.clients.%s.clientid=%s\n", lclientName, clientId)
fmt.Fprintf(&builder, "--oidc.clients.%s.clientsecret=%s\n", lclientName, clientSecret)
fmt.Fprintf(&builder, "--oidc.clients.%s.name=%s\n\n", lclientName, utils.Capitalize(lclientName))
fmt.Fprintf(&buf, "CLI flags:\n\n")
renderToBuf(&buf, []kv{
{
k: fmt.Sprintf("--oidc.clients.%s.clientid", lclientName),
v: clientId,
},
{
k: fmt.Sprintf("--oidc.clients.%s.clientsecret", lclientName),
v: clientSecret,
},
{
k: fmt.Sprintf("--oidc.clients.%s.name", lclientName),
v: utils.Capitalize(lclientName),
},
}, "=")
fmt.Fprintf(&buf, "\n")

// yaml config
fmt.Fprintf(&buf, "YAML config:\n\n")

err = renderYamlToBuf(&buf, &model.OIDCConfig{
Clients: map[string]model.OIDCClientConfig{
lclientName: {
ClientID: clientId,
ClientSecret: clientSecret,
Name: utils.Capitalize(lclientName),
},
},
})

if err != nil {
return fmt.Errorf("failed to render yaml config: %w", err)
}

buf.WriteString("\n")

// footer
fmt.Fprintln(&builder, "You can use either option to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.")
fmt.Fprintln(&buf, "You can use any of the above options to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.")

// print
out := builder.String()
out := buf.String()
fmt.Print(out)
return nil
},
Expand Down
140 changes: 93 additions & 47 deletions cmd/tinyauth/create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package main
import (
"errors"
"fmt"
"os"
"strings"

"charm.land/huh/v2"
"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"github.com/tinyauthapp/tinyauth/internal/model"
"golang.org/x/crypto/bcrypt"
)

Expand All @@ -34,62 +35,107 @@ func createUserCmd() *cli.Command {
&cli.FlagLoader{},
}

return &cli.Command{
cmd := &cli.Command{
Name: "create",
Description: "Create a user",
Configuration: tCfg,
Resources: loaders,
Run: func(_ []string) error {
log := logger.NewLogger().WithSimpleConfig()
log.Init()

if tCfg.Interactive {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
return nil
})),
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error {
if s == "" {
return errors.New("password cannot be empty")
}
return nil
})),
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
),
)

theme := new(themeBase)
err := form.WithTheme(theme).Run()

if err != nil {
return fmt.Errorf("failed to run interactive prompt: %w", err)
}
}
}

if tCfg.Username == "" || tCfg.Password == "" {
return errors.New("username and password cannot be empty")
cmd.Run = func(_ []string) error {
if tCfg.Interactive {
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate(func(s string) error {
if s == "" {
return errors.New("username cannot be empty")
}
if strings.Contains(s, ":") {
return errors.New("username cannot contain ':'")
}
return nil
}),
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate(func(s string) error {
if s == "" {
return errors.New("password cannot be empty")
}
return nil
}),
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
),
)

theme := new(themeBase)

err := form.WithTheme(theme).Run()
if err != nil {
return fmt.Errorf("failed to run interactive prompt: %w", err)
}
}

log.App.Info().Str("username", tCfg.Username).Msg("Creating user")
if tCfg.Username == "" || tCfg.Password == "" {
cmd.PrintHelp(os.Stdout)
return errors.New("username and password cannot be empty")
}

passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
if strings.Contains(tCfg.Username, ":") {
return errors.New("username cannot contain ':'")
}

// If docker format is enabled, escape the dollar sign
passwdStr := string(passwd)
if tCfg.Docker {
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
}
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}

// Only the docker compose output needs $ escaped, the raw hash is correct everywhere else
passwdStr := string(passwd)
outputStr := passwdStr

if tCfg.Docker {
outputStr = strings.ReplaceAll(passwdStr, "$", "$$")
}

user := fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)
escapedUser := fmt.Sprintf("%s:%s", tCfg.Username, outputStr)

log.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
buf := strings.Builder{}

return nil
},
// header
fmt.Fprintf(&buf, "Created user '%s'.\n\n", tCfg.Username)

// environment variable
fmt.Fprint(&buf, "Environment variable:\n\n")
renderToBuf(&buf, []kv{
{"TINYAUTH_AUTH_USERS", escapedUser},
}, "=")

// cli flags
fmt.Fprint(&buf, "\nCLI flags:\n\n")
renderToBuf(&buf, []kv{
{"--auth.users", user},
}, "=")

// yaml config
fmt.Fprint(&buf, "\nYAML config:\n\n")

err = renderYamlToBuf(&buf, &model.Config{
Auth: model.AuthConfig{
Users: []string{user},
},
})

if err != nil {
return fmt.Errorf("failed to render yaml config: %w", err)
}

buf.WriteString("\n")

// footer
fmt.Fprint(&buf, "Use your config option of choice to add the user to Tinyauth and then restart.")

fmt.Println(buf.String())
return nil
}

return cmd
}
Loading