mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 12:45:47 +00:00
Compare commits
4 Commits
d38e7b9cea
...
402e7e565d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402e7e565d | ||
|
|
a629430a88 | ||
|
|
f0a48cc91c | ||
|
|
2f8fa39a9b |
@@ -45,8 +45,6 @@ FROM alpine:3.22 AS runner
|
|||||||
|
|
||||||
WORKDIR /tinyauth
|
WORKDIR /tinyauth
|
||||||
|
|
||||||
RUN apk add --no-cache curl
|
|
||||||
|
|
||||||
COPY --from=builder /tinyauth/tinyauth ./
|
COPY --from=builder /tinyauth/tinyauth ./
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png">
|
<img alt="Tinyauth" title="Tinyauth" width="96" src="assets/logo-rounded.png">
|
||||||
<h1>Tinyauth</h1>
|
<h1>Tinyauth</h1>
|
||||||
<p>The easiest way to secure your apps with a login screen.</p>
|
<p>The simplest way to protect your apps with a login screen.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github and any provider to all of your docker apps. It supports all the popular proxies like Traefik, Nginx and Caddy.
|
Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github or any other provider to all of your apps. It supports all the popular proxies like Traefik, Nginx and Caddy.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
99
cmd/create.go
Normal file
99
cmd/create.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type createUserCmd struct {
|
||||||
|
root *cobra.Command
|
||||||
|
cmd *cobra.Command
|
||||||
|
|
||||||
|
interactive bool
|
||||||
|
docker bool
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateUserCmd(root *cobra.Command) *createUserCmd {
|
||||||
|
return &createUserCmd{
|
||||||
|
root: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *createUserCmd) Register() {
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a user",
|
||||||
|
Long: `Create a user either interactively or by passing flags.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Create a user interactively")
|
||||||
|
c.cmd.Flags().BoolVar(&c.docker, "docker", false, "Format output for docker")
|
||||||
|
c.cmd.Flags().StringVar(&c.username, "username", "", "Username")
|
||||||
|
c.cmd.Flags().StringVar(&c.password, "password", "", "Password")
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *createUserCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *createUserCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if c.interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("Username").Value(&c.username).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("username cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Password").Value(&c.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(&c.docker),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.username == "" || c.password == "" {
|
||||||
|
log.Fatal().Err(errors.New("error invalid input")).Msg("Username and password cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("username", c.username).Msg("Creating user")
|
||||||
|
|
||||||
|
passwd, err := bcrypt.GenerateFromPassword([]byte(c.password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to hash password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If docker format is enabled, escape the dollar sign
|
||||||
|
passwdStr := string(passwd)
|
||||||
|
if c.docker {
|
||||||
|
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("user", fmt.Sprintf("%s:%s", c.username, passwdStr)).Msg("User created")
|
||||||
|
}
|
||||||
120
cmd/generate.go
Normal file
120
cmd/generate.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/mdp/qrterminal/v3"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type generateTotpCmd struct {
|
||||||
|
root *cobra.Command
|
||||||
|
cmd *cobra.Command
|
||||||
|
|
||||||
|
interactive bool
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenerateTotpCmd(root *cobra.Command) *generateTotpCmd {
|
||||||
|
return &generateTotpCmd{
|
||||||
|
root: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *generateTotpCmd) Register() {
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate a totp secret",
|
||||||
|
Long: `Generate a totp secret for a user either interactively or by passing flags.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Run in interactive mode")
|
||||||
|
c.cmd.Flags().StringVar(&c.user, "user", "", "Your current user (username:hash)")
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *generateTotpCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *generateTotpCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if c.interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("Current user (username:hash)").Value(&c.user).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("user cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := utils.ParseUser(c.user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||||
|
}
|
||||||
|
|
||||||
|
docker := false
|
||||||
|
if strings.Contains(c.user, "$$") {
|
||||||
|
docker = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.TotpSecret != "" {
|
||||||
|
log.Fatal().Msg("User already has a TOTP secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := totp.Generate(totp.GenerateOpts{
|
||||||
|
Issuer: "Tinyauth",
|
||||||
|
AccountName: user.Username,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to generate TOTP secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := key.Secret()
|
||||||
|
|
||||||
|
log.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
||||||
|
|
||||||
|
log.Info().Msg("Generated QR code")
|
||||||
|
|
||||||
|
config := qrterminal.Config{
|
||||||
|
Level: qrterminal.L,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
BlackChar: qrterminal.BLACK,
|
||||||
|
WhiteChar: qrterminal.WHITE,
|
||||||
|
QuietZone: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
qrterminal.GenerateWithConfig(key.URL(), config)
|
||||||
|
|
||||||
|
user.TotpSecret = secret
|
||||||
|
|
||||||
|
// If using docker escape re-escape it
|
||||||
|
if docker {
|
||||||
|
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
||||||
|
}
|
||||||
109
cmd/health.go
Normal file
109
cmd/health.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"tinyauth/internal/config"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type healthzResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type healthCmd struct {
|
||||||
|
root *cobra.Command
|
||||||
|
cmd *cobra.Command
|
||||||
|
|
||||||
|
viper *viper.Viper
|
||||||
|
appUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHealthCmd(root *cobra.Command) *healthCmd {
|
||||||
|
return &healthCmd{
|
||||||
|
root: root,
|
||||||
|
viper: viper.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *healthCmd) Register() {
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Use: "health",
|
||||||
|
Short: "Health check",
|
||||||
|
Long: `Use the health check endpoint to verify that Tinyauth is running and it's healthy.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.viper.AutomaticEnv()
|
||||||
|
c.cmd.Flags().StringVar(&c.appUrl, "app-url", "http://localhost:3000", "The URL where the Tinyauth server is running on.")
|
||||||
|
c.viper.BindEnv("app-url", "APP_URL")
|
||||||
|
c.viper.BindPFlags(c.cmd.Flags())
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *healthCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *healthCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
appUrl := c.viper.GetString("app-url")
|
||||||
|
|
||||||
|
if appUrl == "" {
|
||||||
|
log.Fatal().Err(errors.New("app-url is required")).Msg("App URL is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Version == "development" {
|
||||||
|
log.Warn().Msg("Running in development mode. Overriding the app-url to http://localhost:3000")
|
||||||
|
appUrl = "http://localhost:3000"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Health check endpoint is available at %s/api/healthz", appUrl)
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", appUrl+"/api/healthz", nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to create request")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to perform request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Fatal().Err(errors.New("service is not healthy")).Msgf("Service is not healthy. Status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var healthResp healthzResponse
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to read response")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &healthResp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to decode response")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Interface("response", healthResp).Msg("Service is healthy")
|
||||||
|
}
|
||||||
134
cmd/root.go
134
cmd/root.go
@@ -2,8 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
totpCmd "tinyauth/cmd/totp"
|
|
||||||
userCmd "tinyauth/cmd/user"
|
|
||||||
"tinyauth/internal/bootstrap"
|
"tinyauth/internal/bootstrap"
|
||||||
"tinyauth/internal/config"
|
"tinyauth/internal/config"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
@@ -15,55 +13,28 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
type rootCmd struct {
|
||||||
Use: "tinyauth",
|
root *cobra.Command
|
||||||
Short: "The simplest way to protect your apps with a login screen.",
|
cmd *cobra.Command
|
||||||
Long: `Tinyauth is a simple authentication middleware that adds simple username/password login or OAuth with Google, Github and any generic OAuth provider to all of your docker apps.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
var conf config.Config
|
|
||||||
|
|
||||||
err := viper.Unmarshal(&conf)
|
viper *viper.Viper
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to parse config")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate config
|
|
||||||
v := validator.New()
|
|
||||||
|
|
||||||
err = v.Struct(conf)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Invalid config")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel)))
|
|
||||||
log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting tinyauth")
|
|
||||||
|
|
||||||
// Create bootstrap app
|
|
||||||
app := bootstrap.NewBootstrapApp(conf)
|
|
||||||
|
|
||||||
// Run
|
|
||||||
err = app.Setup()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to setup app")
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func newRootCmd() *rootCmd {
|
||||||
rootCmd.FParseErrWhitelist.UnknownFlags = true
|
return &rootCmd{
|
||||||
err := rootCmd.Execute()
|
viper: viper.New(),
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to execute command")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func (c *rootCmd) Register() {
|
||||||
rootCmd.AddCommand(userCmd.UserCmd())
|
c.cmd = &cobra.Command{
|
||||||
rootCmd.AddCommand(totpCmd.TotpCmd())
|
Use: "tinyauth",
|
||||||
|
Short: "The simplest way to protect your apps with a login screen",
|
||||||
|
Long: `Tinyauth is a simple authentication middleware that adds a simple login screen or OAuth with Google, Github or any other provider to all of your docker apps.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
c.viper.AutomaticEnv()
|
||||||
|
|
||||||
configOptions := []struct {
|
configOptions := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -101,17 +72,82 @@ func init() {
|
|||||||
for _, opt := range configOptions {
|
for _, opt := range configOptions {
|
||||||
switch v := opt.defaultVal.(type) {
|
switch v := opt.defaultVal.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
rootCmd.Flags().Bool(opt.name, v, opt.description)
|
c.cmd.Flags().Bool(opt.name, v, opt.description)
|
||||||
case int:
|
case int:
|
||||||
rootCmd.Flags().Int(opt.name, v, opt.description)
|
c.cmd.Flags().Int(opt.name, v, opt.description)
|
||||||
case string:
|
case string:
|
||||||
rootCmd.Flags().String(opt.name, v, opt.description)
|
c.cmd.Flags().String(opt.name, v, opt.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create uppercase env var name
|
// Create uppercase env var name
|
||||||
envVar := strings.ReplaceAll(strings.ToUpper(opt.name), "-", "_")
|
envVar := strings.ReplaceAll(strings.ToUpper(opt.name), "-", "_")
|
||||||
viper.BindEnv(opt.name, envVar)
|
c.viper.BindEnv(opt.name, envVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.BindPFlags(rootCmd.Flags())
|
c.viper.BindPFlags(c.cmd.Flags())
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
var conf config.Config
|
||||||
|
|
||||||
|
err := c.viper.Unmarshal(&conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse config")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := validator.New()
|
||||||
|
err = v.Struct(conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Invalid config")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Logger = log.Level(zerolog.Level(utils.GetLogLevel(conf.LogLevel)))
|
||||||
|
log.Info().Str("version", strings.TrimSpace(config.Version)).Msg("Starting Tinyauth")
|
||||||
|
|
||||||
|
app := bootstrap.NewBootstrapApp(conf)
|
||||||
|
|
||||||
|
err = app.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to setup app")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run() {
|
||||||
|
rootCmd := newRootCmd()
|
||||||
|
rootCmd.Register()
|
||||||
|
root := rootCmd.GetCmd()
|
||||||
|
|
||||||
|
userCmd := &cobra.Command{
|
||||||
|
Use: "user",
|
||||||
|
Short: "User utilities",
|
||||||
|
Long: `Utilities for creating and verifying tinyauth compatible users.`,
|
||||||
|
}
|
||||||
|
totpCmd := &cobra.Command{
|
||||||
|
Use: "totp",
|
||||||
|
Short: "Totp utilities",
|
||||||
|
Long: `Utilities for creating and verifying totp codes.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
newCreateUserCmd(userCmd).Register()
|
||||||
|
newVerifyUserCmd(userCmd).Register()
|
||||||
|
newGenerateTotpCmd(totpCmd).Register()
|
||||||
|
newVersionCmd(root).Register()
|
||||||
|
newHealthCmd(root).Register()
|
||||||
|
|
||||||
|
root.AddCommand(userCmd)
|
||||||
|
root.AddCommand(totpCmd)
|
||||||
|
|
||||||
|
err := root.Execute()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to execute root command")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package generate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"tinyauth/internal/utils"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/mdp/qrterminal/v3"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var interactive bool
|
|
||||||
|
|
||||||
// Input user
|
|
||||||
var iUser string
|
|
||||||
|
|
||||||
var GenerateCmd = &cobra.Command{
|
|
||||||
Use: "generate",
|
|
||||||
Short: "Generate a totp secret",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
log.Logger = log.Level(zerolog.InfoLevel)
|
|
||||||
|
|
||||||
if interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Current username:hash").Value(&iUser).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("user cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Form failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := utils.ParseUser(iUser)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to parse user")
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerEscape := false
|
|
||||||
if strings.Contains(iUser, "$$") {
|
|
||||||
dockerEscape = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.TotpSecret != "" {
|
|
||||||
log.Fatal().Msg("User already has a totp secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{
|
|
||||||
Issuer: "Tinyauth",
|
|
||||||
AccountName: user.Username,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to generate totp secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
secret := key.Secret()
|
|
||||||
|
|
||||||
log.Info().Str("secret", secret).Msg("Generated totp secret")
|
|
||||||
|
|
||||||
log.Info().Msg("Generated QR code")
|
|
||||||
|
|
||||||
config := qrterminal.Config{
|
|
||||||
Level: qrterminal.L,
|
|
||||||
Writer: os.Stdout,
|
|
||||||
BlackChar: qrterminal.BLACK,
|
|
||||||
WhiteChar: qrterminal.WHITE,
|
|
||||||
QuietZone: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
qrterminal.GenerateWithConfig(key.URL(), config)
|
|
||||||
|
|
||||||
user.TotpSecret = secret
|
|
||||||
|
|
||||||
// If using docker escape re-escape it
|
|
||||||
if dockerEscape {
|
|
||||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
GenerateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Run in interactive mode")
|
|
||||||
GenerateCmd.Flags().StringVar(&iUser, "user", "", "Your current username:hash")
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"tinyauth/cmd/totp/generate"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TotpCmd() *cobra.Command {
|
|
||||||
totpCmd := &cobra.Command{
|
|
||||||
Use: "totp",
|
|
||||||
Short: "Totp utilities",
|
|
||||||
Long: `Utilities for creating and verifying totp codes.`,
|
|
||||||
}
|
|
||||||
totpCmd.AddCommand(generate.GenerateCmd)
|
|
||||||
return totpCmd
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package create
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var interactive bool
|
|
||||||
var docker bool
|
|
||||||
|
|
||||||
// i stands for input
|
|
||||||
var iUsername string
|
|
||||||
var iPassword string
|
|
||||||
|
|
||||||
var CreateCmd = &cobra.Command{
|
|
||||||
Use: "create",
|
|
||||||
Short: "Create a user",
|
|
||||||
Long: `Create a user either interactively or by passing flags.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
log.Logger = log.Level(zerolog.InfoLevel)
|
|
||||||
|
|
||||||
if interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("username cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Password").Value(&iPassword).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(&docker),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Form failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if iUsername == "" || iPassword == "" {
|
|
||||||
log.Fatal().Err(errors.New("error invalid input")).Msg("Username and password cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("username", iUsername).Str("password", iPassword).Bool("docker", docker).Msg("Creating user")
|
|
||||||
|
|
||||||
password, err := bcrypt.GenerateFromPassword([]byte(iPassword), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to hash password")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If docker format is enabled, escape the dollar sign
|
|
||||||
passwordString := string(password)
|
|
||||||
if docker {
|
|
||||||
passwordString = strings.ReplaceAll(passwordString, "$", "$$")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Str("user", fmt.Sprintf("%s:%s", iUsername, passwordString)).Msg("User created")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
CreateCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Create a user interactively")
|
|
||||||
CreateCmd.Flags().BoolVar(&docker, "docker", false, "Format output for docker")
|
|
||||||
CreateCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
|
||||||
CreateCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"tinyauth/cmd/user/create"
|
|
||||||
"tinyauth/cmd/user/verify"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func UserCmd() *cobra.Command {
|
|
||||||
userCmd := &cobra.Command{
|
|
||||||
Use: "user",
|
|
||||||
Short: "User utilities",
|
|
||||||
Long: `Utilities for creating and verifying tinyauth compatible users.`,
|
|
||||||
}
|
|
||||||
userCmd.AddCommand(create.CreateCmd)
|
|
||||||
userCmd.AddCommand(verify.VerifyCmd)
|
|
||||||
return userCmd
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package verify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"tinyauth/internal/utils"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var interactive bool
|
|
||||||
var docker bool
|
|
||||||
|
|
||||||
// i stands for input
|
|
||||||
var iUsername string
|
|
||||||
var iPassword string
|
|
||||||
var iTotp string
|
|
||||||
var iUser string
|
|
||||||
|
|
||||||
var VerifyCmd = &cobra.Command{
|
|
||||||
Use: "verify",
|
|
||||||
Short: "Verify a user is set up correctly",
|
|
||||||
Long: `Verify a user is set up correctly meaning that it has a correct username, password and totp code.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
log.Logger = log.Level(zerolog.InfoLevel)
|
|
||||||
|
|
||||||
if interactive {
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().Title("User (username:hash:totp)").Value(&iUser).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("user cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Username").Value(&iUsername).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("username cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Password").Value(&iPassword).Validate((func(s string) error {
|
|
||||||
if s == "" {
|
|
||||||
return errors.New("password cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})),
|
|
||||||
huh.NewInput().Title("Totp Code (if setup)").Value(&iTotp),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var baseTheme *huh.Theme = huh.ThemeBase()
|
|
||||||
err := form.WithTheme(baseTheme).Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Form failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := utils.ParseUser(iUser)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Failed to parse user")
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Username != iUsername {
|
|
||||||
log.Fatal().Msg("Username is incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(iPassword))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Msg("Password is incorrect")
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.TotpSecret == "" {
|
|
||||||
if iTotp != "" {
|
|
||||||
log.Warn().Msg("User does not have 2fa secret")
|
|
||||||
}
|
|
||||||
log.Info().Msg("User verified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := totp.Validate(iTotp, user.TotpSecret)
|
|
||||||
if !ok {
|
|
||||||
log.Fatal().Msg("Totp code incorrect")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Msg("User verified")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
VerifyCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Create a user interactively")
|
|
||||||
VerifyCmd.Flags().BoolVar(&docker, "docker", false, "Is the user formatted for docker?")
|
|
||||||
VerifyCmd.Flags().StringVar(&iUsername, "username", "", "Username")
|
|
||||||
VerifyCmd.Flags().StringVar(&iPassword, "password", "", "Password")
|
|
||||||
VerifyCmd.Flags().StringVar(&iTotp, "totp", "", "Totp code")
|
|
||||||
VerifyCmd.Flags().StringVar(&iUser, "user", "", "Hash (username:hash:totp combination)")
|
|
||||||
}
|
|
||||||
118
cmd/verify.go
Normal file
118
cmd/verify.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type verifyUserCmd struct {
|
||||||
|
root *cobra.Command
|
||||||
|
cmd *cobra.Command
|
||||||
|
|
||||||
|
interactive bool
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
totp string
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVerifyUserCmd(root *cobra.Command) *verifyUserCmd {
|
||||||
|
return &verifyUserCmd{
|
||||||
|
root: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *verifyUserCmd) Register() {
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Use: "verify",
|
||||||
|
Short: "Verify a user is set up correctly",
|
||||||
|
Long: `Verify a user is set up correctly meaning that it has a correct username, password and TOTP code.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cmd.Flags().BoolVarP(&c.interactive, "interactive", "i", false, "Validate a user interactively")
|
||||||
|
c.cmd.Flags().StringVar(&c.username, "username", "", "Username")
|
||||||
|
c.cmd.Flags().StringVar(&c.password, "password", "", "Password")
|
||||||
|
c.cmd.Flags().StringVar(&c.totp, "totp", "", "TOTP code")
|
||||||
|
c.cmd.Flags().StringVar(&c.user, "user", "", "Hash (username:hash:totp)")
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *verifyUserCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *verifyUserCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger = log.Level(zerolog.InfoLevel)
|
||||||
|
|
||||||
|
if c.interactive {
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().Title("User (username:hash:totp)").Value(&c.user).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("user cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Username").Value(&c.username).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("username cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("Password").Value(&c.password).Validate((func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("password cannot be empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})),
|
||||||
|
huh.NewInput().Title("TOTP Code (optional)").Value(&c.totp),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var baseTheme *huh.Theme = huh.ThemeBase()
|
||||||
|
err := form.WithTheme(baseTheme).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Form failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := utils.ParseUser(c.user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to parse user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Username != c.username {
|
||||||
|
log.Fatal().Msg("Username is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(c.password))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Msg("Password is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.TotpSecret == "" {
|
||||||
|
if c.totp != "" {
|
||||||
|
log.Warn().Msg("User does not have TOTP secret")
|
||||||
|
}
|
||||||
|
log.Info().Msg("User verified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := totp.Validate(c.totp, user.TotpSecret)
|
||||||
|
if !ok {
|
||||||
|
log.Fatal().Msg("TOTP code incorrect")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("User verified")
|
||||||
|
}
|
||||||
@@ -7,17 +7,36 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
type versionCmd struct {
|
||||||
Use: "version",
|
root *cobra.Command
|
||||||
Short: "Print the version number of Tinyauth",
|
cmd *cobra.Command
|
||||||
Long: `All software has versions. This is Tinyauth's`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Printf("Version: %s\n", config.Version)
|
|
||||||
fmt.Printf("Commit Hash: %s\n", config.CommitHash)
|
|
||||||
fmt.Printf("Build Timestamp: %s\n", config.BuildTimestamp)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func newVersionCmd(root *cobra.Command) *versionCmd {
|
||||||
rootCmd.AddCommand(versionCmd)
|
return &versionCmd{
|
||||||
|
root: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *versionCmd) Register() {
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version number of Tinyauth",
|
||||||
|
Long: `All software has versions. This is Tinyauth's.`,
|
||||||
|
Run: c.run,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.root != nil {
|
||||||
|
c.root.AddCommand(c.cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *versionCmd) GetCmd() *cobra.Command {
|
||||||
|
return c.cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *versionCmd) run(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Printf("Version: %s\n", config.Version)
|
||||||
|
fmt.Printf("Commit Hash: %s\n", config.CommitHash)
|
||||||
|
fmt.Printf("Build Timestamp: %s\n", config.BuildTimestamp)
|
||||||
}
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/cenkalti/backoff/v5 v5.0.3
|
github.com/cenkalti/backoff/v5 v5.0.3
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.28.0
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.0
|
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/google/go-querystring v1.1.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -87,7 +87,7 @@ require (
|
|||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12
|
github.com/go-ldap/ldap/v3 v3.4.12
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -88,8 +88,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
@@ -113,8 +113,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ func NewHealthController(router *gin.RouterGroup) *HealthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (controller *HealthController) SetupRoutes() {
|
func (controller *HealthController) SetupRoutes() {
|
||||||
controller.router.GET("/health", controller.healthHandler)
|
controller.router.GET("/healthz", controller.healthHandler)
|
||||||
controller.router.HEAD("/health", controller.healthHandler)
|
controller.router.HEAD("/healthz", controller.healthHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *HealthController) healthHandler(c *gin.Context) {
|
func (controller *HealthController) healthHandler(c *gin.Context) {
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -11,5 +11,5 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Caller().Logger()
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Timestamp().Caller().Logger()
|
||||||
cmd.Execute()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user