diff --git a/packages/api/index.ts b/packages/api/index.ts index 5e3fd3b..e07ece2 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -1,6 +1,7 @@ import express, { Application, Request, Response } from "express"; import { NextFunction } from "express"; +import { connectToDatabase } from "./services/database.service"; import cors from "cors"; import getRoutes from "./routes"; @@ -20,3 +21,5 @@ app.get("/", (req: Request, res: Response) => { app.use("/api", getRoutes()); app.listen(5000, () => console.log("Example app is listening on port 5000.")); + +connectToDatabase(); diff --git a/packages/api/routes/index.ts b/packages/api/routes/index.ts index 5744e8b..cfbc673 100644 --- a/packages/api/routes/index.ts +++ b/packages/api/routes/index.ts @@ -4,7 +4,7 @@ import getUserRoutes from "./user"; export default function getRoutes() { const router = express.Router(); - router.use("/user", getUserRoutes()); + router.use("/users", getUserRoutes()); return router; } diff --git a/packages/api/routes/user.ts b/packages/api/routes/user.ts index d154ef5..e26ff1e 100644 --- a/packages/api/routes/user.ts +++ b/packages/api/routes/user.ts @@ -2,6 +2,7 @@ import { GoogleUserInfo, User } from "@nirvana/core/models"; import express, { Application, Request, Response } from "express"; import { ObjectId } from "mongodb"; +import { UserService } from "../services/user.service"; import { authCheck } from "../middleware/auth"; import { collections } from "../services/database.service"; @@ -26,11 +27,11 @@ export default function getUserRoutes() { async function getUserDetails(req: Request, res: Response) { const userId: string = res.locals.userId; - try { - const query = { _id: new ObjectId(userId) }; + console.log(`getting data for ${userId}`); + try { // return user details if it passed auth middleware - const user = (await collections.users?.findOne(query)) as unknown as User; + const user = await UserService.getUserById(userId); res.status(200).send(user); } catch (error) { @@ -46,19 +47,37 @@ async function createUser(req: Request, res: Response) { if (!access_token) { res.status(400); - return; } + + // get google user info from access token + const userInfo: GoogleUserInfo = + await UserService.getGoogleUserInfoWithAccessToken( + access_token as string + ); + + console.log(userInfo); + + // create initial user model object + const newUser = new User( + new ObjectId(userInfo.id), + userInfo.email, + userInfo.verifiedEmail, + userInfo.name, + userInfo.given_name, + userInfo.family_name, + userInfo.picture, + userInfo.locale + ); + + console.log(newUser); + // create user if not exists + const insertResult = await UserService.createUserIfNotExists(newUser); - // get user info from access token - const userInfo: GoogleUserInfo = await ( - await fetch( - `https://www.googleapis.com/oauth2/v1/userinfo?access_token=${access_token}` - ) - ).json(); - - res.send(); + insertResult + ? res.status(200).send("User created") + : res.status(500).send("Failed to create new user"); } catch (error) { res.status(500); } diff --git a/packages/api/services/database.service.ts b/packages/api/services/database.service.ts index 77e6f07..558073c 100644 --- a/packages/api/services/database.service.ts +++ b/packages/api/services/database.service.ts @@ -9,7 +9,7 @@ export const collections: { users?: mongoDB.Collection } = {}; // Initialize Connection export async function connectToDatabase() { const client: mongoDB.MongoClient = new mongoDB.MongoClient( - process.env.DB_CONN_STRING! + "mongodb+srv://default:M9iZXokJlZpN4KLX@cluster0.mkuqa.mongodb.net/default?retryWrites=true&w=majority" ); await client.connect(); diff --git a/packages/api/services/user.service.ts b/packages/api/services/user.service.ts new file mode 100644 index 0000000..43dc0f4 --- /dev/null +++ b/packages/api/services/user.service.ts @@ -0,0 +1,23 @@ +import { ObjectId } from "mongodb"; +import { User } from "@nirvana/core/models"; +import { collections } from "./database.service"; + +export class UserService { + static async getUserById(userId: string) { + const query = { _id: new ObjectId(userId) }; + + return (await collections.users?.findOne(query)) as unknown as User; + } + + static async createUserIfNotExists(newUser: User) { + return await collections.users?.insertOne(newUser); + } + + static async getGoogleUserInfoWithAccessToken(accessToken: string) { + return await ( + await fetch( + `https://www.googleapis.com/oauth2/v1/userinfo?access_token=${accessToken}` + ) + ).json(); + } +} diff --git a/packages/core/models/googleUserInfo.model.ts b/packages/core/models/googleUserInfo.model.ts index ec75c0e..e39225d 100644 --- a/packages/core/models/googleUserInfo.model.ts +++ b/packages/core/models/googleUserInfo.model.ts @@ -2,7 +2,7 @@ import { ObjectId } from "mongodb"; export class GoogleUserInfo { constructor( - public id: ObjectId, + public id: string, public email: string, public verifiedEmail: boolean, public name: string, diff --git a/packages/core/models/user.model.ts b/packages/core/models/user.model.ts index 5eb1cd0..242d2c9 100644 --- a/packages/core/models/user.model.ts +++ b/packages/core/models/user.model.ts @@ -1,29 +1,15 @@ -import { GoogleUserInfo } from "./googleUserInfo.model"; import { ObjectId } from "mongodb"; -export class User extends GoogleUserInfo { +export class User { constructor( - id: ObjectId, - email: string, - verifiedEmail: boolean, - name: string, - given_name: string, - family_name: string, - picture: string, - locale: string, - - // additional properties specific to our users collection - mongoId: ObjectId - ) { - super( - id, - email, - verifiedEmail, - name, - given_name, - family_name, - picture, - locale - ); - } + public _id: ObjectId, + public email: string, + public verifiedEmail: boolean, + public name: string, + public given_name: string, + public family_name: string, + public picture: string, + public locale: string + ) // additional properties specific to our users collection + {} } diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 9522c11..0242ef0 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -47,6 +47,7 @@ "@electron-forge/plugin-webpack", { "mainConfig": "./webpack.main.config.js", + "devContentSecurityPolicy": "connect-src 'self' http://localhost:5000 'unsafe-eval'", "renderer": { "config": "./webpack.renderer.config.js", "entryPoints": [ diff --git a/packages/desktop/src/components/ProtectedRoute/index.tsx b/packages/desktop/src/components/ProtectedRoute/index.tsx index c80a23b..115f4da 100644 --- a/packages/desktop/src/components/ProtectedRoute/index.tsx +++ b/packages/desktop/src/components/ProtectedRoute/index.tsx @@ -1,6 +1,7 @@ import { Route, useNavigate } from "react-router-dom"; import { STORE_ITEMS } from "../../electron/store"; +import { nirvanaApi } from "../../controller/nirvanaApi"; import { useEffect } from "react"; import { useGetUserDetails } from "../../controller/index"; @@ -9,18 +10,18 @@ export default function ProtectedRoute({ ...children }) { useEffect(() => { // console.log(window.electronAPI.store.get(STORE_ITEMS.AUTH_TOKENS)); + // nirvanaApi.getUserDetails(); }, []); const navigate = useNavigate(); if (isLoading) return please wait while we authenticate you; - if (isError) { - // toast.error("Please sign in first!"); - navigate("/login"); - - return you are not allowed here!; - } + useEffect(() => { + if (isError) { + navigate("/login"); + } + }, [isError]); // if we can successfully get user details, we are good to continue diff --git a/packages/desktop/src/controller/index.tsx b/packages/desktop/src/controller/index.tsx index adccbbb..293934e 100644 --- a/packages/desktop/src/controller/index.tsx +++ b/packages/desktop/src/controller/index.tsx @@ -6,7 +6,7 @@ export enum Querytypes { } export function useGetUserDetails() { - return useQuery(Querytypes.GET_USER_DETAILS, nirvanaApi.getUserDetails, { - retry: true, - }); + return useQuery(Querytypes.GET_USER_DETAILS, () => + nirvanaApi.getUserDetails() + ); } diff --git a/packages/desktop/src/controller/nirvanaApi.ts b/packages/desktop/src/controller/nirvanaApi.ts index 7900577..1a215f6 100644 --- a/packages/desktop/src/controller/nirvanaApi.ts +++ b/packages/desktop/src/controller/nirvanaApi.ts @@ -4,38 +4,49 @@ import { User } from "@nirvana/core/models"; // export const localHost = process.env.REACT_APP_API_DOMAIN; -export const localHost = "http://localhost:5000"; +export const localHost = "http://localhost:5000/api"; class NirvanaApi { // auth token from google that our backend will use private _authToken?: string; - setPrivateToken(_googleIdToken: string) { + setGoogleIdToken(_googleIdToken: string) { this._authToken = _googleIdToken; } - async fetch(options: AxiosRequestConfig, privateRoute = false) { + async fetch(url: string, method: string, privateRoute = false) { // use the auth token if it's a private route // error if no auth token and it's a private route // throw error and show message on anything that is an error from the backend - if (privateRoute && this._authToken) { - return await axios.request({ - ...options, - headers: { Authorization: this._authToken }, - }); - } - return axios.request(options); + try { + const fullUrl = localHost + url; + + var res; + if (privateRoute && this._authToken) { + res = await fetch(fullUrl, { + method: method, + headers: { Authorization: this._authToken }, + }); + } else { + res = await fetch(fullUrl); + } + + return await res.json(); + } catch (error) { + console.log(error); + + throw Error(error); + } } - async getUserDetails(): Promise> { - return await this.fetch( - { + async getUserDetails(): Promise { + return await ( + await fetch(localHost + `/users`, { method: "GET", - url: localHost + `/users`, - }, - true - ); + headers: { Authorization: this._authToken }, + }) + ).json(); } } diff --git a/packages/desktop/src/index.ts b/packages/desktop/src/index.ts index ef7171e..9ed40bd 100644 --- a/packages/desktop/src/index.ts +++ b/packages/desktop/src/index.ts @@ -23,10 +23,11 @@ export let browserWindow: BrowserWindow; const createWindow = (): void => { // Create the browser window. browserWindow = new BrowserWindow({ - height: 600, - width: 800, + height: 900, + width: 900, webPreferences: { preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, + sandbox: true, }, }); diff --git a/packages/desktop/src/nirvanaApp.tsx b/packages/desktop/src/nirvanaApp.tsx index 330d355..d50de5f 100644 --- a/packages/desktop/src/nirvanaApp.tsx +++ b/packages/desktop/src/nirvanaApp.tsx @@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "react-query"; import Home from "./pages/Home"; import Login from "./pages/Login"; import ProtectedRoute from "./components/ProtectedRoute"; +import { ReactQueryDevtools } from "react-query/devtools"; import testConnection from "@nirvana/core"; testConnection(); @@ -32,6 +33,8 @@ function NirvanaApp() { + + ); diff --git a/packages/desktop/src/pages/Login/index.tsx b/packages/desktop/src/pages/Login/index.tsx index 226205d..5fa8bee 100644 --- a/packages/desktop/src/pages/Login/index.tsx +++ b/packages/desktop/src/pages/Login/index.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import Channels from "../../electron/constants"; import { CircularProgress } from "@mui/material"; import Logo from "../../components/Logo"; +import { nirvanaApi } from "../../controller/nirvanaApi"; import { useNavigate } from "react-router-dom"; export default function Login() { @@ -19,8 +20,11 @@ export default function Login() { window.electronAPI.auth.receiveTokens(async (tokens: any) => { console.log(tokens); + // todo: implement refresh token procedure in api layer by sending refresh_token and such const { access_token, id_token, refresh_token } = tokens; + nirvanaApi.setGoogleIdToken(id_token); + // home should handle this user now that they have signed in with google navigate("/home"); });