Api with jwt #1
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { useAuthContext } from './context/AuthContext';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import API from './util/API';
|
||||
|
||||
// pages, ui, components, styles
|
||||
import Subscriptions from './components/pages/Subscriptions/Subscriptions';
|
||||
@@ -22,7 +23,7 @@ import { TokenType } from './util/types';
|
||||
import './sass/App.scss';
|
||||
|
||||
function App() {
|
||||
const { setUser, setToken } = useAuthContext();
|
||||
const { setUser, token, setToken } = useAuthContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (document.cookie) {
|
||||
@@ -30,7 +31,11 @@ function App() {
|
||||
setToken(document.cookie.split("=")[1]);
|
||||
setUser(extractedToken.user);
|
||||
}
|
||||
}, []);
|
||||
}, [document.cookie]);
|
||||
|
||||
useEffect(() => {
|
||||
token && API.Settings.setToken(token);
|
||||
}, [token])
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
|
||||
@@ -10,7 +10,7 @@ const AddRecipe = () => {
|
||||
|
||||
const getFormState = useCallback((data: IRecipe) => {
|
||||
setInput(data);
|
||||
}, [])
|
||||
}, [input])
|
||||
|
||||
const handleCreate = () => {
|
||||
for (let field of Object.keys(input)) {
|
||||
@@ -28,20 +28,6 @@ const AddRecipe = () => {
|
||||
}
|
||||
})
|
||||
}, [authContext])
|
||||
|
||||
useEffect(() => {
|
||||
input.authoruserid && setForm(
|
||||
new Form<IRecipe>({
|
||||
parent: "AddRecipe",
|
||||
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
|
||||
labels: ["Recipe Name:", "Prep Time:", "Course:", "Cuisine:", "Ingredients:", "Description:"],
|
||||
dataTypes: ['text', 'text', 'custom picker', 'custom picker', 'custom picker', 'TINYMCE'],
|
||||
initialState: input,
|
||||
getState: getFormState,
|
||||
richTextInitialValue: "<p>Enter recipe details here!</p>"
|
||||
}).mount()
|
||||
)
|
||||
}, [input.authoruserid])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(input);
|
||||
@@ -53,7 +39,18 @@ const AddRecipe = () => {
|
||||
<Divider />
|
||||
|
||||
<Panel>
|
||||
<Form parent={input} _config={{
|
||||
parent: "AddRecipe",
|
||||
keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
|
||||
labels: ["Recipe Name:", "Prep Time:", "Course:", "Cuisine:", "Ingredients:", "Description:"],
|
||||
dataTypes: ['text', 'text', 'custom picker', 'custom picker', 'custom picker', 'TINYMCE'],
|
||||
initialState: input,
|
||||
getState: getFormState,
|
||||
richTextInitialValue: "<p>Enter recipe details here!</p>"
|
||||
}} />
|
||||
|
||||
{ form || <h2>Loading...</h2> }
|
||||
|
||||
<Button onClick={handleCreate}>Create Recipe!</Button>
|
||||
</Panel>
|
||||
</Page>
|
||||
|
||||
@@ -9,8 +9,7 @@ import API from "../../util/API";
|
||||
export default function Login() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const redirect = params.get("redirect");
|
||||
const { user, setUser } = useContext(AuthContext);
|
||||
const [form, setForm] = useState<JSX.Element>();
|
||||
const { user, setToken } = useContext(AuthContext);
|
||||
|
||||
// setup and local state
|
||||
const navigate = useNavigate();
|
||||
@@ -25,11 +24,12 @@ export default function Login() {
|
||||
const handleLogin = async () => {
|
||||
if (!input.email || !input.password) return;
|
||||
const result = await new API.Auth().login(input);
|
||||
console.log(result);
|
||||
|
||||
// const { data, ok } = await attemptLogin(input);
|
||||
// if (ok) setUser(data);
|
||||
// navigate(`/${redirect ?? ''}`);
|
||||
// setting token will trigger ui update
|
||||
setToken(result.token);
|
||||
|
||||
// if there is a redirect, go there, else go home
|
||||
navigate(`/${redirect ?? ''}`);
|
||||
}
|
||||
|
||||
// check for logged in user and mount form
|
||||
@@ -37,10 +37,6 @@ export default function Login() {
|
||||
if (user) navigate('/');
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(input);
|
||||
}, [getFormState])
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<h1>Hello! Nice to see you again.</h1>
|
||||
@@ -54,7 +50,7 @@ export default function Login() {
|
||||
dataTypes: Object.keys(input),
|
||||
initialState: input,
|
||||
getState: getFormState
|
||||
} as FormConfig<typeof input>} />
|
||||
}} />
|
||||
|
||||
<Button onClick={handleLogin}>Log In</Button>
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ export default function Profile() {
|
||||
return (
|
||||
<Protect redirect="profile">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{user?.firstname}'s Profile</h1>
|
||||
<p>Things and stuff!</p>
|
||||
<h1>{user && user.firstname}'s Profile</h1>
|
||||
<Friends />
|
||||
</div>
|
||||
</Protect>
|
||||
|
||||
@@ -5,9 +5,10 @@ import { RegisterVariantType, VariantLabel } from ".";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
import { IUser, IUserAuth } from "../../../schemas";
|
||||
import { attemptLogin, attemptRegister } from "../../../util/apiUtils";
|
||||
import API from "../../../util/API";
|
||||
import { Button, Page, Panel } from "../../ui";
|
||||
import Divider from "../../ui/Divider";
|
||||
import Form, { FormConfig } from "../../ui/Form";
|
||||
import Form from "../../ui/Form";
|
||||
|
||||
const blankUser: IUser = {
|
||||
firstname: '',
|
||||
@@ -20,51 +21,31 @@ const blankUser: IUser = {
|
||||
}
|
||||
|
||||
const AboutYou: RegisterVariantType = ({ transitionDisplay }) => {
|
||||
const auth = new API.Auth();
|
||||
const navigate = useNavigate();
|
||||
const authContext = useAuthContext();
|
||||
const [form, setForm] = useState<JSX.Element>(<p key={v4()}>Loading content...</p>);
|
||||
const { user, setToken } = useAuthContext();
|
||||
const [input, setInput] = useState<IUser>(blankUser);
|
||||
const [regSuccess, setRegSuccess] = useState<any>();
|
||||
|
||||
const getFormState = useCallback((received: IUser) => {
|
||||
setInput(received);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (authContext.user) navigate('/');
|
||||
}, [authContext]);
|
||||
if (user) navigate('/');
|
||||
}, [user]);
|
||||
|
||||
async function handleRegister() {
|
||||
const res = await attemptRegister(input);
|
||||
const res = await auth.register(input);
|
||||
if (res.ok) {
|
||||
setTimeout(async () => {
|
||||
const result = await auth.login(input);
|
||||
setToken(result.token);
|
||||
}, 750);
|
||||
|
||||
transitionDisplay(VariantLabel.InitialCollection, input);
|
||||
}
|
||||
}
|
||||
|
||||
async function unwrapLogin() {
|
||||
const data: IUserAuth = { email: input.email, password: input.password || "" }
|
||||
const login = await attemptLogin(data);
|
||||
if (login) {
|
||||
authContext.user = login.user;
|
||||
}
|
||||
navigate('/');
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setForm(new Form<IUser>({
|
||||
parent: "register",
|
||||
keys: ['firstname', 'lastname', 'handle', 'email', 'password'],
|
||||
initialState: input,
|
||||
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"],
|
||||
dataTypes: ['text', 'text', 'text', 'email', 'password'],
|
||||
getState: getFormState
|
||||
}).mount());
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (regSuccess) unwrapLogin();
|
||||
}, [regSuccess])
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<h1>Hi! Thanks for being here.</h1>
|
||||
@@ -74,7 +55,16 @@ const AboutYou: RegisterVariantType = ({ transitionDisplay }) => {
|
||||
<h2>Tell us a bit about yourself:</h2>
|
||||
|
||||
<Panel extraStyles="form-panel two-columns">
|
||||
{ form || <h2>Loading...</h2> }
|
||||
|
||||
<Form parent={input} _config={{
|
||||
parent: "register",
|
||||
keys: ['firstname', 'lastname', 'handle', 'email', 'password'],
|
||||
initialState: input,
|
||||
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"],
|
||||
dataTypes: ['text', 'text', 'text', 'email', 'password'],
|
||||
getState: getFormState
|
||||
}} />
|
||||
|
||||
<Button onClick={handleRegister}>Register</Button>
|
||||
</Panel>
|
||||
</Page>
|
||||
|
||||
@@ -3,33 +3,23 @@ import { RegisterVariantType, VariantLabel } from ".";
|
||||
import { useNow } from "../../../hooks/useNow";
|
||||
import { ICollection, IUser, IUserAuth } from "../../../schemas";
|
||||
import { attemptLogin, createNewCollection } from "../../../util/apiUtils";
|
||||
import API from "../../../util/API";
|
||||
import { Button, Divider, Page, Panel } from "../../ui";
|
||||
import TextField from "../../ui/TextField";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
|
||||
const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) => {
|
||||
const { user, setUser } = useAuthContext();
|
||||
const [collectionName, setCollectionName] = useState<string>();
|
||||
const [view, setView] = useState<JSX.Element>(<Page><h1>Loading...</h1></Page>);
|
||||
const [user, setUser] = useState<IUser>();
|
||||
const now = useNow();
|
||||
|
||||
async function unwrapLogin(data: IUser) {
|
||||
const userInfo: IUserAuth = { email: data.email, password: data.password! }
|
||||
const login = await attemptLogin(userInfo);
|
||||
setUser(login.user);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (input) {
|
||||
setTimeout(() => {
|
||||
unwrapLogin(input);
|
||||
}, 750);
|
||||
}
|
||||
}, [])
|
||||
const collectionAPI = new API.Collection();
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!user) return;
|
||||
const collection: ICollection = {
|
||||
name: collectionName || (user.firstname + "'s Collection"),
|
||||
name: collectionName ?? (user.firstname + "'s Collection"),
|
||||
active: true,
|
||||
ismaincollection: true,
|
||||
ownerid: user.id!.toString(),
|
||||
@@ -39,7 +29,7 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) =>
|
||||
|
||||
console.log(collection);
|
||||
|
||||
const result = await createNewCollection(collection);
|
||||
const result = await collectionAPI.post(collection);
|
||||
console.log(result);
|
||||
if (result) transitionDisplay(VariantLabel.AddFriends);
|
||||
}
|
||||
@@ -58,7 +48,8 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) =>
|
||||
<Divider />
|
||||
<h3>What would you like to call your main collection?</h3>
|
||||
|
||||
<TextField onChange={(e: ChangeEvent<HTMLInputElement>) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'} />
|
||||
{/* <TextField onChange={(e: ChangeEvent<HTMLInputElement>) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'} /> */}
|
||||
<input type="text" onChange={(e) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'}></input>
|
||||
</Panel>
|
||||
|
||||
<Button onClick={handleClick}>Next</Button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import API from "../../../util/API";
|
||||
import { NavbarType } from "../../../util/types";
|
||||
import { Button, Dropdown } from '..'
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@@ -40,6 +40,10 @@ const LoggedIn = () => {
|
||||
navigate(payload);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(user);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id="navbar">
|
||||
@@ -47,7 +51,7 @@ const LoggedIn = () => {
|
||||
<a onClick={() => navigate('/')}>RECIPIN</a>
|
||||
</div>
|
||||
<div className="navbar-block">
|
||||
<p>Hi, {user?.firstname}.</p>
|
||||
<p>Hi, {user && user.firstname}.</p>
|
||||
<span id="search-icon"></span>
|
||||
<Button onClick={() => handleUIChange("SEARCH")}>Search</Button>
|
||||
<Button onClick={() => handleUIChange("ACTIONS")}>Actions</Button>
|
||||
|
||||
@@ -3,12 +3,15 @@ import { IUser } from "../../../schemas";
|
||||
import { TextField, UserCard } from "..";
|
||||
import { v4 } from "uuid";
|
||||
import { getAllUsers } from "../../../util/apiUtils";
|
||||
import API from "../../../util/API";
|
||||
|
||||
const FriendSearchWidget: FC<{}> = () => {
|
||||
const [searchTerm, setSearchTerm] = useState<string>();
|
||||
const [userPool, setUserPool] = useState<IUser[]>([]);
|
||||
const [friendResults, setFriendResults] = useState<IUser[]>([]);
|
||||
|
||||
const users = new API.User();
|
||||
|
||||
// this isn't really working right now i don't think
|
||||
const handleRequestSent = useCallback((targetid: string | number) => {
|
||||
setUserPool((prev: IUser[]) => {
|
||||
@@ -23,7 +26,7 @@ const FriendSearchWidget: FC<{}> = () => {
|
||||
// load available user pool on mount
|
||||
useEffect(() => {
|
||||
(async function() {
|
||||
const result = await getAllUsers();
|
||||
const result = await users.getAll();
|
||||
if (result) setUserPool(result);
|
||||
})();
|
||||
}, [])
|
||||
|
||||
@@ -3,57 +3,61 @@ import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroc
|
||||
import { default as _instance } from "./axiosInstance";
|
||||
|
||||
module API {
|
||||
const APISTRING = import.meta.env.APISTRING || "http://localhost:8080";
|
||||
export class Settings {
|
||||
private static APISTRING = import.meta.env.APISTRING || "http://localhost:8080";
|
||||
private static token?: string;
|
||||
|
||||
public static getAPISTRING() {
|
||||
return Settings.APISTRING;
|
||||
}
|
||||
|
||||
public static getToken() {
|
||||
return Settings.token;
|
||||
}
|
||||
|
||||
public static setToken(newToken: string) {
|
||||
Settings.token = newToken;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RestController<T> {
|
||||
protected instance = _instance;
|
||||
protected endpoint: string;
|
||||
protected token?: string;
|
||||
protected headers?: any
|
||||
|
||||
constructor(endpoint: string, token?: string) {
|
||||
constructor(endpoint: string) {
|
||||
this.endpoint = endpoint;
|
||||
this.token = token;
|
||||
|
||||
if (token) {
|
||||
if (Settings.getToken()) {
|
||||
this.headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": ("Bearer " + token)
|
||||
"Authorization": ("Bearer " + Settings.getToken())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
if (!this.token) return null;
|
||||
|
||||
const response = await this.instance.get(this.endpoint, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
|
||||
async getByID(id: string) {
|
||||
if (!this.token) return null;
|
||||
|
||||
const response = await this.instance.get(this.endpoint + "/" + id, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
|
||||
async postOne(data: T) {
|
||||
if (!this.token) return null;
|
||||
|
||||
async post(data: T) {
|
||||
console.log(data);
|
||||
const response = await this.instance.post(this.endpoint, data, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
|
||||
async put(id: string, data: T | Partial<T>) {
|
||||
if (!this.token) return null;
|
||||
|
||||
const response = await this.instance.put(this.endpoint + "/" + id, data, this.headers);
|
||||
const response = await this.instance.put(this.endpoint + "/" + id, JSON.stringify(data), this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
if (!this.token) return null;
|
||||
|
||||
const response = await this.instance.delete(this.endpoint + '/' + id, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
@@ -61,7 +65,7 @@ module API {
|
||||
|
||||
export class Auth {
|
||||
private instance = _instance;
|
||||
private endpoint = APISTRING + "/auth";
|
||||
private endpoint = Settings.getAPISTRING() + "/auth";
|
||||
|
||||
async login(data: IUserAuth | Partial<IUser>) {
|
||||
try {
|
||||
@@ -109,18 +113,16 @@ module API {
|
||||
|
||||
export class User extends RestController<IUser> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/users");
|
||||
super(Settings.getAPISTRING() + "/app/users");
|
||||
}
|
||||
}
|
||||
|
||||
export class Friendship extends RestController<IFriendship> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/friends");
|
||||
super(Settings.getAPISTRING() + "/app/friends");
|
||||
}
|
||||
|
||||
async getPendingFriendRequests() {
|
||||
if (!this.token) return null;
|
||||
|
||||
const response = await this.instance.get(this.endpoint + "?pending=true", this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
@@ -128,25 +130,25 @@ module API {
|
||||
|
||||
export class Recipe extends RestController<IRecipe> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/recipes");
|
||||
super(Settings.getAPISTRING() + "/app/recipes");
|
||||
}
|
||||
}
|
||||
|
||||
export class Ingredient extends RestController<IIngredient> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/ingredients");
|
||||
super(Settings.getAPISTRING() + "/app/ingredients");
|
||||
}
|
||||
}
|
||||
|
||||
export class Collection extends RestController<ICollection> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/collections");
|
||||
super(Settings.getAPISTRING() + "/app/collection");
|
||||
}
|
||||
}
|
||||
|
||||
export class GroceryList extends RestController<IGroceryList> {
|
||||
constructor() {
|
||||
super(APISTRING + "/app/grocery-list")
|
||||
super(Settings.getAPISTRING() + "/app/grocery-list")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Express } from 'express';
|
||||
import express, { Express } from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import morgan from 'morgan';
|
||||
@@ -19,8 +19,8 @@ declare module 'express-session' {
|
||||
|
||||
export const expressLoader = async (app: Express) => {
|
||||
app.use(cors({ origin: origin }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
app.use(morgan('tiny'));
|
||||
app.use(requireSessionSecret);
|
||||
|
||||
@@ -56,6 +56,8 @@ export const authRoute = (app: Express) => {
|
||||
|
||||
const safeUserData = {
|
||||
id: user.id,
|
||||
firstname: user.firstname,
|
||||
lastname: user.lastname,
|
||||
handle: user.handle,
|
||||
email: user.email,
|
||||
datecreated: user.datecreated,
|
||||
|
||||
@@ -8,6 +8,12 @@ const router = Router();
|
||||
export const collectionRoute = (app: Express) => {
|
||||
app.use('/app/collection', router);
|
||||
|
||||
router.use((req, res, next) => {
|
||||
console.log('what gives');
|
||||
console.log(req.body);
|
||||
next();
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
@@ -30,7 +36,7 @@ export const collectionRoute = (app: Express) => {
|
||||
|
||||
router.post('/', async (req, res, next) => {
|
||||
const data = req.body;
|
||||
console.log(data);
|
||||
console.log(req.body ?? "sanity check");
|
||||
|
||||
try {
|
||||
const result = await CollectionInstance.post(data);
|
||||
|
||||
@@ -25,7 +25,7 @@ export const routes = async (app: Express) => {
|
||||
console.log(token);
|
||||
|
||||
if (!token) {
|
||||
res.status(403).send("Unauthorized");
|
||||
res.status(403).send("Unauthorized, did not receive token");
|
||||
} else {
|
||||
jwt.verify(token, process.env.SESSIONSECRET as string, (err, data) => {
|
||||
if (err) {
|
||||
|
||||
Reference in New Issue
Block a user