Chapter 2 ยท SECURITY

Authentication & User Storage

๐Ÿ“„ 02_authentication___user_storage.md ๐Ÿท Security

Chapter 2: Authentication & User Storage

Welcome back! In the previous chapter, File System & Indexing, we built the "Librarian" capable of finding and moving files on your hard drive.

However, right now, our library has an open door policy. Anyone who connects to the server can delete your vacation photos or read your private notes.

In this chapter, we will build the Security Guard. We will learn how Filebrowser stores user identities, verifies passwords, and issues "ID badges" (tokens) so users can access files safely.

The Problem: The "Open Door"

Without authentication, a web server processes every request it receives.

  1. User sends: DELETE /important_document.pdf
  2. Server executes: Deletes the file.

We need a system to ask: "Who are you?" and "Are you allowed to do this?"

The Solution: The Guard & The Directory

Filebrowser solves this using three main components:

  1. BoltDB (The User Directory): A fast, local database file that stores usernames and encrypted passwords.
  2. Bcrypt (The Lock): A mathematical way to check passwords without saving the actual plain text password.
  3. JWT (The ID Badge): A digital token given to users after they log in, so they don't have to type their password for every single file they click.

Key Concepts

1. The User Store (database/users)

Filebrowser doesn't use a massive SQL server. It uses BoltDB, a simple key-value store that lives in a single file (filebrowser.db) on your disk.

Here is how a User is defined in the code:

// backend/database/users/users.go

type User struct {
    ID           uint        `json:"id"`
    Username     string      `json:"username"`
    Password     string      `json:"password"` // This is HASHED, not plain text!
    Permissions  Permissions `json:"permissions"`
    Locale       string      `json:"locale"`   // e.g., "en", "fr"
}

Explanation:

2. The ID Badge (JWT)

When you log in successfully, the server gives you a JSON Web Token (JWT).


How it Works: A Use Case

Let's look at the most critical scenario: A user (Alice) wants to Log In.

  1. Request: Alice sends her username and password to /api/auth/login.
  2. Lookup: The system looks for "Alice" in the BoltDB database.
  3. Verification: The system compares the password she sent against the stored hash.
  4. Token Creation: If they match, the system creates a signed JWT string.
  5. Response: The system sends the JWT back to Alice's browser (usually as a Cookie).

Usage Example: The Login Handler

Here is a simplified look at how the HTTP handler processes a login attempt.

// backend/http/auth.go

func loginHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
    // 1. The middleware has already fetched the user from the DB
    user := d.user 
    
    // 2. Check if the user has Two-Factor Auth (OTP) enforced
    if user.LockPassword && user.TOTPSecret == "" {
        return http.StatusForbidden, errors.ErrNoTotpConfigured
    }

    // 3. Issue the "ID Badge" (Token)
    return printToken(w, r, user)
}

Explanation:


Under the Hood: The Authentication Flow

How does the server know "Alice" is actually Alice?

sequenceDiagram participant U as User (Browser) participant H as HTTP Handler participant DB as BoltDB participant C as Crypto (Bcrypt/JWT) U->>H: 1. POST /login (user, pass) H->>DB: 2. Get User "Alice" DB-->>H: Return User (with Hashed Pass) H->>C: 3. Compare(Input Pass, Hashed Pass) C-->>H: Match Confirmed! H->>C: 4. Create Signed JWT (Expires in 2h) C-->>H: Return "eyJhbGciOi..." H-->>U: 5. Set-Cookie: filebrowser_quantum_jwt

Deep Dive: Generating the Token

The printToken function calls makeSignedTokenAPI. This is where the math happens. We use the HMAC-SHA256 algorithm to sign the token using a secret key only the server knows.

// backend/http/auth.go

func makeSignedTokenAPI(user *users.User, ...) (users.AuthToken, error) {
    // 1. Define what info goes onto the "Badge" (Claims)
    claims := users.MinimalAuthToken{
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
            Issuer:    "FileBrowser Quantum",
        },
    }

    // 2. Create the token object
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // 3. Sign it with the server's secret key
    tokenString, err := token.SignedString([]byte(config.Auth.Key))
    
    return tokenString, err
}

Explanation:

  1. Claims: This is the data written on the badge. It includes the expiration date (so the badge stops working automatically after 2 hours).
  2. SignedString: This stamps the badge with the server's secret wax seal. If a hacker tries to change the expiration date, the seal breaks, and the server will reject it.

Deep Dive: Verifying the Request

Once Alice has her cookie, every time she clicks a file, the extractToken function runs.

// backend/http/auth.go

func extractToken(r *http.Request) (string, error) {
    // 1. Check for the Cookie first
    cookie, err := r.Cookie("filebrowser_quantum_jwt")
    if err == nil {
        return cookie.Value, nil
    }

    // 2. Check the URL (e.g. ?auth=...)
    auth := r.URL.Query().Get("auth")
    if auth != "" {
        return auth, nil
    }

    // 3. Check the Headers (Bearer Token)
    authHeader := r.Header.Get("Authorization")
    // ... logic to parse "Bearer <token>" ...

    return "", fmt.Errorf("no token found")
}

Explanation: This function is the "Checkpoint." It looks in the user's pockets (Cookie), their hand (Query Param), and their hat (Header) to find the ID badge. If found, the server allows the request to proceed to the File System we built in Chapter 1.


Permissions: Fine-Grained Control

Authentication answers "Who are you?". Authorization answers "What can you do?".

In backend/database/users/users.go, we see the Permissions struct:

type Permissions struct {
    Admin    bool `json:"admin"`    // Can change settings
    Modify   bool `json:"modify"`   // Can rename/move files
    Delete   bool `json:"delete"`   // Can delete files
    Download bool `json:"download"` // Can download files
    Share    bool `json:"share"`    // Can create public links
}

When a user tries to delete a file, the code checks:

  1. Does the token exist? (Authentication)
  2. Does user.Permissions.Delete == true? (Authorization)

If either is false, the "Security Guard" stops the request.


Summary

In this chapter, we secured our application:

  1. BoltDB acts as our secure user directory.
  2. Login Handler verifies passwords against stored hashes.
  3. JWTs act as temporary ID badges so users stay logged in efficiently.
  4. Permissions ensure users only perform actions they are allowed to do.

Now that we can read files (Chapter 1) and we know who is asking for them (Chapter 2), we need a way to organize these requests into a web server.

Next Chapter: HTTP API & Routing


Generated by Code IQ