Skip to content

Commit 27ab90d

Browse files
feat(auth,accounts,templates): move login/logout endpoints to accounts.api, update routers and templates\n\n- Move login/logout endpoints from users.api to accounts.api\n- Update routers to use new accounts.api for login/logout\n- Update templates to use /auth/accounts/login and /auth/accounts/logout endpoints\n- Fix imports and router registration for accounts API\n- Ensure separation of API and render routes in accounts module
1 parent a85c6be commit 27ab90d

File tree

16 files changed

+213
-90
lines changed

16 files changed

+213
-90
lines changed

example/apps/deps.py

Lines changed: 0 additions & 57 deletions
This file was deleted.

example/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Config:
5050

5151
@app.exception_handler(403)
5252
async def not_found(request, exc):
53-
return RedirectResponse("/accounts/login", status_code=303)
53+
return RedirectResponse("/auth/accounts/login", status_code=303)
5454
# return render_template(request=request, template_name="401.html", context={"request": request})
5555

5656
# Класс для аутентификации

src/cotlette/conf/global_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,11 @@ def gettext_noop(s):
524524

525525
AUTHENTICATION_BACKENDS = ["cotlette.contrib.auth.backends.ModelBackend"]
526526

527-
LOGIN_URL = "/accounts/login/"
527+
LOGIN_URL = "/auth/accounts/login/"
528528

529529
LOGIN_REDIRECT_URL = "/accounts/profile/"
530530

531-
LOGOUT_REDIRECT_URL = None
531+
LOGOUT_REDIRECT_URL = "/auth/accounts/login/"
532532

533533
# The number of seconds a password reset link is valid for (default: 3 days).
534534
PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 3

src/cotlette/contrib/auth/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from cotlette.contrib.auth.users import api as users_api
88
from cotlette.contrib.auth.groups import api as groups_api
99
from cotlette.contrib.auth.accounts import urls as accounts_urls
10+
from cotlette.contrib.auth.accounts import api as accounts_api
1011

1112
# Импортируем модели для удобства
1213
from cotlette.contrib.auth.users.models import UserModel, User, UserCreate
@@ -28,4 +29,7 @@
2829
router.include_router(groups_api.router, prefix="/groups", tags=["groups"])
2930
# Подключаем роуты аккаунтов (регистрация, аутентификация, смена пароля)
3031
if hasattr(accounts_urls, 'router'):
31-
router.include_router(accounts_urls.router, prefix="/accounts", tags=["accounts"])
32+
router.include_router(accounts_urls.router, prefix="/auth/accounts", tags=["accounts"])
33+
# Подключаем роуты аккаунтов (login/logout)
34+
if hasattr(accounts_api, 'router'):
35+
router.include_router(accounts_api.router, prefix="/auth/accounts", tags=["accounts"])
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from fastapi import APIRouter
22

33
from .urls import router as urls_router
4-
4+
from .api import router as api_router
55

66
router = APIRouter()
77
# router.include_router(urls_router, prefix="/admin", include_in_schema=False)
8-
router.include_router(urls_router, prefix="/accounts", tags=["render"])
8+
router.include_router(urls_router, prefix="/accounts", tags=["render"])
9+
router.include_router(api_router, prefix="/accounts", tags=["api"])
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from fastapi import APIRouter
2+
from starlette.responses import JSONResponse, RedirectResponse
3+
from cotlette.contrib.auth.users.models import UserModel
4+
from cotlette.contrib.auth.users.utils import check_password, generate_jwt
5+
6+
router = APIRouter()
7+
8+
@router.route("/login", methods=["POST"])
9+
async def login_user(request):
10+
if 'history' in request.session and len(request.session['history']):
11+
previous = request.session['history'].pop()
12+
else:
13+
previous = '/admin'
14+
15+
form = await request.form()
16+
username = form["email"]
17+
password = form["password"]
18+
19+
user = UserModel.objects.filter(email=username).first() # type: ignore
20+
if not user:
21+
return RedirectResponse(previous, status_code=303)
22+
23+
hashed_pass = user.password_hash
24+
valid_pass = await check_password(password, hashed_pass)
25+
if not valid_pass:
26+
return RedirectResponse(previous, status_code=303)
27+
28+
if previous in ('/users/login', '/users/login/', "/"):
29+
previous = '/admin'
30+
31+
response = RedirectResponse(previous, status_code=303)
32+
if valid_pass:
33+
response.set_cookie('jwt', generate_jwt(user.id), httponly=True)
34+
return response
35+
36+
@router.post("/logout", response_model=None)
37+
def logout():
38+
response = JSONResponse(content={"message": "Logout successful"})
39+
response.delete_cookie("jwt")
40+
return response

src/cotlette/contrib/auth/groups/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,4 @@ async def create_tables():
7474
# @router.get("/", response_model=list[User])
7575
# def get_users():
7676
# users = UserModel.objects.all()
77-
# return [User(name=user.name, age=user.age, email=user.email) for user in users]
77+
# return [User(name=user.name, age=user.age, email=user.email) for user in users]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
11
from cotlette.shortcuts import render
22

