initial commit yo

This commit is contained in:
strangeprogram 2024-07-15 20:55:11 -06:00
parent daa6daa056
commit ecc75a08c0
13 changed files with 979 additions and 1 deletions

101
README.md
View File

@ -1,2 +1,101 @@
# gBBS # GBBS (Go Bulletin Board System)
GBBS is a modern implementation of a classic Bulletin Board System (BBS) written in Go. It provides a nostalgic interface with modern backend technologies, supporting Telnet, SSH, and Web access.
## Features
- Multi-protocol support: Telnet, SSH, and Web
- User authentication and registration
- Message board functionality
- ANSI color support for Telnet and SSH clients
- Customizable welcome screen
- SQLite database for user management
- Concurrent connections handling
## Getting Started
### Prerequisites
- Go 1.16 or later
- SQLite
### Installation
1. Clone the repository:
```
git clone https://github.com/strangeprogram/gbbs.git
cd gbbs
```
2. Install dependencies:
```
go mod tidy
```
3. Create a `config.json` file in the project root:
```json
{
"telnet_port": 2323,
"ssh_port": 2222,
"web_port": 8080,
"guestbook_path": "guestbook.txt",
"web_root": "web",
"welcome_screen_path": "welcome.ans"
}
```
4. Create a `welcome.ans` file with your desired ANSI art welcome screen.
### Running the BBS
To run the BBS in debug mode:
```
go run cmd/gbbs/main.go --debug
```
To compile and run:
```
go build -o gbbs cmd/gbbs/main.go
./gbbs
```
## Connecting to the BBS
- Telnet: `telnet localhost 2323`
- SSH: `ssh localhost -p 2222`
- Web: Open a browser and navigate to `http://localhost:8080`
## Version History
- v0.1: Initial implementation with basic Telnet support
- v0.2: Added SSH support
- v0.3: Implemented web interface
- v0.4: Added user authentication and registration
- v0.5: Introduced message board functionality
- v0.6: Improved ANSI color support and welcome screen customization
- v0.7: Fixed SSH input handling issues
## TODO
- [ ] Implement IRC link integration
- [ ] Add file transfer capabilities
- [ ] Create a more robust web interface
- [ ] Implement user roles and permissions
- [ ] Add support for multiple message boards/forums
- [ ] Implement private messaging between users
- [ ] Create a plugin system for easy feature extensions
- [ ] Add support for external authentication methods (e.g., OAuth)
- [ ] Implement a basic game or interactive feature
- [ ] Create a telnet/SSH client specifically designed for this BBS
## License
This project is licensed under the GNU License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- MysticBBS | Oblivion/v2
- BBS
- archive the dream

105
cmd/gbbs/main.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"flag"
"log"
"os"
"path/filepath"
"sync"
"gbbs/internal/config"
"gbbs/internal/messageboard"
"gbbs/internal/ssh"
"gbbs/internal/telnet"
"gbbs/internal/user"
"gbbs/internal/web"
)
var debug = flag.Bool("debug", false, "Enable debug mode")
func main() {
flag.Parse()
if *debug {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
} else {
log.SetOutput(os.NewFile(0, os.DevNull))
}
// Get the executable path
ex, err := os.Executable()
if err != nil {
log.Fatalf("Failed to get executable path: %v", err)
}
exePath := filepath.Dir(ex)
// Get the current working directory
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("Failed to get current working directory: %v", err)
}
log.Printf("Executable directory: %s", exePath)
log.Printf("Current working directory: %s", cwd)
// Try to load config from multiple locations
configPaths := []string{
filepath.Join(cwd, "config.json"),
filepath.Join(cwd, "cmd", "gbbs", "config.json"),
filepath.Join(exePath, "config.json"),
filepath.Join(exePath, "cmd", "gbbs", "config.json"),
}
var cfg *config.Config
var configErr error
for _, path := range configPaths {
cfg, configErr = config.Load(path)
if configErr == nil {
log.Printf("Loaded configuration from: %s", path)
break
}
}
if configErr != nil {
log.Fatalf("Failed to load configuration: %v\nTried paths: %v", configErr, configPaths)
}
log.Printf("Loaded configuration: %+v", cfg)
userManager, err := user.NewManager("bbs.db")
if err != nil {
log.Fatalf("Failed to initialize user manager: %v", err)
}
defer userManager.Close()
messageBoard, err := messageboard.New(cfg.GuestbookPath)
if err != nil {
log.Fatalf("Failed to initialize message board: %v", err)
}
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
if err := telnet.Serve(cfg, userManager, messageBoard); err != nil {
log.Printf("Telnet server error: %v", err)
}
}()
go func() {
defer wg.Done()
if err := ssh.Serve(cfg, userManager, messageBoard); err != nil {
log.Printf("SSH server error: %v", err)
}
}()
go func() {
defer wg.Done()
if err := web.Serve(cfg.WebPort, cfg.WebRoot, userManager, messageBoard); err != nil {
log.Printf("Web server error: %v", err)
}
}()
log.Printf("BBS is running. Telnet: %d, SSH: %d, Web: %d", cfg.TelnetPort, cfg.SSHPort, cfg.WebPort)
wg.Wait()
}

8
config.json Normal file
View File

@ -0,0 +1,8 @@
{
"telnet_port": 31337,
"ssh_port": 22,
"web_port": 8080,
"guestbook_path": "guestbook.txt",
"web_root": "web",
"welcome_screen_path": "welcome.ans"
}

0
guestbook.txt Normal file
View File

13
internal/ansi/ansi.go Normal file
View File

@ -0,0 +1,13 @@
package ansi
const (
ColorBlack = "\033[0;30m"
ColorRed = "\033[0;31m"
ColorGreen = "\033[0;32m"
ColorYellow = "\033[0;33m"
ColorBlue = "\033[0;34m"
ColorMagenta = "\033[0;35m"
ColorCyan = "\033[0;36m"
ColorWhite = "\033[0;37m"
ColorReset = "\033[0m"
)

52
internal/config/config.go Normal file
View File

@ -0,0 +1,52 @@
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
type Config struct {
TelnetPort int `json:"telnet_port"`
SSHPort int `json:"ssh_port"`
WebPort int `json:"web_port"`
GuestbookPath string `json:"guestbook_path"`
WebRoot string `json:"web_root"`
WelcomeScreenPath string `json:"welcome_screen_path"`
}
func Load(configPath string) (*Config, error) {
cfg := &Config{
TelnetPort: 2323,
SSHPort: 2222,
WebPort: 8080,
GuestbookPath: "guestbook.txt",
WebRoot: "web",
WelcomeScreenPath: "welcome.ans",
}
file, err := os.Open(configPath)
if err != nil {
return nil, fmt.Errorf("error opening config file: %v", err)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(cfg); err != nil {
return nil, fmt.Errorf("error decoding config file: %v", err)
}
// Convert relative paths to absolute paths
cfg.GuestbookPath = makeAbsolute(filepath.Dir(configPath), cfg.GuestbookPath)
cfg.WebRoot = makeAbsolute(filepath.Dir(configPath), cfg.WebRoot)
cfg.WelcomeScreenPath = makeAbsolute(filepath.Dir(configPath), cfg.WelcomeScreenPath)
return cfg, nil
}
func makeAbsolute(basePath, path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(basePath, path)
}

View File

@ -0,0 +1,68 @@
package messageboard
import (
"bufio"
"fmt"
"os"
"strings"
"sync"
"time"
)
type MessageBoard struct {
filePath string
mu sync.Mutex
}
func New(filePath string) (*MessageBoard, error) {
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
file.Close()
return &MessageBoard{filePath: filePath}, nil
}
func (mb *MessageBoard) PostMessage(username, message string) error {
if len(message) == 0 {
return fmt.Errorf("message cannot be empty")
}
if len(message) > 500 {
return fmt.Errorf("message too long (max 500 characters)")
}
mb.mu.Lock()
defer mb.mu.Unlock()
file, err := os.OpenFile(mb.filePath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(fmt.Sprintf("[%s] %s: %s\n", time.Now().Format("2006-01-02 15:04:05"), username, message))
return err
}
func (mb *MessageBoard) GetMessages() ([]string, error) {
mb.mu.Lock()
defer mb.mu.Unlock()
content, err := os.ReadFile(mb.filePath)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(strings.NewReader(string(content)))
var messages []string
for scanner.Scan() {
messages = append(messages, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return messages, nil
}

18
internal/prompt/prompt.go Normal file
View File

@ -0,0 +1,18 @@
package prompt
import (
"fmt"
"gbbs/internal/config"
"os"
)
func ReadWelcomeScreen(cfg *config.Config) (string, error) {
content, err := os.ReadFile(cfg.WelcomeScreenPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Sprintf("Welcome to GBBS!\n\nWelcome screen file not found: %s\n", cfg.WelcomeScreenPath), nil
}
return "", fmt.Errorf("error reading welcome screen: %v", err)
}
return string(content), nil
}

241
internal/ssh/ssh.go Normal file
View File

@ -0,0 +1,241 @@
package ssh
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"io"
"log"
"net"
"strings"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
"gbbs/internal/config"
"gbbs/internal/messageboard"
"gbbs/internal/prompt"
"gbbs/internal/user"
)
func generateSSHKey() (ssh.Signer, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return ssh.NewSignerFromKey(key)
}
func Serve(cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) error {
signer, err := generateSSHKey()
if err != nil {
return fmt.Errorf("failed to generate SSH key: %v", err)
}
config := &ssh.ServerConfig{
NoClientAuth: true,
}
config.AddHostKey(signer)
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.SSHPort))
if err != nil {
return err
}
log.Printf("SSH server listening on port %d", cfg.SSHPort)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("Failed to accept incoming connection: %v", err)
continue
}
go handleConnection(conn, config, cfg, userManager, messageBoard)
}
}
func handleConnection(conn net.Conn, config *ssh.ServerConfig, cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) {
defer conn.Close()
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
log.Printf("Failed to handshake: %v", err)
return
}
defer sshConn.Close()
log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Printf("Could not accept channel: %v", err)
continue
}
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
ok = false
}
case "pty-req":
ok = true
}
req.Reply(ok, nil)
}
}(requests)
term := term.NewTerminal(channel, "> ")
go handleSSHSession(term, cfg, userManager, messageBoard)
}
}
func handleSSHSession(term *term.Terminal, cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) {
defer term.Write([]byte("Goodbye!\n"))
welcomeScreen, err := prompt.ReadWelcomeScreen(cfg)
if err != nil {
fmt.Fprintf(term, "Error reading welcome screen: %v\n", err)
return
}
term.Write([]byte(welcomeScreen))
term.Write([]byte("\n\n\n\n\n")) // new lines to make it look real nice yo
for {
term.SetPrompt("\033[0;32mChoose (L)ogin or (R)egister: \033[0m")
choice, err := term.ReadLine()
if err != nil {
if err == io.EOF {
return
}
fmt.Fprintf(term, "\033[0;31mError reading input: %v\033[0m\n", err)
continue
}
switch strings.ToLower(strings.TrimSpace(choice)) {
case "l":
username, err := login(term, userManager)
if err != nil {
fmt.Fprintf(term, "\033[0;31mLogin failed: %v\033[0m\n", err)
continue
}
fmt.Fprintf(term, "\n\033[1;32mLogin successful! Welcome, %s!\033[0m\n", username)
time.Sleep(2 * time.Second)
handleBBS(term, username, messageBoard)
return
case "r":
username, err := register(term, userManager)
if err != nil {
fmt.Fprintf(term, "\033[0;31mRegistration failed: %v\033[0m\n", err)
continue
}
fmt.Fprintf(term, "\n\033[1;32mRegistration successful! Welcome, %s!\033[0m\n", username)
time.Sleep(2 * time.Second)
handleBBS(term, username, messageBoard)
return
default:
fmt.Fprintf(term, "\033[0;31mInvalid choice. Please enter 'L' or 'R'.\033[0m\n")
}
}
}
func login(term *term.Terminal, userManager *user.Manager) (string, error) {
term.SetPrompt("Username: ")
username, err := term.ReadLine()
if err != nil {
return "", err
}
term.SetPrompt("Password: ")
password, err := term.ReadPassword("Password: ")
if err != nil {
return "", err
}
authenticated, err := userManager.Authenticate(username, password)
if err != nil {
return "", err
}
if !authenticated {
return "", fmt.Errorf("invalid username or password")
}
return username, nil
}
func register(term *term.Terminal, userManager *user.Manager) (string, error) {
term.SetPrompt("Choose a username: ")
username, err := term.ReadLine()
if err != nil {
return "", err
}
term.SetPrompt("Choose a password: ")
password, err := term.ReadPassword("Choose a password: ")
if err != nil {
return "", err
}
err = userManager.CreateUser(username, password)
if err != nil {
return "", err
}
return username, nil
}
func handleBBS(term *term.Terminal, username string, messageBoard *messageboard.MessageBoard) {
for {
term.Write([]byte("\n\033[0;36mBBS Menu:\033[0m\n"))
term.Write([]byte("1. Read messages\n"))
term.Write([]byte("2. Post message\n"))
term.Write([]byte("3. Logout\n"))
term.SetPrompt("Choice: ")
choice, err := term.ReadLine()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading input: %v\033[0m\n", err)
continue
}
switch strings.TrimSpace(choice) {
case "1":
messages, err := messageBoard.GetMessages()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading messages: %v\033[0m\n", err)
} else {
for _, msg := range messages {
fmt.Fprintf(term, "%s\n", msg)
}
}
case "2":
term.SetPrompt("Enter your message: ")
message, err := term.ReadLine()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading message: %v\033[0m\n", err)
continue
}
err = messageBoard.PostMessage(username, message)
if err != nil {
fmt.Fprintf(term, "\033[0;31mError posting message: %v\033[0m\n", err)
} else {
fmt.Fprintf(term, "\033[0;32mMessage posted successfully!\033[0m\n")
}
case "3":
return
default:
fmt.Fprintf(term, "\033[0;31mInvalid choice. Please try again.\033[0m\n")
}
}
}

170
internal/telnet/telnet.go Normal file
View File

@ -0,0 +1,170 @@
package telnet
import (
"bufio"
"fmt"
"gbbs/internal/config"
"gbbs/internal/messageboard"
"gbbs/internal/prompt"
"gbbs/internal/user"
"net"
"strings"
"time"
)
func Serve(cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) error {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.TelnetPort))
if err != nil {
return err
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleConnection(conn, cfg, userManager, messageBoard)
}
}
func handleConnection(conn net.Conn, cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) {
defer conn.Close()
writer := bufio.NewWriter(conn)
reader := bufio.NewReader(conn)
welcomeScreen, err := prompt.ReadWelcomeScreen(cfg)
if err != nil {
fmt.Fprintf(writer, "Error reading welcome screen: %v\n", err)
return
}
fmt.Fprint(writer, welcomeScreen)
writer.Flush()
for {
fmt.Fprintf(writer, "\n\033[0;31mPlease choose an option:\033[0m\n")
fmt.Fprintf(writer, "\033[1;32mL\033[0m - Login\n")
fmt.Fprintf(writer, "\033[1;32mR\033[0m - Register\n\n")
fmt.Fprintf(writer, "\033[0;32mYour choice: \033[0m")
writer.Flush()
choice, _ := reader.ReadString('\n')
choice = strings.TrimSpace(choice)
if strings.EqualFold(choice, "L") {
username, err := login(reader, writer, userManager)
if err != nil {
fmt.Fprintf(writer, "\033[0;31mLogin failed: %v\033[0m\n", err)
writer.Flush()
continue
}
fmt.Fprintf(writer, "\n\033[1;32mLogin successful! Welcome, %s!\033[0m\n", username)
writer.Flush()
time.Sleep(2 * time.Second) // Pause for 2 seconds to show the message
handleBBS(username, reader, writer, messageBoard)
return
} else if strings.EqualFold(choice, "R") {
username, err := register(reader, writer, userManager)
if err != nil {
fmt.Fprintf(writer, "\033[0;31mRegistration failed: %v\033[0m\n", err)
writer.Flush()
continue
}
fmt.Fprintf(writer, "\n\033[1;32mRegistration successful! Welcome, %s!\033[0m\n", username)
writer.Flush()
time.Sleep(2 * time.Second) // Pause for 2 seconds to show the message
handleBBS(username, reader, writer, messageBoard)
return
} else {
fmt.Fprintf(writer, "\033[0;31mInvalid choice. Please try again.\033[0m\n")
writer.Flush()
}
}
}
func login(reader *bufio.Reader, writer *bufio.Writer, userManager *user.Manager) (string, error) {
fmt.Fprintf(writer, "Username: ")
writer.Flush()
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
fmt.Fprintf(writer, "Password: ")
writer.Flush()
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
authenticated, err := userManager.Authenticate(username, password)
if err != nil {
return "", err
}
if !authenticated {
return "", fmt.Errorf("invalid username or password")
}
return username, nil
}
func register(reader *bufio.Reader, writer *bufio.Writer, userManager *user.Manager) (string, error) {
fmt.Fprintf(writer, "Choose a username: ")
writer.Flush()
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
fmt.Fprintf(writer, "Choose a password: ")
writer.Flush()
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
err := userManager.CreateUser(username, password)
if err != nil {
return "", err
}
return username, nil
}
func handleBBS(username string, reader *bufio.Reader, writer *bufio.Writer, messageBoard *messageboard.MessageBoard) {
for {
fmt.Fprintf(writer, "\n\033[0;36mBBS Menu:\033[0m\n")
fmt.Fprintf(writer, "1. Read messages\n")
fmt.Fprintf(writer, "2. Post message\n")
fmt.Fprintf(writer, "3. Logout\n")
fmt.Fprintf(writer, "Choice: ")
writer.Flush()
choice, _ := reader.ReadString('\n')
choice = strings.TrimSpace(choice)
switch choice {
case "1":
messages, err := messageBoard.GetMessages()
if err != nil {
fmt.Fprintf(writer, "\033[0;31mError reading messages: %v\033[0m\n", err)
} else {
for _, msg := range messages {
fmt.Fprintf(writer, "%s\n", msg)
}
}
case "2":
fmt.Fprintf(writer, "Enter your message: ")
writer.Flush()
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
err := messageBoard.PostMessage(username, message)
if err != nil {
fmt.Fprintf(writer, "\033[0;31mError posting message: %v\033[0m\n", err)
} else {
fmt.Fprintf(writer, "\033[0;32mMessage posted successfully!\033[0m\n")
}
case "3":
fmt.Fprintf(writer, "\033[0;33mGoodbye!\033[0m\n")
writer.Flush()
return
default:
fmt.Fprintf(writer, "\033[0;31mInvalid choice. Please try again.\033[0m\n")
}
writer.Flush()
}
}

95
internal/user/user.go Normal file
View File

@ -0,0 +1,95 @@
package user
import (
"database/sql"
"errors"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt"
)
type Manager struct {
db *sql.DB
}
var (
ErrInvalidUsername = errors.New("invalid username")
ErrInvalidPassword = errors.New("invalid password")
ErrUserExists = errors.New("user already exists")
)
func NewManager(dbPath string) (*Manager, error) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return nil, err
}
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password TEXT
)
`)
if err != nil {
return nil, err
}
return &Manager{db: db}, nil
}
func (m *Manager) Close() error {
return m.db.Close()
}
func (m *Manager) Authenticate(username, password string) (bool, error) {
var storedPassword string
err := m.db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&storedPassword)
if err != nil {
if err == sql.ErrNoRows {
return false, nil
}
return false, err
}
err = bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(password))
return err == nil, nil
}
func (m *Manager) CreateUser(username, password string) error {
if err := validateUsername(username); err != nil {
return err
}
if err := validatePassword(password); err != nil {
return err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
_, err = m.db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", username, string(hashedPassword))
if err != nil {
if err.Error() == "UNIQUE constraint failed: users.username" {
return ErrUserExists
}
return err
}
return nil
}
func validateUsername(username string) error {
if len(username) < 3 || len(username) > 20 {
return ErrInvalidUsername
}
return nil
}
func validatePassword(password string) error {
if len(password) < 8 {
return ErrInvalidPassword
}
return nil
}

99
internal/web/web.go Normal file
View File

@ -0,0 +1,99 @@
package web
import (
"encoding/json"
"fmt"
"gbbs/internal/messageboard"
"gbbs/internal/user"
"net/http"
)
func Serve(port int, webRoot string, userManager *user.Manager, messageBoard *messageboard.MessageBoard) error {
http.Handle("/", http.FileServer(http.Dir(webRoot)))
http.HandleFunc("/api/login", loginHandler(userManager))
http.HandleFunc("/api/register", registerHandler(userManager))
http.HandleFunc("/api/messages", messagesHandler(messageBoard))
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
func loginHandler(userManager *user.Manager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var creds struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
authenticated, err := userManager.Authenticate(creds.Username, creds.Password)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if !authenticated {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"message": "Login successful"})
}
}
func registerHandler(userManager *user.Manager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var creds struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := userManager.CreateUser(creds.Username, creds.Password); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"message": "User created successfully"})
}
}
func messagesHandler(messageBoard *messageboard.MessageBoard) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
messages, err := messageBoard.GetMessages()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(messages)
case http.MethodPost:
var msg struct {
Username string `json:"username"`
Message string `json:"message"`
}
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := messageBoard.PostMessage(msg.Username, msg.Message); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"message": "Message posted successfully"})
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
}

10
welcome.ans Normal file
View File

@ -0,0 +1,10 @@
Welcome to
░▒▓██████▓▒░░▒▓███████▓▒░░▒▓███████▓▒░ ░▒▓███████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░
░▒▓█▓▒▒▓███▓▒░▒▓███████▓▒░░▒▓███████▓▒░ ░▒▓██████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓██████▓▒░░▒▓███████▓▒░░▒▓███████▓▒░░▒▓███████▓▒░
ver 0.7 alpha