This project is a web forum designed to enhance user interaction and streamline content organization. Registered users can share posts with categorized tags, engage through likes and dislikes on both posts and comments, and easily navigate content with awesome filtering option. It also functions as a SPA that promotes private messages and real-time events, ensuring seamless communication. Built with Go for a fast and reliable backend, SQLite for lightweight yet robust data storage, and Docker for seamless deployment, this platform combines performance, simplicity, and scalability to deliver an exceptional user experience.
For a live preview of the project please check the link: https://dwi.fly.dev/
Some Explanations: https://excalidraw.com/#json=aaPfOnBdAFyYjrF4KL7Gp,qf57RNVURw8aO1VGdsyJZg
| Software | Purpose | Installation Guide |
|---|---|---|
| Go | To compile and run the application locally. | Install Go |
| Docker | To build and run the application in a container. | Install Docker |
| SQLite | The application's database management system. | Install SQLite |
Using a terminal, clone and navigate to the repository:
git clone https://github.com/sadiqui/real-time-forum
cd real-time-forum
Then, you can run the application either locally or using Docker, access on localhost.
| Command | Description |
|---|---|
make go |
Runs the application locally using Go. |
make docker |
Builds a Docker image and starts the application container. |
make clean |
Stops container and cleans up all Docker resources related to the application. |
make deepClean |
Stops all Docker resources, even if they are not related to this application. |
The -v $(PWD)/database:/app/database option in the docker run or make docker command is used to create a volume mapping between the host machine and the container. This mapping ensures that any changes made to the database files in the container (stored in /app/database) are reflected on the host machine (in the database directory) and vice versa. This setup is particularly useful for persisting database changes made during the container's lifecycle, even after the container stops or is removed.
The application supports running on a flexible port. By default, it binds to a random available port when no specific port is specified. To set a specific port for local execution, export the PORT environment variable (e.g., export PORT=8080), and the application will use it. To revert to a random port, unset the variable with unset PORT. When running the application in Docker, specify the desired port using PORT=<port> in Makefile. If no port is specified, Docker will default to using the random port generated by the application.
To use OAuth, you can obtain your own GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET from the Google Developer Console by searching for OAuth setup (the same applies to GitHub or other providers) and set them as environment variables, e.g.:
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."
GITHUB_CLIENT_ID="..."
GITHUB_CLIENT_SECRET="..."During deployment, you can set these environment variables on the hosting server.
For Fly.io, use the flyctl secrets command to securely store them:
flyctl secrets set GOOGLE_CLIENT_ID="your-google-client-id"
flyctl secrets set GOOGLE_CLIENT_SECRET="your-google-client-secret"
flyctl secrets set GITHUB_CLIENT_SECRET="..."
flyctl secrets set GITHUB_CLIENT_ID="..."These secrets will be available as environment variables in your deployed application.
This project uses several Go packages that contribute to security in different ways:
| Package | Security Role |
|---|---|
| gofrs/uuid | Prevents predictable IDs & session ID enumeration attacks |
| sqlite3 | Mitigates SQL injection, improves DB security |
| bcrypt | Provides password hashing/salting, randomness, and prevents rainbow table attacks |
`CreateSession() function in session.go
cookie := &http.Cookie{
Name: "session_token",
Value: token,
Expires: expiresAt,
Path: "/",
// Moderate CSRF protection, send cookie on links but not on embedded requests
SameSite: http.SameSiteLaxMode,
// Protects against XSS, blocks access to document.cookie
HttpOnly: true,
// Only send the cookie over HTTPS
Secure: true,
}This cookie configuration implements several security measures to protect user sessions. The HttpOnly flag prevents JavaScript access to the cookie, mitigating the risk of Cross-Site Scripting (XSS) attacks. The Secure flag ensures that the cookie is transmitted only over HTTPS, preventing exposure in plaintext over unsecured connections. The SameSite=Lax setting provides moderate protection against Cross-Site Request Forgery (CSRF) by allowing the cookie to be sent with top-level navigations (e.g., clicking a link) but restricting its use in cross-origin subrequests (e.g., iframes or AJAX calls). Additionally, setting an expiration (Expires) ensures the session token is not stored indefinitely, reducing the impact of session hijacking. Finally, the cookie is scoped to the entire site (Path="/"), ensuring it is available across all pages. This setup balances security and usability, protecting against common web vulnerabilities while maintaining session persistence.
`secureHeaders() function in helpers.go
w.Header().Set("Content-Security-Policy", "script-src 'self';")- Allows scripts only from the same origin ('self').
- If an attacker injects a
<script>tag from another domain, it won't run.
w.Header().Set("X-Frame-Options", "DENY")- Prevents clickjacking attacks by blocking
<iframe>embedding. - No one can embed your site in an
<iframe>.
w.Header().Set("X-Content-Type-Options", "nosniff")- Prevents MIME sniffing attacks.
- Ensures browsers only execute files with declared content types.
- Prevents content-type spoofing (e.g., treating a text file as HTML/JavaScript).
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")- Forces HTTPS by telling browsers to always use HTTPS instead of HTTP.
- max-age=31536000 → Enforces HTTPS for 1 year.
- includeSubDomains → Applies to all subdomains.
`Server() function in server.go
A secure client-server connection over HTTPS relies on four parameters: key exchange, authentication, symmetric encryption, and hashing.
- Key Exchange Protocol ensures that both parties can securely generate and exchange the necessary encryption keys.
- Authentication verifies the server’s identity, preventing man-in-the-middle attacks.
- Symmetric Encryption guarantees confidentiality and privacy by encrypting the data exchanged.
- Hashing Algorithms maintain data integrity, ensuring that the transmitted data has not been altered.
These four parameters are combined into Cipher Suites, which define the cryptographic algorithms used in a TLS connection. Each cipher suite follows the format:
TLS_{KeyExchange}{Authentication}_WITH_{Encryption}{Hash}
Example: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Where each part specifies the protocol for key exchange, authentication, encryption, and hashing. The TLS (Transport Layer Security) protocol is responsible for selecting and enforcing cipher suites in HTTPS:
- During the TLS handshake, the client (browser) sends a list of supported cipher suites.
- The server picks the most secure one it supports from the list.
- The chosen cipher suite defines how the four parameters will work for that session.
The actual selection of a cipher suite depends on:
- The client’s and server’s supported cipher suites.
- The server’s configuration (it can enforce strong ciphers).
- The TLS version (newer versions deprecate weaker ciphers).
A seamless TLS handshake enables the encrypted data transmission that secures our digital world. It allows safe online commerce, communication, and connectivity by:
- Verifying you are connected to the authentic site and not an impersonator
- Encrypting all data exchanged during the session.
- Ensuring no third party can read or modify the information as it travels across the internet
Without the TLS handshake, our sensitive information would be exposed online.
Although Let’s Encrypt offers free trusted certificates, we wanted to generate our own self-signed certificate. In order to do so, we first created a configuration file named san.conf where we included the Subject Alternative Name (SAN) to match the used domain (localhost for development). Then, we generated our certificates with SAN support:
# Generate CA key and certificate
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt -subj "/CN=MyCA"
# Generate server key
openssl genrsa -out server.key 4096
# Generate CSR using the san.conf
openssl req -new -key server.key -out server.csr -subj "/CN=127.0.0.1" -config san.conf
# Generate server certificate
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 -sha256 -extensions v3_req -extfile san.conf2048-bit: Good security, faster performance.
4096-bit: Stronger security, slower performance.
- san.conf: Configuration file specifying certificate attributes, including Subject Alternative Names (SAN) for IP and DNS.
- ca.key: Private key of the Certificate Authority (CA), used to sign certificates.
- ca.crt: Public certificate of the CA, used to verify certificates it signs.
- server.key: Private key for the server, used in SSL/TLS encryption.
- server.csr: Certificate Signing Request (CSR) for the server, sent to the CA to obtain a signed certificate.
- server.crt: Server’s signed certificate, issued by the CA, used for HTTPS authentication and encryption.
Lastly, we have imported the CA certificate ca.crt into the browser's trusted root certificates.
TLS excali: https://excalidraw.com/#json=deZ-na0KKm5T9M8rbE9yV,KaJgeOjLKFIs9Yus8PW6Yg