chore: Adds login and user creation support to the API.
This commit is contained in:
parent
96833b238b
commit
843cd34785
13 changed files with 683 additions and 12 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,13 +3,11 @@ backend/sshbackend/sshbackend
|
|||
backend/dummybackend/dummybackend
|
||||
backend/externalbackendlauncher/externalbackendlauncher
|
||||
backend/api/api
|
||||
frontend/frontend
|
||||
|
||||
# Backup artifacts
|
||||
*.json.gz
|
||||
|
||||
# LOM
|
||||
sshfrontend/keys
|
||||
|
||||
# Output
|
||||
out
|
||||
|
||||
|
|
21
apiclient/apiclient.go
Normal file
21
apiclient/apiclient.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package apiclient
|
||||
|
||||
import "git.terah.dev/imterah/hermes/apiclient/users"
|
||||
|
||||
type HermesAPIClient struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
/// Users
|
||||
|
||||
func (api *HermesAPIClient) UserGetRefreshToken(username *string, email *string, password string) (string, error) {
|
||||
return users.GetRefreshToken(api.URL, username, email, password)
|
||||
}
|
||||
|
||||
func (api *HermesAPIClient) UserGetJWTFromToken(refreshToken string) (string, error) {
|
||||
return users.GetJWTFromToken(api.URL, refreshToken)
|
||||
}
|
||||
|
||||
func (api *HermesAPIClient) UserCreate(fullName, username, email, password string, isBot bool) (string, error) {
|
||||
return users.CreateUser(api.URL, fullName, username, email, password, isBot)
|
||||
}
|
102
apiclient/backendstructs/struct.go
Normal file
102
apiclient/backendstructs/struct.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package backendstructs
|
||||
|
||||
type BackendCreationRequest struct {
|
||||
Token string `validate:"required"`
|
||||
Name string `validate:"required"`
|
||||
Description *string `json:"description"`
|
||||
Backend string `validate:"required"`
|
||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
||||
}
|
||||
|
||||
type BackendLookupRequest struct {
|
||||
Token string `validate:"required"`
|
||||
BackendID *uint `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Backend *string `json:"backend"`
|
||||
}
|
||||
|
||||
type BackendRemovalRequest struct {
|
||||
Token string `validate:"required"`
|
||||
BackendID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type ConnectionsRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
Id uint `validate:"required" json:"id"`
|
||||
}
|
||||
|
||||
type ProxyCreationRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
Name string `validate:"required" json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Protocol string `validate:"required" json:"protocol"`
|
||||
SourceIP string `validate:"required" json:"sourceIP"`
|
||||
SourcePort uint16 `validate:"required" json:"sourcePort"`
|
||||
DestinationPort uint16 `validate:"required" json:"destinationPort"`
|
||||
ProviderID uint `validate:"required" json:"providerID"`
|
||||
AutoStart *bool `json:"autoStart"`
|
||||
}
|
||||
|
||||
type ProxyLookupRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
Id *uint `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Protocol *string `json:"protocol"`
|
||||
SourceIP *string `json:"sourceIP"`
|
||||
SourcePort *uint16 `json:"sourcePort"`
|
||||
DestinationPort *uint16 `json:"destPort"`
|
||||
ProviderID *uint `json:"providerID"`
|
||||
AutoStart *bool `json:"autoStart"`
|
||||
}
|
||||
|
||||
type ProxyRemovalRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
ID uint `validate:"required" json:"id"`
|
||||
}
|
||||
|
||||
type ProxyStartRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
ID uint `validate:"required" json:"id"`
|
||||
}
|
||||
|
||||
type ProxyStopRequest struct {
|
||||
Token string `validate:"required" json:"token"`
|
||||
ID uint `validate:"required" json:"id"`
|
||||
}
|
||||
|
||||
type UserCreationRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
|
||||
ExistingUserToken string `json:"token"`
|
||||
IsBot bool `json:"isBot"`
|
||||
}
|
||||
|
||||
type UserLoginRequest struct {
|
||||
Username *string `json:"username"`
|
||||
Email *string `json:"email"`
|
||||
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type UserLookupRequest struct {
|
||||
Token string `validate:"required"`
|
||||
UID *uint `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Email *string `json:"email"`
|
||||
Username *string `json:"username"`
|
||||
IsBot *bool `json:"isServiceAccount"`
|
||||
}
|
||||
|
||||
type UserRefreshRequest struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
}
|
||||
|
||||
type UserRemovalRequest struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
UID *uint `json:"uid"`
|
||||
}
|
99
apiclient/users/auth.go
Normal file
99
apiclient/users/auth.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"git.terah.dev/imterah/hermes/apiclient/backendstructs"
|
||||
)
|
||||
|
||||
type refreshTokenResponse struct {
|
||||
Success bool `json:"success"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
type jwtTokenResponse struct {
|
||||
Success bool `json:"success"`
|
||||
JWT string `json:"token"`
|
||||
}
|
||||
|
||||
func GetRefreshToken(url string, username, email *string, password string) (string, error) {
|
||||
body, err := json.Marshal(&backendstructs.UserLoginRequest{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Password: password,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/login", url), "application/json", bytes.NewBuffer(body))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bodyContents, err := io.ReadAll(res.Body)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
||||
}
|
||||
|
||||
response := &refreshTokenResponse{}
|
||||
|
||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return "", fmt.Errorf("failed to get refresh token")
|
||||
}
|
||||
|
||||
if response.RefreshToken == "" {
|
||||
return "", fmt.Errorf("refresh token is empty")
|
||||
}
|
||||
|
||||
return response.RefreshToken, nil
|
||||
}
|
||||
|
||||
func GetJWTFromToken(url, refreshToken string) (string, error) {
|
||||
body, err := json.Marshal(&backendstructs.UserRefreshRequest{
|
||||
Token: refreshToken,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/refresh", url), "application/json", bytes.NewBuffer(body))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bodyContents, err := io.ReadAll(res.Body)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
||||
}
|
||||
|
||||
response := &jwtTokenResponse{}
|
||||
|
||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return "", fmt.Errorf("failed to get JWT token")
|
||||
}
|
||||
|
||||
if response.JWT == "" {
|
||||
return "", fmt.Errorf("JWT token is empty")
|
||||
}
|
||||
|
||||
return response.JWT, nil
|
||||
}
|
63
apiclient/users/create.go
Normal file
63
apiclient/users/create.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"git.terah.dev/imterah/hermes/apiclient/backendstructs"
|
||||
)
|
||||
|
||||
type createUserResponse struct {
|
||||
Error string `json:"error"`
|
||||
Success bool `json:"success"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
func CreateUser(url, fullName, username, email, password string, isBot bool) (string, error) {
|
||||
body, err := json.Marshal(&backendstructs.UserCreationRequest{
|
||||
Username: username,
|
||||
Name: fullName,
|
||||
Email: email,
|
||||
Password: password,
|
||||
IsBot: isBot,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := http.Post(fmt.Sprintf("%s/api/v1/users/create", url), "application/json", bytes.NewBuffer(body))
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bodyContents, err := io.ReadAll(res.Body)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response body: %s", err.Error())
|
||||
}
|
||||
|
||||
response := &createUserResponse{}
|
||||
|
||||
if err := json.Unmarshal(bodyContents, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if response.Error != "" {
|
||||
return "", fmt.Errorf("error from server: %s", response.Error)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return "", fmt.Errorf("failed to get refresh token")
|
||||
}
|
||||
|
||||
if response.RefreshToken == "" {
|
||||
return "", fmt.Errorf("refresh token is empty")
|
||||
}
|
||||
|
||||
return response.RefreshToken, nil
|
||||
}
|
115
frontend/commands/users/create.go
Normal file
115
frontend/commands/users/create.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"git.terah.dev/imterah/hermes/apiclient"
|
||||
"git.terah.dev/imterah/hermes/frontend/config"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/term"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CreateUserCommand(cCtx *cli.Context) error {
|
||||
configPath := cCtx.String("config-path")
|
||||
|
||||
var configContents *config.Config
|
||||
|
||||
_, err := os.Stat(configPath)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
configContents = &config.Config{}
|
||||
} else {
|
||||
return fmt.Errorf("failed to get configuration file information: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
configContents, err = config.ReadAndParseConfig(configPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read and parse configuration file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
username := cCtx.String("username")
|
||||
|
||||
if username == "" {
|
||||
if configContents.Username == "" {
|
||||
return fmt.Errorf("username not specified and username is not in the configuration file")
|
||||
}
|
||||
|
||||
username = configContents.Username
|
||||
}
|
||||
|
||||
var password string
|
||||
|
||||
if cCtx.Bool("ask-password") {
|
||||
fmt.Print("Password: ")
|
||||
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Print("\n")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read password from console: %s", err.Error())
|
||||
}
|
||||
|
||||
password = string(passwordBytes)
|
||||
} else {
|
||||
password = cCtx.String("password")
|
||||
|
||||
if password == "" {
|
||||
return fmt.Errorf("password is not specified and password asking is not enabled")
|
||||
}
|
||||
}
|
||||
|
||||
var serverURL string
|
||||
|
||||
if cCtx.String("server-url") == "" {
|
||||
if configContents.APIPath == "" {
|
||||
return fmt.Errorf("server URL not specified and server URL is not in the configuration file")
|
||||
}
|
||||
|
||||
serverURL = configContents.APIPath
|
||||
} else {
|
||||
serverURL = cCtx.String("server-url")
|
||||
}
|
||||
|
||||
fullName := cCtx.String("full-name")
|
||||
email := cCtx.String("email")
|
||||
isBot := cCtx.Bool("user-is-bot")
|
||||
|
||||
log.Info("Creating user...")
|
||||
|
||||
api := &apiclient.HermesAPIClient{
|
||||
URL: serverURL,
|
||||
}
|
||||
|
||||
refreshToken, err := api.UserCreate(fullName, username, email, password, isBot)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Info("Successfully created user.")
|
||||
|
||||
if cCtx.Bool("do-not-save-configuration") {
|
||||
return nil
|
||||
}
|
||||
|
||||
configContents.Username = username
|
||||
configContents.RefreshToken = refreshToken
|
||||
configContents.APIPath = serverURL
|
||||
|
||||
data, err := yaml.Marshal(configContents)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal configuration data: %s", err.Error())
|
||||
}
|
||||
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
|
||||
return nil
|
||||
}
|
98
frontend/commands/users/login.go
Normal file
98
frontend/commands/users/login.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"git.terah.dev/imterah/hermes/apiclient"
|
||||
"git.terah.dev/imterah/hermes/frontend/config"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/term"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetRefreshTokenCommand(cCtx *cli.Context) error {
|
||||
configPath := cCtx.String("config-path")
|
||||
|
||||
var configContents *config.Config
|
||||
|
||||
_, err := os.Stat(configPath)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
configContents = &config.Config{}
|
||||
} else {
|
||||
return fmt.Errorf("failed to get configuration file information: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
configContents, err = config.ReadAndParseConfig(configPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read and parse configuration file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var username string
|
||||
var password string
|
||||
|
||||
if cCtx.String("username") == "" {
|
||||
if configContents.Username == "" {
|
||||
return fmt.Errorf("username not specified and username is not in the configuration file")
|
||||
}
|
||||
|
||||
username = configContents.Username
|
||||
} else {
|
||||
username = cCtx.String("username")
|
||||
}
|
||||
|
||||
if cCtx.Bool("ask-password") {
|
||||
fmt.Print("Password: ")
|
||||
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Print("\n")
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read password from console: %s", err.Error())
|
||||
}
|
||||
|
||||
password = string(passwordBytes)
|
||||
} else {
|
||||
password = cCtx.String("password")
|
||||
|
||||
if password == "" {
|
||||
return fmt.Errorf("password is not specified and password asking is not enabled")
|
||||
}
|
||||
}
|
||||
|
||||
serverURL := cCtx.String("server-url")
|
||||
log.Info("Authenticating with API...")
|
||||
|
||||
api := &apiclient.HermesAPIClient{
|
||||
URL: serverURL,
|
||||
}
|
||||
|
||||
refreshToken, err := api.UserGetRefreshToken(&username, nil, password)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate with the API: %s", err.Error())
|
||||
}
|
||||
|
||||
configContents.Username = username
|
||||
configContents.RefreshToken = refreshToken
|
||||
configContents.APIPath = serverURL
|
||||
|
||||
log.Info("Writing configuration file...")
|
||||
data, err := yaml.Marshal(configContents)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal configuration data: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Infof("config path: %s", configPath)
|
||||
|
||||
os.WriteFile(configPath, data, 0644)
|
||||
|
||||
return nil
|
||||
}
|
30
frontend/config/config.go
Normal file
30
frontend/config/config.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `json:"username"`
|
||||
RefreshToken string `json:"token"`
|
||||
APIPath string `json:"api_path"`
|
||||
}
|
||||
|
||||
func ReadAndParseConfig(configFile string) (*Config, error) {
|
||||
configFileContents, err := os.ReadFile(configFile)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &Config{}
|
||||
err = yaml.Unmarshal(configFileContents, config)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
1
frontend/dev.env
Normal file
1
frontend/dev.env
Normal file
|
@ -0,0 +1 @@
|
|||
HERMES_LOG_LEVEL=debug
|
143
frontend/main.go
Normal file
143
frontend/main.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.terah.dev/imterah/hermes/frontend/commands/users"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
|
||||
if logLevel != "" {
|
||||
switch logLevel {
|
||||
case "debug":
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
case "info":
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
case "warn":
|
||||
log.SetLevel(log.WarnLevel)
|
||||
|
||||
case "error":
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
|
||||
case "fatal":
|
||||
log.SetLevel(log.FatalLevel)
|
||||
}
|
||||
}
|
||||
|
||||
configDir, err := os.UserConfigDir()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get configuration directory: %s", err.Error())
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "hermcli",
|
||||
Usage: "client for Hermes -- port forwarding across boundaries",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "config-path",
|
||||
Aliases: []string{"config", "cp", "c"},
|
||||
Value: path.Join(configDir, "hermcli.yml"),
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "login",
|
||||
Usage: "log in to the API",
|
||||
Action: users.GetRefreshTokenCommand,
|
||||
Aliases: []string{"l"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"user", "u"},
|
||||
Usage: "username to authenticate as",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Aliases: []string{"pass", "p"},
|
||||
Usage: "password to authenticate with",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "server-url",
|
||||
Aliases: []string{"server", "s"},
|
||||
Usage: "URL of the server to authenticate with",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ask-password",
|
||||
Aliases: []string{"ask-pass", "ap"},
|
||||
Usage: "asks you the password to authenticate with",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "users",
|
||||
Usage: "user management commands",
|
||||
Aliases: []string{"u"},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "create a user",
|
||||
Action: users.CreateUserCommand,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "full-name",
|
||||
Aliases: []string{"name", "n"},
|
||||
Usage: "full name for the user",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Aliases: []string{"user", "us"},
|
||||
Usage: "username to give the user",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "email",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "email to give the user",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Aliases: []string{"pass", "p"},
|
||||
Usage: "password to give the user",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "server-url",
|
||||
Aliases: []string{"server", "s"},
|
||||
Usage: "URL of the server to connect with",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ask-password",
|
||||
Aliases: []string{"ask-pass", "ap"},
|
||||
Usage: "asks you the password to give the user",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "user-is-bot",
|
||||
Aliases: []string{"user-bot", "ub", "u"},
|
||||
Usage: "if set, makes the user flagged as a bot",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "do-not-save-configuration",
|
||||
Aliases: []string{"no-save", "ns"},
|
||||
Usage: "doesn't save the authenticated user credentials",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
|
@ -9,6 +9,8 @@ require (
|
|||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/term v0.28.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
|
@ -57,8 +59,7 @@ require (
|
|||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.36.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -125,10 +125,10 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
|||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||
|
|
6
init.sh
6
init.sh
|
@ -7,11 +7,11 @@ if [ ! -d "backend/.tmp" ]; then
|
|||
mkdir backend/.tmp
|
||||
fi
|
||||
|
||||
if [ ! -f "backend-legacy/.env" ]; then
|
||||
cp backend-legacy/dev.env backend-legacy/.env
|
||||
if [ ! -f "frontend/.env" ]; then
|
||||
cp frontend/dev.env frontend/.env
|
||||
fi
|
||||
|
||||
set -a
|
||||
source backend-legacy/.env
|
||||
source backend/.env
|
||||
source frontend/.env
|
||||
set +a
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue