Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
8e9c7f120f | |||
75b12f2053 | |||
b93bf456b5 | |||
d56a8eb7bf | |||
71d53990de | |||
1cefe64f88 | |||
83f80af405 | |||
7dee159d5f | |||
17b10c9b19 | |||
5c503f0421 |
35 changed files with 2052 additions and 2268 deletions
16
.prettierrc
16
.prettierrc
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"htmlWhitespaceSensitivity": "css",
|
|
||||||
"insertPragma": false,
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"printWidth": 80,
|
|
||||||
"proseWrap": "always",
|
|
||||||
"quoteProps": "as-needed",
|
|
||||||
"requirePragma": false,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"useTabs": false
|
|
||||||
}
|
|
49
CHANGELOG.md
49
CHANGELOG.md
|
@ -1,49 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
## [v1.1.2](https://github.com/imterah/nextnet/tree/v1.1.2) (2024-09-29)
|
|
||||||
|
|
||||||
## [v1.1.1](https://github.com/imterah/nextnet/tree/v1.1.1) (2024-09-29)
|
|
||||||
|
|
||||||
## [v1.1.0](https://github.com/imterah/nextnet/tree/v1.1.0) (2024-09-22)
|
|
||||||
|
|
||||||
**Fixed bugs:**
|
|
||||||
|
|
||||||
- Desktop app fails to build on macOS w/ `nix-shell` [\#1](https://github.com/imterah/nextnet/issues/1)
|
|
||||||
|
|
||||||
**Merged pull requests:**
|
|
||||||
|
|
||||||
- chore\(deps\): bump find-my-way from 8.1.0 to 8.2.2 in /api [\#17](https://github.com/imterah/nextnet/pull/17)
|
|
||||||
- chore\(deps\): bump axios from 1.6.8 to 1.7.4 in /lom [\#16](https://github.com/imterah/nextnet/pull/16)
|
|
||||||
- chore\(deps\): bump micromatch from 4.0.5 to 4.0.8 in /lom [\#15](https://github.com/imterah/nextnet/pull/15)
|
|
||||||
- chore\(deps\): bump braces from 3.0.2 to 3.0.3 in /lom [\#13](https://github.com/imterah/nextnet/pull/13)
|
|
||||||
- chore\(deps-dev\): bump braces from 3.0.2 to 3.0.3 in /api [\#11](https://github.com/imterah/nextnet/pull/11)
|
|
||||||
- chore\(deps\): bump ws from 8.17.0 to 8.17.1 in /api [\#10](https://github.com/imterah/nextnet/pull/10)
|
|
||||||
|
|
||||||
## [v1.0.1](https://github.com/imterah/nextnet/tree/v1.0.1) (2024-05-18)
|
|
||||||
|
|
||||||
**Merged pull requests:**
|
|
||||||
|
|
||||||
- Adds public key authentication [\#6](https://github.com/imterah/nextnet/pull/6)
|
|
||||||
- Add support for eslint [\#5](https://github.com/imterah/nextnet/pull/5)
|
|
||||||
|
|
||||||
## [v1.0.0](https://github.com/imterah/nextnet/tree/v1.0.0) (2024-05-10)
|
|
||||||
|
|
||||||
## [v0.1.1](https://github.com/imterah/nextnet/tree/v0.1.1) (2024-05-05)
|
|
||||||
|
|
||||||
## [v0.1.0](https://github.com/imterah/nextnet/tree/v0.1.0) (2024-05-05)
|
|
||||||
|
|
||||||
**Implemented enhancements:**
|
|
||||||
|
|
||||||
- \(potentially\) Migrate nix shell to nix flake [\#2](https://github.com/imterah/nextnet/issues/2)
|
|
||||||
|
|
||||||
**Closed issues:**
|
|
||||||
|
|
||||||
- add precommit hooks [\#3](https://github.com/imterah/nextnet/issues/3)
|
|
||||||
|
|
||||||
**Merged pull requests:**
|
|
||||||
|
|
||||||
- Reimplements PassyFire as a possible backend [\#4](https://github.com/imterah/nextnet/pull/4)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2024, Greyson
|
Copyright (c) 2024, Tera
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
1. Copy and change the default password (or username & db name too) from the template file `prod-docker.env`:
|
1. Copy and change the default password (or username & db name too) from the template file `prod-docker.env`:
|
||||||
```bash
|
```bash
|
||||||
sed "s/POSTGRES_PASSWORD=hermes/POSTGRES_PASSWORD=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" prod-docker.env > .env
|
sed -e "s/POSTGRES_PASSWORD=hermes/POSTGRES_PASSWORD=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" -e "s/JWT_SECRET=hermes/JWT_SECRET=$(head -c 500 /dev/random | sha512sum | cut -d " " -f 1)/g" prod-docker.env > .env
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build the docker stack: `docker compose --env-file .env up -d`
|
2. Build the docker stack: `docker compose --env-file .env up -d`
|
||||||
|
|
|
@ -6,10 +6,10 @@ var (
|
||||||
AvailableBackends []*Backend
|
AvailableBackends []*Backend
|
||||||
RunningBackends map[uint]*Runtime
|
RunningBackends map[uint]*Runtime
|
||||||
TempDir string
|
TempDir string
|
||||||
isDevelopmentMode bool
|
shouldLog bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RunningBackends = make(map[uint]*Runtime)
|
RunningBackends = make(map[uint]*Runtime)
|
||||||
isDevelopmentMode = os.Getenv("HERMES_DEVELOPMENT_MODE") != ""
|
shouldLog = os.Getenv("HERMES_DEVELOPMENT_MODE") != "" || os.Getenv("HERMES_BACKEND_LOGGING_ENABLED") != "" || os.Getenv("HERMES_LOG_LEVEL") == "debug"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@ import (
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO TODO TODO(imterah):
|
||||||
|
// This code is a mess. This NEEDS to be rearchitected and refactored to work better. Or at the very least, this code needs to be documented heavily.
|
||||||
|
|
||||||
func handleCommand(command interface{}, sock net.Conn, rtcChan chan interface{}) error {
|
func handleCommand(command interface{}, sock net.Conn, rtcChan chan interface{}) error {
|
||||||
bytes, err := commonbackend.Marshal(command)
|
bytes, err := commonbackend.Marshal(command)
|
||||||
|
|
||||||
|
@ -160,6 +163,9 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
|
|
||||||
OuterLoop:
|
OuterLoop:
|
||||||
for {
|
for {
|
||||||
|
_ = <-runtime.startProcessingNotification
|
||||||
|
runtime.isRuntimeCurrentlyProcessing = true
|
||||||
|
|
||||||
for chanIndex, messageData := range runtime.messageBuffer {
|
for chanIndex, messageData := range runtime.messageBuffer {
|
||||||
if messageData == nil {
|
if messageData == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -177,6 +183,8 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
|
|
||||||
runtime.messageBuffer[chanIndex] = nil
|
runtime.messageBuffer[chanIndex] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.isRuntimeCurrentlyProcessing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
sock.Close()
|
sock.Close()
|
||||||
|
@ -235,6 +243,7 @@ func (runtime *Runtime) Start() error {
|
||||||
runtime.messageBuffer = make([]*messageForBuf, 10)
|
runtime.messageBuffer = make([]*messageForBuf, 10)
|
||||||
runtime.messageBufferLock = sync.Mutex{}
|
runtime.messageBufferLock = sync.Mutex{}
|
||||||
|
|
||||||
|
runtime.startProcessingNotification = make(chan bool)
|
||||||
runtime.processRestartNotification = make(chan bool, 1)
|
runtime.processRestartNotification = make(chan bool, 1)
|
||||||
|
|
||||||
runtime.logger = &writeLogger{
|
runtime.logger = &writeLogger{
|
||||||
|
@ -322,6 +331,10 @@ SchedulingLoop:
|
||||||
schedulingAttempts++
|
schedulingAttempts++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !runtime.isRuntimeCurrentlyProcessing {
|
||||||
|
runtime.startProcessingNotification <- true
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch response and close Channel
|
// Fetch response and close Channel
|
||||||
response, ok := <-commandChannel
|
response, ok := <-commandChannel
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,14 @@ type Backend struct {
|
||||||
|
|
||||||
type messageForBuf struct {
|
type messageForBuf struct {
|
||||||
Channel chan interface{}
|
Channel chan interface{}
|
||||||
|
// TODO(imterah): could this be refactored to just be a []byte instead? Look into this
|
||||||
Message interface{}
|
Message interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
isRuntimeRunning bool
|
isRuntimeRunning bool
|
||||||
|
isRuntimeCurrentlyProcessing bool
|
||||||
|
startProcessingNotification chan bool
|
||||||
logger *writeLogger
|
logger *writeLogger
|
||||||
currentProcess *exec.Cmd
|
currentProcess *exec.Cmd
|
||||||
currentListener net.Listener
|
currentListener net.Listener
|
||||||
|
@ -42,7 +45,7 @@ type writeLogger struct {
|
||||||
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
||||||
logSplit := strings.Split(string(p), "\n")
|
logSplit := strings.Split(string(p), "\n")
|
||||||
|
|
||||||
if isDevelopmentMode {
|
if shouldLog {
|
||||||
for _, logLine := range logSplit {
|
for _, logLine := range logSplit {
|
||||||
if logLine == "" {
|
if logLine == "" {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,298 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
|
||||||
"github.com/charmbracelet/log"
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Data structures
|
|
||||||
type BackupBackend struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Backend string `json:"backend" validate:"required"`
|
|
||||||
BackendParameters string `json:"connectionDetails" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackupProxy struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
BackendID uint `json:"destProviderID" validate:"required"`
|
|
||||||
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Protocol string `json:"protocol" validate:"required"`
|
|
||||||
SourceIP string `json:"sourceIP" validate:"required"`
|
|
||||||
SourcePort uint16 `json:"sourcePort" validate:"required"`
|
|
||||||
DestinationPort uint16 `json:"destPort" validate:"required"`
|
|
||||||
AutoStart bool `json:"enabled" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackupPermission struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
|
|
||||||
PermissionNode string `json:"permission" validate:"required"`
|
|
||||||
HasPermission bool `json:"has" validate:"required"`
|
|
||||||
UserID uint `json:"userID" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackupUser struct {
|
|
||||||
ID uint `json:"id" validate:"required"`
|
|
||||||
|
|
||||||
Email string `json:"email" validate:"required"`
|
|
||||||
Username *string `json:"username"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Password string `json:"password" validate:"required"`
|
|
||||||
IsBot *bool `json:"isRootServiceAccount"`
|
|
||||||
|
|
||||||
Token *string `json:"rootToken" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackupData struct {
|
|
||||||
Backends []*BackupBackend `json:"destinationProviders" validate:"required"`
|
|
||||||
Proxies []*BackupProxy `json:"forwardRules" validate:"required"`
|
|
||||||
Permissions []*BackupPermission `json:"allPermissions" validate:"required"`
|
|
||||||
Users []*BackupUser `json:"users" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://stackoverflow.com/questions/54461423/efficient-way-to-remove-all-non-alphanumeric-characters-from-large-text
|
|
||||||
// Strips all alphanumeric characters from a string
|
|
||||||
func stripAllAlphanumeric(s string) string {
|
|
||||||
var result strings.Builder
|
|
||||||
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
b := s[i]
|
|
||||||
if ('a' <= b && b <= 'z') ||
|
|
||||||
('A' <= b && b <= 'Z') ||
|
|
||||||
('0' <= b && b <= '9') {
|
|
||||||
result.WriteByte(b)
|
|
||||||
} else {
|
|
||||||
result.WriteByte('_')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func backupRestoreEntrypoint(cCtx *cli.Context) error {
|
|
||||||
log.Info("Decompressing backup...")
|
|
||||||
|
|
||||||
backupFile, err := os.Open(cCtx.String("backup-path"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open backup: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := gzip.NewReader(backupFile)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to initialize Gzip (compression) reader: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
backupDataBytes, err := io.ReadAll(reader)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read backup contents: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Decompressed backup. Cleaning up...")
|
|
||||||
|
|
||||||
err = reader.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close Gzip reader: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = backupFile.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to close backup: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Parsing backup into internal structures...")
|
|
||||||
|
|
||||||
backupData := &BackupData{}
|
|
||||||
|
|
||||||
err = json.Unmarshal(backupDataBytes, backupData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse backup: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validator.New().Struct(backupData); err != nil {
|
|
||||||
return fmt.Errorf("failed to validate backup: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Initializing database and opening it...")
|
|
||||||
|
|
||||||
err = dbcore.InitializeDatabase(&gorm.Config{})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to initialize database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Running database migrations...")
|
|
||||||
|
|
||||||
if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil {
|
|
||||||
return fmt.Errorf("Failed to run database migrations: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Restoring database...")
|
|
||||||
bestEffortOwnerUIDFromBackup := -1
|
|
||||||
|
|
||||||
log.Info("Attempting to find user to use as owner of resources...")
|
|
||||||
|
|
||||||
for _, user := range backupData.Users {
|
|
||||||
foundUser := false
|
|
||||||
failedAdministrationCheck := false
|
|
||||||
|
|
||||||
for _, permission := range backupData.Permissions {
|
|
||||||
if permission.UserID != user.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
foundUser = true
|
|
||||||
|
|
||||||
if !strings.HasPrefix(permission.PermissionNode, "routes.") && permission.PermissionNode != "permissions.see" && !permission.HasPermission {
|
|
||||||
log.Infof("User with email '%s' and ID of '%d' failed administration check (lacks all permissions required). Attempting to find better user", user.Email, user.ID)
|
|
||||||
failedAdministrationCheck = true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !foundUser {
|
|
||||||
log.Warnf("User with email '%s' and ID of '%d' lacks any permissions!", user.Email, user.ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if failedAdministrationCheck {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Using user with email '%s', and ID of '%d'", user.Email, user.ID)
|
|
||||||
bestEffortOwnerUIDFromBackup = int(user.ID)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if bestEffortOwnerUIDFromBackup == -1 {
|
|
||||||
log.Warnf("Could not find Administrative level user to use as the owner of resources. Using user with email '%s', and ID of '%d'", backupData.Users[0].Email, backupData.Users[0].ID)
|
|
||||||
bestEffortOwnerUIDFromBackup = int(backupData.Users[0].ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bestEffortOwnerUID uint
|
|
||||||
|
|
||||||
for _, user := range backupData.Users {
|
|
||||||
log.Infof("Migrating user with email '%s' and ID of '%d'", user.Email, user.ID)
|
|
||||||
tokens := make([]dbcore.Token, 0)
|
|
||||||
permissions := make([]dbcore.Permission, 0)
|
|
||||||
|
|
||||||
if user.Token != nil {
|
|
||||||
tokens = append(tokens, dbcore.Token{
|
|
||||||
Token: *user.Token,
|
|
||||||
DisableExpiry: true,
|
|
||||||
CreationIPAddr: "127.0.0.1", // We don't know the creation IP address...
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, permission := range backupData.Permissions {
|
|
||||||
if permission.UserID != user.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
permissions = append(permissions, dbcore.Permission{
|
|
||||||
PermissionNode: permission.PermissionNode,
|
|
||||||
HasPermission: permission.HasPermission,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
username := ""
|
|
||||||
|
|
||||||
if user.Username == nil {
|
|
||||||
username = strings.ToLower(stripAllAlphanumeric(user.Email))
|
|
||||||
log.Warnf("User with ID of '%d' doesn't have a username. Derived username from email is '%s' (email is '%s')", user.ID, username, user.Email)
|
|
||||||
} else {
|
|
||||||
username = *user.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
userDatabase := &dbcore.User{
|
|
||||||
Email: user.Email,
|
|
||||||
Username: username,
|
|
||||||
Name: user.Name,
|
|
||||||
Password: base64.StdEncoding.EncodeToString([]byte(user.Password)),
|
|
||||||
IsBot: user.IsBot,
|
|
||||||
|
|
||||||
Tokens: tokens,
|
|
||||||
Permissions: permissions,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbcore.DB.Create(userDatabase).Error; err != nil {
|
|
||||||
log.Errorf("Failed to create user: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if uint(bestEffortOwnerUIDFromBackup) == user.ID {
|
|
||||||
bestEffortOwnerUID = userDatabase.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, backend := range backupData.Backends {
|
|
||||||
log.Infof("Migrating backend ID '%d' with name '%s'", backend.ID, backend.Name)
|
|
||||||
|
|
||||||
backendDatabase := &dbcore.Backend{
|
|
||||||
UserID: bestEffortOwnerUID,
|
|
||||||
Name: backend.Name,
|
|
||||||
Description: backend.Description,
|
|
||||||
Backend: backend.Backend,
|
|
||||||
BackendParameters: base64.StdEncoding.EncodeToString([]byte(backend.BackendParameters)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbcore.DB.Create(backendDatabase).Error; err != nil {
|
|
||||||
log.Errorf("Failed to create backend: %s", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Migrating proxies for backend ID '%d'", backend.ID)
|
|
||||||
|
|
||||||
for _, proxy := range backupData.Proxies {
|
|
||||||
if proxy.BackendID != backend.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Migrating proxy ID '%d' with name '%s'", proxy.ID, proxy.Name)
|
|
||||||
|
|
||||||
proxyDatabase := &dbcore.Proxy{
|
|
||||||
BackendID: backendDatabase.ID,
|
|
||||||
UserID: bestEffortOwnerUID,
|
|
||||||
|
|
||||||
Name: proxy.Name,
|
|
||||||
Description: proxy.Description,
|
|
||||||
Protocol: proxy.Protocol,
|
|
||||||
SourceIP: proxy.SourceIP,
|
|
||||||
SourcePort: proxy.SourcePort,
|
|
||||||
DestinationPort: proxy.DestinationPort,
|
|
||||||
AutoStart: proxy.AutoStart,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbcore.DB.Create(proxyDatabase).Error; err != nil {
|
|
||||||
log.Errorf("Failed to create proxy: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Successfully upgraded to Hermes from NextNet.")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -7,13 +7,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendCreationRequest struct {
|
type BackendCreationRequest struct {
|
||||||
|
@ -24,7 +23,8 @@ type BackendCreationRequest struct {
|
||||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBackend(c *gin.Context) {
|
func SetupCreateBackend(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/backends/create", func(c *gin.Context) {
|
||||||
var req BackendCreationRequest
|
var req BackendCreationRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -35,7 +35,7 @@ func CreateBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,7 @@ func CreateBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -190,7 +190,7 @@ func CreateBackend(c *gin.Context) {
|
||||||
|
|
||||||
log.Info("Passed backend checks successfully")
|
log.Info("Passed backend checks successfully")
|
||||||
|
|
||||||
backendInDatabase := &dbcore.Backend{
|
backendInDatabase := &db.Backend{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
|
@ -198,7 +198,7 @@ func CreateBackend(c *gin.Context) {
|
||||||
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := dbcore.DB.Create(&backendInDatabase); result.Error != nil {
|
if result := state.DB.DB.Create(&backendInDatabase); result.Error != nil {
|
||||||
log.Warnf("Failed to create backend: %s", result.Error.Error())
|
log.Warnf("Failed to create backend: %s", result.Error.Error())
|
||||||
|
|
||||||
err = backend.Stop()
|
err = backend.Stop()
|
||||||
|
@ -266,4 +266,5 @@ func CreateBackend(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendLookupRequest struct {
|
type BackendLookupRequest struct {
|
||||||
|
@ -38,7 +37,8 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedBackend `json:"data"`
|
Data []*SanitizedBackend `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupBackend(c *gin.Context) {
|
func SetupLookupBackend(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/backends/lookup", func(c *gin.Context) {
|
||||||
var req BackendLookupRequest
|
var req BackendLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -49,7 +49,7 @@ func LookupBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -57,7 +57,7 @@ func LookupBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -85,7 +85,7 @@ func LookupBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backends := []dbcore.Backend{}
|
backends := []db.Backend{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func LookupBackend(c *gin.Context) {
|
||||||
queryParameters = append(queryParameters, req.Backend)
|
queryParameters = append(queryParameters, req.Backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&backends).Error; err != nil {
|
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&backends).Error; err != nil {
|
||||||
log.Warnf("Failed to get backends: %s", err.Error())
|
log.Warnf("Failed to get backends: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -160,4 +160,5 @@ func LookupBackend(c *gin.Context) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedBackends,
|
Data: sanitizedBackends,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendRemovalRequest struct {
|
type BackendRemovalRequest struct {
|
||||||
|
@ -18,7 +17,8 @@ type BackendRemovalRequest struct {
|
||||||
BackendID uint `json:"id" validate:"required"`
|
BackendID uint `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveBackend(c *gin.Context) {
|
func SetupRemoveBackend(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/backends/remove", func(c *gin.Context) {
|
||||||
var req BackendRemovalRequest
|
var req BackendRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -29,7 +29,7 @@ func RemoveBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -37,7 +37,7 @@ func RemoveBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -65,8 +65,8 @@ func RemoveBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend *dbcore.Backend
|
var backend *db.Backend
|
||||||
backendRequest := dbcore.DB.Where("id = ?", req.BackendID).Find(&backend)
|
backendRequest := state.DB.DB.Where("id = ?", req.BackendID).Find(&backend)
|
||||||
|
|
||||||
if backendRequest.Error != nil {
|
if backendRequest.Error != nil {
|
||||||
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
||||||
|
@ -88,7 +88,7 @@ func RemoveBackend(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbcore.DB.Delete(backend).Error; err != nil {
|
if err := state.DB.DB.Delete(backend).Error; err != nil {
|
||||||
log.Warnf("failed to delete backend: %s", err.Error())
|
log.Warnf("failed to delete backend: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -120,4 +120,5 @@ func RemoveBackend(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConnectionsRequest struct {
|
type ConnectionsRequest struct {
|
||||||
|
@ -37,7 +36,8 @@ type ConnectionsResponse struct {
|
||||||
Data []*SanitizedConnection `json:"data"`
|
Data []*SanitizedConnection `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConnections(c *gin.Context) {
|
func SetupGetConnections(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/forward/connections", func(c *gin.Context) {
|
||||||
var req ConnectionsRequest
|
var req ConnectionsRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -48,7 +48,7 @@ func GetConnections(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -56,7 +56,8 @@ func GetConnections(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -83,8 +84,8 @@ func GetConnections(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy dbcore.Proxy
|
var proxy db.Proxy
|
||||||
proxyRequest := dbcore.DB.Where("id = ?", req.Id).First(&proxy)
|
proxyRequest := state.DB.DB.Where("id = ?", req.Id).First(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find proxy: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find proxy: %s", proxyRequest.Error.Error())
|
||||||
|
@ -160,4 +161,5 @@ func GetConnections(c *gin.Context) {
|
||||||
"error": "Got illegal response type",
|
"error": "Got illegal response type",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyCreationRequest struct {
|
type ProxyCreationRequest struct {
|
||||||
|
@ -26,7 +25,8 @@ type ProxyCreationRequest struct {
|
||||||
AutoStart *bool `json:"autoStart"`
|
AutoStart *bool `json:"autoStart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateProxy(c *gin.Context) {
|
func SetupCreateProxy(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/forward/create", func(c *gin.Context) {
|
||||||
var req ProxyCreationRequest
|
var req ProxyCreationRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -37,7 +37,7 @@ func CreateProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -45,7 +45,8 @@ func CreateProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -80,8 +81,8 @@ func CreateProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend dbcore.Backend
|
var backend db.Backend
|
||||||
backendRequest := dbcore.DB.Where("id = ?", req.ProviderID).First(&backend)
|
backendRequest := state.DB.DB.Where("id = ?", req.ProviderID).First(&backend)
|
||||||
|
|
||||||
if backendRequest.Error != nil {
|
if backendRequest.Error != nil {
|
||||||
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
log.Warnf("failed to find if backend exists or not: %s", backendRequest.Error.Error())
|
||||||
|
@ -105,7 +106,7 @@ func CreateProxy(c *gin.Context) {
|
||||||
autoStart = *req.AutoStart
|
autoStart = *req.AutoStart
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := &dbcore.Proxy{
|
proxy := &db.Proxy{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
BackendID: req.ProviderID,
|
BackendID: req.ProviderID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
@ -117,7 +118,7 @@ func CreateProxy(c *gin.Context) {
|
||||||
AutoStart: autoStart,
|
AutoStart: autoStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := dbcore.DB.Create(proxy); result.Error != nil {
|
if result := state.DB.DB.Create(proxy); result.Error != nil {
|
||||||
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
log.Warnf("failed to create proxy: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -172,4 +173,5 @@ func CreateProxy(c *gin.Context) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"id": proxy.ID,
|
"id": proxy.ID,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyLookupRequest struct {
|
type ProxyLookupRequest struct {
|
||||||
|
@ -43,7 +42,8 @@ type ProxyLookupResponse struct {
|
||||||
Data []*SanitizedProxy `json:"data"`
|
Data []*SanitizedProxy `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupProxy(c *gin.Context) {
|
func SetupLookupProxy(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/forward/lookup", func(c *gin.Context) {
|
||||||
var req ProxyLookupRequest
|
var req ProxyLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -54,7 +54,7 @@ func LookupProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,7 @@ func LookupProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -100,7 +100,7 @@ func LookupProxy(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies := []dbcore.Proxy{}
|
proxies := []db.Proxy{}
|
||||||
|
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
@ -150,7 +150,7 @@ func LookupProxy(c *gin.Context) {
|
||||||
queryParameters = append(queryParameters, req.Protocol)
|
queryParameters = append(queryParameters, req.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
||||||
log.Warnf("failed to get proxies: %s", err.Error())
|
log.Warnf("failed to get proxies: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -180,4 +180,5 @@ func LookupProxy(c *gin.Context) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedProxies,
|
Data: sanitizedProxies,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyRemovalRequest struct {
|
type ProxyRemovalRequest struct {
|
||||||
|
@ -19,7 +18,8 @@ type ProxyRemovalRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveProxy(c *gin.Context) {
|
func SetupRemoveProxy(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/forward/remove", func(c *gin.Context) {
|
||||||
var req ProxyRemovalRequest
|
var req ProxyRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -30,7 +30,7 @@ func RemoveProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,8 @@ func RemoveProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -65,8 +66,8 @@ func RemoveProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *dbcore.Proxy
|
var proxy *db.Proxy
|
||||||
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -88,7 +89,7 @@ func RemoveProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbcore.DB.Delete(proxy).Error; err != nil {
|
if err := state.DB.DB.Delete(proxy).Error; err != nil {
|
||||||
log.Warnf("failed to delete proxy: %s", err.Error())
|
log.Warnf("failed to delete proxy: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -133,8 +134,10 @@ func RemoveProxy(c *gin.Context) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to stop proxy. Proxy was still successfully deleted",
|
"error": "Failed to stop proxy. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
return
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
@ -142,11 +145,6 @@ func RemoveProxy(c *gin.Context) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyStartRequest struct {
|
type ProxyStartRequest struct {
|
||||||
|
@ -19,7 +18,8 @@ type ProxyStartRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartProxy(c *gin.Context) {
|
func SetupStartProxy(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/forward/start", func(c *gin.Context) {
|
||||||
var req ProxyStartRequest
|
var req ProxyStartRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -30,7 +30,7 @@ func StartProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,8 @@ func StartProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -65,8 +66,8 @@ func StartProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *dbcore.Proxy
|
var proxy *db.Proxy
|
||||||
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -114,29 +115,22 @@ func StartProxy(c *gin.Context) {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to get response from backend",
|
"error": "failed to get response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if !responseMessage.IsActive {
|
if !responseMessage.IsActive {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to start proxy",
|
"error": "failed to start proxy",
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
return
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
"error": "Got invalid response from backend. Proxy was likely still successfully started",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyStopRequest struct {
|
type ProxyStopRequest struct {
|
||||||
|
@ -19,8 +18,9 @@ type ProxyStopRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func StopProxy(c *gin.Context) {
|
func SetupStopProxy(state *state.State) {
|
||||||
var req ProxyStopRequest
|
state.Engine.POST("/api/v1/forward/stop", func(c *gin.Context) {
|
||||||
|
var req ProxyStartRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
@ -30,7 +30,7 @@ func StopProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,8 @@ func StopProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
@ -65,8 +66,8 @@ func StopProxy(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *dbcore.Proxy
|
var proxy *db.Proxy
|
||||||
proxyRequest := dbcore.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
||||||
|
|
||||||
if proxyRequest.Error != nil {
|
if proxyRequest.Error != nil {
|
||||||
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
log.Warnf("failed to find if proxy exists or not: %s", proxyRequest.Error.Error())
|
||||||
|
@ -107,36 +108,29 @@ func StopProxy(c *gin.Context) {
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
switch responseMessage := backendResponse.(type) {
|
||||||
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, err.Error())
|
case error:
|
||||||
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, responseMessage.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to get response from backend",
|
"error": "failed to get response from backend",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
|
||||||
case *commonbackend.ProxyStatusResponse:
|
case *commonbackend.ProxyStatusResponse:
|
||||||
if responseMessage.IsActive {
|
if responseMessage.IsActive {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "failed to stop proxy",
|
"error": "failed to stop proxy",
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
return
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
log.Errorf("Got illegal response type for backend #%d: %T", proxy.BackendID, responseMessage)
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
"error": "Got invalid response from backend. Proxy was likely still successfully stopped",
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"success": true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
permissionHelper "git.terah.dev/imterah/hermes/backend/api/permissions"
|
permissionHelper "git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
@ -22,13 +20,11 @@ type UserCreationRequest struct {
|
||||||
Email string `validate:"required"`
|
Email string `validate:"required"`
|
||||||
Password string `validate:"required"`
|
Password string `validate:"required"`
|
||||||
Username string `validate:"required"`
|
Username string `validate:"required"`
|
||||||
|
|
||||||
// TODO: implement support
|
|
||||||
ExistingUserToken string `json:"token"`
|
|
||||||
IsBot bool
|
IsBot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateUser(c *gin.Context) {
|
func SetupCreateUser(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/users/create", func(c *gin.Context) {
|
||||||
if !signupEnabled && !unsafeSignup {
|
if !signupEnabled && !unsafeSignup {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"error": "Signing up is not enabled at this time.",
|
"error": "Signing up is not enabled at this time.",
|
||||||
|
@ -47,7 +43,7 @@ func CreateUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -55,8 +51,8 @@ func CreateUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *dbcore.User
|
var user *db.User
|
||||||
userRequest := dbcore.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user)
|
userRequest := state.DB.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -90,7 +86,7 @@ func CreateUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions := []dbcore.Permission{}
|
permissions := []db.Permission{}
|
||||||
|
|
||||||
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
||||||
permissionEnabledState := false
|
permissionEnabledState := false
|
||||||
|
@ -99,7 +95,7 @@ func CreateUser(c *gin.Context) {
|
||||||
permissionEnabledState = true
|
permissionEnabledState = true
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions = append(permissions, dbcore.Permission{
|
permissions = append(permissions, db.Permission{
|
||||||
PermissionNode: permission,
|
PermissionNode: permission,
|
||||||
HasPermission: permissionEnabledState,
|
HasPermission: permissionEnabledState,
|
||||||
})
|
})
|
||||||
|
@ -117,14 +113,14 @@ func CreateUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user = &dbcore.User{
|
user = &db.User{
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
IsBot: &req.IsBot,
|
IsBot: &req.IsBot,
|
||||||
Password: base64.StdEncoding.EncodeToString(passwordHashed),
|
Password: base64.StdEncoding.EncodeToString(passwordHashed),
|
||||||
Permissions: permissions,
|
Permissions: permissions,
|
||||||
Tokens: []dbcore.Token{
|
Tokens: []db.Token{
|
||||||
{
|
{
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
DisableExpiry: forceNoExpiryTokens,
|
DisableExpiry: forceNoExpiryTokens,
|
||||||
|
@ -133,7 +129,7 @@ func CreateUser(c *gin.Context) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := dbcore.DB.Create(&user); result.Error != nil {
|
if result := state.DB.DB.Create(&user); result.Error != nil {
|
||||||
log.Warnf("Failed to create user: %s", result.Error.Error())
|
log.Warnf("Failed to create user: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -143,7 +139,7 @@ func CreateUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := jwtcore.Generate(user.ID)
|
jwt, err := state.JWT.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -160,4 +156,5 @@ func CreateUser(c *gin.Context) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +20,8 @@ type UserLoginRequest struct {
|
||||||
Password string `validate:"required"`
|
Password string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginUser(c *gin.Context) {
|
func SetupLoginUser(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/users/login", func(c *gin.Context) {
|
||||||
var req UserLoginRequest
|
var req UserLoginRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -32,7 +32,7 @@ func LoginUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -61,8 +61,8 @@ func LoginUser(c *gin.Context) {
|
||||||
userFindRequest += "username = ?"
|
userFindRequest += "username = ?"
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *dbcore.User
|
var user *db.User
|
||||||
userRequest := dbcore.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
userRequest := state.DB.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -119,7 +119,7 @@ func LoginUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := &dbcore.Token{
|
token := &db.Token{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
|
@ -127,7 +127,7 @@ func LoginUser(c *gin.Context) {
|
||||||
CreationIPAddr: c.ClientIP(),
|
CreationIPAddr: c.ClientIP(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := dbcore.DB.Create(&token); result.Error != nil {
|
if result := state.DB.DB.Create(&token); result.Error != nil {
|
||||||
log.Warnf("Failed to create user: %s", result.Error.Error())
|
log.Warnf("Failed to create user: %s", result.Error.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -137,7 +137,7 @@ func LoginUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := jwtcore.Generate(user.ID)
|
jwt, err := state.JWT.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -154,4 +154,5 @@ func LoginUser(c *gin.Context) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserLookupRequest struct {
|
type UserLookupRequest struct {
|
||||||
|
@ -35,7 +34,8 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedUsers `json:"data"`
|
Data []*SanitizedUsers `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupUser(c *gin.Context) {
|
func SetupLookupUser(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/users/lookup", func(c *gin.Context) {
|
||||||
var req UserLookupRequest
|
var req UserLookupRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -46,7 +46,7 @@ func LookupUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ func LookupUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -74,7 +74,7 @@ func LookupUser(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []dbcore.User{}
|
users := []db.User{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func LookupUser(c *gin.Context) {
|
||||||
queryParameters = append(queryParameters, req.IsBot)
|
queryParameters = append(queryParameters, req.IsBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil {
|
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil {
|
||||||
log.Warnf("Failed to get users: %s", err.Error())
|
log.Warnf("Failed to get users: %s", err.Error())
|
||||||
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
@ -133,4 +133,5 @@ func LookupUser(c *gin.Context) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedUsers,
|
Data: sanitizedUsers,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,18 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRefreshRequest struct {
|
type UserRefreshRequest struct {
|
||||||
Token string `validate:"required"`
|
Token string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshUserToken(c *gin.Context) {
|
func SetupRefreshUserToken(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/users/refresh", func(c *gin.Context) {
|
||||||
var req UserRefreshRequest
|
var req UserRefreshRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -27,7 +27,7 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -35,8 +35,8 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenInDatabase *dbcore.Token
|
var tokenInDatabase *db.Token
|
||||||
tokenRequest := dbcore.DB.Where("token = ?", req.Token).Find(&tokenInDatabase)
|
tokenRequest := state.DB.DB.Where("token = ?", req.Token).Find(&tokenInDatabase)
|
||||||
|
|
||||||
if tokenRequest.Error != nil {
|
if tokenRequest.Error != nil {
|
||||||
log.Warnf("failed to find if token exists or not: %s", tokenRequest.Error.Error())
|
log.Warnf("failed to find if token exists or not: %s", tokenRequest.Error.Error())
|
||||||
|
@ -65,7 +65,7 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
"error": "Token has expired",
|
"error": "Token has expired",
|
||||||
})
|
})
|
||||||
|
|
||||||
tx := dbcore.DB.Delete(tokenInDatabase)
|
tx := state.DB.DB.Delete(tokenInDatabase)
|
||||||
|
|
||||||
if tx.Error != nil {
|
if tx.Error != nil {
|
||||||
log.Warnf("Failed to delete expired token from database: %s", tx.Error.Error())
|
log.Warnf("Failed to delete expired token from database: %s", tx.Error.Error())
|
||||||
|
@ -75,8 +75,8 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user to check if the user exists before doing anything
|
// Get the user to check if the user exists before doing anything
|
||||||
var user *dbcore.User
|
var user *db.User
|
||||||
userRequest := dbcore.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user)
|
userRequest := state.DB.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user)
|
||||||
|
|
||||||
if tokenRequest.Error != nil {
|
if tokenRequest.Error != nil {
|
||||||
log.Warnf("failed to find if token user or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if token user or not: %s", userRequest.Error.Error())
|
||||||
|
@ -98,7 +98,7 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := jwtcore.Generate(user.ID)
|
jwt, err := state.JWT.Generate(user.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to generate JWT: %s", err.Error())
|
log.Warnf("Failed to generate JWT: %s", err.Error())
|
||||||
|
@ -114,4 +114,5 @@ func RefreshUserToken(c *gin.Context) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
"git.terah.dev/imterah/hermes/backend/api/permissions"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRemovalRequest struct {
|
type UserRemovalRequest struct {
|
||||||
|
@ -17,7 +16,8 @@ type UserRemovalRequest struct {
|
||||||
UID *uint `json:"uid"`
|
UID *uint `json:"uid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveUser(c *gin.Context) {
|
func SetupRemoveUser(state *state.State) {
|
||||||
|
state.Engine.POST("/api/v1/users/remove", func(c *gin.Context) {
|
||||||
var req UserRemovalRequest
|
var req UserRemovalRequest
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
|
@ -28,7 +28,7 @@ func RemoveUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&req); err != nil {
|
if err := state.Validator.Struct(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@ func RemoveUser(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
user, err := state.JWT.GetUserFromJWT(req.Token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||||
|
@ -73,8 +73,8 @@ func RemoveUser(c *gin.Context) {
|
||||||
// Make sure the user exists first if we have a custom UserID
|
// Make sure the user exists first if we have a custom UserID
|
||||||
|
|
||||||
if uid != user.ID {
|
if uid != user.ID {
|
||||||
var customUser *dbcore.User
|
var customUser *db.User
|
||||||
userRequest := dbcore.DB.Where("id = ?", uid).Find(customUser)
|
userRequest := state.DB.DB.Where("id = ?", uid).Find(customUser)
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
if userRequest.Error != nil {
|
||||||
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
@ -97,9 +97,10 @@ func RemoveUser(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbcore.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
state.DB.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
77
backend/api/db/db.go
Normal file
77
backend/api/db/db.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(backend, params string) (*DB, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
dialector, err := initDialector(backend, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize physical database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
database, err := gorm.Open(dialector)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DB{DB: database}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) DoMigrations() error {
|
||||||
|
if err := db.DB.AutoMigrate(&Proxy{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB.AutoMigrate(&Backend{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB.AutoMigrate(&Permission{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB.AutoMigrate(&Token{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DB.AutoMigrate(&User{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDialector(backend, params string) (gorm.Dialector, error) {
|
||||||
|
switch backend {
|
||||||
|
case "sqlite":
|
||||||
|
if params == "" {
|
||||||
|
return nil, fmt.Errorf("sqlite database file not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlite.Open(params), nil
|
||||||
|
case "postgresql":
|
||||||
|
if params == "" {
|
||||||
|
return nil, fmt.Errorf("postgres DSN not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return postgres.Open(params), nil
|
||||||
|
case "":
|
||||||
|
return nil, fmt.Errorf("no database backend specified in environment variables")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown database backend specified: %s", os.Getenv(backend))
|
||||||
|
}
|
||||||
|
}
|
66
backend/api/db/models.go
Normal file
66
backend/api/db/models.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backend struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Description *string
|
||||||
|
Backend string
|
||||||
|
BackendParameters string
|
||||||
|
|
||||||
|
Proxies []Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
BackendID uint
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Description *string
|
||||||
|
Protocol string
|
||||||
|
SourceIP string
|
||||||
|
SourcePort uint16
|
||||||
|
DestinationPort uint16
|
||||||
|
AutoStart bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
PermissionNode string
|
||||||
|
HasPermission bool
|
||||||
|
UserID uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
UserID uint
|
||||||
|
|
||||||
|
Token string
|
||||||
|
DisableExpiry bool
|
||||||
|
CreationIPAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
Email string `gorm:"unique"`
|
||||||
|
Username string `gorm:"unique"`
|
||||||
|
Name string
|
||||||
|
Password string
|
||||||
|
IsBot *bool
|
||||||
|
|
||||||
|
Permissions []Permission
|
||||||
|
OwnedProxies []Proxy
|
||||||
|
OwnedBackends []Backend
|
||||||
|
Tokens []Token
|
||||||
|
}
|
|
@ -1,142 +0,0 @@
|
||||||
package dbcore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Backend struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Name string
|
|
||||||
Description *string
|
|
||||||
Backend string
|
|
||||||
BackendParameters string
|
|
||||||
|
|
||||||
Proxies []Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
BackendID uint
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Name string
|
|
||||||
Description *string
|
|
||||||
Protocol string
|
|
||||||
SourceIP string
|
|
||||||
SourcePort uint16
|
|
||||||
DestinationPort uint16
|
|
||||||
AutoStart bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Permission struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
PermissionNode string
|
|
||||||
HasPermission bool
|
|
||||||
UserID uint
|
|
||||||
}
|
|
||||||
|
|
||||||
type Token struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
UserID uint
|
|
||||||
|
|
||||||
Token string
|
|
||||||
DisableExpiry bool
|
|
||||||
CreationIPAddr string
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
gorm.Model
|
|
||||||
|
|
||||||
Email string `gorm:"unique"`
|
|
||||||
Username string `gorm:"unique"`
|
|
||||||
Name string
|
|
||||||
Password string
|
|
||||||
IsBot *bool
|
|
||||||
|
|
||||||
Permissions []Permission
|
|
||||||
OwnedProxies []Proxy
|
|
||||||
OwnedBackends []Backend
|
|
||||||
Tokens []Token
|
|
||||||
}
|
|
||||||
|
|
||||||
var DB *gorm.DB
|
|
||||||
|
|
||||||
func InitializeDatabaseDialector() (gorm.Dialector, error) {
|
|
||||||
databaseBackend := os.Getenv("HERMES_DATABASE_BACKEND")
|
|
||||||
|
|
||||||
switch databaseBackend {
|
|
||||||
case "sqlite":
|
|
||||||
filePath := os.Getenv("HERMES_SQLITE_FILEPATH")
|
|
||||||
|
|
||||||
if filePath == "" {
|
|
||||||
return nil, fmt.Errorf("sqlite database file not specified (missing HERMES_SQLITE_FILEPATH)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sqlite.Open(filePath), nil
|
|
||||||
case "postgresql":
|
|
||||||
postgresDSN := os.Getenv("HERMES_POSTGRES_DSN")
|
|
||||||
|
|
||||||
if postgresDSN == "" {
|
|
||||||
return nil, fmt.Errorf("postgres DSN not specified (missing HERMES_POSTGRES_DSN)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return postgres.Open(postgresDSN), nil
|
|
||||||
case "":
|
|
||||||
return nil, fmt.Errorf("no database backend specified in environment variables (missing HERMES_DATABASE_BACKEND)")
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown database backend specified: %s", os.Getenv(databaseBackend))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitializeDatabase(config *gorm.Config) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
dialector, err := InitializeDatabaseDialector()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to initialize physical database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
DB, err = gorm.Open(dialector, config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open database: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoDatabaseMigrations(db *gorm.DB) error {
|
|
||||||
if err := db.AutoMigrate(&Proxy{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(&Backend{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(&Permission{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(&Token{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(&User{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
107
backend/api/jwt/jwt.go
Normal file
107
backend/api/jwt/jwt.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DevelopmentModeTimings = time.Duration(60*24) * time.Minute
|
||||||
|
NormalModeTimings = time.Duration(3) * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWTCore struct {
|
||||||
|
Key []byte
|
||||||
|
Database *db.DB
|
||||||
|
TimeMultiplier time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(key []byte, database *db.DB, timeMultiplier time.Duration) *JWTCore {
|
||||||
|
jwtCore := &JWTCore{
|
||||||
|
Key: key,
|
||||||
|
Database: database,
|
||||||
|
TimeMultiplier: timeMultiplier,
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwtCore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwtCore *JWTCore) Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
|
||||||
|
return jwt.Parse(tokenString, jwtCore.jwtKeyCallback, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwtCore *JWTCore) GetUserFromJWT(token string) (*db.User, error) {
|
||||||
|
if jwtCore.Database == nil {
|
||||||
|
return nil, fmt.Errorf("database is not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedJWT, err := jwtCore.Parse(token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return nil, fmt.Errorf("token is expired")
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audience, err := parsedJWT.Claims.GetAudience()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(audience) < 1 {
|
||||||
|
return nil, fmt.Errorf("audience is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(audience[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &db.User{}
|
||||||
|
userRequest := jwtCore.Database.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
||||||
|
|
||||||
|
if userRequest.Error != nil {
|
||||||
|
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
userExists := userRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !userExists {
|
||||||
|
return user, fmt.Errorf("user does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwtCore *JWTCore) Generate(uid uint) (string, error) {
|
||||||
|
currentJWTTime := jwt.NewNumericDate(time.Now())
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtCore.TimeMultiplier)),
|
||||||
|
IssuedAt: currentJWTTime,
|
||||||
|
NotBefore: currentJWTTime,
|
||||||
|
// Convert the user ID to a string, and then set it as the audience parameters only value (there's only 1 user per key)
|
||||||
|
Audience: []string{strconv.Itoa(int(uid))},
|
||||||
|
})
|
||||||
|
|
||||||
|
signedToken, err := token.SignedString(jwtCore.Key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwtCore *JWTCore) jwtKeyCallback(*jwt.Token) (any, error) {
|
||||||
|
return jwtCore.Key, nil
|
||||||
|
}
|
|
@ -1,117 +0,0 @@
|
||||||
package jwtcore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
JWTKey []byte
|
|
||||||
developmentMode bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetupJWT() error {
|
|
||||||
var err error
|
|
||||||
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
|
|
||||||
|
|
||||||
if jwtDataString == "" {
|
|
||||||
return fmt.Errorf("JWT secret isn't set (missing HERMES_JWT_SECRET)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("HERMES_JWT_BASE64_ENCODED") != "" {
|
|
||||||
JWTKey, err = base64.StdEncoding.DecodeString(jwtDataString)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to decode base64 JWT: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
JWTKey = []byte(jwtDataString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" {
|
|
||||||
developmentMode = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error) {
|
|
||||||
return jwt.Parse(tokenString, JWTKeyCallback, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserFromJWT(token string) (*dbcore.User, error) {
|
|
||||||
parsedJWT, err := Parse(token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
|
||||||
return nil, fmt.Errorf("token is expired")
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audience, err := parsedJWT.Claims.GetAudience()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(audience) < 1 {
|
|
||||||
return nil, fmt.Errorf("audience is too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
uid, err := strconv.Atoi(audience[0])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &dbcore.User{}
|
|
||||||
userRequest := dbcore.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
|
||||||
|
|
||||||
if userRequest.Error != nil {
|
|
||||||
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
userExists := userRequest.RowsAffected > 0
|
|
||||||
|
|
||||||
if !userExists {
|
|
||||||
return user, fmt.Errorf("user does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Generate(uid uint) (string, error) {
|
|
||||||
timeMultiplier := 3
|
|
||||||
|
|
||||||
if developmentMode {
|
|
||||||
timeMultiplier = 60 * 24
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(timeMultiplier) * time.Minute)),
|
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
||||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
|
||||||
Audience: []string{strconv.Itoa(int(uid))},
|
|
||||||
})
|
|
||||||
|
|
||||||
signedToken, err := token.SignedString(JWTKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signedToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func JWTKeyCallback(*jwt.Token) (interface{}, error) {
|
|
||||||
return JWTKey, nil
|
|
||||||
}
|
|
|
@ -9,18 +9,19 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
"git.terah.dev/imterah/hermes/backend/api/backendruntime"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/backends"
|
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/backends"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/proxies"
|
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/proxies"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/users"
|
"git.terah.dev/imterah/hermes/backend/api/controllers/v1/users"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
"git.terah.dev/imterah/hermes/backend/api/jwt"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/state"
|
||||||
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
"git.terah.dev/imterah/hermes/backend/commonbackend"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiEntrypoint(cCtx *cli.Context) error {
|
func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
@ -34,7 +35,26 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
log.Info("Hermes is initializing...")
|
log.Info("Hermes is initializing...")
|
||||||
log.Debug("Initializing database and opening it...")
|
log.Debug("Initializing database and opening it...")
|
||||||
|
|
||||||
err := dbcore.InitializeDatabase(&gorm.Config{})
|
databaseBackendName := os.Getenv("HERMES_DATABASE_BACKEND")
|
||||||
|
var databaseBackendParams string
|
||||||
|
|
||||||
|
if databaseBackendName == "sqlite" {
|
||||||
|
databaseBackendParams = os.Getenv("HERMES_SQLITE_FILEPATH")
|
||||||
|
|
||||||
|
if databaseBackendParams == "" {
|
||||||
|
log.Fatal("HERMES_SQLITE_FILEPATH is not set")
|
||||||
|
}
|
||||||
|
} else if databaseBackendName == "postgresql" {
|
||||||
|
databaseBackendParams = os.Getenv("HERMES_POSTGRES_DSN")
|
||||||
|
|
||||||
|
if databaseBackendParams == "" {
|
||||||
|
log.Fatal("HERMES_POSTGRES_DSN is not set")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Unsupported database backend: %s", databaseBackendName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbInstance, err := db.New(databaseBackendName, databaseBackendParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize database: %s", err)
|
log.Fatalf("Failed to initialize database: %s", err)
|
||||||
|
@ -42,16 +62,38 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Running database migrations...")
|
log.Debug("Running database migrations...")
|
||||||
|
|
||||||
if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil {
|
if err := dbInstance.DoMigrations(); err != nil {
|
||||||
return fmt.Errorf("Failed to run database migrations: %s", err)
|
return fmt.Errorf("Failed to run database migrations: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Initializing the JWT subsystem...")
|
log.Debug("Initializing the JWT subsystem...")
|
||||||
|
|
||||||
if err := jwtcore.SetupJWT(); err != nil {
|
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
|
||||||
return fmt.Errorf("Failed to initialize the JWT subsystem: %s", err.Error())
|
var jwtKey []byte
|
||||||
|
var jwtValidityTimeDuration time.Duration
|
||||||
|
|
||||||
|
if jwtDataString == "" {
|
||||||
|
log.Fatalf("HERMES_JWT_SECRET is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.Getenv("HERMES_JWT_BASE64_ENCODED") != "" {
|
||||||
|
jwtKey, err = base64.StdEncoding.DecodeString(jwtDataString)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to decode base64 JWT: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jwtKey = []byte(jwtDataString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if developmentMode {
|
||||||
|
jwtValidityTimeDuration = jwt.DevelopmentModeTimings
|
||||||
|
} else {
|
||||||
|
jwtValidityTimeDuration = jwt.NormalModeTimings
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtInstance := jwt.New(jwtKey, dbInstance, jwtValidityTimeDuration)
|
||||||
|
|
||||||
log.Debug("Initializing the backend subsystem...")
|
log.Debug("Initializing the backend subsystem...")
|
||||||
|
|
||||||
backendMetadataPath := cCtx.String("backends-path")
|
backendMetadataPath := cCtx.String("backends-path")
|
||||||
|
@ -76,9 +118,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Enumerating backends...")
|
log.Debug("Enumerating backends...")
|
||||||
|
|
||||||
backendList := []dbcore.Backend{}
|
backendList := []db.Backend{}
|
||||||
|
|
||||||
if err := dbcore.DB.Find(&backendList).Error; err != nil {
|
if err := dbInstance.DB.Find(&backendList).Error; err != nil {
|
||||||
return fmt.Errorf("Failed to enumerate backends: %s", err.Error())
|
return fmt.Errorf("Failed to enumerate backends: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,9 +183,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Warnf("Backend #%d has reinitialized! Starting up auto-starting proxies...", backend.ID)
|
log.Warnf("Backend #%d has reinitialized! Starting up auto-starting proxies...", backend.ID)
|
||||||
|
|
||||||
autoStartProxies := []dbcore.Proxy{}
|
autoStartProxies := []db.Proxy{}
|
||||||
|
|
||||||
if err := dbcore.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
||||||
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -243,9 +285,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Infof("Successfully initialized backend #%d", backend.ID)
|
log.Infof("Successfully initialized backend #%d", backend.ID)
|
||||||
|
|
||||||
autoStartProxies := []dbcore.Proxy{}
|
autoStartProxies := []db.Proxy{}
|
||||||
|
|
||||||
if err := dbcore.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
||||||
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
log.Errorf("Failed to query proxies to autostart: %s", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -309,23 +351,25 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
engine.SetTrustedProxies(nil)
|
engine.SetTrustedProxies(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state := state.New(dbInstance, jwtInstance, engine)
|
||||||
|
|
||||||
// Initialize routes
|
// Initialize routes
|
||||||
engine.POST("/api/v1/users/create", users.CreateUser)
|
users.SetupCreateUser(state)
|
||||||
engine.POST("/api/v1/users/login", users.LoginUser)
|
users.SetupLoginUser(state)
|
||||||
engine.POST("/api/v1/users/refresh", users.RefreshUserToken)
|
users.SetupRefreshUserToken(state)
|
||||||
engine.POST("/api/v1/users/remove", users.RemoveUser)
|
users.SetupRemoveUser(state)
|
||||||
engine.POST("/api/v1/users/lookup", users.LookupUser)
|
users.SetupLookupUser(state)
|
||||||
|
|
||||||
engine.POST("/api/v1/backends/create", backends.CreateBackend)
|
backends.SetupCreateBackend(state)
|
||||||
engine.POST("/api/v1/backends/remove", backends.RemoveBackend)
|
backends.SetupRemoveBackend(state)
|
||||||
engine.POST("/api/v1/backends/lookup", backends.LookupBackend)
|
backends.SetupLookupBackend(state)
|
||||||
|
|
||||||
engine.POST("/api/v1/forward/create", proxies.CreateProxy)
|
proxies.SetupCreateProxy(state)
|
||||||
engine.POST("/api/v1/forward/lookup", proxies.LookupProxy)
|
proxies.SetupRemoveProxy(state)
|
||||||
engine.POST("/api/v1/forward/remove", proxies.RemoveProxy)
|
proxies.SetupLookupProxy(state)
|
||||||
engine.POST("/api/v1/forward/start", proxies.StartProxy)
|
proxies.SetupStartProxy(state)
|
||||||
engine.POST("/api/v1/forward/stop", proxies.StopProxy)
|
proxies.SetupStopProxy(state)
|
||||||
engine.POST("/api/v1/forward/connections", proxies.GetConnections)
|
proxies.SetupGetConnections(state)
|
||||||
|
|
||||||
log.Infof("Listening on '%s'", listeningAddress)
|
log.Infof("Listening on '%s'", listeningAddress)
|
||||||
err = engine.Run(listeningAddress)
|
err = engine.Run(listeningAddress)
|
||||||
|
@ -362,22 +406,6 @@ func main() {
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Name: "hermes",
|
Name: "hermes",
|
||||||
Usage: "port forwarding across boundaries",
|
Usage: "port forwarding across boundaries",
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "import",
|
|
||||||
Usage: "imports from legacy NextNet/Hermes source",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "backup-path",
|
|
||||||
Aliases: []string{"bp"},
|
|
||||||
Usage: "path to the backup file",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: backupRestoreEntrypoint,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "backends-path",
|
Name: "backends-path",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package permissions
|
package permissions
|
||||||
|
|
||||||
import "git.terah.dev/imterah/hermes/backend/api/dbcore"
|
import "git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
|
|
||||||
var DefaultPermissionNodes []string = []string{
|
var DefaultPermissionNodes []string = []string{
|
||||||
"routes.add",
|
"routes.add",
|
||||||
|
@ -27,7 +27,7 @@ var DefaultPermissionNodes []string = []string{
|
||||||
"users.edit",
|
"users.edit",
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserHasPermission(user *dbcore.User, node string) bool {
|
func UserHasPermission(user *db.User, node string) bool {
|
||||||
for _, permission := range user.Permissions {
|
for _, permission := range user.Permissions {
|
||||||
if permission.PermissionNode == node && permission.HasPermission {
|
if permission.PermissionNode == node && permission.HasPermission {
|
||||||
return true
|
return true
|
||||||
|
|
24
backend/api/state/state.go
Normal file
24
backend/api/state/state.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/db"
|
||||||
|
"git.terah.dev/imterah/hermes/backend/api/jwt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
DB *db.DB
|
||||||
|
JWT *jwt.JWTCore
|
||||||
|
Engine *gin.Engine
|
||||||
|
Validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *db.DB, jwt *jwt.JWTCore, engine *gin.Engine) *State {
|
||||||
|
return &State{
|
||||||
|
DB: db,
|
||||||
|
JWT: jwt,
|
||||||
|
Engine: engine,
|
||||||
|
Validator: validator.New(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,8 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validatorInstance *validator.Validate
|
||||||
|
|
||||||
type TCPProxy struct {
|
type TCPProxy struct {
|
||||||
proxyInformation *commonbackend.AddProxy
|
proxyInformation *commonbackend.AddProxy
|
||||||
connections map[uint16]net.Conn
|
connections map[uint16]net.Conn
|
||||||
|
@ -62,6 +64,11 @@ type SSHAppBackend struct {
|
||||||
|
|
||||||
func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
||||||
log.Info("SSHAppBackend is initializing...")
|
log.Info("SSHAppBackend is initializing...")
|
||||||
|
|
||||||
|
if validatorInstance == nil {
|
||||||
|
validatorInstance = validator.New()
|
||||||
|
}
|
||||||
|
|
||||||
backend.globalNonCriticalMessageChan = make(chan interface{})
|
backend.globalNonCriticalMessageChan = make(chan interface{})
|
||||||
backend.tcpProxies = map[uint16]*TCPProxy{}
|
backend.tcpProxies = map[uint16]*TCPProxy{}
|
||||||
backend.udpProxies = map[uint16]*UDPProxy{}
|
backend.udpProxies = map[uint16]*UDPProxy{}
|
||||||
|
@ -72,7 +79,7 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&backendData); err != nil {
|
if err := validatorInstance.Struct(&backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,6 +592,10 @@ func (backend *SSHAppBackend) CheckParametersForConnections(clientParameters *co
|
||||||
func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
||||||
var backendData SSHAppBackendData
|
var backendData SSHAppBackendData
|
||||||
|
|
||||||
|
if validatorInstance == nil {
|
||||||
|
validatorInstance = validator.New()
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
|
@ -592,7 +603,7 @@ func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&backendData); err != nil {
|
if err := validatorInstance.Struct(&backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validatorInstance *validator.Validate
|
||||||
|
|
||||||
type ConnWithTimeout struct {
|
type ConnWithTimeout struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
ReadTimeout time.Duration
|
ReadTimeout time.Duration
|
||||||
|
@ -53,31 +55,46 @@ type SSHListener struct {
|
||||||
Listeners []net.Listener
|
Listeners []net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSHBackendData struct {
|
||||||
|
IP string `json:"ip" validate:"required"`
|
||||||
|
Port uint16 `json:"port" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
PrivateKey string `json:"privateKey" validate:"required"`
|
||||||
|
DisablePIDCheck bool `json:"disablePIDCheck"`
|
||||||
|
ListenOnIPs []string `json:"listenOnIPs"`
|
||||||
|
}
|
||||||
|
|
||||||
type SSHBackend struct {
|
type SSHBackend struct {
|
||||||
config *SSHBackendData
|
config *SSHBackendData
|
||||||
conn *ssh.Client
|
conn *ssh.Client
|
||||||
clients []*commonbackend.ProxyClientConnection
|
clients []*commonbackend.ProxyClientConnection
|
||||||
proxies []*SSHListener
|
proxies []*SSHListener
|
||||||
arrayPropMutex sync.Mutex
|
arrayPropMutex sync.Mutex
|
||||||
}
|
pid int
|
||||||
|
isReady bool
|
||||||
type SSHBackendData struct {
|
inReinitLoop bool
|
||||||
IP string `json:"ip" validate:"required"`
|
|
||||||
Port uint16 `json:"port" validate:"required"`
|
|
||||||
Username string `json:"username" validate:"required"`
|
|
||||||
PrivateKey string `json:"privateKey" validate:"required"`
|
|
||||||
ListenOnIPs []string `json:"listenOnIPs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
||||||
log.Info("SSHBackend is initializing...")
|
log.Info("SSHBackend is initializing...")
|
||||||
|
|
||||||
|
if validatorInstance == nil {
|
||||||
|
validatorInstance = validator.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
if backend.inReinitLoop {
|
||||||
|
for !backend.isReady {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var backendData SSHBackendData
|
var backendData SSHBackendData
|
||||||
|
|
||||||
if err := json.Unmarshal(bytes, &backendData); err != nil {
|
if err := json.Unmarshal(bytes, &backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&backendData); err != nil {
|
if err := validatorInstance.Struct(&backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +144,42 @@ func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
||||||
client := ssh.NewClient(c, chans, reqs)
|
client := ssh.NewClient(c, chans, reqs)
|
||||||
backend.conn = client
|
backend.conn = client
|
||||||
|
|
||||||
|
if !backendData.DisablePIDCheck {
|
||||||
|
if backend.pid != 0 {
|
||||||
|
session, err := client.NewSession()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Run(fmt.Sprintf("kill -9 %d", backend.pid))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to kill process: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent PID of the shell so we can kill it if we disconnect
|
||||||
|
output, err := session.Output("ps --no-headers -fp $$ | awk '{print $3}'")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip the new line and convert to int
|
||||||
|
backend.pid, err = strconv.Atoi(string(output)[:len(output)-1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go backend.backendDisconnectHandler()
|
go backend.backendDisconnectHandler()
|
||||||
go backend.backendKeepaliveHandler()
|
go backend.backendKeepaliveHandler()
|
||||||
|
|
||||||
|
@ -364,6 +417,10 @@ func (backend *SSHBackend) CheckParametersForConnections(clientParameters *commo
|
||||||
func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
|
||||||
var backendData SSHBackendData
|
var backendData SSHBackendData
|
||||||
|
|
||||||
|
if validatorInstance == nil {
|
||||||
|
validatorInstance = validator.New()
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
if err := json.Unmarshal(arguments, &backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
|
@ -371,7 +428,7 @@ func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validator.New().Struct(&backendData); err != nil {
|
if err := validatorInstance.Struct(&backendData); err != nil {
|
||||||
return &commonbackend.CheckParametersResponse{
|
return &commonbackend.CheckParametersResponse{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
Message: fmt.Sprintf("failed validation of parameters: %s", err.Error()),
|
||||||
|
@ -404,6 +461,9 @@ func (backend *SSHBackend) backendDisconnectHandler() {
|
||||||
backend.conn.Wait()
|
backend.conn.Wait()
|
||||||
backend.conn.Close()
|
backend.conn.Close()
|
||||||
|
|
||||||
|
backend.isReady = false
|
||||||
|
backend.inReinitLoop = true
|
||||||
|
|
||||||
log.Info("Disconnected from the remote SSH server. Attempting to reconnect in 5 seconds...")
|
log.Info("Disconnected from the remote SSH server. Attempting to reconnect in 5 seconds...")
|
||||||
} else {
|
} else {
|
||||||
log.Info("Retrying reconnection in 5 seconds...")
|
log.Info("Retrying reconnection in 5 seconds...")
|
||||||
|
@ -459,6 +519,46 @@ func (backend *SSHBackend) backendDisconnectHandler() {
|
||||||
client := ssh.NewClient(c, chans, reqs)
|
client := ssh.NewClient(c, chans, reqs)
|
||||||
backend.conn = client
|
backend.conn = client
|
||||||
|
|
||||||
|
if !backend.config.DisablePIDCheck {
|
||||||
|
if backend.pid != 0 {
|
||||||
|
session, err := client.NewSession()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to create SSH command session: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Run(fmt.Sprintf("kill -9 %d", backend.pid))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to kill process: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := client.NewSession()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to create SSH command session: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent PID of the shell so we can kill it if we disconnect
|
||||||
|
output, err := session.Output("ps --no-headers -fp $$ | awk '{print $3}'")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to execute command to fetch PID: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip the new line and convert to int
|
||||||
|
backend.pid, err = strconv.Atoi(string(output)[:len(output)-1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to parse PID: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go backend.backendKeepaliveHandler()
|
go backend.backendKeepaliveHandler()
|
||||||
|
|
||||||
log.Info("SSHBackend has reconnected successfully. Attempting to set up proxies again...")
|
log.Info("SSHBackend has reconnected successfully. Attempting to set up proxies again...")
|
||||||
|
|
|
@ -6,27 +6,12 @@ services:
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}?schema=nextnet
|
DATABASE_URL: postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}?schema=nextnet
|
||||||
HERMES_POSTGRES_DSN: postgres://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}
|
HERMES_POSTGRES_DSN: postgres://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@nextnet-postgres:5432/${POSTGRES_DB}
|
||||||
|
HERMES_JWT_SECRET: ${JWT_SECRET}
|
||||||
HERMES_DATABASE_BACKEND: postgresql
|
HERMES_DATABASE_BACKEND: postgresql
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
|
|
||||||
# WARN: The LOM is deprecated and likely broken currently.
|
|
||||||
#
|
|
||||||
# NOTE: For this to work correctly, the nextnet-api must be version > 0.1.1
|
|
||||||
# or have a version with backported username support, incl. logins
|
|
||||||
lom:
|
|
||||||
image: ghcr.io/imterah/hermes-lom:latest
|
|
||||||
container_name: hermes-lom
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- 2222:2222
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
volumes:
|
|
||||||
- ssh_key_data:/app/keys
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:17.2
|
image: postgres:17.2
|
||||||
container_name: nextnet-postgres
|
container_name: nextnet-postgres
|
||||||
|
@ -37,7 +22,6 @@ services:
|
||||||
POSTGRES_USER: ${POSTGRES_USERNAME}
|
POSTGRES_USER: ${POSTGRES_USERNAME}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
ssh_key_data:
|
ssh_key_data:
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# These are default values, please change these!
|
|
||||||
|
|
||||||
POSTGRES_USERNAME=hermes
|
POSTGRES_USERNAME=hermes
|
||||||
POSTGRES_PASSWORD=hermes
|
POSTGRES_PASSWORD=hermes
|
||||||
POSTGRES_DB=hermes
|
POSTGRES_DB=hermes
|
||||||
|
JWT_SECRET=hermes
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
}: pkgs.mkShell {
|
}: pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
# api/
|
# api/
|
||||||
nodejs
|
|
||||||
go
|
go
|
||||||
gopls
|
gopls
|
||||||
];
|
];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue