Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mohameodo/nano/llms.txt

Use this file to discover all available pages before exploring further.

nano includes an optional authentication layer that gates the entire app behind a login page. When enabled, unauthenticated visitors are redirected to /login before they can search or watch anything. Authentication is a simple username/password system — suitable for protecting personal or family instances — backed by either a PostgreSQL database or a local JSON file.
Authentication in nano is a standalone local system, not OAuth, SSO, or any federated identity provider. It is designed to protect a personal or small-group self-hosted instance, not for multi-tenant or publicly registered deployments.

Enabling authentication

Set the ENABLE_AUTH variable to activate the login gate:
ENABLE_AUTH=true
With this flag set, every page in nano checks for a user cookie. If the cookie is absent, the request is redirected to /login. After a successful login or sign-up, the cookie is set with path=/ and the user is redirected to the home page.

Database setup

nano stores user accounts in the configured database. Two database types are supported:

How authentication works

nano implements password hashing using Node.js’s built-in crypto module — no external auth library is required. Password hashing — when a user signs up, a random 16-byte salt is generated and a derived key is produced using PBKDF2:
// src/server/auth.ts
function hashPassword(password: string, salt: string): string {
  return crypto.pbkdf2Sync(password, salt, 1000, 64, "sha512").toString("hex");
}

export async function handleSignup(username: string, password: string) {
  const salt = crypto.randomBytes(16).toString("hex");
  const hash = hashPassword(password, salt);
  await query(
    "INSERT INTO users (username, password, salt) VALUES ($1, $2, $3)",
    [username, hash, salt]
  );
}
Login — the stored salt is retrieved for the given username, the submitted password is hashed with the same parameters, and the result is compared to the stored hash. Accounts created before salted hashing was introduced fall back to a direct password comparison:
export async function handleLogin(username: string, password: string) {
  const res = await query("SELECT * FROM users WHERE username = $1", [username]);
  if (res.rows.length === 0) throw new Error("User not found");

  const user = res.rows[0];
  const salt = user.salt;

  if (salt) {
    const hash = hashPassword(password, salt);
    if (user.password !== hash) throw new Error("Invalid password");
  } else {
    // Legacy: accounts without a salt use a plain-text comparison
    if (user.password !== password) throw new Error("Invalid password");
  }
}
Session — after a successful login or sign-up, the API route at POST /api/auth sets a user cookie containing the username. There is no JWT or session store — the presence of the cookie is the session token.

API endpoints

The auth API is handled by a single endpoint:
action
string
required
One of "login", "signup", or "logout".
username
string
required
The account username.
password
string
required
The account password. Sent over HTTPS; hashed server-side before storage or comparison.
# Log in
POST /api/auth
{ "action": "login", "username": "alice", "password": "hunter2" }

# Sign up
POST /api/auth
{ "action": "signup", "username": "alice", "password": "hunter2" }

# Log out
POST /api/auth
{ "action": "logout" }
A successful response returns { "success": true } with HTTP 200. Errors return { "error": "<message>" } with HTTP 400 or 500.

User management

There is no built-in admin panel for managing user accounts. Users can create accounts directly through the login page at /login using the “create account” toggle. To pre-create accounts or delete them, insert or remove rows directly in the database. The login page automatically adapts its labels and button text to the active locale — all strings (login, signUp, createAccount, backToLogin, etc.) are pulled from the TRANSLATIONS object and rendered server-side in the user’s language.

Build docs developers (and LLMs) love