(FEAT): Working on auth still
This commit is contained in:
parent
1749a91bf9
commit
3177a4d089
@ -38,3 +38,10 @@ func (s *Server) GoogleCallbackHandlerV2(ctx *gin.Context) {
|
|||||||
ctx.Redirect(http.StatusSeeOther, url)
|
ctx.Redirect(http.StatusSeeOther, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BUG: This is not working, not yet sure why
|
||||||
|
func (s *Server) LogoutHandlerV2(ctx *gin.Context) {
|
||||||
|
s.SetCookie(ctx, "jwt_token", "", -1)
|
||||||
|
// s.SetCookie(ctx, "search-filters", "", -1) // TODO: This was copied, might function differently now
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
@ -20,8 +20,8 @@ func (s *Server) SetCookie(ctx *gin.Context, name, value string, duration time.D
|
|||||||
path string = "/"
|
path string = "/"
|
||||||
httpOnly bool = false // NOTE: Should use false so React can see it!
|
httpOnly bool = false // NOTE: Should use false so React can see it!
|
||||||
maxAge int
|
maxAge int
|
||||||
secure bool
|
secure bool = false
|
||||||
domain string
|
domain string = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
if duration < 0 {
|
if duration < 0 {
|
||||||
@ -43,11 +43,6 @@ func (s *Server) SetCookie(ctx *gin.Context, name, value string, duration time.D
|
|||||||
secure = false
|
secure = false
|
||||||
// domain = s.deps.EnvironmentConfig.Domain
|
// domain = s.deps.EnvironmentConfig.Domain
|
||||||
domain = "localhost"
|
domain = "localhost"
|
||||||
|
|
||||||
} else {
|
|
||||||
// Defaults
|
|
||||||
secure = false
|
|
||||||
domain = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
|
ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
|
||||||
|
|||||||
@ -9,6 +9,13 @@ import (
|
|||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// JwtAuthMiddlewareV2 is responsible to protecting routes. Anything that may go wrong
|
||||||
|
// will be returned via JSON with a 'message' field and a 401 error code. When this
|
||||||
|
// middleware is successful, it will set the 'userId' and 'userEmail' fields and pass
|
||||||
|
// to the next function in the chain.
|
||||||
|
//
|
||||||
|
// Functions that are called after this can assume that those values defined are always
|
||||||
|
// set.
|
||||||
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
func JwtAuthMiddlewareV2(jwtSecretKey []byte) gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
tokenString, err := ctx.Cookie("jwt_token")
|
tokenString, err := ctx.Cookie("jwt_token")
|
||||||
|
|||||||
@ -204,6 +204,9 @@ func (s *Server) Setup() *Server {
|
|||||||
router_api_v2.GET("/recipe/of-the-week", s.GetRecipeOfTheWeekHandlerV2)
|
router_api_v2.GET("/recipe/of-the-week", s.GetRecipeOfTheWeekHandlerV2)
|
||||||
router_api_v2.GET("/auth/login", s.GetGoogleAuthUrlHandlerV2)
|
router_api_v2.GET("/auth/login", s.GetGoogleAuthUrlHandlerV2)
|
||||||
router_api_v2.GET("/auth/callback", s.GoogleCallbackHandlerV2)
|
router_api_v2.GET("/auth/callback", s.GoogleCallbackHandlerV2)
|
||||||
|
router_api_v2.GET("/auth/logout", s.LogoutHandlerV2)
|
||||||
|
|
||||||
|
router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2)
|
||||||
|
|
||||||
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
||||||
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
||||||
|
|||||||
25
internal/app/server/user_handler_v2.go
Normal file
25
internal/app/server/user_handler_v2.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) {
|
||||||
|
user := s.deps.UserService.GetAuthenicatedUser(ctx)
|
||||||
|
if user == nil {
|
||||||
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"status": http.StatusUnauthorized,
|
||||||
|
"message": "[UNAUTHORIZED] Could not fetch authenticated user.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{
|
||||||
|
"status": http.StatusOK,
|
||||||
|
"message": "[OK] Successfully retrieved authenticated user.",
|
||||||
|
"user": user,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -1,73 +1,4 @@
|
|||||||
# React + TypeScript + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
# IF BACKEND CANNOT GET COOKIE
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
Do not forget to send the axios request with the `{ withCredentials: true }` flags.
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
|
|
||||||
## React Compiler
|
|
||||||
|
|
||||||
The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress.
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ function ProtectedRoute({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) return children;
|
if (isLoggedIn) return children;
|
||||||
|
|
||||||
// Redirect to login page if not authenicated
|
// Redirect to login page if not authenicated
|
||||||
return <Navigate to="/v2/web/login" replace />
|
return <Navigate to="/v2/web/login" replace />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default function Navigation() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className="block md:fixed w-full z-10">
|
<nav className="block md:fixed w-full z-20">
|
||||||
<div
|
<div
|
||||||
className="relative w-full px-8 md:px-44 p-4 border-b border-gray-300 shadow-sm shadow-gray-300 bg-white flex justify-between items-center"
|
className="relative w-full px-8 md:px-44 p-4 border-b border-gray-300 shadow-sm shadow-gray-300 bg-white flex justify-between items-center"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -3,6 +3,11 @@ import { createContext } from "react";
|
|||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
isLoggedIn: boolean | undefined;
|
isLoggedIn: boolean | undefined;
|
||||||
setIsLoggedIn: (state: boolean) => void;
|
setIsLoggedIn: (state: boolean) => void;
|
||||||
|
getJwt: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContextType>({ isLoggedIn: undefined, setIsLoggedIn: () => { return } });
|
export const AuthContext = createContext<AuthContextType>({
|
||||||
|
isLoggedIn: undefined,
|
||||||
|
setIsLoggedIn: () => { return },
|
||||||
|
getJwt: () => ""
|
||||||
|
});
|
||||||
|
|||||||
@ -4,10 +4,19 @@ import { useCookies } from 'react-cookie';
|
|||||||
|
|
||||||
// BUG: The rerender issue is ridiclious, and needs to be updated. Maybe using a global
|
// BUG: The rerender issue is ridiclious, and needs to be updated. Maybe using a global
|
||||||
// state management tool instead of a context
|
// state management tool instead of a context
|
||||||
|
//
|
||||||
|
// BUG: We do not want to have access to these cookies in the UI, for security reasons.
|
||||||
|
// Instead, we should implement an api `/auth/status` can be requested to validate
|
||||||
|
// a token. Not sure how often this should get called, but it should be implemented.
|
||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [cookies] = useCookies(["jwt_token"]);
|
const [cookies] = useCookies(["jwt_token"]);
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState<boolean | undefined>(cookies.jwt_token !== undefined);
|
const [isLoggedIn, setIsLoggedIn] = useState<boolean | undefined>(cookies.jwt_token !== undefined);
|
||||||
|
|
||||||
|
const getJwt = (): string => {
|
||||||
|
if (!cookies.jwt_token) return "";
|
||||||
|
return cookies.jwt_token as string;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoggedIn(cookies.jwt_token !== undefined);
|
setIsLoggedIn(cookies.jwt_token !== undefined);
|
||||||
}, [cookies]);
|
}, [cookies]);
|
||||||
@ -18,7 +27,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext value={{ isLoggedIn, setIsLoggedIn }}>
|
<AuthContext value={{ isLoggedIn, setIsLoggedIn, getJwt }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext>
|
</AuthContext>
|
||||||
);
|
);
|
||||||
|
|||||||
2
web/src/context/ProtectedRoute.tsx
Normal file
2
web/src/context/ProtectedRoute.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
export function ProtectedRoute
|
||||||
@ -10,7 +10,6 @@ import ContentCardSmall from "../components/cards/ContentCardSmall";
|
|||||||
import RecipeSearchBar from "../components/inputs/RecipeSearchBar";
|
import RecipeSearchBar from "../components/inputs/RecipeSearchBar";
|
||||||
import { GetRecipeOfTheWeek } from "../services/RecipeService";
|
import { GetRecipeOfTheWeek } from "../services/RecipeService";
|
||||||
import { isApiError, type ApiError } from "../types/api/error";
|
import { isApiError, type ApiError } from "../types/api/error";
|
||||||
import axios from "axios";
|
|
||||||
import { AuthContext } from "../context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@ -113,6 +112,8 @@ export default function Home() {
|
|||||||
setViewedRecipes(recipes);
|
setViewedRecipes(recipes);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// TODO: Fetch other items when needed
|
||||||
|
// Fetch the recipe of the week
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
const result: Recipe | ApiError = await GetRecipeOfTheWeek();
|
const result: Recipe | ApiError = await GetRecipeOfTheWeek();
|
||||||
@ -131,13 +132,6 @@ export default function Home() {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
// BUG: This is useless, just for testing
|
|
||||||
useEffect(() => {
|
|
||||||
// NOTE: Be sure to call this WITH CREDENTIALS
|
|
||||||
void axios.get("http://localhost:3000/v2/api/protected", { withCredentials: true });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Intro Section */}
|
{/* Intro Section */}
|
||||||
|
|||||||
@ -1,16 +1,29 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { use, useEffect, useState } from "react";
|
||||||
import type { User } from "../types/user";
|
import type { User } from "../types/user";
|
||||||
import type { Recipe } from "../types/recipe";
|
import type { Recipe } from "../types/recipe";
|
||||||
import RecipeListItem from "../components/results/RecipeListItem";
|
import RecipeListItem from "../components/results/RecipeListItem";
|
||||||
import type { Engagement } from "../types/engagement";
|
import type { Engagement } from "../types/engagement";
|
||||||
import ActivityListItem from "../components/results/ActivityListItem";
|
import ActivityListItem from "../components/results/ActivityListItem";
|
||||||
|
import { AuthContext } from "../context/AuthContext";
|
||||||
|
import { GetAuthenticatedUser } from "../services/UserService";
|
||||||
|
import { isApiError, type ApiError } from "../types/api/error";
|
||||||
|
import { Logout } from "../services/AuthService";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
|
// Context
|
||||||
|
const { getJwt } = use(AuthContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Page state
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [recipes, setRecipes] = useState<Recipe[]>([]);
|
const [recipes, setRecipes] = useState<Recipe[]>([]);
|
||||||
const [favorites, setFavorites] = useState<Recipe[]>([]);
|
const [favorites, setFavorites] = useState<Recipe[]>([]);
|
||||||
const [activity, setActivity] = useState<Engagement[]>([]);
|
const [activity, setActivity] = useState<Engagement[]>([]);
|
||||||
|
const [jwt, setJwt] = useState<string>("");
|
||||||
|
|
||||||
|
// BUG: Remove this, used for testing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const recipe: Recipe = {
|
const recipe: Recipe = {
|
||||||
Id: 1,
|
Id: 1,
|
||||||
@ -99,23 +112,43 @@ export default function Profile() {
|
|||||||
Created: new Date(),
|
Created: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const user: User = {
|
setRecipes([recipe, recipe2]);
|
||||||
Id: 1,
|
|
||||||
GoogleId: "a",
|
|
||||||
Name: "Hayden Hargreaves",
|
|
||||||
Email: "hhargreaves2006@gmail.com",
|
|
||||||
ImageUrl: "https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c",
|
|
||||||
GoogleRefreshToken: "a",
|
|
||||||
Created: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
setUser(user);
|
|
||||||
|
|
||||||
setRecipes([recipe, recipe2, recipe, recipe, recipe, recipe, recipe]);
|
|
||||||
setFavorites([recipe, recipe2]);
|
setFavorites([recipe, recipe2]);
|
||||||
setActivity([eng]);
|
setActivity([eng]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Log the user out and direct to the home page
|
||||||
|
const logoutHandler = (): void => {
|
||||||
|
void Logout();
|
||||||
|
void navigate("/v2/web/home");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the JWT from the cookies
|
||||||
|
useEffect(() => {
|
||||||
|
setJwt(getJwt());
|
||||||
|
}, [getJwt]);
|
||||||
|
|
||||||
|
// Get the user when the JWTS change
|
||||||
|
useEffect(() => {
|
||||||
|
// No jwt, we can't get a user
|
||||||
|
if (!jwt) return;
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const result: User | ApiError = await GetAuthenticatedUser();
|
||||||
|
if (isApiError(result)) {
|
||||||
|
setError(result.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUser(result);
|
||||||
|
}
|
||||||
|
void fetch();
|
||||||
|
}, [jwt]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error)
|
||||||
|
console.log("@error", error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* User Details Section */}
|
{/* User Details Section */}
|
||||||
@ -195,9 +228,9 @@ export default function Profile() {
|
|||||||
|
|
||||||
{/* Logout Section TODO: Click event*/}
|
{/* Logout Section TODO: Click event*/}
|
||||||
<section className="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300 mt-auto">
|
<section className="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300 mt-auto">
|
||||||
<a href="" className="text-center border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300">
|
<button onClick={logoutHandler} className="text-center border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300">
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import type { GetGoogleAuthUrlResponse } from "../types/api/auth";
|
import type { GetGoogleAuthUrlResponse, LogoutResponse } from "../types/api/auth";
|
||||||
import type { ApiError } from "../types/api/error";
|
import type { ApiError } from "../types/api/error";
|
||||||
|
|
||||||
|
|
||||||
@ -16,3 +16,11 @@ export async function GetGoogleAuthUrl (): Promise<string | ApiError> {
|
|||||||
|
|
||||||
return response.data.url;
|
return response.data.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function Logout (): Promise<void> {
|
||||||
|
const response = await axios.get<LogoutResponse>("http://localhost:3000/v2/api/auth/logout");
|
||||||
|
|
||||||
|
// This should never happen
|
||||||
|
if (response.status !== 204)
|
||||||
|
console.error("LOGOUT FAILED");
|
||||||
|
}
|
||||||
|
|||||||
19
web/src/services/UserService.ts
Normal file
19
web/src/services/UserService.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import type { ApiError } from "../types/api/error";
|
||||||
|
import type { User } from "../types/user";
|
||||||
|
import type { GetAuthenticateUserResponse } from "../types/api/user";
|
||||||
|
|
||||||
|
|
||||||
|
export async function GetAuthenticatedUser(): Promise<User | ApiError> {
|
||||||
|
const response = await axios.get<GetAuthenticateUserResponse>("http://localhost:3000/v2/api/user", { withCredentials: true });
|
||||||
|
|
||||||
|
if (response.data.status !== 200 || response.data.user === undefined){
|
||||||
|
const err: ApiError = {
|
||||||
|
status: response.data.status,
|
||||||
|
message: response.data.message
|
||||||
|
};
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data.user;
|
||||||
|
}
|
||||||
@ -4,3 +4,7 @@ export interface GetGoogleAuthUrlResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LogoutResponse {
|
||||||
|
stauts: number;
|
||||||
|
}
|
||||||
|
|||||||
7
web/src/types/api/user.ts
Normal file
7
web/src/types/api/user.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { User } from "../user";
|
||||||
|
|
||||||
|
export interface GetAuthenticateUserResponse {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
user?: User;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user