3 Commits

Author SHA1 Message Date
4ffaec9d9a refactor: inject SessionRegistry interface instead of individual functions
All checks were successful
Docker Build and Push / build-and-push-branches (push) Has been skipped
Docker Build and Push / build-and-push-tags (push) Successful in 4m16s
2026-01-05 16:49:17 +07:00
6de0a618ee update: proto file to v1.3.0
All checks were successful
Docker Build and Push / build-and-push-branches (push) Has been skipped
Docker Build and Push / build-and-push-tags (push) Successful in 4m0s
2026-01-05 00:55:51 +07:00
8cc70fa45e feat(session): use session key for registry 2026-01-05 00:50:42 +07:00
12 changed files with 179 additions and 107 deletions

2
go.mod
View File

@@ -3,7 +3,7 @@ module tunnel_pls
go 1.25.5 go 1.25.5
require ( require (
git.fossy.my.id/bagas/tunnel-please-grpc v1.2.0 git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0
github.com/caddyserver/certmagic v0.25.0 github.com/caddyserver/certmagic v0.25.0
github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/bubbletea v1.3.10

6
go.sum
View File

@@ -1,5 +1,7 @@
git.fossy.my.id/bagas/tunnel-please-grpc v1.2.0 h1:BS1dJU3wa2ILgTGwkV95Knle0il0OQtErGqyb6xV7SU= git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0 h1:RhcBKUG41/om4jgN+iF/vlY/RojTeX1QhBa4p4428ec=
git.fossy.my.id/bagas/tunnel-please-grpc v1.2.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY= git.fossy.my.id/bagas/tunnel-please-grpc v1.3.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0 h1:tpJSKjaSmV+vxxbVx6qnStjxFVXjj2M0rygWXxLb99o=
git.fossy.my.id/bagas/tunnel-please-grpc v1.4.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=

View File

@@ -9,6 +9,7 @@ import (
"log" "log"
"time" "time"
"tunnel_pls/internal/config" "tunnel_pls/internal/config"
"tunnel_pls/types"
"tunnel_pls/session" "tunnel_pls/session"
@@ -39,7 +40,6 @@ type Client struct {
conn *grpc.ClientConn conn *grpc.ClientConn
config *GrpcConfig config *GrpcConfig
sessionRegistry session.Registry sessionRegistry session.Registry
slugService proto.SlugChangeClient
eventService proto.EventServiceClient eventService proto.EventServiceClient
authorizeConnectionService proto.UserServiceClient authorizeConnectionService proto.UserServiceClient
closing bool closing bool
@@ -114,14 +114,12 @@ func New(config *GrpcConfig, sessionRegistry session.Registry) (*Client, error)
return nil, fmt.Errorf("failed to connect to gRPC server at %s: %w", config.Address, err) return nil, fmt.Errorf("failed to connect to gRPC server at %s: %w", config.Address, err)
} }
slugService := proto.NewSlugChangeClient(conn)
eventService := proto.NewEventServiceClient(conn) eventService := proto.NewEventServiceClient(conn)
authorizeConnectionService := proto.NewUserServiceClient(conn) authorizeConnectionService := proto.NewUserServiceClient(conn)
return &Client{ return &Client{
conn: conn, conn: conn,
config: config, config: config,
slugService: slugService,
sessionRegistry: sessionRegistry, sessionRegistry: sessionRegistry,
eventService: eventService, eventService: eventService,
authorizeConnectionService: authorizeConnectionService, authorizeConnectionService: authorizeConnectionService,
@@ -201,7 +199,6 @@ func (c *Client) SubscribeEvents(ctx context.Context, identity, authToken string
if c.isConnectionError(err) { if c.isConnectionError(err) {
log.Printf("Reconnect to controller within %v sec", backoff.Seconds()) log.Printf("Reconnect to controller within %v sec", backoff.Seconds())
if err = wait(); err != nil { if err = wait(); err != nil {
fmt.Println(err)
return err return err
} }
growBackoff() growBackoff()
@@ -220,9 +217,14 @@ func (c *Client) processEventStream(subscribe grpc.BidiStreamingClient[proto.Nod
} }
switch recv.GetType() { switch recv.GetType() {
case proto.EventType_SLUG_CHANGE: case proto.EventType_SLUG_CHANGE:
user := recv.GetSlugEvent().GetUser()
oldSlug := recv.GetSlugEvent().GetOld() oldSlug := recv.GetSlugEvent().GetOld()
newSlug := recv.GetSlugEvent().GetNew() newSlug := recv.GetSlugEvent().GetNew()
sess, err := c.sessionRegistry.Get(oldSlug) var userSession *session.SSHSession
userSession, err = c.sessionRegistry.Get(types.SessionKey{
Id: oldSlug,
Type: types.HTTP,
})
if err != nil { if err != nil {
errSend := subscribe.Send(&proto.Node{ errSend := subscribe.Send(&proto.Node{
Type: proto.EventType_SLUG_CHANGE_RESPONSE, Type: proto.EventType_SLUG_CHANGE_RESPONSE,
@@ -241,7 +243,13 @@ func (c *Client) processEventStream(subscribe grpc.BidiStreamingClient[proto.Nod
} }
continue continue
} }
err = c.sessionRegistry.Update(oldSlug, newSlug) err = c.sessionRegistry.Update(user, types.SessionKey{
Id: oldSlug,
Type: types.HTTP,
}, types.SessionKey{
Id: newSlug,
Type: types.HTTP,
})
if err != nil { if err != nil {
errSend := subscribe.Send(&proto.Node{ errSend := subscribe.Send(&proto.Node{
Type: proto.EventType_SLUG_CHANGE_RESPONSE, Type: proto.EventType_SLUG_CHANGE_RESPONSE,
@@ -260,7 +268,7 @@ func (c *Client) processEventStream(subscribe grpc.BidiStreamingClient[proto.Nod
} }
continue continue
} }
sess.GetInteraction().Redraw() userSession.GetInteraction().Redraw()
err = subscribe.Send(&proto.Node{ err = subscribe.Send(&proto.Node{
Type: proto.EventType_SLUG_CHANGE_RESPONSE, Type: proto.EventType_SLUG_CHANGE_RESPONSE,
Payload: &proto.Node_SlugEventResponse{ Payload: &proto.Node_SlugEventResponse{

View File

@@ -13,6 +13,7 @@ import (
"time" "time"
"tunnel_pls/internal/config" "tunnel_pls/internal/config"
"tunnel_pls/session" "tunnel_pls/session"
"tunnel_pls/types"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@@ -313,7 +314,10 @@ func (hs *httpServer) handler(conn net.Conn) {
return return
} }
sshSession, err := hs.sessionRegistry.Get(slug) sshSession, err := hs.sessionRegistry.Get(types.SessionKey{
Id: slug,
Type: types.HTTP,
})
if err != nil { if err != nil {
_, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" + _, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" +
fmt.Sprintf("Location: https://tunnl.live/tunnel-not-found?slug=%s\r\n", slug) + fmt.Sprintf("Location: https://tunnl.live/tunnel-not-found?slug=%s\r\n", slug) +

View File

@@ -9,6 +9,7 @@ import (
"net" "net"
"strings" "strings"
"tunnel_pls/internal/config" "tunnel_pls/internal/config"
"tunnel_pls/types"
) )
func (hs *httpServer) ListenAndServeTLS() error { func (hs *httpServer) ListenAndServeTLS() error {
@@ -89,7 +90,10 @@ func (hs *httpServer) handlerTLS(conn net.Conn) {
return return
} }
sshSession, err := hs.sessionRegistry.Get(slug) sshSession, err := hs.sessionRegistry.Get(types.SessionKey{
Id: slug,
Type: types.HTTP,
})
if err != nil { if err != nil {
_, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" + _, err = conn.Write([]byte("HTTP/1.1 301 Moved Permanently\r\n" +
fmt.Sprintf("Location: https://tunnl.live/tunnel-not-found?slug=%s\r\n", slug) + fmt.Sprintf("Location: https://tunnl.live/tunnel-not-found?slug=%s\r\n", slug) +

View File

@@ -88,7 +88,6 @@ func (s *Server) handleConnection(conn net.Conn) {
_, u, _ := s.grpcClient.AuthorizeConn(ctx, sshConn.User()) _, u, _ := s.grpcClient.AuthorizeConn(ctx, sshConn.User())
user = u user = u
} }
sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry, user) sshSession := session.New(sshConn, forwardingReqs, chans, s.sessionRegistry, user)
err = sshSession.Start() err = sshSession.Start()
if err != nil { if err != nil {

View File

@@ -164,8 +164,9 @@ func (s *SSHSession) HandleTCPIPForward(req *ssh.Request) {
func (s *SSHSession) HandleHTTPForward(req *ssh.Request, portToBind uint16) { func (s *SSHSession) HandleHTTPForward(req *ssh.Request, portToBind uint16) {
slug := random.GenerateRandomString(20) slug := random.GenerateRandomString(20)
key := types.SessionKey{Id: slug, Type: types.HTTP}
if !s.registry.Register(slug, s) { if !s.registry.Register(key, s) {
log.Printf("Failed to register client with slug: %s", slug) log.Printf("Failed to register client with slug: %s", slug)
err := req.Reply(false, nil) err := req.Reply(false, nil)
if err != nil { if err != nil {
@@ -178,7 +179,7 @@ func (s *SSHSession) HandleHTTPForward(req *ssh.Request, portToBind uint16) {
err := binary.Write(buf, binary.BigEndian, uint32(portToBind)) err := binary.Write(buf, binary.BigEndian, uint32(portToBind))
if err != nil { if err != nil {
log.Println("Failed to write port to buffer:", err) log.Println("Failed to write port to buffer:", err)
s.registry.Remove(slug) s.registry.Remove(key)
err = req.Reply(false, nil) err = req.Reply(false, nil)
if err != nil { if err != nil {
log.Println("Failed to reply to request:", err) log.Println("Failed to reply to request:", err)
@@ -190,7 +191,7 @@ func (s *SSHSession) HandleHTTPForward(req *ssh.Request, portToBind uint16) {
err = req.Reply(true, buf.Bytes()) err = req.Reply(true, buf.Bytes())
if err != nil { if err != nil {
log.Println("Failed to reply to request:", err) log.Println("Failed to reply to request:", err)
s.registry.Remove(slug) s.registry.Remove(key)
err = req.Reply(false, nil) err = req.Reply(false, nil)
if err != nil { if err != nil {
log.Println("Failed to reply to request:", err) log.Println("Failed to reply to request:", err)
@@ -225,10 +226,29 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, portToBind
return return
} }
key := types.SessionKey{Id: fmt.Sprintf("%d", portToBind), Type: types.TCP}
if !s.registry.Register(key, s) {
log.Printf("Failed to register TCP client with id: %s", key.Id)
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
log.Printf("Failed to reset port status: %v", setErr)
}
if closeErr := listener.Close(); closeErr != nil {
log.Printf("Failed to close listener: %s", closeErr)
}
err = req.Reply(false, nil)
if err != nil {
log.Println("Failed to reply to request:", err)
}
_ = s.lifecycle.Close()
return
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err = binary.Write(buf, binary.BigEndian, uint32(portToBind)) err = binary.Write(buf, binary.BigEndian, uint32(portToBind))
if err != nil { if err != nil {
log.Println("Failed to write port to buffer:", err) log.Println("Failed to write port to buffer:", err)
s.registry.Remove(key)
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil { if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
log.Printf("Failed to reset port status: %v", setErr) log.Printf("Failed to reset port status: %v", setErr)
} }
@@ -244,6 +264,7 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, portToBind
err = req.Reply(true, buf.Bytes()) err = req.Reply(true, buf.Bytes())
if err != nil { if err != nil {
log.Println("Failed to reply to request:", err) log.Println("Failed to reply to request:", err)
s.registry.Remove(key)
if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil { if setErr := portUtil.Default.SetPortStatus(portToBind, false); setErr != nil {
log.Printf("Failed to reset port status: %v", setErr) log.Printf("Failed to reset port status: %v", setErr)
} }
@@ -258,6 +279,7 @@ func (s *SSHSession) HandleTCPForward(req *ssh.Request, addr string, portToBind
s.forwarder.SetType(types.TCP) s.forwarder.SetType(types.TCP)
s.forwarder.SetListener(listener) s.forwarder.SetListener(listener)
s.forwarder.SetForwardedPort(portToBind) s.forwarder.SetForwardedPort(portToBind)
s.slugManager.Set(key.Id)
s.lifecycle.SetStatus(types.RUNNING) s.lifecycle.SetStatus(types.RUNNING)
go s.forwarder.AcceptTCPConnections() go s.forwarder.AcceptTCPConnections()
s.interaction.Start() s.interaction.Start()

View File

@@ -23,15 +23,20 @@ import (
type Lifecycle interface { type Lifecycle interface {
Close() error Close() error
GetUser() string
}
type SessionRegistry interface {
Update(user string, oldKey, newKey types.SessionKey) error
} }
type Controller interface { type Controller interface {
SetChannel(channel ssh.Channel) SetChannel(channel ssh.Channel)
SetLifecycle(lifecycle Lifecycle) SetLifecycle(lifecycle Lifecycle)
SetSlugModificator(func(oldSlug, newSlug string) error)
Start() Start()
SetWH(w, h int) SetWH(w, h int)
Redraw() Redraw()
SetSessionRegistry(registry SessionRegistry)
} }
type Forwarder interface { type Forwarder interface {
@@ -41,14 +46,14 @@ type Forwarder interface {
} }
type Interaction struct { type Interaction struct {
channel ssh.Channel channel ssh.Channel
slugManager slug.Manager slugManager slug.Manager
forwarder Forwarder forwarder Forwarder
lifecycle Lifecycle lifecycle Lifecycle
updateClientSlug func(oldSlug, newSlug string) error sessionRegistry SessionRegistry
program *tea.Program program *tea.Program
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
} }
func (i *Interaction) SetWH(w, h int) { func (i *Interaction) SetWH(w, h int) {
@@ -102,17 +107,21 @@ type tickMsg time.Time
func NewInteraction(slugManager slug.Manager, forwarder Forwarder) *Interaction { func NewInteraction(slugManager slug.Manager, forwarder Forwarder) *Interaction {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &Interaction{ return &Interaction{
channel: nil, channel: nil,
slugManager: slugManager, slugManager: slugManager,
forwarder: forwarder, forwarder: forwarder,
lifecycle: nil, lifecycle: nil,
updateClientSlug: nil, sessionRegistry: nil,
program: nil, program: nil,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
} }
func (i *Interaction) SetSessionRegistry(registry SessionRegistry) {
i.sessionRegistry = registry
}
func (i *Interaction) SetLifecycle(lifecycle Lifecycle) { func (i *Interaction) SetLifecycle(lifecycle Lifecycle) {
i.lifecycle = lifecycle i.lifecycle = lifecycle
} }
@@ -121,10 +130,6 @@ func (i *Interaction) SetChannel(channel ssh.Channel) {
i.channel = channel i.channel = channel
} }
func (i *Interaction) SetSlugModificator(modificator func(oldSlug, newSlug string) error) {
i.updateClientSlug = modificator
}
func (i *Interaction) Stop() { func (i *Interaction) Stop() {
if i.cancel != nil { if i.cancel != nil {
i.cancel() i.cancel()
@@ -218,7 +223,13 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(tea.ClearScreen, textinput.Blink) return m, tea.Batch(tea.ClearScreen, textinput.Blink)
case "enter": case "enter":
inputValue := m.slugInput.Value() inputValue := m.slugInput.Value()
if err := m.interaction.updateClientSlug(m.interaction.slugManager.Get(), inputValue); err != nil { if err := m.interaction.sessionRegistry.Update(m.interaction.lifecycle.GetUser(), types.SessionKey{
Id: m.interaction.slugManager.Get(),
Type: types.HTTP,
}, types.SessionKey{
Id: inputValue,
Type: types.HTTP,
}); err != nil {
m.slugError = err.Error() m.slugError = err.Error()
return m, nil return m, nil
} }

View File

@@ -19,30 +19,36 @@ type Forwarder interface {
GetForwardedPort() uint16 GetForwardedPort() uint16
} }
type Lifecycle struct { type SessionRegistry interface {
status types.Status Remove(key types.SessionKey)
conn ssh.Conn
channel ssh.Channel
forwarder Forwarder
slugManager slug.Manager
unregisterClient func(slug string)
startedAt time.Time
} }
func NewLifecycle(conn ssh.Conn, forwarder Forwarder, slugManager slug.Manager) *Lifecycle { type Lifecycle struct {
status types.Status
conn ssh.Conn
channel ssh.Channel
forwarder Forwarder
sessionRegistry SessionRegistry
slugManager slug.Manager
startedAt time.Time
user string
}
func NewLifecycle(conn ssh.Conn, forwarder Forwarder, slugManager slug.Manager, user string) *Lifecycle {
return &Lifecycle{ return &Lifecycle{
status: types.INITIALIZING, status: types.INITIALIZING,
conn: conn, conn: conn,
channel: nil, channel: nil,
forwarder: forwarder, forwarder: forwarder,
slugManager: slugManager, slugManager: slugManager,
unregisterClient: nil, sessionRegistry: nil,
startedAt: time.Now(), startedAt: time.Now(),
user: user,
} }
} }
func (l *Lifecycle) SetUnregisterClient(unregisterClient func(slug string)) { func (l *Lifecycle) SetSessionRegistry(registry SessionRegistry) {
l.unregisterClient = unregisterClient l.sessionRegistry = registry
} }
type SessionLifecycle interface { type SessionLifecycle interface {
@@ -50,12 +56,17 @@ type SessionLifecycle interface {
SetStatus(status types.Status) SetStatus(status types.Status)
GetConnection() ssh.Conn GetConnection() ssh.Conn
GetChannel() ssh.Channel GetChannel() ssh.Channel
GetUser() string
SetChannel(channel ssh.Channel) SetChannel(channel ssh.Channel)
SetUnregisterClient(unregisterClient func(slug string)) SetSessionRegistry(registry SessionRegistry)
IsActive() bool IsActive() bool
StartedAt() time.Time StartedAt() time.Time
} }
func (l *Lifecycle) GetUser() string {
return l.user
}
func (l *Lifecycle) GetChannel() ssh.Channel { func (l *Lifecycle) GetChannel() ssh.Channel {
return l.channel return l.channel
} }
@@ -94,12 +105,13 @@ func (l *Lifecycle) Close() error {
} }
clientSlug := l.slugManager.Get() clientSlug := l.slugManager.Get()
if clientSlug != "" { if clientSlug != "" && l.sessionRegistry.Remove != nil {
l.unregisterClient(clientSlug) key := types.SessionKey{Id: clientSlug, Type: l.forwarder.GetTunnelType()}
l.sessionRegistry.Remove(key)
} }
if l.forwarder.GetTunnelType() == types.TCP { if l.forwarder.GetTunnelType() == types.TCP {
err := portUtil.Default.SetPortStatus(l.forwarder.GetForwardedPort(), false) err = portUtil.Default.SetPortStatus(l.forwarder.GetForwardedPort(), false)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -3,96 +3,103 @@ package session
import ( import (
"fmt" "fmt"
"sync" "sync"
"tunnel_pls/types"
) )
type Key = types.SessionKey
type Registry interface { type Registry interface {
Get(slug string) (session *SSHSession, err error) Get(key Key) (session *SSHSession, err error)
Update(oldSlug, newSlug string) error Update(user string, oldKey, newKey Key) error
Register(slug string, session *SSHSession) (success bool) Register(key Key, session *SSHSession) (success bool)
Remove(slug string) Remove(key Key)
GetAllSessionFromUser(user string) []*SSHSession GetAllSessionFromUser(user string) []*SSHSession
} }
type registry struct { type registry struct {
mu sync.RWMutex mu sync.RWMutex
byUser map[string]map[string]*SSHSession byUser map[string]map[Key]*SSHSession
slugIndex map[string]string slugIndex map[Key]string
} }
func NewRegistry() Registry { func NewRegistry() Registry {
return &registry{ return &registry{
byUser: make(map[string]map[string]*SSHSession), byUser: make(map[string]map[Key]*SSHSession),
slugIndex: make(map[string]string), slugIndex: make(map[Key]string),
} }
} }
func (r *registry) Get(slug string) (session *SSHSession, err error) { func (r *registry) Get(key Key) (session *SSHSession, err error) {
r.mu.RLock() r.mu.RLock()
defer r.mu.RUnlock() defer r.mu.RUnlock()
userID, ok := r.slugIndex[slug] userID, ok := r.slugIndex[key]
if !ok { if !ok {
return nil, fmt.Errorf("session not found") return nil, fmt.Errorf("session not found")
} }
client, ok := r.byUser[userID][slug] client, ok := r.byUser[userID][key]
if !ok { if !ok {
return nil, fmt.Errorf("session not found") return nil, fmt.Errorf("session not found")
} }
return client, nil return client, nil
} }
func (r *registry) Update(oldSlug, newSlug string) error { func (r *registry) Update(user string, oldKey, newKey Key) error {
if isForbiddenSlug(newSlug) { if oldKey.Type != newKey.Type {
return fmt.Errorf("tunnel type cannot change")
}
if newKey.Type != types.HTTP {
return fmt.Errorf("non http tunnel cannot change slug")
}
if isForbiddenSlug(newKey.Id) {
return fmt.Errorf("this subdomain is reserved. Please choose a different one") return fmt.Errorf("this subdomain is reserved. Please choose a different one")
} else if !isValidSlug(newSlug) { }
if !isValidSlug(newKey.Id) {
return fmt.Errorf("invalid subdomain. Follow the rules") return fmt.Errorf("invalid subdomain. Follow the rules")
} }
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
userID, ok := r.slugIndex[oldSlug] if _, exists := r.slugIndex[newKey]; exists && newKey != oldKey {
if !ok {
return fmt.Errorf("session not found")
}
if _, exists := r.slugIndex[newSlug]; exists && newSlug != oldSlug {
return fmt.Errorf("someone already uses this subdomain") return fmt.Errorf("someone already uses this subdomain")
} }
client, ok := r.byUser[user][oldKey]
client, ok := r.byUser[userID][oldSlug]
if !ok { if !ok {
return fmt.Errorf("session not found") return fmt.Errorf("session not found")
} }
delete(r.byUser[userID], oldSlug) delete(r.byUser[user], oldKey)
delete(r.slugIndex, oldSlug) delete(r.slugIndex, oldKey)
client.slugManager.Set(newSlug) client.slugManager.Set(newKey.Id)
r.slugIndex[newSlug] = userID r.slugIndex[newKey] = user
if r.byUser[userID] == nil { if r.byUser[user] == nil {
r.byUser[userID] = make(map[string]*SSHSession) r.byUser[user] = make(map[Key]*SSHSession)
} }
r.byUser[userID][newSlug] = client r.byUser[user][newKey] = client
return nil return nil
} }
func (r *registry) Register(slug string, session *SSHSession) (success bool) { func (r *registry) Register(key Key, session *SSHSession) (success bool) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
if _, exists := r.slugIndex[slug]; exists { if _, exists := r.slugIndex[key]; exists {
return false return false
} }
userID := session.userID userID := session.lifecycle.GetUser()
if r.byUser[userID] == nil { if r.byUser[userID] == nil {
r.byUser[userID] = make(map[string]*SSHSession) r.byUser[userID] = make(map[Key]*SSHSession)
} }
r.byUser[userID][slug] = session r.byUser[userID][key] = session
r.slugIndex[slug] = userID r.slugIndex[key] = userID
return true return true
} }
@@ -112,20 +119,20 @@ func (r *registry) GetAllSessionFromUser(user string) []*SSHSession {
return sessions return sessions
} }
func (r *registry) Remove(slug string) { func (r *registry) Remove(key Key) {
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
userID, ok := r.slugIndex[slug] userID, ok := r.slugIndex[key]
if !ok { if !ok {
return return
} }
delete(r.byUser[userID], slug) delete(r.byUser[userID], key)
if len(r.byUser[userID]) == 0 { if len(r.byUser[userID]) == 0 {
delete(r.byUser, userID) delete(r.byUser, userID)
} }
delete(r.slugIndex, slug) delete(r.slugIndex, key)
} }
func isValidSlug(slug string) bool { func isValidSlug(slug string) bool {

View File

@@ -28,7 +28,6 @@ type SSHSession struct {
forwarder forwarder.ForwardingController forwarder forwarder.ForwardingController
slugManager slug.Manager slugManager slug.Manager
registry Registry registry Registry
userID string
} }
func (s *SSHSession) GetLifecycle() lifecycle.SessionLifecycle { func (s *SSHSession) GetLifecycle() lifecycle.SessionLifecycle {
@@ -47,16 +46,16 @@ func (s *SSHSession) GetSlugManager() slug.Manager {
return s.slugManager return s.slugManager
} }
func New(conn *ssh.ServerConn, forwardingReq <-chan *ssh.Request, sshChan <-chan ssh.NewChannel, sessionRegistry Registry, userID string) *SSHSession { func New(conn *ssh.ServerConn, forwardingReq <-chan *ssh.Request, sshChan <-chan ssh.NewChannel, sessionRegistry Registry, user string) *SSHSession {
slugManager := slug.NewManager() slugManager := slug.NewManager()
forwarderManager := forwarder.NewForwarder(slugManager) forwarderManager := forwarder.NewForwarder(slugManager)
interactionManager := interaction.NewInteraction(slugManager, forwarderManager) interactionManager := interaction.NewInteraction(slugManager, forwarderManager)
lifecycleManager := lifecycle.NewLifecycle(conn, forwarderManager, slugManager) lifecycleManager := lifecycle.NewLifecycle(conn, forwarderManager, slugManager, user)
interactionManager.SetLifecycle(lifecycleManager) interactionManager.SetLifecycle(lifecycleManager)
interactionManager.SetSlugModificator(sessionRegistry.Update)
forwarderManager.SetLifecycle(lifecycleManager) forwarderManager.SetLifecycle(lifecycleManager)
lifecycleManager.SetUnregisterClient(sessionRegistry.Remove) interactionManager.SetSessionRegistry(sessionRegistry)
lifecycleManager.SetSessionRegistry(sessionRegistry)
return &SSHSession{ return &SSHSession{
initialReq: forwardingReq, initialReq: forwardingReq,
@@ -66,7 +65,6 @@ func New(conn *ssh.ServerConn, forwardingReq <-chan *ssh.Request, sshChan <-chan
forwarder: forwarderManager, forwarder: forwarderManager,
slugManager: slugManager, slugManager: slugManager,
registry: sessionRegistry, registry: sessionRegistry,
userID: userID,
} }
} }
@@ -82,7 +80,7 @@ func (s *SSHSession) Detail() Detail {
return Detail{ return Detail{
ForwardingType: string(s.forwarder.GetTunnelType()), ForwardingType: string(s.forwarder.GetTunnelType()),
Slug: s.slugManager.Get(), Slug: s.slugManager.Get(),
UserID: s.userID, UserID: s.lifecycle.GetUser(),
Active: s.lifecycle.IsActive(), Active: s.lifecycle.IsActive(),
StartedAt: s.lifecycle.StartedAt(), StartedAt: s.lifecycle.StartedAt(),
} }

View File

@@ -15,6 +15,11 @@ const (
TCP TunnelType = "TCP" TCP TunnelType = "TCP"
) )
type SessionKey struct {
Id string
Type TunnelType
}
var BadGatewayResponse = []byte("HTTP/1.1 502 Bad Gateway\r\n" + var BadGatewayResponse = []byte("HTTP/1.1 502 Bad Gateway\r\n" +
"Content-Length: 11\r\n" + "Content-Length: 11\r\n" +
"Content-Type: text/plain\r\n\r\n" + "Content-Type: text/plain\r\n\r\n" +