Skip to content

Latest commit

 

History

History
105 lines (80 loc) · 6.39 KB

ARCHITECTURE.md

File metadata and controls

105 lines (80 loc) · 6.39 KB

The CS2KZ API Architecture

This document aims to explain how this project is structured, which libraries you should be familiar with, and how the API works at a high level. If you're new to the project, this is the right place to start!

Important

As this is a Rust project, you should be familiar with Cargo, its main build tool and package manager. It is expected that you have a general understanding of how Rust projects are structured and how the module system works.

Repository Structure

This project is organized into several crates within a Cargo workspace. The next few sections will focus on the most important ones. The main crate, cs2kz-api, lives in ./src/ and declares its metadata inside Cargo.toml. ./lib/ contains helper libraries, most notably cs2kz.

  • The ./database/ directory contains database migrations, test fixtures, and docker volumes.
  • The ./nix/ directory contains .nix files referenced by flake.nix.
  • The ./logs/ directory will be created when using the default tracing.files.path configuration value as specified in config.example.toml, and stores log files created by the API.
  • The ./workshop/ directory will be created when using the default steam.workshop-artifacts-path configuration value as specified in config.example.toml, and stores downloaded Steam Workshop files.
  • The ./docker/ directory will contain directories for mounted volumes used by the API container.

cs2kz crate

The "standard library" of CS2KZ.

It mosty contains type definitions for core concepts such as SteamID and Mode, and is used by most other crates in the workspace.

cs2kz-api-macros crate

A companion crate for cs2kz-api, containing procedural macros.

Currently it is necessary to define procedural macros in their own crates with a special proc-macro = true flag in their Cargo.toml. Any macros that were written specifically for this project live in that crate.

Services

tower::Service is the core abstraction that axum builds on to handle requests. As such, they are an important concept to understand. You'll find that cs2kz-api exports a module called services. This module contains types which handle different parts of the system/domain, such as the PlayerService or the MapService. These usually map directly to HTTP routes, such as /players or /maps. Some services don't, and are instead used by other services, like JwtService. It's important to note that these types do not actually implement the tower::Service trait. As they are application code, and not used in generic contexts, it would make little sense to actually implement those traits for them. They follow the general structure of async fn(Request) -> Response by exposing public functions taking a single req parameter and returning some response type. They are only concerned with business logic and don't know anything about HTTP. The HTTP handlers for each service live in their own module, usually http.rs, and just call into the service. Each service is then passed as router state using Router::with_state() and extracted in the handler functions. The request/response types are defined in models.rs and also publicly exported.

They do know about database queries; there is no "repository" abstraction. This might change in the future, but currently 99% of the "business logic" consists of database queries. I don't think there is a good reason to abstract this away further, as it would just needlessly complicate things.

Authentication & Authorization

The API provides several ways to authenticate requests:

  1. Sessions
  2. JWTs
  3. API Keys

Session Authentication

Sessions are how other applications, such as websites, can authenticate with the API. We use Steam as an OpenID provider to perform the actual login process, and then store the user's SteamID alongside an opaque session ID in the database. The session ID is given back to the user in a cookie, and they can use it for future requests.

For authorization we primarily use a custom permission system. These are modeled as bitflags, and every user has them. They are checked whenever a session is fetched from the database, and can then be used to perform authorization. There are other methods as well, all encapsulated in the AuthorizeSession trait. Check the AuthService documentation and implementation for details.

JWT Authentication

JWTs are how CS2 servers authenticate with the API. Every server has a permanent refresh key, which they can use to obtain temporary access tokens (JWTs). These access tokens are short-lived and are used to authenticate all requests. Server owners receive their refresh key when their server is approved, and can reset it at any time. Global Admins may delete a server's refresh key, preventing it from generating new access tokens. This is usually done if a server breaks the rules, and server owners are informed when it happens.

Key Authentication

The API also stores a table of opaque keys that are used for one-off purposes, such as GitHub actions. These are supposed to be used for internal processes, and aren't given out to random people.