A comprehensive Go backend server providing REST APIs for IRC server management, account/channel administration, temporary image hosting, and JWT-authenticated IRC data access. Built with SQLite, Gorilla Mux, and WebSocket RPC connectivity.
- IRC Server Integration: WebSocket RPC connection to UnrealIRCd for real-time data
- Account Management: User registration, login verification, metadata, and email verification
- Channel Management: Channel creation, updates, permissions, and metadata
- JWT Authentication: IRCv3 extjwt support for secure client access
- Image Upload Service: Temporary image hosting with automatic cleanup
- SQLite Database: Full schema for users, channels, metadata, and permissions
- Multiple Auth Methods: JWT tokens, server API keys, and public endpoints
- RESTful API: Clean, well-documented endpoints for all operations
- Go 1.16 or higher
# Clone or navigate to the project directory
cd backend
# Set required environment variables
export JWT_SECRET=testsecret
export IRC_SERVER_KEY=testkey
# Run directly
go run .
# Or build and run
CGO_ENABLED=1 go build .
./backendThe server starts on http://localhost:8080
Note: For full functionality, also set up the IRC server connection variables if using IRC features.
The server automatically creates an SQLite database (irc_backend.db) on first run with the following schema:
- users: Account information with Argon2id hashed passwords
- user_metadata: Key-value metadata for users
- channels: Channel information
- channel_metadata: Key-value metadata for channels
- channel_permissions: User permissions for channels
- email_verifications: Email verification codes
An admin user is created automatically for testing.
This backend uses multiple authentication methods:
- JWT Tokens: For IRC clients accessing privileged data
- Server API Key: For IRC server managing accounts/channels (
X-ObsidianIRC-Keyheader) - Public: Image serving and some informational endpoints
| Endpoint | Method | Auth Required | Description |
|---|---|---|---|
/upload |
POST | JWT | Upload images |
/images/{filename} |
GET | None | Serve uploaded images |
/irc/users |
GET | JWT + IRCop | Get IRC users |
/irc/channels |
GET | JWT + IRCop | Get IRC channels |
/irc/bans |
GET | JWT + IRCop | Get server bans |
/irc/stats |
GET | JWT + IRCop | Get server stats |
/irc/server-uptime |
GET | JWT + IRCop | Get server uptime |
/irc/accounts/register |
POST | Server Key | Register account |
/irc/accounts/login |
POST | Server Key | Login (verify password) |
/irc/accounts/{id} |
GET | Server Key | Get account |
/irc/accounts/{id} |
PUT | Server Key | Update account |
/irc/accounts/{id}/metadata |
POST | Server Key | Set user metadata |
/irc/accounts/verify/{code} |
GET | Server Key | Verify email |
/irc/channels/register |
POST | Server Key | Register channel |
/irc/channels/{id} |
GET | Server Key | Get channel |
/irc/channels/{id} |
PUT | Server Key | Update channel |
/irc/channels/{id}/metadata |
POST | Server Key | Set channel metadata |
/irc/channels/{id}/permissions |
POST | Server Key | Set channel permissions |
Endpoint: POST /upload
Authentication: JWT token required
Upload an image using one of three methods. All methods return the same response format.
Image Processing: All uploaded images are automatically processed:
- Resized to fit within 1080p resolution (1920x1080 max)
- Converted to JPEG format
- All existing EXIF data is stripped for privacy
- Custom EXIF data is added containing the uploader's account name and expiration timestamp
Perfect for importing images from external sources.
Request:
curl -X POST http://localhost:8080/upload \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/image.jpg"}'JSON Payload:
{
"url": "https://example.com/image.jpg"
}Standard HTML form upload—works with any HTTP client.
Request:
curl -X POST http://localhost:8080/upload \
-F "image=@/path/to/image.jpg"Form Field: image (required)
Send image bytes directly with proper content-type header.
Request:
curl -X POST http://localhost:8080/upload \
-H "Content-Type: image/jpeg" \
--data-binary @/path/to/image.jpgSupported Content-Types: image/jpeg, image/png, image/gif, image/webp, etc.
Status: 200 OK
{
"saved_url": "http://localhost:8080/images/1759624622919675760.jpg"
}Status: 400 Bad Request
Failed to parse multipart form
Invalid JSON
Failed to get uploaded file
Status: 405 Method Not Allowed
Method not allowed
Status: 500 Internal Server Error
Failed to download image
Failed to save image
Endpoint: GET /images/{filename}
Retrieve an uploaded image by its filename.
Request:
curl http://localhost:8080/images/1759624622919675760.jpgResponse: Image binary data with appropriate Content-Type header
Status Codes:
200 OK- Image found and returned404 Not Found- Image expired or doesn't exist
Upload → Stored (2 minutes) → Automatically Deleted
All uploaded images are automatically deleted 2 minutes after upload. This ensures:
- Privacy protection
- Disk space management
- No manual cleanup required
Status: 404 Not Found
Image expired or doesn't exist
This backend supports JWT-based authentication using the IRCv3 extjwt extension, allowing IRC clients to securely access privileged endpoints (e.g., IRC server information) without exposing credentials.
- UnrealIRCd server with
extjwtmodule enabled - IRC client that supports
EXTJWTcommand JWT_SECRETenvironment variable set on the backend
- IRC Server Generates Token: When you send
EXTJWT *in IRC, the server creates a signed JWT containing your user info (nick, account, modes). - Client Receives Token: The JWT is sent back via IRC protocol.
- Client Uses Token: Include the JWT in API requests to access protected endpoints.
Note: The /irc/accounts/login endpoint is for IRC servers to verify passwords during login, not for clients to obtain JWTs. JWTs are provided by the IRC server via the EXTJWT command.
Send the EXTJWT command to your IRC server:
/EXTJWT *
The server responds with something like:
EXTJWT * * eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Parse the IRC message to extract the JWT token (the long base64 string).
Include the token in the Authorization header for protected endpoints:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:8080/irc/usersAll /irc/* endpoints require:
- Valid JWT token
- User must be an IRC operator (
umodesincludes"o")
| Endpoint | Method | Description |
|---|---|---|
/irc/users |
GET | List all IRC users |
/irc/channels |
GET | List all channels |
/irc/bans |
GET | List server bans |
/irc/stats |
GET | Server statistics |
/irc/server-uptime?server=name |
GET | Server uptime info |
# First get JWT token via IRC
/EXTJWT *
# Then use it in API
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
http://localhost:8080/irc/usersResponse:
[
{
"name": "user1",
"hostname": "example.com",
"ip": "192.168.1.1",
"connected_since": 1638360000,
"modes": ["o"]
}
]// Assuming you have an IRC client library
const ircClient = new IRCClient('irc.example.com', 'mynick');
// Connect and request JWT
ircClient.connect().then(() => {
ircClient.send('EXTJWT *');
});
// Listen for EXTJWT response
ircClient.on('message', (message) => {
if (message.command === 'EXTJWT') {
const jwtToken = message.params[2]; // Extract token
// Use token for API calls
fetch('http://localhost:8080/irc/users', {
headers: {
'Authorization': `Bearer ${jwtToken}`
}
})
.then(res => res.json())
.then(users => console.log('IRC Users:', users));
}
});import irc.client
import requests
class JWTIRCClient(irc.client.SimpleIRCClient):
def __init__(self, server, nick):
super().__init__()
self.server = server
self.nick = nick
self.jwt_token = None
def on_welcome(self, connection, event):
# Request JWT token
connection.send_raw('EXTJWT *')
def on_extjwt(self, connection, event):
# Parse EXTJWT response
self.jwt_token = event.arguments[2]
print(f"Received JWT: {self.jwt_token}")
# Use token for API
headers = {'Authorization': f'Bearer {self.jwt_token}'}
response = requests.get('http://localhost:8080/irc/users', headers=headers)
users = response.json()
print('IRC Users:', users)
# Usage
client = JWTIRCClient('irc.example.com', 'mynick')
client.connect('irc.example.com', 6667, 'mynick')
client.start()- Tokens expire in 30 seconds (IRC server setting)
- Clients must request fresh tokens via
EXTJWT *before each API call - Expired tokens return
401 Unauthorized
Invalid/Missing Token:
HTTP 401 Unauthorized
Authorization header required
Not an IRC Operator:
HTTP 403 Forbidden
IRCop status required
Token Expired:
HTTP 401 Unauthorized
Invalid token
These endpoints are intended only for the IRC server to manage accounts and channels. They require the X-ObsidianIRC-Key header with the server API key.
IRC_SERVER_KEYenvironment variable set- Requests must include:
X-ObsidianIRC-Key: <server_key>
Endpoint: POST /irc/accounts/register
Request:
{
"account_name": "johndoe",
"email_address": "john@example.com",
"password_b64": "cGFzc3dvcmQ=" // base64 encoded password
}Response:
{
"success": true,
"code": "ACCOUNT_CREATED",
"message": "Account created successfully",
"data": {
"user_id": 1,
"verification_code": "abc123..."
}
}Endpoint: POST /irc/accounts/login
Authentication: Server API key required
Used by IRC servers to verify user passwords during login. Returns user information on successful authentication.
Request:
{
"account_name": "johndoe",
"password_b64": "cGFzc3dvcmQ="
}Response:
{
"success": true,
"code": "LOGIN_SUCCESS",
"message": "Login successful",
"data": {
"id": 1,
"account_name": "johndoe",
"email_address": "john@example.com",
"date_registered": "2025-10-12T10:00:00Z",
"last_logged_in": "2025-10-12T12:00:00Z"
}
}Endpoint: PUT /irc/accounts/{id}
Request:
{
"email_address": "newemail@example.com",
"password_b64": "bmV3cGFzcw=="
}Endpoint: GET /irc/accounts/{id}
Endpoint: POST /irc/accounts/{id}/metadata
Request:
{
"key": "avatar",
"value": "https://example.com/avatar.jpg"
}Endpoint: GET /irc/accounts/verify/{code}
Endpoint: POST /irc/channels/register
Request:
{
"channel_name": "#mychannel",
"channel_topic": "Welcome to my channel",
"modes": "+nt"
}Endpoint: PUT /irc/channels/{id}
Request:
{
"channel_topic": "Updated topic",
"modes": "+nt"
}Endpoint: GET /irc/channels/{id}
Endpoint: POST /irc/channels/{id}/metadata
Request:
{
"key": "description",
"value": "A cool channel"
}Endpoint: POST /irc/channels/{id}/permissions
Request:
{
"user_id": 1,
"permissions": "o" // IRC mode: o=op, ao=auto-op, qo=quiet-op, v=voice
}The backend uses SQLite with the following tables:
- users: id, account_name, email_address, date_registered, last_logged_in, password_hash
- user_metadata: id, user_id, key, value
- channels: id, channel_name, channel_topic, modes
- channel_metadata: id, channel_id, key, value
- channel_permissions: id, user_id, channel_id, permissions
- email_verifications: id, user_id, code, expires_at, verified
- Passwords are hashed with Argon2id
- All endpoints require the server API key
- Email verification codes expire in 24 hours
- Permissions use IRC mode syntax (o, ao, qo, v)
- JWT Authentication: Tokens are signed by the IRC server using
JWT_SECRET - Server API Key: All
/irc/accounts/*and/irc/channels/*endpoints requireX-ObsidianIRC-Keyheader - IRC Operator Check: IRC data endpoints require the user to have operator status (
"o"inumodes) - Password Security: Passwords are hashed with Argon2id using configurable salt size (
PASSWORD_SALT_SIZE) - File Uploads: Require valid JWT but not operator status
- Image Expiration: Automatic cleanup prevents storage abuse
Upload → Stored (2 minutes) → Automatically Deleted
All uploaded images are automatically deleted 2 minutes after upload. This ensures:
- Privacy protection
- Disk space management
- No manual cleanup required
Note: Accessing an expired image URL will return a
404 Not Founderror.
backend/
├── main.go # Main server implementation and routing
├── auth.go # JWT and server key authentication middleware
├── account.go # Account management endpoints
├── channel.go # Channel management endpoints
├── irc.go # IRC RPC communication handlers
├── db.go # SQLite database operations
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── .env # Environment variables (create this)
├── images/ # Temporary image storage (auto-created)
├── ai_docs/ # Documentation for IRC extensions
├── README.md # This file
└── backend # Compiled binary (after go build)
# Enable CGO for SQLite support
export CGO_ENABLED=1
# Build optimized binary
go build -ldflags="-s -w" -o backend .
# Run in background
nohup ./backend > server.log 2>&1 &
# Or use systemd, docker, or your preferred deployment method# Test 1: URL Upload
curl -X POST http://localhost:8080/upload \
-H "Content-Type: application/json" \
-d '{"url": "https://httpbin.org/image/png"}'
# Test 2: File Upload
echo "test image" > test.jpg
curl -X POST http://localhost:8080/upload \
-F "image=@test.jpg"
# Test 3: Binary Upload
curl -X POST http://localhost:8080/upload \
-H "Content-Type: image/jpeg" \
--data-binary @test.jpg
# Verify image is accessible
curl -I http://localhost:8080/images/{filename_from_response}
# Wait 2+ minutes and verify expiration
sleep 120
curl -I http://localhost:8080/images/{filename_from_response} # Should return 404| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Server port |
DELETE_TIMEOUT_MINUTES |
2 |
Image auto-deletion time in minutes |
MAX_UPLOAD_SIZE_MB |
32 |
Maximum upload size in MB |
JWT_SECRET |
(required) | Secret key for JWT token verification |
IRC_SERVER_KEY |
(required) | API key for server-only endpoints |
PASSWORD_SALT_SIZE |
32 |
Size of salt for password hashing in bytes |
UNREALIRCD_WS_URL |
wss://127.0.0.1:8600/ |
IRC server WebSocket URL |
UNREALIRCD_API_USERNAME |
adminpanel |
IRC API username |
UNREALIRCD_API_PASSWORD |
password |
IRC API password |
PORT=8080
DELETE_TIMEOUT_MINUTES=2
MAX_UPLOAD_SIZE_MB=32
JWT_SECRET=your_jwt_secret_here
IRC_SERVER_KEY=your_server_key_here
PASSWORD_SALT_SIZE=32
UNREALIRCD_WS_URL=wss://127.0.0.1:8600/
UNREALIRCD_API_USERNAME=adminpanel
UNREALIRCD_API_PASSWORD=password| Setting | Environment Variable | Default |
|---|---|---|
| Port | PORT |
8080 |
| Image deletion timeout | DELETE_TIMEOUT_MINUTES |
2 minutes |
| Max upload size | MAX_UPLOAD_SIZE_MB |
32MB |
| Password salt size | PASSWORD_SALT_SIZE |
32 bytes |
- Temporary Screenshot Sharing - Share screenshots that auto-delete
- Image Proxy Service - Download and cache external images temporarily
- Development & Testing - Quick image hosting for dev environments
- Mobile App Backend - Temporary image uploads for user avatars/previews
- Bot Integration - Process and host images from chat bots
// URL Upload
const uploadFromUrl = async (imageUrl) => {
const response = await fetch('http://localhost:8080/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: imageUrl })
});
return await response.json();
};
// File Upload
const uploadFile = async (file) => {
const formData = new FormData();
formData.append('image', file);
const response = await fetch('http://localhost:8080/upload', {
method: 'POST',
body: formData
});
return await response.json();
};import requests
# URL Upload
def upload_from_url(image_url):
response = requests.post(
'http://localhost:8080/upload',
json={'url': image_url}
)
return response.json()
# File Upload
def upload_file(file_path):
with open(file_path, 'rb') as f:
response = requests.post(
'http://localhost:8080/upload',
files={'image': f}
)
return response.json()<form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required>
<button type="submit">Upload Image</button>
</form>- Concurrency: Handles multiple simultaneous uploads
- Memory: Efficient streaming for large files
- Startup: < 50ms cold start
- Response Time: < 10ms for local uploads, variable for URL downloads
This is a basic implementation. For production use, consider:
- Rate limiting to prevent abuse
- Authentication/authorization
- File type validation beyond extension
- Size limits per user/IP
- HTTPS/TLS encryption
- CORS configuration (currently allows all origins for development)
- Input sanitization
- Virus scanning for uploaded files
Open source - use freely for any purpose.
# Enable CGO for SQLite support
export CGO_ENABLED=1
go build# Find and kill existing process
lsof -i :8080
kill -9 <PID>mkdir -p images
chmod 755 images- Ensure server is running:
ps aux | grep backend - Check server logs:
tail -f server.log - Verify port is listening:
netstat -an | grep 8080
- Verify IRC server is running on port 8600
- Check
UNREALIRCD_*environment variables - Ensure JSON-RPC is enabled on the IRC server
- Configurable expiration time via environment variables
- Multiple storage backends (S3, local, memory)
- Image transformation (resize, compress, format conversion)
- Upload history and analytics
- Webhook notifications on upload/expiration
- Docker containerization
- Health check endpoint
- Prometheus metrics
Built with Go | Server ready in < 100 lines of code