# How CMS Authorization Is Structured with Groups and Permissions in Mini-Shop-Server

> Explore CMS authorization in mini-shop-server. Understand how groups and permissions structure access control via RBAC and API endpoint validation.

- Repository: [A粒麦子/mini-shop-server](https://github.com/allen7d/mini-shop-server)
- Tags: internals
- Published: 2026-02-24

---

**Mini-shop-server implements a group-based RBAC system where users inherit permissions through group membership, with each API endpoint declaring required access metadata that is validated against the Auth table at request time.**

The mini-shop-server repository uses a classic group-based permission model for its Content Management System (CMS). This architecture separates users into groups, links those groups to specific permission records, and validates access at runtime by matching endpoint metadata against stored authorizations.

## Core Models: Groups and Auth Records

The authorization system relies on two primary SQLAlchemy models that establish the relationship between users and their permitted actions.

### The Group Model

Each CMS user belongs to exactly one group, defined in [`app/models/group.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/group.py). The `Group` class stores the group identity and maintains relationships to routes and UI elements.

```python

# app/models/group.py

class Group(Base):
    __tablename__ = 'group'
    id = Column(Integer, primary_key=True)
    name = Column(String(60), unique=True)   # 权限组名称

    info = Column(String(255))               # 描述

    route = relationship('Route', secondary='menu', back_populates='group')
    elements = relationship('Element', secondary='group_2_element', back_populates='groups')

```

The `auth_list` property (not fully shown in the excerpt) collects all **Auth** records associated with the group, effectively representing the permission portfolio available to group members.

### The Auth Model

Individual permissions are stored as `Auth` records in [`app/models/auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/auth.py). Each row links a specific group to a permission name and its functional module.

```python

# app/models/auth.py

class Auth(Base):
    __tablename__ = 'auth'
    id = Column(Integer, primary_key=True)
    group_id = Column(Integer, nullable=False)   # 所属权限组 id

    name = Column(String(60))                    # 权限字段 (e.g. “新增商品”)

    module = Column(String(50))                  # 权限所属模块 (e.g. “商品”)

```

This design allows granular control where a group can be granted "新增商品" (Add Product) permission within the "商品" (Product) module without inheriting unrelated privileges.

## Declaring Permissions with Route Metadata

The system uses a metadata registration pattern to declare what permissions each API endpoint requires. This metadata is later used during the authorization check.

### The Meta Namedtuple and Registry

In [`app/core/redprint.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/redprint.py), the system defines a `Meta` namedtuple to store permission identifiers and a global registry to map view functions to their requirements.

```python

# app/core/redprint.py

from collections import namedtuple

Meta = namedtuple('Meta', ['name', 'module'])
route_meta_infos = {}  # key: unique view-function id, value: Meta

```

### The route_meta Decorator

The `route_meta` method attaches permission requirements to view functions. When applied, it stores the permission name and module in the `route_meta_infos` dictionary.

```python

# app/core/redprint.py

def route_meta(self, auth: str, module: str = 'common', mount: bool = True):
    # registers Meta for the view function

    def decorator(f):
        # ... registration logic ...

        return f
    return decorator

```

In practice, CMS endpoints combine this decorator with the `@auth.group_required` decorator to enforce checks:

```python

# app/api/cms/product.py

from app.core.auth import auth
from app.core.redprint import api

@api.route('/product/add', methods=['POST'])
@auth.group_required
@api.route_meta('新增商品', module='商品')
def add_product():
    """Add a new product (CMS)"""
    return jsonify({'msg': 'product added'})

```

At application startup, the `mount_route_meta_to_endpoint` function in [`app/core/auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/auth.py) populates `current_app.config['EP_META']`, mapping Flask endpoint strings (e.g., `cms.product_add`) to their corresponding `Meta` objects.

## Runtime Authorization Flow

When a protected request arrives, the system executes a multi-step validation process to determine if the user's group possesses the required permission.

### Token Verification and Group Extraction

The `verify_group` callback in [`app/core/token_auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/token_auth.py) intercepts all requests to CMS endpoints decorated with `@auth.group_required`. It decrypts the JWT token to extract the user ID, retrieves the user record, and identifies the associated `group_id`.

```python

# app/core/token_auth.py

@auth.verify_group
def verify_group(token, password):
    (uid, ac_type, scope) = decrypt_token(token)
    current_user = User.get_or_404(id=uid)
    group_id = current_user.group_id
    
    if not current_user.is_admin:
        if group_id is None:
            raise AuthFailed(msg='您还不属于任何权限组，请联系系统管理员获得权限')
        allowed = is_in_auth_scope(group_id, request.endpoint)
        if not allowed:
            raise AuthFailed(msg='权限不够，请联系系统管理员获得权限')
    g.user = current_user

```

If the user has no `group_id`, the request is rejected immediately. Super-admin users bypass this entire check via a separate `verify_admin` callback.

### The is_in_auth_scope Validation

The core authorization logic resides in [`app/core/auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/auth.py). The `is_in_auth_scope` function retrieves the endpoint's metadata from the `EP_META` registry and queries the `Auth` table for a matching record.

```python

# app/core/auth.py

def is_in_auth_scope(group_id, endpoint):
    meta = current_app.config['EP_META'].get(endpoint)  # Meta(name, module)

    allowed = False
    if meta:
        allowed = Auth.get(group_id=group_id, name=meta.name, module=meta.module)
    return True if allowed else False

```

The function returns `True` only if the database contains an `Auth` row matching the user's `group_id`, the endpoint's permission `name`, and the endpoint's `module`. If no match exists, the request is denied.

## Super-Admin Bypass

The system maintains a privileged administrative tier that circumvents group-based checks entirely. In [`app/core/token_auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/token_auth.py), the `verify_admin` decorator handles authentication for super-admin-only endpoints.

```python

# app/core/token_auth.py

@auth.verify_admin
def verify_admin(token, password):
    (uid, ac_type, scope) = decrypt_token(token)
    current_user = User.get_or_404(id=uid)
    if not current_user.is_admin:
        raise AuthFailed(msg='该接口为超级管理员权限操作')
    g.user = current_user

```

When `current_user.is_admin` evaluates to `True`, the request proceeds without invoking `is_in_auth_scope` or validating group memberships.

## Summary

- **Group-based RBAC**: Users are assigned to groups via [`app/models/group.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/group.py), and permissions are granted to groups rather than individual users.
- **Auth records**: The `Auth` model in [`app/models/auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/auth.py) stores permission tuples of `(name, module)` linked to specific groups.
- **Route metadata**: The `route_meta` decorator in [`app/core/redprint.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/redprint.py) declares required permissions, which are mapped to endpoints in the `EP_META` configuration.
- **Runtime validation**: The `verify_group` callback in [`app/core/token_auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/token_auth.py) extracts the user's group and calls `is_in_auth_scope` to validate access against the `Auth` table.
- **Admin override**: Super-admins identified by `User.is_admin` bypass all group permission checks through the `verify_admin` mechanism.

## Frequently Asked Questions

### How does the system map API endpoints to specific permission requirements?

The system uses a global registry called `EP_META` populated at application startup by `mount_route_meta_to_endpoint` in [`app/core/auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/auth.py). When developers decorate a view function with `@api.route_meta('新增商品', module='商品')`, the metadata is stored and later mapped to the Flask endpoint string. During request processing, `is_in_auth_scope` retrieves this metadata via `current_app.config['EP_META'].get(endpoint)` to determine what permission name and module are required for access.

### What happens if a CMS user does not belong to any group?

If `current_user.group_id` is `None`, the `verify_group` function in [`app/core/token_auth.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/core/token_auth.py) raises an `AuthFailed` exception with the message "您还不属于任何权限组，请联系系统管理员获得权限". This check occurs before any permission validation, ensuring that ungrouped users cannot access any CMS functionality protected by `@auth.group_required`.

### How are permissions programmatically assigned to a group?

Permissions are created as `Auth` records linking a group ID to a permission name and module. You can add permissions via database operations:

```python
from app.models.auth import Auth
from app.core.db import db

new_auth = Auth(group_id=2, name='新增商品', module='商品')
db.session.add(new_auth)
db.session.commit()

```

Once committed, any user with `group_id=2` can access endpoints requiring the "新增商品" permission within the "商品" module.

### Can individual users have permissions without being assigned to a group?

No. The architecture enforces group-based authorization exclusively. The `verify_group` logic explicitly checks for `group_id` presence and rejects requests from users without group membership. Individual permissions are not supported; all authorizations must be granted through the `Auth` table linked to a `Group` record. Super-admins bypass this system entirely via the `is_admin` flag rather than individual permission grants.