33
# Create your views here.
4+
5+
# --- API ROUTES (moved from api.py) ---
6+
from typing import Union
7+
8+
from fastapi import APIRouter, Depends, HTTPException, status
9+
from pydantic import BaseModel
10+
from jose import jwt, JWTError
11+
from datetime import timedelta, datetime
12+
from .models import GroupModel
13+
# from .utils import hash_password, generate_jwt, check_password
14+
15+
from starlette.responses import JSONResponse, \
16+
PlainTextResponse, \
17+
RedirectResponse, \
18+
StreamingResponse, \
19+
FileResponse, \
20+
HTMLResponse
21+
22+
from fastapi.security import OAuth2PasswordBearer
23+
from fastapi.responses import JSONResponse
24+
25+
router = APIRouter()
26+
27+
# Создание таблицы при запуске приложения
28+
@router.on_event("startup")
29+
async def create_tables():
30+
GroupModel.create_table()
31+
owners_group = GroupModel.objects.filter(name="Owners").first() # type: ignore
32+
if not owners_group:
33+
GroupModel.objects.create(name="Owners")
34+
35+
# (Закомментированные примеры моделей и ручек оставлены для истории)

src/cotlette/contrib/auth/users/api.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,6 @@ class TokenData(BaseModel):
3939
# JWT settings
4040
ACCESS_TOKEN_EXPIRE_MINUTES = 30
4141

42-
@router.on_event("startup")
43-
async def create_tables():
44-
db.initialize()
45-
UserModel.create_table()
46-
existing_user = UserModel.objects.filter(email="[email protected]").first() # type: ignore
47-
if not existing_user:
48-
test_user = await create_user(UserCreate(
49-
name="vova",
50-
age=26,
51-
52-
password="dotvej-Fawne4-migqaw",
53-
group_id=1,
54-
organization="N/A organization"
55-
))
56-
print('test_user', test_user)
57-
5842
@router.route("/login", methods=["POST"])
5943
async def login_user(request):
6044
# Redirect to previous path after login
@@ -130,4 +114,4 @@ async def create_user(user: UserCreate):
130114
@router.get("/", response_model=list[User])
131115
async def get_users():
132116
users = await UserModel.objects.all().execute() # type: ignore
133-
return [User(name=user.name, age=user.age, email=user.email) for user in users]
117+
return [User(name=user.name, age=user.age, email=user.email) for user in users]
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,122 @@
11
from cotlette.shortcuts import render
22

33
# Create your views here.
4+
5+
# --- API ROUTES (moved from api.py) ---
6+
from typing import Union
7+
8+
from fastapi import APIRouter, Depends, HTTPException, status
9+
from pydantic import BaseModel
10+
from jose import jwt, JWTError
11+
from datetime import timedelta
12+
from .models import UserModel, UserCreate, User
13+
from .utils import hash_password, generate_jwt, check_password
14+
15+
from starlette.responses import JSONResponse, \
16+
PlainTextResponse, \
17+
RedirectResponse, \
18+
StreamingResponse, \
19+
FileResponse, \
20+
HTMLResponse
21+
22+
from cotlette.core.database.sqlalchemy import db
23+
from cotlette.contrib.auth.groups.models import GroupModel
24+
25+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
26+
27+
router = APIRouter()
28+
29+
# Pydantic model for user login
30+
class UserLogin(BaseModel):
31+
email: str
32+
password: str
33+
34+
# Pydantic model for token
35+
class Token(BaseModel):
36+
access_token: str
37+
token_type: str
38+
39+
# Pydantic model for token data
40+
class TokenData(BaseModel):
41+
email: Union[str] = None
42+
43+
44+
# JWT settings
45+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
46+
47+
@router.route("/login", methods=["POST"])
48+
async def login_user(request):
49+
# Redirect to previous path after login
50+
if 'history' in request.session and len(request.session['history']):
51+
previous = request.session['history'].pop()
52+
else:
53+
previous = '/admin'
54+
55+
# Get form data
56+
form = await request.form()
57+
username = form["email"]
58+
password = form["password"]
59+
60+
# Search for user in database
61+
user = UserModel.objects.filter(email=username).first() # type: ignore
62+
if not user:
63+
return RedirectResponse(previous, status_code=303)
64+
65+
hashed_pass = user.password_hash
66+
67+
# Check password
68+
valid_pass = await check_password(password, hashed_pass)
69+
if not valid_pass:
70+
return RedirectResponse(previous, status_code=303)
71+
72+
if previous in ('/users/login', '/users/login/', "/"):
73+
previous = '/admin'
74+
75+
response = RedirectResponse(previous, status_code=303)
76+
if valid_pass:
77+
response.set_cookie('jwt', generate_jwt(user.id), httponly=True)
78+
return response
79+
80+
81+
@router.post("/logout", response_model=None)
82+
def logout():
83+
response = JSONResponse(content={"message": "Logout successful"})
84+
response.delete_cookie("jwt")
85+
return response
86+
87+
88+
# Create new user (POST)
89+
@router.post("/", response_model=None)
90+
async def create_user(user: UserCreate):
91+
hashed_password = await hash_password(user.password)
92+
group = await GroupModel.objects.filter(id=user.group_id).first() # type: ignore
93+
94+
# Check if user doesn't exist
95+
existing_user = await UserModel.objects.filter(email=user.email).first() # type: ignore
96+
if existing_user:
97+
return JSONResponse(
98+
status_code=400,
99+
content={"message": "User with this email already exists"}
100+
)
101+
102+
new_user = await UserModel.objects.create(
103+
name=user.name,
104+
age=user.age,
105+
email=user.email,
106+
password_hash=hashed_password,
107+
group=group.id
108+
)
109+
return User(
110+
id=new_user.id,
111+
name=new_user.name,
112+
age=new_user.age,
113+
email=new_user.email,
114+
group=new_user.group.id
115+
)
116+
117+
118+
# Get all users (GET)
119+
@router.get("/", response_model=list[User])
120+
async def get_users():
121+
users = await UserModel.objects.all().execute() # type: ignore
122+
return [User(name=user.name, age=user.age, email=user.email) for user in users]

0 commit comments

Comments
 (0)