diff --git a/api/api/__main__.py b/api/api/__main__.py index 6e57ec2..f00b342 100644 --- a/api/api/__main__.py +++ b/api/api/__main__.py @@ -73,6 +73,7 @@ if __name__ == "__main__": log_level="info", ) +app.add_middleware(MiddlewareAccessTokenValidadtion) app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -80,5 +81,3 @@ app.add_middleware( allow_methods=["GET", "POST", "OPTIONS", "DELETE", "PUT"], allow_headers=["*"], ) - -app.add_middleware(MiddlewareAccessTokenValidadtion) diff --git a/api/api/db/logic/auth.py b/api/api/db/logic/auth.py index 393598d..20e9573 100644 --- a/api/api/db/logic/auth.py +++ b/api/api/db/logic/auth.py @@ -50,13 +50,12 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]: return user, password -async def upgrade_old_refresh_token(connection: AsyncConnection, user, refresh_token) -> Optional[User]: +async def upgrade_old_refresh_token(connection: AsyncConnection, refresh_token) -> Optional[User]: new_status = KeyStatus.EXPIRED update_query = ( update(account_keyring_table) .where( - account_table.c.id == user.id, account_keyring_table.c.status == KeyStatus.ACTIVE, account_keyring_table.c.key_type == KeyType.REFRESH_TOKEN, account_keyring_table.c.key_value == refresh_token, diff --git a/api/api/endpoints/auth.py b/api/api/endpoints/auth.py index bd0cfe8..bed6f3e 100644 --- a/api/api/endpoints/auth.py +++ b/api/api/endpoints/auth.py @@ -4,9 +4,9 @@ from fastapi import ( APIRouter, Depends, HTTPException, - Request, Response, status, + Request, ) from loguru import logger @@ -22,7 +22,7 @@ from api.services.auth import authenticate_user from api.db.logic.auth import add_new_refresh_token, upgrade_old_refresh_token -from api.schemas.endpoints.auth import Auth, Access +from api.schemas.endpoints.auth import Auth, Tokens api_router = APIRouter( prefix="/auth", @@ -33,7 +33,7 @@ api_router = APIRouter( class Settings(BaseModel): authjwt_secret_key: str = get_settings().SECRET_KEY # Configure application to store and get JWT from cookies - authjwt_token_location: set = {"headers", "cookies"} + authjwt_token_location: set = {"headers"} authjwt_cookie_domain: str = get_settings().DOMAIN # Only allow JWT cookies to be sent over https @@ -48,7 +48,7 @@ def get_config(): return Settings() -@api_router.post("", response_model=Access) +@api_router.post("", response_model=Tokens) async def login_for_access_token( user: Auth, response: Response, @@ -69,7 +69,6 @@ async def login_for_access_token( ) access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES) - refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS) logger.debug(f"refresh_token_expires {refresh_token_expires}") @@ -81,35 +80,27 @@ async def login_for_access_token( await add_new_refresh_token(connection, refresh_token, refresh_token_expires_time, user) - Authorize.set_refresh_cookies(refresh_token) - - return Access(access_token=access_token) + return Tokens(access_token=access_token, refresh_token=refresh_token) -@api_router.post("/refresh", response_model=Access) +@api_router.post("/refresh", response_model=Tokens) async def refresh( - request: Request, connection: AsyncConnection = Depends(get_connection_dep), Authorize: AuthJWT = Depends() -): - refresh_token = request.cookies.get("refresh_token_cookie") - # print("Refresh Token:", refresh_token) - - if not refresh_token: - raise HTTPException(status_code=401, detail="Refresh token is missing") - + request: Request, + connection: AsyncConnection = Depends(get_connection_dep), + Authorize: AuthJWT = Depends(), +) -> Tokens: try: Authorize.jwt_refresh_token_required() current_user = Authorize.get_jwt_subject() - - except Exception as e: - await upgrade_old_refresh_token(connection, current_user, refresh_token) - + except Exception: + refresh_token = request.headers.get("Authorization").split(" ")[1] + await upgrade_old_refresh_token(connection, refresh_token) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token", ) access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES) - new_access_token = Authorize.create_access_token(subject=current_user, expires_time=access_token_expires) - return Access(access_token=new_access_token) + return Tokens(access_token=new_access_token) diff --git a/api/api/schemas/endpoints/auth.py b/api/api/schemas/endpoints/auth.py index 7c8659c..c9ccd53 100644 --- a/api/api/schemas/endpoints/auth.py +++ b/api/api/schemas/endpoints/auth.py @@ -8,9 +8,6 @@ class Auth(Base): password: str -class Refresh(Base): - refresh_token: str - - -class Access(Base): +class Tokens(Base): access_token: str + refresh_token: str | None = None diff --git a/api/api/services/middleware.py b/api/api/services/middleware.py index a44070d..68e511c 100644 --- a/api/api/services/middleware.py +++ b/api/api/services/middleware.py @@ -1,3 +1,4 @@ +from fastapi_jwt_auth import AuthJWT from starlette.middleware.base import BaseHTTPMiddleware from fastapi import ( Request, @@ -11,9 +12,6 @@ import re from re import escape -from fastapi_jwt_auth import AuthJWT - - class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware): def __init__(self, app): super().__init__(app) @@ -22,40 +20,34 @@ class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware): self.excluded_routes = [ re.compile(r"^" + re.escape(self.prefix) + r"/auth/refresh/?$"), re.compile(r"^" + re.escape(self.prefix) + r"/auth/?$"), + re.compile(r"^" + r"/swagger"), + re.compile(r"^" + r"/openapi"), ] async def dispatch(self, request: Request, call_next): - if request.method in ["GET", "POST", "PUT", "DELETE"]: - if any(pattern.match(request.url.path) for pattern in self.excluded_routes): - return await call_next(request) - else: - auth_header = request.headers.get("Authorization") - if not auth_header: - return JSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - content={"detail": "Missing authorization header."}, - headers={"WWW-Authenticate": "Bearer"}, - ) - - token = auth_header.split(" ")[1] - Authorize = AuthJWT(request) - - try: - current_user = Authorize.get_jwt_subject() - request.state.current_user = current_user - return await call_next(request) - - except Exception: - return JSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - content={"detail": "The access token is invalid or expired."}, - headers={"WWW-Authenticate": "Bearer"}, - ) - - # async with get_connection() as connection: - # authorize_user = await get_user_login(connection, current_user) - # print(authorize_user) - # if authorize_user is None : - # return JSONResponse( - # status_code=status.HTTP_404_NOT_FOUND , - # detail="User not found.") + if request.method not in ["GET", "POST", "PUT", "DELETE"]: + return JSONResponse( + status_code=status.HTTP_405_METHOD_NOT_ALLOWED, + content={"detail": "Method not allowed"}, + ) + if any(pattern.match(request.url.path) for pattern in self.excluded_routes): + return await call_next(request) + auth_header = request.headers.get("Authorization") + if not auth_header: + return JSONResponse( + status_code=status.HTTP_401_UNAUTHORIZED, + content={"detail": "Missing authorization header."}, + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + token = auth_header.split(" ")[1] + Authorize = AuthJWT(request) + current_user = Authorize.get_jwt_subject() + request.state.current_user = current_user + except Exception: + return JSONResponse( + status_code=status.HTTP_401_UNAUTHORIZED, + content={"detail": "The access token is invalid or expired."}, + headers={"WWW-Authenticate": "Bearer"}, + ) + return await call_next(request) diff --git a/client/package-lock.json b/client/package-lock.json index 838d78d..4f0bc7c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "client", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "client", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@ant-design/icons": "^5.6.1", "@testing-library/dom": "^10.4.0", @@ -18,6 +18,8 @@ "@types/react": "^19.0.11", "@types/react-dom": "^19.0.4", "antd": "^5.24.7", + "axios": "^1.9.0", + "axios-retry": "^4.5.0", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "react": "^18.3.1", @@ -26,7 +28,8 @@ "react-router-dom": "^7.5.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.5" } }, "node_modules/@adobe/css-tools": { @@ -5262,6 +5265,45 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -10063,6 +10105,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-root": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", @@ -14085,6 +14139,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -18743,6 +18803,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz", + "integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/client/package.json b/client/package.json index c991ffb..cc6e3a8 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,8 @@ "@types/react": "^19.0.11", "@types/react-dom": "^19.0.4", "antd": "^5.24.7", + "axios": "^1.9.0", + "axios-retry": "^4.5.0", "i18next": "^25.0.1", "i18next-browser-languagedetector": "^8.0.5", "react": "^18.3.1", @@ -21,7 +23,8 @@ "react-router-dom": "^7.5.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.5" }, "scripts": { "start": "react-scripts start", diff --git a/client/public/icons/logo.svg b/client/public/icons/logo.svg new file mode 100644 index 0000000..882c169 --- /dev/null +++ b/client/public/icons/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/App.tsx b/client/src/App.tsx index 7fc96c3..97e095a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,13 +1,25 @@ -import React from 'react'; +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useEffect } from 'react'; import { Route, Routes } from 'react-router-dom'; import MainLayout from './pages/MainLayout'; import ProtectedRoute from './pages/ProtectedRoute'; +import LoginPage from './pages/LoginPage'; +import { useSetUserSelector } from './store/userStore'; function App() { + const setUser = useSetUserSelector(); + + useEffect(() => { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + setUser(JSON.parse(storedUser)); + } + }, []); + return (
- login
} /> + } /> }> }> diff --git a/client/src/api/api.ts b/client/src/api/api.ts new file mode 100644 index 0000000..3b7226a --- /dev/null +++ b/client/src/api/api.ts @@ -0,0 +1,108 @@ +import axios from 'axios'; +import { Auth, Tokens } from '../types/auth'; +import { User } from '../types/user'; +import { AuthService } from '../services/authService'; +import axiosRetry from 'axios-retry'; +import { useAuthStore } from '../store/authStore'; + +const baseURL = `${process.env.REACT_APP_HTTP_PROTOCOL}://${process.env.REACT_APP_API_URL}/api/v1`; + +const base = axios.create({ + baseURL, + withCredentials: true, + headers: { + accepts: 'application/json', + }, +}); + +base.interceptors.request.use((config) => { + if (config.url === '/auth/refresh') { + return config; + } + const token = useAuthStore.getState().accessToken; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +axiosRetry(base, { + retries: 3, + retryDelay: (retryCount: number) => { + console.log(`retry attempt: ${retryCount}`); + return retryCount * 2000; + }, + retryCondition: async (error: any) => { + if (error.code === 'ERR_CANCELED') { + return true; + } + return false; + }, +}); + +base.interceptors.response.use( + (response) => { + return response; + }, + async function (error) { + console.log('error', error); + const originalRequest = error.response.config; + const urlTokens = error?.request?.responseURL.split('/'); + const url = urlTokens[urlTokens.length - 1]; + console.log('url', url); + if ( + error.response.status === 401 && + !(originalRequest?._retry != null) && + url !== 'login' && + url !== 'refresh' && + url !== 'logout' + ) { + originalRequest._retry = true; + try { + await AuthService.refresh(); + return base(originalRequest); + } catch (error) { + await AuthService.logout(); + return new Promise(() => {}); + } + } + return await Promise.reject(error); + } +); + +const api = { + // auth + async login(auth: Auth): Promise { + const response = await base.post('/auth', auth); + return response.data; + }, + + async refreshToken(): Promise { + const token = localStorage.getItem('refreshToken'); + const response = await base.post( + '/auth/refresh', + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + return response.data; + }, + + // user + async getProfile(): Promise { + const response = await base.get('/profile'); + return response.data; + }, + + async getUsers(page: number, limit: number): Promise { + const response = await base.get( + `/account?page=${page}&limit=${limit}` + ); + return response.data; + }, +}; + +export default api; diff --git a/client/src/pages/AccountsPage.tsx b/client/src/pages/AccountsPage.tsx index 4387052..e66b21d 100644 --- a/client/src/pages/AccountsPage.tsx +++ b/client/src/pages/AccountsPage.tsx @@ -3,6 +3,10 @@ import { useState } from 'react'; import ContentDrawer from '../components/ContentDrawer'; import UserCreate from '../components/UserCreate'; import { useTranslation } from 'react-i18next'; +import { Button } from 'antd'; +import { UserService } from '../services/userService'; +import { User } from '../types/user'; +import { AuthService } from '../services/authService'; export default function AccountsPage() { const { t } = useTranslation(); @@ -11,6 +15,8 @@ export default function AccountsPage() { const showDrawer = () => setOpen(true); const closeDrawer = () => setOpen(false); + const [accounts, setAccounts] = useState([]); + return ( <>
{ + await AuthService.login(values as Auth); + navigate('/'); + }; + + return ( +
+
+
+ logo +
+ +
+ + } /> + + + + + visible ? : + } + /> + + + + + + + + Нажимая кнопку Войти, Вы полностью принимаете{' '} + + Публичную оферту + {' '} + и{' '} + + Политику обработки персональных данных + + +
+ +
+ Забыли пароль? +
+
+
+ ); +} diff --git a/client/src/pages/ProtectedRoute.tsx b/client/src/pages/ProtectedRoute.tsx index 43fc045..537cfca 100644 --- a/client/src/pages/ProtectedRoute.tsx +++ b/client/src/pages/ProtectedRoute.tsx @@ -1,8 +1,19 @@ +/* eslint-disable react-hooks/exhaustive-deps */ // ProtectedRoute.js -import { Outlet } from 'react-router-dom'; -import React from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import React, { useEffect } from 'react'; +import { useUserSelector } from '../store/userStore'; const ProtectedRoute = (): React.JSX.Element => { + const navigate = useNavigate(); + const user = useUserSelector(); + + useEffect(() => { + if (!user?.id) { + navigate('/login'); + } + }, [user]); + return ; }; export default ProtectedRoute; diff --git a/client/src/services/authService.ts b/client/src/services/authService.ts new file mode 100644 index 0000000..e70e4cc --- /dev/null +++ b/client/src/services/authService.ts @@ -0,0 +1,30 @@ +import api from '../api/api'; +import { useAuthStore } from '../store/authStore'; +import { useUserStore } from '../store/userStore'; +import { Auth } from '../types/auth'; +import { UserService } from './userService'; + +export class AuthService { + static async login(auth: Auth) { + const token = await api.login(auth); + useAuthStore.getState().setAccessToken(token.accessToken); + localStorage.setItem('refreshToken', token.refreshToken as string); + await UserService.getProfile().then((user) => { + useUserStore.getState().setUser(user); + }); + } + + static async logout() { + console.log('logout'); + useUserStore.getState().setUser(null); + useAuthStore.getState().setAccessToken(null); + localStorage.removeItem('userInfo'); + localStorage.removeItem('refreshToken'); + } + + static async refresh() { + console.log('refresh'); + const token = await api.refreshToken(); + useAuthStore.getState().setAccessToken(token.accessToken); + } +} diff --git a/client/src/services/userService.ts b/client/src/services/userService.ts new file mode 100644 index 0000000..16e6bcf --- /dev/null +++ b/client/src/services/userService.ts @@ -0,0 +1,16 @@ +import api from '../api/api'; +import { User } from '../types/user'; + +export class UserService { + static async getProfile(): Promise { + console.log('getProfile'); + const user = api.getProfile(); + + return user; + } + + static async getUsers(page: number = 1, limit: number = 10): Promise { + const users = api.getUsers(page, limit); + return users; + } +} diff --git a/client/src/store/authStore.ts b/client/src/store/authStore.ts new file mode 100644 index 0000000..4d586c2 --- /dev/null +++ b/client/src/store/authStore.ts @@ -0,0 +1,18 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +type AuthState = { + accessToken: string | null; + setAccessToken: (token: string | null) => void; +}; + +export const useAuthStore = create()( + devtools((set) => ({ + accessToken: null, + setAccessToken: (token) => set({ accessToken: token }), + })) +); + +export const useAuthSelector = () => { + return useAuthStore((state) => state.accessToken); +}; diff --git a/client/src/store/userStore.ts b/client/src/store/userStore.ts new file mode 100644 index 0000000..667d4aa --- /dev/null +++ b/client/src/store/userStore.ts @@ -0,0 +1,36 @@ +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; +import { User } from '../types/user'; + +const userInfo = localStorage.getItem('userInfo'); + +type UserStoreState = { + user: User | null; + loading: boolean; +}; + +type UserStoreActions = { + setUser: (user: User | null) => void; +}; + +type UserStore = UserStoreState & UserStoreActions; + +export const useUserStore = create()( + devtools( + persist( + (set, get) => ({ + user: userInfo != null ? JSON.parse(userInfo) : ({} as User), + loading: false, + setUser: (user: User | null) => set({ user }), + }), + { name: 'userInfo' } + ) + ) +); + +export const useUserSelector = () => { + return useUserStore((state) => state.user); +}; +export const useSetUserSelector = () => { + return useUserStore((state) => state.setUser); +}; diff --git a/client/src/types/auth.ts b/client/src/types/auth.ts new file mode 100644 index 0000000..8e0f549 --- /dev/null +++ b/client/src/types/auth.ts @@ -0,0 +1,4 @@ +import { components } from './openapi-types'; + +export type Auth = components['schemas']['Auth']; +export type Tokens = components['schemas']['Tokens']; diff --git a/client/src/types/openapi-types.ts b/client/src/types/openapi-types.ts new file mode 100644 index 0000000..b23e43f --- /dev/null +++ b/client/src/types/openapi-types.ts @@ -0,0 +1,694 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/v1/auth": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Login For Access Token + * @description Авторизирует, выставляет токены в куки. + */ + post: operations["login_for_access_token_api_v1_auth_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Refresh */ + post: operations["refresh_api_v1_auth_refresh_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Profile */ + get: operations["get_profile_api_v1_profile_get"]; + /** Update Profile */ + put: operations["update_profile_api_v1_profile_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/account": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get All Account */ + get: operations["get_all_account_api_v1_account_get"]; + put?: never; + /** Create Account */ + post: operations["create_account_api_v1_account_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/account/{user_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Account */ + get: operations["get_account_api_v1_account__user_id__get"]; + /** Update Account */ + put: operations["update_account_api_v1_account__user_id__put"]; + post?: never; + /** Delete Account */ + delete: operations["delete_account_api_v1_account__user_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/keyring/{user_id}/{key_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Keyring */ + get: operations["get_keyring_api_v1_keyring__user_id___key_id__get"]; + /** Update Keyring */ + put: operations["update_keyring_api_v1_keyring__user_id___key_id__put"]; + /** Create Keyring */ + post: operations["create_keyring_api_v1_keyring__user_id___key_id__post"]; + /** Delete Keyring */ + delete: operations["delete_keyring_api_v1_keyring__user_id___key_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** AccountKeyring */ + AccountKeyring: { + /** Ownerid */ + ownerId: number; + keyType: components["schemas"]["KeyType"]; + /** Keyid */ + keyId?: string | null; + /** Keyvalue */ + keyValue: string; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + /** Expiry */ + expiry?: string | null; + status: components["schemas"]["KeyStatus"]; + }; + /** AccountKeyringUpdate */ + AccountKeyringUpdate: { + /** Ownerid */ + ownerId?: number | null; + keyType?: components["schemas"]["KeyType"] | null; + /** Keyid */ + keyId?: string | null; + /** Keyvalue */ + keyValue?: string | null; + /** Createdat */ + createdAt?: string | null; + /** Expiry */ + expiry?: string | null; + status?: components["schemas"]["KeyStatus"] | null; + }; + /** + * AccountRole + * @enum {string} + */ + AccountRole: "OWNER" | "ADMIN" | "EDITOR" | "VIEWER"; + /** + * AccountStatus + * @enum {string} + */ + AccountStatus: "ACTIVE" | "DISABLED" | "BLOCKED" | "DELETED"; + /** AllUser */ + AllUser: { + /** Id */ + id: number; + /** Name */ + name: string; + /** Login */ + login: string; + /** Email */ + email?: string | null; + /** Bindtenantid */ + bindTenantId?: string | null; + role: components["schemas"]["AccountRole"]; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + status: components["schemas"]["AccountStatus"]; + }; + /** AllUserResponse */ + AllUserResponse: { + /** Users */ + users: components["schemas"]["AllUser"][]; + /** Amountcount */ + amountCount: number; + /** Amountpages */ + amountPages: number; + }; + /** Auth */ + Auth: { + /** Login */ + login: string; + /** Password */ + password: string; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * KeyStatus + * @enum {string} + */ + KeyStatus: "ACTIVE" | "EXPIRED" | "DELETED"; + /** + * KeyType + * @enum {string} + */ + KeyType: "PASSWORD" | "ACCESS_TOKEN" | "REFRESH_TOKEN" | "API_KEY"; + /** Tokens */ + Tokens: { + /** Accesstoken */ + accessToken: string; + /** Refreshtoken */ + refreshToken?: string | null; + }; + /** User */ + User: { + /** Id */ + id?: number | null; + /** Name */ + name: string; + /** Login */ + login: string; + /** Email */ + email?: string | null; + /** Bindtenantid */ + bindTenantId?: string | null; + role: components["schemas"]["AccountRole"]; + /** Meta */ + meta: { + [key: string]: unknown; + }; + /** Creatorid */ + creatorId?: number | null; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + status: components["schemas"]["AccountStatus"]; + }; + /** UserUpdate */ + UserUpdate: { + /** Id */ + id?: number | null; + /** Name */ + name?: string | null; + /** Login */ + login?: string | null; + /** Email */ + email?: string | null; + /** Bindtenantid */ + bindTenantId?: string | null; + role?: components["schemas"]["AccountRole"] | null; + /** Meta */ + meta?: { + [key: string]: unknown; + } | null; + /** Creatorid */ + creatorId?: number | null; + /** Createdat */ + createdAt?: string | null; + status?: components["schemas"]["AccountStatus"] | null; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + login_for_access_token_api_v1_auth_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Auth"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Tokens"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + refresh_api_v1_auth_refresh_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Tokens"]; + }; + }; + }; + }; + get_profile_api_v1_profile_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + }; + }; + update_profile_api_v1_profile_put: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_all_account_api_v1_account_get: { + parameters: { + query?: { + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AllUserResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_account_api_v1_account_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_account_api_v1_account__user_id__get: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_account_api_v1_account__user_id__put: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_account_api_v1_account__user_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["User"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_keyring_api_v1_keyring__user_id___key_id__get: { + parameters: { + query?: never; + header?: never; + path: { + key_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountKeyring"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_keyring_api_v1_keyring__user_id___key_id__put: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + key_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AccountKeyringUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountKeyring"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_keyring_api_v1_keyring__user_id___key_id__post: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + key_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AccountKeyringUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountKeyring"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_keyring_api_v1_keyring__user_id___key_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + key_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountKeyring"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; +} diff --git a/client/src/types/user.ts b/client/src/types/user.ts new file mode 100644 index 0000000..9134b70 --- /dev/null +++ b/client/src/types/user.ts @@ -0,0 +1,3 @@ +import { components } from "./openapi-types" + +export type User = components["schemas"]["User"];