feat: create new user with password
This commit is contained in:
		@@ -6,9 +6,10 @@ from typing import Optional
 | 
			
		||||
from sqlalchemy import func, insert, select
 | 
			
		||||
from sqlalchemy.ext.asyncio import AsyncConnection
 | 
			
		||||
 | 
			
		||||
from api.db.logic.keyring import create_password_key
 | 
			
		||||
from api.db.tables.account import account_table
 | 
			
		||||
from api.schemas.account.account import User
 | 
			
		||||
from api.schemas.endpoints.account import all_user_adapter, AllUserResponse, UserUpdate
 | 
			
		||||
from api.schemas.endpoints.account import all_user_adapter, AllUser, AllUserResponse, UserCreate, UserUpdate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_user_accaunt_page(connection: AsyncConnection, page, limit) -> Optional[AllUserResponse]:
 | 
			
		||||
@@ -54,7 +55,7 @@ async def get_user_accaunt_page(connection: AsyncConnection, page, limit) -> Opt
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_user_by_id(connection: AsyncConnection, user_id: int) -> Optional[UserUpdate]:
 | 
			
		||||
async def get_user_by_id(connection: AsyncConnection, user_id: int) -> Optional[AllUser]:
 | 
			
		||||
    """
 | 
			
		||||
    Получает юзера по id.
 | 
			
		||||
    """
 | 
			
		||||
