Compare commits
No commits in common. "dev" and "v2.2.2" have entirely different histories.
33 changed files with 2281 additions and 1991 deletions
49
CHANGELOG.md
Normal file
49
CHANGELOG.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# 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, Tera
|
Copyright (c) 2024, Greyson
|
||||||
|
|
||||||
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 -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
|
sed "s/POSTGRES_PASSWORD=hermes/POSTGRES_PASSWORD=$(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`
|
||||||
|
|
|
@ -15,9 +15,6 @@ 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)
|
||||||
|
|
||||||
|
@ -163,9 +160,6 @@ 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
|
||||||
|
@ -183,8 +177,6 @@ func (runtime *Runtime) goRoutineHandler() error {
|
||||||
|
|
||||||
runtime.messageBuffer[chanIndex] = nil
|
runtime.messageBuffer[chanIndex] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.isRuntimeCurrentlyProcessing = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sock.Close()
|
sock.Close()
|
||||||
|
@ -243,7 +235,6 @@ 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{
|
||||||
|
@ -331,10 +322,6 @@ 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,14 +16,11 @@ 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
|
||||||
|
|
298
backend/api/backup.go
Normal file
298
backend/api/backup.go
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
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,12 +7,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -23,8 +24,7 @@ type BackendCreationRequest struct {
|
||||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupCreateBackend(state *state.State) {
|
func CreateBackend(c *gin.Context) {
|
||||||
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 SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupCreateBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupCreateBackend(state *state.State) {
|
||||||
|
|
||||||
log.Info("Passed backend checks successfully")
|
log.Info("Passed backend checks successfully")
|
||||||
|
|
||||||
backendInDatabase := &db.Backend{
|
backendInDatabase := &dbcore.Backend{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
|
@ -198,7 +198,7 @@ func SetupCreateBackend(state *state.State) {
|
||||||
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&backendInDatabase); result.Error != nil {
|
if result := dbcore.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,5 +266,4 @@ func SetupCreateBackend(state *state.State) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -37,8 +38,7 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedBackend `json:"data"`
|
Data []*SanitizedBackend `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupBackend(state *state.State) {
|
func LookupBackend(c *gin.Context) {
|
||||||
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 SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupLookupBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backends := []db.Backend{}
|
backends := []dbcore.Backend{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ func SetupLookupBackend(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.Backend)
|
queryParameters = append(queryParameters, req.Backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&backends).Error; err != nil {
|
if err := dbcore.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,5 +160,4 @@ func SetupLookupBackend(state *state.State) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedBackends,
|
Data: sanitizedBackends,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -17,8 +18,7 @@ type BackendRemovalRequest struct {
|
||||||
BackendID uint `json:"id" validate:"required"`
|
BackendID uint `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveBackend(state *state.State) {
|
func RemoveBackend(c *gin.Context) {
|
||||||
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 SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend *db.Backend
|
var backend *dbcore.Backend
|
||||||
backendRequest := state.DB.DB.Where("id = ?", req.BackendID).Find(&backend)
|
backendRequest := dbcore.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 SetupRemoveBackend(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Delete(backend).Error; err != nil {
|
if err := dbcore.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,5 +120,4 @@ func SetupRemoveBackend(state *state.State) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -36,8 +37,7 @@ type ConnectionsResponse struct {
|
||||||
Data []*SanitizedConnection `json:"data"`
|
Data []*SanitizedConnection `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupGetConnections(state *state.State) {
|
func GetConnections(c *gin.Context) {
|
||||||
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 SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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,8 +56,7 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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{
|
||||||
|
@ -84,8 +83,8 @@ func SetupGetConnections(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy db.Proxy
|
var proxy dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.Id).First(&proxy)
|
proxyRequest := dbcore.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())
|
||||||
|
@ -161,5 +160,4 @@ func SetupGetConnections(state *state.State) {
|
||||||
"error": "Got illegal response type",
|
"error": "Got illegal response type",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -25,8 +26,7 @@ type ProxyCreationRequest struct {
|
||||||
AutoStart *bool `json:"autoStart"`
|
AutoStart *bool `json:"autoStart"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupCreateProxy(state *state.State) {
|
func CreateProxy(c *gin.Context) {
|
||||||
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 SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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,8 +45,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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{
|
||||||
|
@ -81,8 +80,8 @@ func SetupCreateProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var backend db.Backend
|
var backend dbcore.Backend
|
||||||
backendRequest := state.DB.DB.Where("id = ?", req.ProviderID).First(&backend)
|
backendRequest := dbcore.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())
|
||||||
|
@ -106,7 +105,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
autoStart = *req.AutoStart
|
autoStart = *req.AutoStart
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := &db.Proxy{
|
proxy := &dbcore.Proxy{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
BackendID: req.ProviderID,
|
BackendID: req.ProviderID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
@ -118,7 +117,7 @@ func SetupCreateProxy(state *state.State) {
|
||||||
AutoStart: autoStart,
|
AutoStart: autoStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(proxy); result.Error != nil {
|
if result := dbcore.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{
|
||||||
|
@ -173,5 +172,4 @@ func SetupCreateProxy(state *state.State) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"id": proxy.ID,
|
"id": proxy.ID,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -42,8 +43,7 @@ type ProxyLookupResponse struct {
|
||||||
Data []*SanitizedProxy `json:"data"`
|
Data []*SanitizedProxy `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupProxy(state *state.State) {
|
func LookupProxy(c *gin.Context) {
|
||||||
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 SetupLookupProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupLookupProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupLookupProxy(state *state.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies := []db.Proxy{}
|
proxies := []dbcore.Proxy{}
|
||||||
|
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
@ -150,7 +150,7 @@ func SetupLookupProxy(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.Protocol)
|
queryParameters = append(queryParameters, req.Protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&proxies).Error; err != nil {
|
if err := dbcore.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,5 +180,4 @@ func SetupLookupProxy(state *state.State) {
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: sanitizedProxies,
|
Data: sanitizedProxies,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -18,8 +19,7 @@ type ProxyRemovalRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveProxy(state *state.State) {
|
func RemoveProxy(c *gin.Context) {
|
||||||
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 SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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,8 +38,7 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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{
|
||||||
|
@ -66,8 +65,8 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.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())
|
||||||
|
@ -89,7 +88,7 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Delete(proxy).Error; err != nil {
|
if err := dbcore.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{
|
||||||
|
@ -134,10 +133,8 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
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 {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"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)
|
||||||
|
@ -145,6 +142,11 @@ func SetupRemoveProxy(state *state.State) {
|
||||||
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,12 +5,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -18,8 +19,7 @@ type ProxyStartRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupStartProxy(state *state.State) {
|
func StartProxy(c *gin.Context) {
|
||||||
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 SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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,8 +38,7 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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{
|
||||||
|
@ -66,8 +65,8 @@ func SetupStartProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.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())
|
||||||
|
@ -115,22 +114,29 @@ func SetupStartProxy(state *state.State) {
|
||||||
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 {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"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 likely still successfully started",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -18,9 +19,8 @@ type ProxyStopRequest struct {
|
||||||
ID uint `validate:"required" json:"id"`
|
ID uint `validate:"required" json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupStopProxy(state *state.State) {
|
func StopProxy(c *gin.Context) {
|
||||||
state.Engine.POST("/api/v1/forward/stop", func(c *gin.Context) {
|
var req ProxyStopRequest
|
||||||
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 SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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,8 +38,7 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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{
|
||||||
|
@ -66,8 +65,8 @@ func SetupStopProxy(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var proxy *db.Proxy
|
var proxy *dbcore.Proxy
|
||||||
proxyRequest := state.DB.DB.Where("id = ?", req.ID).Find(&proxy)
|
proxyRequest := dbcore.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())
|
||||||
|
@ -108,29 +107,36 @@ func SetupStopProxy(state *state.State) {
|
||||||
Protocol: proxy.Protocol,
|
Protocol: proxy.Protocol,
|
||||||
})
|
})
|
||||||
|
|
||||||
switch responseMessage := backendResponse.(type) {
|
if err != nil {
|
||||||
case error:
|
log.Warnf("Failed to get response for backend #%d: %s", proxy.BackendID, err.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 {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
return
|
||||||
"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 likely still successfully stopped",
|
"error": "Got invalid response from backend. Proxy was still successfully deleted",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
@ -20,11 +22,13 @@ 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 SetupCreateUser(state *state.State) {
|
func CreateUser(c *gin.Context) {
|
||||||
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.",
|
||||||
|
@ -43,7 +47,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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()),
|
||||||
})
|
})
|
||||||
|
@ -51,8 +55,8 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user)
|
userRequest := dbcore.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())
|
||||||
|
@ -86,7 +90,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions := []db.Permission{}
|
permissions := []dbcore.Permission{}
|
||||||
|
|
||||||
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
||||||
permissionEnabledState := false
|
permissionEnabledState := false
|
||||||
|
@ -95,7 +99,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
permissionEnabledState = true
|
permissionEnabledState = true
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions = append(permissions, db.Permission{
|
permissions = append(permissions, dbcore.Permission{
|
||||||
PermissionNode: permission,
|
PermissionNode: permission,
|
||||||
HasPermission: permissionEnabledState,
|
HasPermission: permissionEnabledState,
|
||||||
})
|
})
|
||||||
|
@ -113,14 +117,14 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user = &db.User{
|
user = &dbcore.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: []db.Token{
|
Tokens: []dbcore.Token{
|
||||||
{
|
{
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
DisableExpiry: forceNoExpiryTokens,
|
DisableExpiry: forceNoExpiryTokens,
|
||||||
|
@ -129,7 +133,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&user); result.Error != nil {
|
if result := dbcore.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{
|
||||||
|
@ -139,7 +143,7 @@ func SetupCreateUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.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())
|
||||||
|
@ -156,5 +160,4 @@ func SetupCreateUser(state *state.State) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,8 +21,7 @@ type UserLoginRequest struct {
|
||||||
Password string `validate:"required"`
|
Password string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLoginUser(state *state.State) {
|
func LoginUser(c *gin.Context) {
|
||||||
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 SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupLoginUser(state *state.State) {
|
||||||
userFindRequest += "username = ?"
|
userFindRequest += "username = ?"
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where(userFindRequest, userFindRequestArguments...).Find(&user)
|
userRequest := dbcore.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 SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := &db.Token{
|
token := &dbcore.Token{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
|
||||||
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
Token: base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
|
@ -127,7 +127,7 @@ func SetupLoginUser(state *state.State) {
|
||||||
CreationIPAddr: c.ClientIP(),
|
CreationIPAddr: c.ClientIP(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result := state.DB.DB.Create(&token); result.Error != nil {
|
if result := dbcore.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 SetupLoginUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.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,5 +154,4 @@ func SetupLoginUser(state *state.State) {
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
"refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData),
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -34,8 +35,7 @@ type LookupResponse struct {
|
||||||
Data []*SanitizedUsers `json:"data"`
|
Data []*SanitizedUsers `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupLookupUser(state *state.State) {
|
func LookupUser(c *gin.Context) {
|
||||||
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 SetupLookupUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupLookupUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupLookupUser(state *state.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users := []db.User{}
|
users := []dbcore.User{}
|
||||||
queryString := []string{}
|
queryString := []string{}
|
||||||
queryParameters := []interface{}{}
|
queryParameters := []interface{}{}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func SetupLookupUser(state *state.State) {
|
||||||
queryParameters = append(queryParameters, req.IsBot)
|
queryParameters = append(queryParameters, req.IsBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.DB.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil {
|
if err := dbcore.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,5 +133,4 @@ func SetupLookupUser(state *state.State) {
|
||||||
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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/state"
|
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
||||||
"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 SetupRefreshUserToken(state *state.State) {
|
func RefreshUserToken(c *gin.Context) {
|
||||||
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 SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenInDatabase *db.Token
|
var tokenInDatabase *dbcore.Token
|
||||||
tokenRequest := state.DB.DB.Where("token = ?", req.Token).Find(&tokenInDatabase)
|
tokenRequest := dbcore.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 SetupRefreshUserToken(state *state.State) {
|
||||||
"error": "Token has expired",
|
"error": "Token has expired",
|
||||||
})
|
})
|
||||||
|
|
||||||
tx := state.DB.DB.Delete(tokenInDatabase)
|
tx := dbcore.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 SetupRefreshUserToken(state *state.State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *db.User
|
var user *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user)
|
userRequest := dbcore.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 SetupRefreshUserToken(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := state.JWT.Generate(user.ID)
|
jwt, err := jwtcore.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,5 +114,4 @@ func SetupRefreshUserToken(state *state.State) {
|
||||||
"success": true,
|
"success": true,
|
||||||
"token": jwt,
|
"token": jwt,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/backend/api/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
"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 {
|
||||||
|
@ -16,8 +17,7 @@ type UserRemovalRequest struct {
|
||||||
UID *uint `json:"uid"`
|
UID *uint `json:"uid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupRemoveUser(state *state.State) {
|
func RemoveUser(c *gin.Context) {
|
||||||
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 SetupRemoveUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := state.Validator.Struct(&req); err != nil {
|
if err := validator.New().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 SetupRemoveUser(state *state.State) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := state.JWT.GetUserFromJWT(req.Token)
|
user, err := jwtcore.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 SetupRemoveUser(state *state.State) {
|
||||||
// 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 *db.User
|
var customUser *dbcore.User
|
||||||
userRequest := state.DB.DB.Where("id = ?", uid).Find(customUser)
|
userRequest := dbcore.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,10 +97,9 @@ func SetupRemoveUser(state *state.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.DB.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
dbcore.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,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
142
backend/api/dbcore/db.go
Normal file
142
backend/api/dbcore/db.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
117
backend/api/jwtcore/jwt.go
Normal file
117
backend/api/jwtcore/jwt.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
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,19 +9,18 @@ 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/db"
|
"git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/backend/api/jwt"
|
"git.terah.dev/imterah/hermes/backend/api/jwtcore"
|
||||||
"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 {
|
||||||
|
@ -35,26 +34,7 @@ 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...")
|
||||||
|
|
||||||
databaseBackendName := os.Getenv("HERMES_DATABASE_BACKEND")
|
err := dbcore.InitializeDatabase(&gorm.Config{})
|
||||||
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)
|
||||||
|
@ -62,38 +42,16 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Running database migrations...")
|
log.Debug("Running database migrations...")
|
||||||
|
|
||||||
if err := dbInstance.DoMigrations(); err != nil {
|
if err := dbcore.DoDatabaseMigrations(dbcore.DB); 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...")
|
||||||
|
|
||||||
jwtDataString := os.Getenv("HERMES_JWT_SECRET")
|
if err := jwtcore.SetupJWT(); err != nil {
|
||||||
var jwtKey []byte
|
return fmt.Errorf("Failed to initialize the JWT subsystem: %s", err.Error())
|
||||||
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")
|
||||||
|
@ -118,9 +76,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Debug("Enumerating backends...")
|
log.Debug("Enumerating backends...")
|
||||||
|
|
||||||
backendList := []db.Backend{}
|
backendList := []dbcore.Backend{}
|
||||||
|
|
||||||
if err := dbInstance.DB.Find(&backendList).Error; err != nil {
|
if err := dbcore.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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,9 +141,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 := []db.Proxy{}
|
autoStartProxies := []dbcore.Proxy{}
|
||||||
|
|
||||||
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
if err := dbcore.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
|
||||||
}
|
}
|
||||||
|
@ -285,9 +243,9 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
log.Infof("Successfully initialized backend #%d", backend.ID)
|
log.Infof("Successfully initialized backend #%d", backend.ID)
|
||||||
|
|
||||||
autoStartProxies := []db.Proxy{}
|
autoStartProxies := []dbcore.Proxy{}
|
||||||
|
|
||||||
if err := dbInstance.DB.Where("backend_id = ? AND auto_start = true", backend.ID).Find(&autoStartProxies).Error; err != nil {
|
if err := dbcore.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
|
||||||
}
|
}
|
||||||
|
@ -351,25 +309,23 @@ func apiEntrypoint(cCtx *cli.Context) error {
|
||||||
engine.SetTrustedProxies(nil)
|
engine.SetTrustedProxies(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := state.New(dbInstance, jwtInstance, engine)
|
|
||||||
|
|
||||||
// Initialize routes
|
// Initialize routes
|
||||||
users.SetupCreateUser(state)
|
engine.POST("/api/v1/users/create", users.CreateUser)
|
||||||
users.SetupLoginUser(state)
|
engine.POST("/api/v1/users/login", users.LoginUser)
|
||||||
users.SetupRefreshUserToken(state)
|
engine.POST("/api/v1/users/refresh", users.RefreshUserToken)
|
||||||
users.SetupRemoveUser(state)
|
engine.POST("/api/v1/users/remove", users.RemoveUser)
|
||||||
users.SetupLookupUser(state)
|
engine.POST("/api/v1/users/lookup", users.LookupUser)
|
||||||
|
|
||||||
backends.SetupCreateBackend(state)
|
engine.POST("/api/v1/backends/create", backends.CreateBackend)
|
||||||
backends.SetupRemoveBackend(state)
|
engine.POST("/api/v1/backends/remove", backends.RemoveBackend)
|
||||||
backends.SetupLookupBackend(state)
|
engine.POST("/api/v1/backends/lookup", backends.LookupBackend)
|
||||||
|
|
||||||
proxies.SetupCreateProxy(state)
|
engine.POST("/api/v1/forward/create", proxies.CreateProxy)
|
||||||
proxies.SetupRemoveProxy(state)
|
engine.POST("/api/v1/forward/lookup", proxies.LookupProxy)
|
||||||
proxies.SetupLookupProxy(state)
|
engine.POST("/api/v1/forward/remove", proxies.RemoveProxy)
|
||||||
proxies.SetupStartProxy(state)
|
engine.POST("/api/v1/forward/start", proxies.StartProxy)
|
||||||
proxies.SetupStopProxy(state)
|
engine.POST("/api/v1/forward/stop", proxies.StopProxy)
|
||||||
proxies.SetupGetConnections(state)
|
engine.POST("/api/v1/forward/connections", proxies.GetConnections)
|
||||||
|
|
||||||
log.Infof("Listening on '%s'", listeningAddress)
|
log.Infof("Listening on '%s'", listeningAddress)
|
||||||
err = engine.Run(listeningAddress)
|
err = engine.Run(listeningAddress)
|
||||||
|
@ -406,6 +362,22 @@ 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/db"
|
import "git.terah.dev/imterah/hermes/backend/api/dbcore"
|
||||||
|
|
||||||
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 *db.User, node string) bool {
|
func UserHasPermission(user *dbcore.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
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
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,8 +26,6 @@ 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
|
||||||
|
@ -64,11 +62,6 @@ 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{}
|
||||||
|
@ -79,7 +72,7 @@ func (backend *SSHAppBackend) StartBackend(configBytes []byte) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().Struct(&backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,10 +585,6 @@ 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,
|
||||||
|
@ -603,7 +592,7 @@ func (backend *SSHAppBackend) CheckParametersForBackend(arguments []byte) *commo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().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,8 +19,6 @@ 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
|
||||||
|
@ -78,10 +76,6 @@ type SSHBackend struct {
|
||||||
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 {
|
if backend.inReinitLoop {
|
||||||
for !backend.isReady {
|
for !backend.isReady {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
@ -94,7 +88,7 @@ func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().Struct(&backendData); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,10 +411,6 @@ 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,
|
||||||
|
@ -428,7 +418,7 @@ func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validatorInstance.Struct(&backendData); err != nil {
|
if err := validator.New().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()),
|
||||||
|
|
|
@ -6,12 +6,27 @@ 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
|
||||||
|
@ -22,6 +37,7 @@ 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,4 +1,5 @@
|
||||||
|
# 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,6 +3,7 @@
|
||||||
}: 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