Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5691f3133 | ||
|
|
5f80a7a86f | ||
|
|
b588ebcf8a | ||
|
|
a9b88d87d6 | ||
|
|
7163fe778e |
7 changed files with 37 additions and 60 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
# Apps
|
# Apps
|
||||||
|
|
||||||
Apps are a way to group database models and API routes that contextually belong together.
|
Apps are a way to group database models and API routes that contextually belong together.
|
||||||
For example, OhMyAPI comes bundled with an `auth` app that carries a `User` and `Group` model and provides API endpoints for JWT authentication.
|
For example, OhMyAPI comes bundled with an `auth` app that carries a `User` model and provides API endpoints for JWT authentication.
|
||||||
|
|
||||||
Apps help organizing projects by isolating individual components (or "features") from one another.
|
Apps help organizing projects by isolating individual components (or "features") from one another.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "ohmyapi"
|
name = "ohmyapi"
|
||||||
version = "0.5.6"
|
version = "0.6.2"
|
||||||
description = "Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations"
|
description = "Django-flavored scaffolding and management layer around FastAPI, Pydantic, TortoiseORM and Aerich migrations"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["fastapi", "tortoise", "orm", "pydantic", "async", "web-framework"]
|
keywords = ["fastapi", "tortoise", "orm", "pydantic", "async", "web-framework"]
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__VERSION__ = "0.5.6"
|
__VERSION__ = "0.6.2"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
from functools import wraps
|
|
||||||
from secrets import token_bytes
|
|
||||||
from typing import List, Optional
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from passlib.context import CryptContext
|
|
||||||
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
|
||||||
|
|
||||||
from ohmyapi.db import Model, field, Q
|
from ohmyapi.db import Model, field, Q
|
||||||
from ohmyapi.router import HTTPException
|
from ohmyapi.router import HTTPException
|
||||||
|
|
||||||
from .utils import hmac_hash
|
from .utils import hmac_hash
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from typing import Optional
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||||
|
|
||||||
|
|
||||||
class Group(Model):
|
|
||||||
id: UUID = field.data.UUIDField(pk=True)
|
|
||||||
name: str = field.CharField(max_length=42, index=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name if self.name else ""
|
|
||||||
|
|
||||||
|
|
||||||
class User(Model):
|
class User(Model):
|
||||||
id: UUID = field.data.UUIDField(pk=True)
|
id: UUID = field.data.UUIDField(pk=True)
|
||||||
username: str = field.CharField(max_length=150, unique=True)
|
username: str = field.CharField(max_length=150, unique=True)
|
||||||
|
|
@ -29,20 +18,22 @@ class User(Model):
|
||||||
password_hash: str = field.CharField(max_length=128)
|
password_hash: str = field.CharField(max_length=128)
|
||||||
is_admin: bool = field.BooleanField(default=False)
|
is_admin: bool = field.BooleanField(default=False)
|
||||||
is_staff: bool = field.BooleanField(default=False)
|
is_staff: bool = field.BooleanField(default=False)
|
||||||
groups: field.ManyToManyRelation[Group] = field.ManyToManyField(
|
created_at: datetime = field.DatetimeField(auto_now_add=True)
|
||||||
"ohmyapi_auth.Group",
|
updated_at: datetime = field.DatetimeField(auto_now=True)
|
||||||
related_name="users",
|
|
||||||
through="ohmyapi_auth.UserGroups",
|
|
||||||
forward_key="user_id",
|
|
||||||
backward_key="group_id",
|
|
||||||
)
|
|
||||||
|
|
||||||
class Schema:
|
class Schema:
|
||||||
exclude = ["password_hash", "email_hash"]
|
include = {
|
||||||
|
"id",
|
||||||
|
"username",
|
||||||
|
"is_admin",
|
||||||
|
"is_staff"
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
fields = {
|
fields = {
|
||||||
'username': self.username if self.username else "-",
|
'username': self.username,
|
||||||
'is_admin': 'y' if self.is_admin else 'n',
|
'is_admin': 'y' if self.is_admin else 'n',
|
||||||
'is_staff': 'y' if self.is_staff else 'n',
|
'is_staff': 'y' if self.is_staff else 'n',
|
||||||
}
|
}
|
||||||
|
|
@ -67,20 +58,3 @@ class User(Model):
|
||||||
if user and user.verify_password(password):
|
if user and user.verify_password(password):
|
||||||
return user
|
return user
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class UserGroups(Model):
|
|
||||||
user: field.ForeignKeyRelation[User] = field.ForeignKeyField(
|
|
||||||
"ohmyapi_auth.User",
|
|
||||||
related_name="user_groups",
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
group: field.ForeignKeyRelation[Group] = field.ForeignKeyField(
|
|
||||||
"ohmyapi_auth.Group",
|
|
||||||
related_name="group_users",
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
table = "ohmyapi_auth_user_groups"
|
|
||||||
constraints = [("UNIQUE", ("user_id", "group_id"))]
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from fastapi.security.utils import get_authorization_scheme_param
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from tortoise.exceptions import DoesNotExist
|
from tortoise.exceptions import DoesNotExist
|
||||||
|
|
||||||
from ohmyapi.builtin.auth.models import Group, User
|
from ohmyapi.builtin.auth.models import User
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import settings
|
import settings
|
||||||
|
|
@ -53,7 +53,6 @@ class Claims(BaseModel):
|
||||||
type: str
|
type: str
|
||||||
sub: str
|
sub: str
|
||||||
user: ClaimsUser
|
user: ClaimsUser
|
||||||
roles: List[str]
|
|
||||||
exp: str
|
exp: str
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -80,7 +79,7 @@ class TokenType(str, Enum):
|
||||||
refresh = "refresh"
|
refresh = "refresh"
|
||||||
|
|
||||||
|
|
||||||
def claims(token_type: TokenType, user: User, groups: List[Group] = []) -> Claims:
|
def claims(token_type: TokenType, user: User = []) -> Claims:
|
||||||
return Claims(
|
return Claims(
|
||||||
type=token_type,
|
type=token_type,
|
||||||
sub=str(user.id),
|
sub=str(user.id),
|
||||||
|
|
@ -89,7 +88,6 @@ def claims(token_type: TokenType, user: User, groups: List[Group] = []) -> Claim
|
||||||
is_admin=user.is_admin,
|
is_admin=user.is_admin,
|
||||||
is_staff=user.is_staff,
|
is_staff=user.is_staff,
|
||||||
),
|
),
|
||||||
roles=[g.name for g in groups],
|
|
||||||
exp="",
|
exp="",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -342,9 +342,9 @@ class App:
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
return
|
return
|
||||||
|
|
||||||
getter = getattr(mod, "get", None)
|
installer = getattr(mod, "install", None)
|
||||||
if getter is not None:
|
if installer is not None:
|
||||||
for middleware in getter():
|
for middleware in installer():
|
||||||
self._middlewares.append(middleware)
|
self._middlewares.append(middleware)
|
||||||
|
|
||||||
def __serialize_route(self, route):
|
def __serialize_route(self, route):
|
||||||
|
|
@ -404,10 +404,15 @@ class App:
|
||||||
"""
|
"""
|
||||||
Convenience method for serializing the runtime data.
|
Convenience method for serializing the runtime data.
|
||||||
"""
|
"""
|
||||||
|
# An app may come without any models
|
||||||
|
models = []
|
||||||
|
if f"{self.name}.models" in self._models:
|
||||||
|
models = [
|
||||||
|
f"{self.name}.{m.__name__}"
|
||||||
|
for m in self._models[f"{self.name}.models"]
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
"models": [
|
"models": models,
|
||||||
f"{self.name}.{m.__name__}" for m in self._models[f"{self.name}.models"]
|
|
||||||
],
|
|
||||||
"middlewares": self.__serialize_middleware(),
|
"middlewares": self.__serialize_middleware(),
|
||||||
"routes": self.__serialize_router(),
|
"routes": self.__serialize_router(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ CORS_CONFIG: Dict[str, Any] = getattr(settings, "MIDDLEWARE_CORS", {})
|
||||||
if not isinstance(CORS_CONFIG, dict):
|
if not isinstance(CORS_CONFIG, dict):
|
||||||
raise ValueError("MIDDLEWARE_CORS must be of type dict")
|
raise ValueError("MIDDLEWARE_CORS must be of type dict")
|
||||||
|
|
||||||
middleware = [
|
middleware = (
|
||||||
(CORSMiddleware, {
|
CORSMiddleware,
|
||||||
|
{
|
||||||
"allow_origins": CORS_CONFIG.get("ALLOW_ORIGINS", DEFAULT_ORIGINS),
|
"allow_origins": CORS_CONFIG.get("ALLOW_ORIGINS", DEFAULT_ORIGINS),
|
||||||
"allow_credentials": CORS_CONFIG.get("ALLOW_CREDENTIALS", DEFAULT_CREDENTIALS),
|
"allow_credentials": CORS_CONFIG.get("ALLOW_CREDENTIALS", DEFAULT_CREDENTIALS),
|
||||||
"allow_methods": CORS_CONFIG.get("ALLOW_METHODS", DEFAULT_METHODS),
|
"allow_methods": CORS_CONFIG.get("ALLOW_METHODS", DEFAULT_METHODS),
|
||||||
"allow_headers": CORS_CONFIG.get("ALLOW_HEADERS", DEFAULT_HEADERS),
|
"allow_headers": CORS_CONFIG.get("ALLOW_HEADERS", DEFAULT_HEADERS),
|
||||||
}),
|
}
|
||||||
]
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue