first
This commit is contained in:
commit
ab75431b8a
|
@ -0,0 +1,17 @@
|
|||
FROM golang:alpine AS builder
|
||||
LABEL builder=true
|
||||
WORKDIR /build
|
||||
|
||||
ADD go.mod .
|
||||
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" -o ./cmd/app/main ./cmd/app/main.go
|
||||
|
||||
FROM alpine
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY --from=builder /build/cmd/app/main /build/cmd/main
|
||||
|
||||
CMD ["./cmd/main"]
|
|
@ -0,0 +1,4 @@
|
|||
run:
|
||||
go run ./cmd/app/main.go
|
||||
update:
|
||||
go get -u ./...
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gommunicator/internal/communicator"
|
||||
"gommunicator/internal/input"
|
||||
"gommunicator/internal/logger"
|
||||
"gommunicator/internal/memory"
|
||||
"gommunicator/internal/ssh"
|
||||
"gommunicator/internal/usecase"
|
||||
)
|
||||
|
||||
const key = `-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACC2W7jqv+IrEDb6dg2vR81uoUHnpCUueBZB0ajetr13hAAAAJjLUSTky1Ek
|
||||
5AAAAAtzc2gtZWQyNTUxOQAAACC2W7jqv+IrEDb6dg2vR81uoUHnpCUueBZB0ajetr13hA
|
||||
AAAEA1C1fC0k8MZ5S5vjcg43y2//ikwDvRbOp6GsLLAn/ZzrZbuOq/4isQNvp2Da9HzW6h
|
||||
QeekJS54FkHRqN62vXeEAAAADnlhc2hAeWFzaC1hc3VzAQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----`
|
||||
|
||||
func main() {
|
||||
log := logger.SetupLogger("prod")
|
||||
|
||||
mem := memory.New()
|
||||
communicator := communicator.New().WithDefaultServerClient()
|
||||
uc := usecase.New(log, mem, communicator)
|
||||
|
||||
go input.New(uc, log).Run()
|
||||
|
||||
srv, err := ssh.New(log, uc, []byte(key))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := srv.Start("127.0.0.1:8022"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
module gommunicator
|
||||
|
||||
go 1.23.6
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.10.0 // indirect
|
||||
github.com/charmbracelet/log v0.4.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/crypto v0.34.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
)
|
|
@ -0,0 +1,40 @@
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
|
@ -0,0 +1,48 @@
|
|||
package communicator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"gommunicator/internal/entity"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Send(msg string) error
|
||||
Username() string
|
||||
}
|
||||
|
||||
type Communicator struct {
|
||||
log *slog.Logger
|
||||
clients []Client
|
||||
}
|
||||
|
||||
func New() *Communicator {
|
||||
return &Communicator{
|
||||
clients: make([]Client, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Communicator) WithDefaultServerClient() *Communicator {
|
||||
client := entity.NewServerClient()
|
||||
c.clients = append(c.clients, client)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Communicator) AddClient(client Client) {
|
||||
c.clients = append(c.clients, client)
|
||||
}
|
||||
|
||||
func (c *Communicator) SendMsg(msg, fromUsername string) {
|
||||
// format
|
||||
msg = fmt.Sprintf("%s > %s", fromUsername, msg)
|
||||
// send
|
||||
for _, client := range c.clients {
|
||||
if fromUsername == client.Username() {
|
||||
continue
|
||||
}
|
||||
if err := client.Send(msg); err != nil {
|
||||
c.log.Error("failed to send message", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type ServerClient struct{}
|
||||
|
||||
func NewServerClient() *ServerClient {
|
||||
return &ServerClient{}
|
||||
}
|
||||
|
||||
func (c *ServerClient) Send(msg string) error {
|
||||
fmt.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServerClient) Username() string {
|
||||
return "server"
|
||||
}
|
||||
|
||||
type TerminalClient struct {
|
||||
terminal *term.Terminal
|
||||
username string
|
||||
}
|
||||
|
||||
func NewTerminalClient(term *term.Terminal, username string) *TerminalClient {
|
||||
return &TerminalClient{
|
||||
terminal: term,
|
||||
username: username,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TerminalClient) Send(msg string) error {
|
||||
_, err := fmt.Fprintf(c.terminal, "%s\n", msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TerminalClient) Username() string {
|
||||
return c.username
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package input
|
||||
|
||||
const helpMessage = `server commands:
|
||||
/help, ? - show this message
|
||||
/new-password - generate new password for clients`
|
|
@ -0,0 +1,57 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"gommunicator/internal/usecase"
|
||||
)
|
||||
|
||||
type Input struct {
|
||||
log *slog.Logger
|
||||
uc *usecase.Usecase
|
||||
}
|
||||
|
||||
func New(uc *usecase.Usecase, log *slog.Logger) *Input {
|
||||
return &Input{
|
||||
log: log,
|
||||
uc: uc,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Input) Run() {
|
||||
for {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
msg, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
i.log.Error("failed to scan input", "err", err)
|
||||
continue
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
continue
|
||||
}
|
||||
// observe command
|
||||
if msg[0] == '/' || msg == "?" {
|
||||
switch msg {
|
||||
case "?", "/help":
|
||||
fmt.Println(helpMessage)
|
||||
case "/new-password":
|
||||
pass, err := i.uc.GenNewPassword()
|
||||
if err != nil {
|
||||
i.log.Error("failed to generate new client's password", "err", err)
|
||||
break
|
||||
}
|
||||
i.log.Info("new server password generated", "password", pass)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if msg[len(msg)-1] == '\n' {
|
||||
msg = msg[:len(msg)-1]
|
||||
}
|
||||
|
||||
i.uc.SendMessage(msg, "server")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
prettyLogger "github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
const (
|
||||
envDev = "dev"
|
||||
envProd = "prod"
|
||||
)
|
||||
|
||||
func SetupLogger(env string) *slog.Logger {
|
||||
var log *slog.Logger
|
||||
|
||||
switch env {
|
||||
case envDev:
|
||||
handler := prettyLogger.NewWithOptions(os.Stdout, prettyLogger.Options{
|
||||
Level: prettyLogger.DebugLevel,
|
||||
ReportTimestamp: true,
|
||||
})
|
||||
log = slog.New(handler)
|
||||
case envProd:
|
||||
handler := prettyLogger.NewWithOptions(os.Stdout, prettyLogger.Options{
|
||||
Level: prettyLogger.InfoLevel,
|
||||
ReportTimestamp: true,
|
||||
})
|
||||
log = slog.New(handler)
|
||||
}
|
||||
|
||||
return log
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package memory
|
||||
|
||||
import "sync"
|
||||
|
||||
type Memory struct {
|
||||
passwords map[string]struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func New() *Memory {
|
||||
mem := Memory{
|
||||
passwords: make(map[string]struct{}, 16),
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
return &mem
|
||||
}
|
||||
|
||||
func (m *Memory) SavePassword(password string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.passwords[password] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memory) PasswordExists(password string) (bool, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if _, ok := m.passwords[password]; ok {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package ssh
|
||||
|
||||
const helpMessage = `server commands:
|
||||
/help - show this message
|
||||
/exit - disconnect from the server
|
||||
`
|
|
@ -0,0 +1,44 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func (s *Server) newPassword() error {
|
||||
password, err := s.uc.GenNewPassword()
|
||||
if err != nil {
|
||||
s.log.Error("failed to generate new password", "err", err)
|
||||
return err
|
||||
}
|
||||
s.log.Info("new server password generated", "password", password)
|
||||
return nil
|
||||
}
|
||||
|
||||
// get user password from terminal
|
||||
func (s *Server) passwordRequest(term *term.Terminal) error {
|
||||
// check password
|
||||
attempts := 3
|
||||
for {
|
||||
pass, err := term.ReadPassword("Enter your password: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ok, err := s.uc.PasswordExists(pass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
term.Write([]byte("Login success\n"))
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
attempts -= 1
|
||||
if attempts <= 0 {
|
||||
return errors.New("invalid password")
|
||||
}
|
||||
term.Write([]byte("Permission denied, please try again.\n"))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"gommunicator/internal/entity"
|
||||
"gommunicator/internal/usecase"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
log *slog.Logger
|
||||
uc *usecase.Usecase
|
||||
sshConfig *ssh.ServerConfig
|
||||
sshSigner *ssh.Signer
|
||||
socket *net.Listener
|
||||
}
|
||||
|
||||
func New(log *slog.Logger, uc *usecase.Usecase, privateKey []byte) (*Server, error) {
|
||||
signer, err := ssh.ParsePrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := ssh.ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
config.AddHostKey(signer)
|
||||
|
||||
server := Server{
|
||||
sshConfig: &config,
|
||||
sshSigner: &signer,
|
||||
log: log,
|
||||
uc: uc,
|
||||
}
|
||||
|
||||
return &server, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start(laddr string) error {
|
||||
// Once a ServerConfig has been configured, connections can be accepted.
|
||||
socket, err := net.Listen("tcp", laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.socket = &socket
|
||||
s.log.Info("Listening on", "addr", laddr)
|
||||
// generate password
|
||||
if err := s.newPassword(); err != nil {
|
||||
return err
|
||||
}
|
||||
// handle connnections
|
||||
for {
|
||||
conn, err := socket.Accept()
|
||||
if err != nil {
|
||||
s.log.Info("Failed to accept connection, aborting loop", "err", err)
|
||||
return err
|
||||
}
|
||||
// From a standard TCP connection to an encrypted SSH connection
|
||||
sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig)
|
||||
if err != nil {
|
||||
s.log.Info("Failed to handshake", "err", err)
|
||||
continue
|
||||
}
|
||||
s.log.Info("New connection", "addr", sshConn.RemoteAddr(), "user", sshConn.User())
|
||||
|
||||
go ssh.DiscardRequests(requests)
|
||||
go s.handleChannels(channels, sshConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleChannels(channels <-chan ssh.NewChannel, conn *ssh.ServerConn) {
|
||||
for ch := range channels {
|
||||
if t := ch.ChannelType(); t != "session" {
|
||||
ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
|
||||
continue
|
||||
}
|
||||
|
||||
channel, requests, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
go func(in <-chan *ssh.Request) {
|
||||
defer channel.Close()
|
||||
for req := range in {
|
||||
// log.Println("Request: ", req.Type, string(req.Payload))
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "shell":
|
||||
// We don't accept any commands (Payload),
|
||||
// only the default shell.
|
||||
if len(req.Payload) == 0 {
|
||||
ok = true
|
||||
}
|
||||
case "pty-req":
|
||||
// Responding 'ok' here will let the client
|
||||
// know we have a pty ready for input
|
||||
ok = true
|
||||
case "window-change":
|
||||
continue // no response
|
||||
}
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
go s.handleShell(channel, conn.User())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleShell(channel ssh.Channel, username string) {
|
||||
defer func() {
|
||||
channel.Close()
|
||||
s.log.Info("User disconnected", "user", username)
|
||||
}()
|
||||
// create terminal
|
||||
term := term.NewTerminal(channel, fmt.Sprintf("%s > ", username))
|
||||
// check password
|
||||
err := s.passwordRequest(term)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// add as client
|
||||
s.uc.AddMessageClient(entity.NewTerminalClient(term, username))
|
||||
// Recieve user input and send to server
|
||||
for {
|
||||
// get user input
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if len(line) > 0 {
|
||||
if string(line[0]) == "/" {
|
||||
switch line {
|
||||
case "/exit":
|
||||
return
|
||||
case "/help":
|
||||
term.Write([]byte(helpMessage))
|
||||
default:
|
||||
term.Write([]byte(helpMessage))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
s.log.Debug("new message", "user", username, "messg", line)
|
||||
|
||||
// send message to room
|
||||
s.uc.SendMessage(line, username)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package usecase
|
||||
|
||||
import "gommunicator/internal/communicator"
|
||||
|
||||
func (u *Usecase) AddMessageClient(client communicator.Client) {
|
||||
u.communicator.AddClient(client)
|
||||
}
|
||||
|
||||
func (u *Usecase) SendMessage(msg, fromUsername string) {
|
||||
u.communicator.SendMsg(msg, fromUsername)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
// GenNewPassword generates new password, writes it to the memory and returns.
|
||||
func (u *Usecase) GenNewPassword() (string, error) {
|
||||
rand.Seed(uint64(time.Now().UnixNano()))
|
||||
// gen new password
|
||||
pass := genPasswordString()
|
||||
// save to the memory
|
||||
if err := u.memory.SavePassword(pass); err != nil {
|
||||
return "", fmt.Errorf("failed to save password in the memory: %w", err)
|
||||
}
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
const (
|
||||
passwordLen = 8
|
||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
)
|
||||
|
||||
func genPasswordString() string {
|
||||
b := make([]byte, passwordLen)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// PasswordExists checks that password exists in the memory.
|
||||
func (u *Usecase) PasswordExists(password string) (bool, error) {
|
||||
exists, err := u.memory.PasswordExists(password)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check password in the memory: %w", err)
|
||||
}
|
||||
return exists, nil
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"gommunicator/internal/communicator"
|
||||
"gommunicator/internal/memory"
|
||||
)
|
||||
|
||||
type Usecase struct {
|
||||
log *slog.Logger
|
||||
memory *memory.Memory
|
||||
communicator *communicator.Communicator
|
||||
}
|
||||
|
||||
func New(log *slog.Logger, memory *memory.Memory, communicator *communicator.Communicator) *Usecase {
|
||||
return &Usecase{
|
||||
log: log,
|
||||
memory: memory,
|
||||
communicator: communicator,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACC2W7jqv+IrEDb6dg2vR81uoUHnpCUueBZB0ajetr13hAAAAJjLUSTky1Ek
|
||||
5AAAAAtzc2gtZWQyNTUxOQAAACC2W7jqv+IrEDb6dg2vR81uoUHnpCUueBZB0ajetr13hA
|
||||
AAAEA1C1fC0k8MZ5S5vjcg43y2//ikwDvRbOp6GsLLAn/ZzrZbuOq/4isQNvp2Da9HzW6h
|
||||
QeekJS54FkHRqN62vXeEAAAADnlhc2hAeWFzaC1hc3VzAQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
|||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILZbuOq/4isQNvp2Da9HzW6hQeekJS54FkHRqN62vXeE yash@yash-asus
|
Loading…
Reference in New Issue