@@ -66,7 +67,7 @@ async def get_user_by_id(connection: AsyncConnection, user_id: int) -> Optional[
 | 
			
		||||
    if not user:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    return UserUpdate.model_validate(user)
 | 
			
		||||
    return AllUser.model_validate(user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_user_by_login(connection: AsyncConnection, login: str) -> Optional[User]:
 | 
			
		||||
@@ -102,7 +103,7 @@ async def update_user_by_id(connection: AsyncConnection, update_values, user) ->
 | 
			
		||||
    await connection.commit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def create_user(connection: AsyncConnection, user: UserUpdate, creator_id: int) -> Optional[UserUpdate]:
 | 
			
		||||
async def create_user(connection: AsyncConnection, user: UserCreate, creator_id: int) -> Optional[AllUser]:
 | 
			
		||||
    """
 | 
			
		||||
    Создает нове поле в таблице account_table.
 | 
			
		||||
    """
 | 
			
		||||
@@ -112,7 +113,7 @@ async def create_user(connection: AsyncConnection, user: UserUpdate, creator_id:
 | 
			
		||||
        email=user.email,
 | 
			
		||||
        bind_tenant_id=user.bind_tenant_id,
 | 
			
		||||
        role=user.role.value,
 | 
			
		||||
        meta=user.meta,
 | 
			
		||||
        meta=user.meta or {},
 | 
			
		||||
        creator_id=creator_id,
 | 
			
		||||
        created_at=datetime.now(timezone.utc),
 | 
			
		||||
        status=user.status.value,
 | 
			
		||||
@@ -121,6 +122,7 @@ async def create_user(connection: AsyncConnection, user: UserUpdate, creator_id:
 | 
			
		||||
    res = await connection.execute(query)
 | 
			
		||||
 | 
			
		||||
    await connection.commit()
 | 
			
		||||
    user = await get_user_by_id(connection, res.lastrowid)
 | 
			
		||||
    new_user = await get_user_by_id(connection, res.lastrowid)
 | 
			
		||||
    await create_password_key(connection, user.password, new_user.id)
 | 
			
		||||
 | 
			
		||||
    return user
 | 
			
		||||
    return new_user
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,14 @@ from api.db.tables.account import account_table, account_keyring_table, KeyType,
 | 
			
		||||
 | 
			
		||||
from api.schemas.account.account import User
 | 
			
		||||
from api.schemas.account.account_keyring import AccountKeyring
 | 
			
		||||
from api.schemas.endpoints.account import AllUser
 | 
			
		||||
 | 
			
		||||
from api.utils.key_id_gen import KeyIdGenerator
 | 
			
		||||
 | 
			
		||||
from datetime import datetime, timezone
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
 | 
			
		||||
async def get_user(connection: AsyncConnection, login: str) -> tuple[Optional[AllUser], Optional[AccountKeyring]]:
 | 
			
		||||
    query = (
 | 
			
		||||
        select(account_table, account_keyring_table)
 | 
			
		||||
        .join(account_keyring_table, account_table.c.id == account_keyring_table.c.owner_id)
 | 
			
		||||
@@ -45,7 +46,7 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
 | 
			
		||||
        for column in account_keyring_table.columns
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    user = User.model_validate(user_data)
 | 
			
		||||
    user = AllUser.model_validate(user_data)
 | 
			
		||||
    password = AccountKeyring.model_validate(password_data)
 | 
			
		||||
    return user, password
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from datetime import datetime, timezone
 | 
			
		||||
from datetime import datetime, timedelta, timezone
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from sqlalchemy import insert, select
 | 
			
		||||
from sqlalchemy.ext.asyncio import AsyncConnection
 | 
			
		||||
 | 
			
		||||
from api.db.tables.account import account_keyring_table
 | 
			
		||||
 | 
			
		||||
from api.db.tables.account import account_keyring_table, KeyStatus, KeyType
 | 
			
		||||
from api.schemas.account.account_keyring import AccountKeyring
 | 
			
		||||
from api.utils.hasher import hasher
 | 
			
		||||
from api.utils.key_id_gen import KeyIdGenerator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_key_by_id(connection: AsyncConnection, key_id: str) -> Optional[AccountKeyring]:
 | 
			
		||||
@@ -67,3 +68,19 @@ async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id: i
 | 
			
		||||
    await connection.commit()
 | 
			
		||||
 | 
			
		||||
    return key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def create_password_key(connection: AsyncConnection, password: str | None, owner_id: int):
 | 
			
		||||
    if password is None:
 | 
			
		||||
        password = hasher.generate_password()
 | 
			
		||||
    stmt = insert(account_keyring_table).values(
 | 
			
		||||
        owner_id=owner_id,
 | 
			
		||||
        key_type=KeyType.PASSWORD.value,
 | 
			
		||||
        key_id=KeyIdGenerator(),
 | 
			
		||||
        key_value=hasher.hash_data(password),
 | 
			
		||||
        created_at=datetime.now(timezone.utc),
 | 
			
		||||
        expiry=datetime.now(timezone.utc) + timedelta(days=365),
 | 
			
		||||
        status=KeyStatus.ACTIVE.value,
 | 
			
		||||
    )
 | 
			
		||||
    await connection.execute(stmt)
 | 
			
		||||
    await connection.commit()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ from api.db.logic.account import (
 | 
			
		||||
from api.db.tables.account import AccountStatus
 | 
			
		||||
from api.schemas.account.account import User
 | 
			
		||||
from api.schemas.base import bearer_schema
 | 
			
		||||
from api.schemas.endpoints.account import AllUserResponse, UserUpdate
 | 
			
		||||
from api.schemas.endpoints.account import AllUser, AllUserResponse, UserCreate, UserUpdate
 | 
			
		||||
from api.services.auth import get_current_user
 | 
			
		||||
from api.services.update_data_validation import update_user_data_changes
 | 
			
		||||
from api.services.user_role_validation import db_user_role_validation
 | 
			
		||||
@@ -61,9 +61,9 @@ async def get_account(
 | 
			
		||||
    return user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=UserUpdate)
 | 
			
		||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=AllUser)
 | 
			
		||||
async def create_account(
 | 
			
		||||
    user: UserUpdate,
 | 
			
		||||
    user: UserCreate,
 | 
			
		||||
    connection: AsyncConnection = Depends(get_connection_dep),
 | 
			
		||||
    current_user=Depends(get_current_user),
 | 
			
		||||
):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
from typing import Optional, List
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from pydantic import EmailStr, Field, TypeAdapter
 | 
			
		||||
 | 
			
		||||
from api.db.tables.account import AccountRole, AccountStatus
 | 
			
		||||
 | 
			
		||||
from api.schemas.base import Base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -20,6 +20,17 @@ class UserUpdate(Base):
 | 
			
		||||
    status: Optional[AccountStatus] = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserCreate(Base):
 | 
			
		||||
    name: Optional[str] = Field(None, max_length=100)
 | 
			
		||||
    login: Optional[str] = Field(None, max_length=100)
 | 
			
		||||
    email: Optional[EmailStr] = None
 | 
			
		||||
    password: Optional[str] = None
 | 
			
		||||
    bind_tenant_id: Optional[str] = Field(None, max_length=40)
 | 
			
		||||
    role: Optional[AccountRole] = None
 | 
			
		||||
    meta: Optional[dict] = None
 | 
			
		||||
    status: Optional[AccountStatus] = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AllUser(Base):
 | 
			
		||||
    id: int
 | 
			
		||||
    name: str
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,25 @@
 | 
			
		||||
from fastapi import Request, HTTPException
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from fastapi import HTTPException, Request
 | 
			
		||||
from sqlalchemy.ext.asyncio import AsyncConnection
 | 
			
		||||
 | 
			
		||||
from api.db.logic.auth import get_user
 | 
			
		||||
 | 
			
		||||
# # from backend.schemas.users.token import TokenData
 | 
			
		||||
from api.schemas.account.account import User
 | 
			
		||||
from api.db.tables.account import AccountStatus
 | 
			
		||||
 | 
			
		||||
from api.utils.hasher import Hasher
 | 
			
		||||
from api.schemas.endpoints.account import AllUser
 | 
			
		||||
from api.utils.hasher import hasher
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_current_user(request: Request) -> Optional[User]:
 | 
			
		||||
async def get_current_user(request: Request) -> str | HTTPException:
 | 
			
		||||
    if not hasattr(request.state, "current_user"):
 | 
			
		||||
        return HTTPException(status_code=401, detail="Unauthorized")
 | 
			
		||||
    return request.state.current_user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def authenticate_user(connection: AsyncConnection, username: str, password: str) -> Optional[User]:
 | 
			
		||||
async def authenticate_user(connection: AsyncConnection, username: str, password: str) -> Optional[AllUser]:
 | 
			
		||||
    sql_user, sql_password = await get_user(connection, username)
 | 
			
		||||
 | 
			
		||||
    if not sql_user or sql_user.status != AccountStatus.ACTIVE:
 | 
			
		||||
        return None
 | 
			
		||||
    hasher = Hasher()
 | 
			
		||||
    if not hasher.verify_data(password, sql_password.key_value):
 | 
			
		||||
        return None
 | 
			
		||||
    return sql_user
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import hashlib
 | 
			
		||||
import secrets
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Хешер для работы с паролем.
 | 
			
		||||
 | 
			
		||||
@@ -14,3 +16,10 @@ class Hasher:
 | 
			
		||||
    def verify_data(self, password: str, hashed: str) -> bool:
 | 
			
		||||
        # Проверяет пароль путем сравнения его хеша с сохраненным хешем.
 | 
			
		||||
        return self.hash_data(password) == hashed
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def generate_password() -> str:
 | 
			
		||||
        return secrets.token_urlsafe(20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
hasher = Hasher()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +1,23 @@
 | 
			
		||||
import os
 | 
			
		||||
import asyncio
 | 
			
		||||
import hashlib
 | 
			
		||||
import secrets
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from api.db.connection.session import get_connection
 | 
			
		||||
from api.db.tables.account import account_table, account_keyring_table, AccountRole, KeyType, KeyStatus
 | 
			
		||||
from api.db.tables.account import account_keyring_table, account_table, AccountRole, KeyStatus, KeyType
 | 
			
		||||
from api.utils.hasher import hasher
 | 
			
		||||
from api.utils.key_id_gen import KeyIdGenerator
 | 
			
		||||
 | 
			
		||||
INIT_LOCK_FILE = "../init.lock"
 | 
			
		||||
DEFAULT_LOGIN = "vorkout"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def hash_password(password: str) -> str:
 | 
			
		||||
    return hashlib.sha256(password.encode()).hexdigest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_password() -> str:
 | 
			
		||||
    return secrets.token_urlsafe(20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def init():
 | 
			
		||||
    if os.path.exists(INIT_LOCK_FILE):
 | 
			
		||||
        print("Sorry, service is already initialized")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    async with get_connection() as conn:
 | 
			
		||||
        password = generate_password()
 | 
			
		||||
        hashed_password = hash_password(password)
 | 
			
		||||
        password = hasher.generate_password()
 | 
			
		||||
        hashed_password = hasher.hash_data(password)
 | 
			
		||||
 | 
			
		||||
        create_user_query = account_table.insert().values(
 | 
			
		||||
            name=DEFAULT_LOGIN,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
// import { Auth, Tokens } from '../types/auth';
 | 
			
		||||
import axiosRetry from 'axios-retry';
 | 
			
		||||
import { Auth, Tokens } from '@/types/auth';
 | 
			
		||||
import { useAuthStore } from '@/store/authStore';
 | 
			
		||||
import { AuthService } from '@/services/authService';
 | 
			
		||||
import { User, UserUpdate } from '@/types/user';
 | 
			
		||||
import { User, UserCreate } from '@/types/user';
 | 
			
		||||
 | 
			
		||||
const baseURL = `${import.meta.env.VITE_APP_HTTP_PROTOCOL}://${
 | 
			
		||||
  import.meta.env.VITE_APP_API_URL
 | 
			
		||||
@@ -115,10 +114,13 @@ const api = {
 | 
			
		||||
    return response.data;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async createUser(user: UserUpdate): Promise<User> {
 | 
			
		||||
  async createUser(user: UserCreate): Promise<User> {
 | 
			
		||||
    const response = await base.post<User>('/account', user);
 | 
			
		||||
    return response.data;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // keyrings
 | 
			
		||||
  async setPassword(userId: number, password: string): Promise<any> {},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default api;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import {
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useUserSelector } from '@/store/userStore';
 | 
			
		||||
import { UserUpdate } from '@/types/user';
 | 
			
		||||
import { UserCreate as NewUserCreate } from '@/types/user';
 | 
			
		||||
import { UserService } from '@/services/userService';
 | 
			
		||||
import { LoadingOutlined } from '@ant-design/icons';
 | 
			
		||||
 | 
			
		||||
@@ -55,9 +55,10 @@ export default function UserCreate({ closeDrawer }: UserCreateProps) {
 | 
			
		||||
  const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
 | 
			
		||||
    setFileList(newFileList);
 | 
			
		||||
 | 
			
		||||
  const onFinish = async (values: UserUpdate) => {
 | 
			
		||||
  const onFinish = async (values: NewUserCreate) => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    await UserService.createUser(values);
 | 
			
		||||
 | 
			
		||||
    closeDrawer();
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
    message.info(t('createdAccountMessage'), 4);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import api from '@/api/api';
 | 
			
		||||
import { AllUserResponse, User, UserUpdate } from '@/types/user';
 | 
			
		||||
import { AllUserResponse, User, UserCreate } from '@/types/user';
 | 
			
		||||
 | 
			
		||||
export class UserService {
 | 
			
		||||
  static async getProfile(): Promise<User> {
 | 
			
		||||
@@ -24,7 +24,7 @@ export class UserService {
 | 
			
		||||
    return user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async createUser(user: UserUpdate): Promise<User> {
 | 
			
		||||
  static async createUser(user: UserCreate): Promise<User> {
 | 
			
		||||
    console.log('createUser');
 | 
			
		||||
    const createdUser = api.createUser(user);
 | 
			
		||||
    return createdUser;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user