diff --git a/app/layout.tsx b/app/layout.tsx index d2c11872..22053d8f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Montserrat } from "next/font/google"; import "./globals.css"; +//import Link from "next/link"; import Navbar from "@/components/navbar"; import Testbar from "@/components/testbar"; @@ -12,6 +13,8 @@ export const metadata: Metadata = { description: "Generated by create next app", }; + + // This is the height of the navbar which will also be used to set the padding-top of the main content // NOTE: you should modify this value to match the height of the navbar const navbarHeight: string = "60px"; @@ -29,7 +32,9 @@ export default function RootLayout({ {children} + + ); } diff --git a/app/loginPage/page.tsx b/app/loginPage/page.tsx new file mode 100644 index 00000000..cdc66b26 --- /dev/null +++ b/app/loginPage/page.tsx @@ -0,0 +1,82 @@ +"use client"; +import { useEffect, useState } from "react"; +import { GoogleOAuthProvider, useGoogleLogin, googleLogout, TokenResponse} from "@react-oauth/google"; +import { jwtDecode } from "jwt-decode"; +import axios from "axios"; + +interface User { + name: string; + email: string; + picture: string; +} +const LoginPage = () => { + return( + + + + ) +} + +const LoginComponent = () => { + const [user, setUser] = useState(null); + + + + useEffect(() => { + console.log("LoginPage component mounted"); + }, []); + + const handleLoginSuccess = (tokenResponse: any) => { + if ('code' in tokenResponse) { + // Handle authorization code flow + console.log('Authorization Code:', tokenResponse.code); + // Exchange code for tokens here + } else { + // Handle implicit flow + console.log('Token Received:', tokenResponse.access_token); + const decoded: User = jwtDecode(tokenResponse.id_token); + setUser({ + name: decoded.name, + email: decoded.email, + picture: decoded.picture + }); + // Send token to backend if necessary + axios.post('http://localhost:8000/myapi/users/google-oauth2/', { token: tokenResponse.access_token }) + .then(res => console.log('Backend login successful', res)) + .catch(err => console.error('Backend login failed', err)); + } + + }; + const login = useGoogleLogin({ + flow: "auth-code", + + onSuccess: (tokenResponse) => handleLoginSuccess, + onError: (errorResponse) => console.error('Login Failed', errorResponse), + }); + const handleLogout = () => { + googleLogout(); + setUser(null); // Clears the user state, effectively logging out the user + console.log('Logged out successfully'); + }; + return ( +
+ + {user && ( +
+ User profile +

Welcome, {user.name} - {user.email}

+ +
+ )} + +
+ + ); +}; + +export default LoginPage; + diff --git a/app/page.tsx b/app/page.tsx index ee575108..cb2be774 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,12 +2,6 @@ import { useState, useEffect } from "react"; import axios from "axios"; import Link from "next/link"; -import { - GoogleOAuthProvider, - GoogleLogin, - googleLogout, -} from "@react-oauth/google"; -import { jwtDecode } from "jwt-decode"; interface Food { name: string; @@ -20,6 +14,7 @@ interface subCategory { } interface Category { + forEach(arg0: (category: any) => void): unknown; name: string; sub_categories: Array; } @@ -51,7 +46,7 @@ function ButtonLink(props: any) { ); } -function Home() { +export default function Home() { const [dhs, setDhs] = useState([]); const [dhs_names, set_dhs_names] = useState([""]); const [searchInput, setSearchInput] = useState(""); @@ -84,7 +79,7 @@ function Home() { const allFoods: { food: Food; dhName: string; categoryName: string }[] = []; dhs.forEach((dh) => { dh.categories.forEach((category) => { - category.sub_categories.forEach((subCategory) => { + category.sub_categories.forEach((subCategory: { foods: any[]; }) => { subCategory.foods.forEach((food) => { allFoods.push({ food, @@ -159,55 +154,7 @@ function Home() { + {/* Account Button */} ); } - -export default function Page() { - const [user, setUser] = useState(null); - - useEffect(() => { - console.log( - "Page component loaded and GoogleOAuthProvider should be active", - ); - }, []); - - const handleLogout = () => { - googleLogout(); - setUser(null); // Clear user state on logout - console.log("Logout Successful"); - }; - - const handleLoginSuccess = (credentialResponse: any) => { - console.log("Login Successful", credentialResponse); - const decoded: User = jwtDecode(credentialResponse.credential); - setUser({ - name: decoded.name, - picture: decoded.picture, - }); - }; - - return ( - - - { - console.log("Login Failed"); - }} - /> - {user && ( -
- User profile -

{user.name}

-
- )} - -
- ); -} diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 91fac86d..febeef87 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -44,6 +44,9 @@ "corsheaders", "rest_framework", "myapi", + 'oauth2_provider', + 'drf_social_oauth2', + 'social_django', ] MIDDLEWARE = [ @@ -55,6 +58,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", + 'social_django.middleware.SocialAuthExceptionMiddleware', ] CORS_ORIGIN_ALLOW_ALL = True @@ -72,6 +76,8 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + 'social_django.context_processors.backends', + 'social_django.context_processors.login_redirect', ], }, }, @@ -131,3 +137,36 @@ # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_URL = '/admin/login/' + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + 'drf_social_oauth2.authentication.SocialAuthentication', + ), +} + +AUTHENTICATION_BACKENDS = ( + 'social_core.backends.google.GoogleOAuth2', + 'django.contrib.auth.backends.ModelBackend', + 'drf_social_oauth2.backends.DjangoOAuth2', +) +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '1040494859138-vji3ddfil5jancg23ifaginvmn71hktf.apps.googleusercontent.com' +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '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' + +SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', +] + +SOCIAL_AUTH_PIPELINE = ( + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details', +) diff --git a/backend/backend/urls.py b/backend/backend/urls.py index f276300a..2a289d7f 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -23,4 +23,8 @@ path("admin/", admin.site.urls), # For the api path("myapi/", include("myapi.urls")), + path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), + path('auth/',include('drf_social_oauth2.urls',namespace='drf')), + path('social-auth/', include('social_django.urls', namespace='social')), + ] diff --git a/backend/myapi/migrations/0001_initial.py b/backend/myapi/migrations/0001_initial.py new file mode 100644 index 00000000..c1ac70c7 --- /dev/null +++ b/backend/myapi/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.4 on 2024-05-07 04:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="UserProfile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("first_name", models.CharField(blank=True, max_length=50)), + ("last_name", models.CharField(blank=True, max_length=50)), + ("email", models.EmailField(blank=True, max_length=254)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/backend/myapi/migrations/0002_alter_userprofile_email.py b/backend/myapi/migrations/0002_alter_userprofile_email.py new file mode 100644 index 00000000..0cb77b87 --- /dev/null +++ b/backend/myapi/migrations/0002_alter_userprofile_email.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-05-07 19:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("myapi", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="userprofile", + name="email", + field=models.EmailField(max_length=254, unique=True), + ), + ] diff --git a/backend/myapi/migrations/0003_delete_userprofile.py b/backend/myapi/migrations/0003_delete_userprofile.py new file mode 100644 index 00000000..e1114b2d --- /dev/null +++ b/backend/myapi/migrations/0003_delete_userprofile.py @@ -0,0 +1,16 @@ +# Generated by Django 5.0.4 on 2024-05-08 04:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("myapi", "0002_alter_userprofile_email"), + ] + + operations = [ + migrations.DeleteModel( + name="UserProfile", + ), + ] diff --git a/backend/myapi/models.py b/backend/myapi/models.py index a04bc1cf..99386b4a 100644 --- a/backend/myapi/models.py +++ b/backend/myapi/models.py @@ -1,6 +1,6 @@ from django.db import models from db_connection import db - +from django.contrib.auth.models import User # Create your models here. # locations Model diff --git a/backend/myapi/urls.py b/backend/myapi/urls.py index 0e34851d..6c3558e5 100644 --- a/backend/myapi/urls.py +++ b/backend/myapi/urls.py @@ -1,7 +1,10 @@ -from django.urls import path +from django.urls import path, re_path from . import views urlpatterns = [ path("hello-world/", views.hello_world, name="hello_world"), path("locations/", views.get_locations, name="locations"), + #path('users/', views.create_user, name='create_user'), + path('users//', views.create_user, name='create_user'), + #path('getuser/', views.get_user, name='get_user'), ] diff --git a/backend/myapi/views.py b/backend/myapi/views.py index 9575c9af..e4a1cdb4 100644 --- a/backend/myapi/views.py +++ b/backend/myapi/views.py @@ -1,3 +1,14 @@ +from rest_framework.response import Response +from rest_framework import status +from rest_framework.decorators import api_view, permission_classes +from django.contrib.auth.models import User +from .webscraper.food_options import FoodOptions +from .db_functions.dining_halls import get_all_dining_halls_from_db +from rest_framework.authtoken.models import Token +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import AllowAny + +from social_django.utils import psa from django.conf.locale import fr from rest_framework.response import Response from rest_framework.decorators import api_view @@ -59,3 +70,39 @@ def get_locations(request): json_data = {"locations": locations} return Response(json_data) +#user information +@api_view(['POST']) +@permission_classes([AllowAny]) +@psa('social:complete') +def create_user(request,backend): + # Extract the token sent from the frontend + + token = request.data.get('access_token') + print(token) + + # `do_auth` method will try to authenticate the token with Google + try: + user = request.backend.do_auth(token) + except Exception as e: + # Log the exception to understand why authentication fails + print(f'Authentication failed: {str(e)}') + return Response( + {'errors': {'token': 'Authentication failed', 'detail': str(e)}}, + status=status.HTTP_403_FORBIDDEN, + ) + + + if user: + # If user is authenticated, get or create a token for your application + token, _ = Token.objects.get_or_create(user=user) + return Response({ + 'id': user.id, + 'name': user.username, + 'email': user.email, + 'token': token.key # Send the token key to the frontend + }) + else: + return Response( + {'errors': {'token': 'Invalid token'}}, + status=status.HTTP_400_BAD_REQUEST, + ) \ No newline at end of file diff --git a/components/navbar.tsx b/components/navbar.tsx index a49d7505..0bc58e90 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -25,7 +25,7 @@ export default function Navbar({ height }: { height: string }) {
  • - + Account {/* pr-X dicates how far off right we want. */} diff --git a/db.sqlite3 b/db.sqlite3 index 4480a300..e5ebef7d 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