Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/
35 changes: 30 additions & 5 deletions cmd/tinyauth/config.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
package main

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

"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model"
"gopkg.in/yaml.v3"
)

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")

yout, err := yaml.Marshal(&tconfig)

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

for l := range strings.SplitSeq(string(yout), "\n") {
if l == "" {
continue
}
if strings.HasPrefix(strings.TrimLeft(l, " "), "- ") {
buf.WriteString(greenStyle.Render(l))
buf.WriteString("\n")
continue
}
lp := strings.SplitN(l, ":", 2)
buf.WriteString(redStyle.Render(lp[0]))
buf.WriteString(grayStyle.Render(":"))
if len(lp) == 2 {
buf.WriteString(greenStyle.Render(lp[1]))
}
buf.WriteString("\n")
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

fmt.Println(buf.String())
return nil
},
}
Expand Down
94 changes: 77 additions & 17 deletions cmd/tinyauth/create_oidc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ 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"
"gopkg.in/yaml.v3"
)

func createOidcClientCmd() *cli.Command {
Expand Down Expand Up @@ -38,33 +40,91 @@ func createOidcClientCmd() *cli.Command {
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")

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

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

for l := range strings.SplitSeq(string(yout), "\n") {
if l == "" {
continue
}
lp := strings.SplitN(l, ":", 2)
buf.WriteString(redStyle.Render(lp[0]))
buf.WriteString(grayStyle.Render(":"))
if len(lp) == 2 {
buf.WriteString("")
buf.WriteString(greenStyle.Render(lp[1]))
}
buf.WriteString("\n")
}

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
145 changes: 94 additions & 51 deletions cmd/tinyauth/create_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package main
import (
"errors"
"fmt"
"os"
"strings"

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

Expand All @@ -34,62 +34,105 @@ 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")
}

log.App.Info().Str("username", tCfg.Username).Msg("Creating user")
}

passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
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 hash password: %w", err)
return fmt.Errorf("failed to run interactive prompt: %w", err)
}

// If docker format is enabled, escape the dollar sign
passwdStr := string(passwd)
if tCfg.Docker {
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
}

log.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")

return nil
},
}

if tCfg.Username == "" || tCfg.Password == "" {
cmd.PrintHelp(os.Stdout)
return errors.New("username and password cannot be empty")
}

if strings.Contains(tCfg.Username, ":") {
return errors.New("username cannot contain ':'")
}

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)
escapedUser = `"` + escapedUser + `"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.

buf := strings.Builder{}

// 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", escapedUser},
}, "=")

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

buf.WriteString(redStyle.Render("auth"))
buf.WriteString(grayStyle.Render(":"))
buf.WriteString("\n")
buf.WriteString(redStyle.Render(" users"))
buf.WriteString(grayStyle.Render(":"))
buf.WriteString(" ")
buf.WriteString(greenStyle.Render(user))
buf.WriteString("\n\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