# Backend Documentation [TOC] ## Authentication & Authorization ### Database We have splitted authentication and authorization data in the database. The new `users` table no longer consists of the old `role` column. The `user_group` table has a new `permission` column which replaces the old `role` column. Separating authentication and authorization is crucial to future integration of other services from the former esystem. We plan to add SSO for a centralized authentication, and service-specific `user_group` tables for separated authorization. ### JWT Tokens We use `djangorestframework-simplejwt` for JWT authentication. #### Token Types - **Access Token**: Short-lived (30 minutes), sent in request header as `Authorization: Bearer ` - **Refresh Token**: Long-lived (7 days), stored in HttpOnly cookie #### Token Storage The refresh token is stored in an HttpOnly cookie (`refresh_token`) to prevent XSS attacks. The cookie settings are: - `HttpOnly`: Always enabled (not accessible via JavaScript) - `Secure`: Enabled in production (HTTPS only) - `SameSite`: `Lax` - `Path`: `/api/v1/auth/` (only sent to auth endpoints) #### Token Rotation In production, refresh tokens are rotated on each refresh request. The old token is blacklisted and a new one is issued. In development, tokens are not rotated for easier debugging. #### Authentication Flow 1. User logs in via `POST /api/v1/auth/token/` with username and password 2. Server returns access token in response body and sets refresh token in HttpOnly cookie 3. Client includes access token in `Authorization` header for subsequent requests 4. When access token expires, client calls `POST /api/v1/auth/token/refresh/` (cookie is sent automatically) 5. Server returns new access token (and rotates refresh token in production) 6. On logout, `POST /api/v1/auth/logout/` blacklists the refresh token and clears the cookie ## CSpace Authorization Rules ### Group Management There is an admin group named `cspace`. All the other groups, whether from LDAP or not, are all normal groups. There are two roles, `admin` and `member`, in all groups. - `normal` - `admin`: can add / remove other users to / from the same group - `normal` - `member`: no permissions - `cspace` - `admin`: can add / remove other users to / from any group - `cspace` - `member`: can add /remove other users to / from any group except for the `cspace` group Additionally, `cspace` group members can create or delete groups. ### Registration All users from `normal` groups can view / make / modify / remove registrations for their own group. All users from the `cspace` group can view / make / modify / remove registrations for any group. ### Superuser There is one single superuser `ta217` that is the `admin` of the `cspace` group. It will be added by the `entrypoint.sh` that is executed when the container starts. ## Docker ### Dev We mount the whole directory under /app so we can reflect changes of the db to local computer for better debugging. The database backend is SQLite (`/tmp/db.sqlite3`). It is in the `/tmp` directory in the container and it will be removed when the container exits. The ldap container creates ldap users (username, password): - ta217, ta217 - thisway, hachu - chdir, plateau - user1, pw1 - user2, pw2 - ... - user100, pw100 The `cspace` group is created and `ta217` is added to it by default when the container starts. The list of default groups (name[, description]) created on startup are as below: ``` cspace faculty, gidNumber = 100 lab208 lab301 ... ``` The full list is at `docs/default-groups.txt`. The default groups, which are groups that had permissions to make registrations in the old system, are filtered from the original group list (by hand). To start the development server: 1. cd into `cspace-backend` 2. create a file `.env` ``` DB_USER=[enter username] DB_PASSWORD=[enter password] DJANGO_SECRET_KEY=[enter anything e.g. your-secret-key] ``` 3. Then execute: ``` docker compose -f docker-compose.yml -f docker-compose.dev.yml build --no-cache docker compose -f docker-compose.yml -f docker-compose.dev.yml up ``` To stop: cd into `cspace-backend` ``` docker compose -f docker-compose.yml -f docker-compose.dev.yml down ``` ### Production We should not mount the whole directory under /app. Instead, we should COPY all the necessary files into the image when building. To start the production server: cd into `cspace-backend` ``` docker compose build --no-cache docker compose up ``` To stop: cd into `cspace-backend` ``` docker compose down ``` The `.env` file should also include ``` DJANGO_ALLOWED_HOSTS=[production machine ip],localhost,127.0.0.1 ``` **Note**: We don't create the `cspace` group and the `ta217` user on startup. They are imported manually through the adminer interface. **Note**: We don't create default groups on startup, groups are imported manually from the old db. We should manually remove unused groups from the web interface during setup. ## Time Related Formats We use [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format for date, and datetime. ### Date Format: YYYY-MM-DD Example: `2026-03-01` ### Datetime Django's `DateTimeField()` accepts ISO 8601 formatted datetime strings with timezone offsets. It will be converted into a python `datetime.datetime` object and stored into the database. Format: YYYY-MM-DDThh:mm:ss±hh:mm Example: - Input: `2026-03-01T13:00:00+08:00` (1 PM in UTC+8) - Stored as: `2026-03-01 05:00:00 UTC` (5 AM UTC) All datetime strings posted from the frontend should include timezone info (e.g., `2026-03-01T13:00:00Z` for UTC or `2026-03-01T13:00:00+08:00` for UTC+8). However, if no timezone info is included (e.g., `2026-03-01T13:00:00Z`), Django will default to `TIME_ZONE = 'Asia/Taipei'`. ## Cookies ### SameSite= | Setting | Sent on cross-site POST? | Sent on link click? | Security | | ------- | ------------------------ | ------------------- | --------------------------- | | Strict | ❌ No | ❌ No | 🔒 Highest | | Lax | ❌ No | ✅ Yes | 🔒 Recommended | | None | ✅ Yes | ✅ Yes | ⚠️ Requires CSRF protection | ## Database Tables & Columns Descriptions ### users User identity and login metadata (authentication identity is LDAP-backed). - `id` (PK): Internal user identifier. - `username`: Unique login/account name. - `displayname`: Human-readable display name. - `last_login_time`: Last successful login timestamp. - `last_login_ip`: Source IP for the last successful login. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. ### groups Authorization groups used for access control and registration ownership. - `id` (PK): Group identifier. - `name`: Group name (for example `cspace`, `faculty`). - `description`: Optional group description. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. ### user_group Many-to-many mapping between users and groups, including per-group permission. - `id` (PK): Membership record identifier. - `user_id` (FK -> `users.id`): Member user. - `group_id` (FK -> `groups.id`): Group the user belongs to. - `permissions`: Role in the group (`admin` or `member`). - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. ### classrooms Classroom master data and bookability flags. - `id` (PK): Classroom identifier. - `name`: Classroom name. - `capacity`: Maximum number of people. - `equipment`: Equipment description (json). - `floor`: Floor number. - `require_permission`: Whether special permission is required to register. - `is_deleted`: Soft-delete flag. - `version`: Version number for optimistic locking/auditing. - `is_valid`: Validation/availability flag. ### draw_events Defines a draw/lottery window and its booking period. - `id` (PK): Draw event identifier. - `event_start_time (Datetime)`: Start time of booking period. - `event_end_time (Datetime)`: End time of booking period. - `target_start_date`: Start date of the allocation window. - `target_end_date`: End date of the allocation window. - `is_completed`: Whether draw processing has completed. - `is_deleted`: Soft-delete flag. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. - `version`: Version number for optimistic locking/auditing. ### draw_registrations Applications submitted into a draw event before final registration creation. - `id` (PK): Draw registration identifier. - `draw_event_id` (FK -> `draw_events.id`): Parent draw event. - `classroom_id` (FK -> `classrooms.id`): Requested classroom. - `user_id` (FK -> `users.id`): Applicant user. - `group_id` (FK -> `groups.id`): Applicant group. - `is_selected`: Whether selected by the draw. - `start_time` (integer): Requested start time. - Stored as number of seconds since start of the week (every Sunday midnight 00:00). - `end_time` (integer): Requested end time. - Stored as number of seconds since start of the week (every Sunday midnight 00:00). - `importance`: Priority/importance score. - `description`: Applicant note/details. - `is_deleted`: Soft-delete flag. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. - `version`: Version number for optimistic locking/auditing. Although `start_time` and `end_time` is being stored as integers, which might seem a little bit weird, we decide to follow the same design as the old system for easier data migration. Also, registrations need not to be made from the start of a period to the end of a period. It can be from any time to any time. Moreover, this design allows for better extensibility of the system. We have considered storing them as `start_period`, `end_period`, and `day_of_week`. But after thorough consideration, we decided to stay the same. The same follows for the table `longterm_registrations` below. ### longterm_registrations Recurring booking templates (for example weekly recurring reservations). - `id` (PK): Long-term registration identifier. - `user_id` (FK -> `users.id`): Owner user. - `group_id` (FK -> `groups.id`): Owner group. - `classroom_id` (FK -> `classrooms.id`): Target classroom. - `start_date`: Effective recurrence start date. - `end_date`: Effective recurrence end date. - `start_time` (Datetime): Start time. - `end_time` (Datetime): End time. - `day_of_week` (Chars): Day of week. - `description`: Notes/details. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. - `version`: Version number for optimistic locking/auditing. Although `start_time` and `end_time` are stored as Datetime, we only need the time of the day from it. ### registrations Finalized booking records (single reservations), optionally linked to draw/long-term sources. - `id` (PK): Registration identifier. - `user_id` (FK -> `users.id`): Booking owner user. - `group_id` (FK -> `groups.id`): Booking owner group. - `classroom_id` (FK -> `classrooms.id`): Reserved classroom. - `draw_registration_id` (FK -> `draw_registrations.id`, nullable): Source draw registration. - `longterm_id` (FK -> `longterm_registrations.id`, nullable): Source long-term template. - `start_time` (Datetime): Reservation start timestamp. - `end_time` (Datetime): Reservation end timestamp. - `importance`: Priority/importance value. - `description`: Reservation note/details. - `is_pending`: Approval status flag (`true` = pending). - `is_deleted`: Soft-delete flag. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. - `version`: Version number for optimistic locking/auditing. ### notifications System notification content. - `id` (PK): Notification identifier. - `description`: Notification body/content. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. ### notification_users Per-user/group delivery state for notifications. - `id` (PK): Delivery-state record identifier. - `notification_id` (FK -> `notifications.id`): Notification reference. - `user_id` (FK -> `users.id`): Target user. - `group_id` (FK -> `groups.id`): Related group context. - `archived`: Whether archived by user. - `new`: Whether still marked as unread/new. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp. ### change_logs Auditable service logs for actions/changes. - `id` (PK): Log record identifier. - `user_id` (FK -> `users.id`): Actor user. - `group_id` (FK -> `groups.id`): Actor group context. - `service`: Service/module name (for example `login`, `groups`). - `subject`: Log subject/category (for example `ok`, `failed`, `add`, `remove`). - `log`: Detailed log message/body. - `created_at`: Record creation timestamp. - `updated_at`: Record update timestamp.