Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/169/implement oauth on backend #201

Merged
merged 10 commits into from
Nov 28, 2023
75 changes: 75 additions & 0 deletions backend/app/api/endpoints/login.py
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dotenv import load_dotenv
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from google.auth.transport import requests
from google.oauth2 import id_token
from jose import JWTError, jwt
from pydantic import BaseModel
from sqlalchemy.orm import Session
Expand All @@ -21,6 +23,9 @@
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# Google Client ID
CLIENT_ID = "710734565405-3nkf5plf0m4p460osals94rnksheoh93.apps.googleusercontent.com"
johndpjr marked this conversation as resolved.
Show resolved Hide resolved

users_db = get_db()

router = APIRouter()
Expand All @@ -39,6 +44,8 @@ def hash_password(password: str):

class User(BaseModel):
username: str
google_id: str | None = None
made_password: bool | None = None
email: str | None = None
full_name: str | None = None
disabled: bool | None = None
Expand Down Expand Up @@ -110,6 +117,11 @@ def authenticate_user(username: str, password: str, db: Session = Depends(get_db
user = get_user(db, username)
if not user:
raise HTTPException(status_code=400, detail="Incorrect username or password")
if not user.made_password:
raise HTTPException(
status_code=400,
detail="This account didn't make a password. Please login via Google.",
)
encoded_password = password.encode("utf-8")
if not bcrypt.checkpw(encoded_password, user.password.encode("utf-8")):
raise HTTPException(status_code=400, detail="Incorrect username or password")
Expand Down Expand Up @@ -155,6 +167,7 @@ async def register_user(
"full_name": full_name,
"email": email,
"password": hashed_password.decode("utf-8"),
"made_password": True,
"disabled": False,
}
)
Expand All @@ -176,3 +189,65 @@ async def delete_user(
return {"deleted": True}
else:
return {"deleted": False}


@router.post("/users/google-login")
async def google_login(token: str, db: Session = Depends(get_db)):
try:
# Specify the CLIENT_ID of the app that accesses the backend:
print("testing:", token)
idinfo = id_token.verify_oauth2_token(
token,
requests.Request(),
CLIENT_ID,
clock_skew_in_seconds=1000000,
)
print("Success")
# print("ID_info:", idinfo)
# Or, if multiple clients access the backend server:
# idinfo = id_token.verify_oauth2_token(token, requests.Request())
# if idinfo['aud'] not in [CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]:
# raise ValueError('Could not verify audience.')

# If auth request is from a G Suite domain:
# if idinfo['hd'] != GSUITE_DOMAIN_NAME:
# raise ValueError('Wrong hosted domain.')

# ID token is valid. Get the user's Google Account ID from the decoded token.
# print("Getting user info")
userid = idinfo["sub"]
name = idinfo["name"]
# print(userid)
email = idinfo["email"]
# print(email)
user = crud.search_users_by_google_id(db, userid)
if user is None or len(user) == 0:
# Register a new user
new_user = UserModel(
**{
"username": "{}.{}".format(email, userid),
"google_id": userid,
"made_password": False,
"full_name": name,
"email": email,
"password": "",
"disabled": False,
}
)
user_list = [new_user]
crud.create_users(db, *user_list)
user = new_user
else:
user = user[0]
print(user.username)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}

except ValueError as e:
# Invalid token
print("Invalid token:", token)
print(e)
return {"Error!"}
14 changes: 14 additions & 0 deletions backend/app/crud/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ def search_users(db: Session, username: str, skip: int = 0, limit: int = 1000):
return convert_users(*results)


def search_users_by_google_id(
db: Session, field_val: str, skip: int = 0, limit: int = 1000
):
"""Searches for users by a given field value in a field"""
results = (
db.query(UserModel)
.filter(UserModel.google_id == field_val)
.offset(skip)
.limit(limit)
.all()
)
return convert_users(*results)


def delete_user(db: Session, username: str):
"""Deletes a user with the given username"""
user = db.query(UserModel).filter(UserModel.username == username).first()
Expand Down
4 changes: 3 additions & 1 deletion backend/app/models/user.py
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy import Boolean, Column, Integer, String

from backend.app.database import DatabaseModel

Expand All @@ -7,6 +7,8 @@ class User(DatabaseModel):
__tablename__ = "user"

id = Column(Integer, primary_key=True, index=True)
google_id = Column(String)
made_password = Column(Boolean)
username = Column(String)
password = Column(String)
full_name = Column(String)
Expand Down
2 changes: 2 additions & 0 deletions backend/app/schemas/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

class UserBase(BaseModel):
id: Union[int, None] = None
google_id: str = ""
made_password: bool = False
username: str = ""
password: str = ""
full_name: str = ""
Expand Down
7 changes: 5 additions & 2 deletions backend/app/utils/logger.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import logging
import logging.handlers
from os import makedirs
import time
from os import getpid, makedirs
from pathlib import Path


def setup_logger(name):
# logger settings
makedirs(Path.cwd().joinpath("log"), exist_ok=True)
file = "log/agtern.log"
file = "log/agtern.log.{}.{}".format(
time.strftime("(%Y-%m-%d)-(%H-%M-%S)"), getpid()
)
format = "%(asctime)s [%(levelname)s] %(message)s"

# setup handlers and formatters
Expand Down
57 changes: 57 additions & 0 deletions debugdump.txt
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
== Info: Trying 93.184.216.34:80...
== Info: Connected to www.example.com (93.184.216.34) port 80 (#0)
=> Send header, 79 bytes (0x4f)
0000: GET / HTTP/1.1
0010: Host: www.example.com
0027: User-Agent: curl/7.81.0
0040: Accept: */*
004d:
== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 13 bytes (0xd)
0000: Age: 250108
<= Recv header, 31 bytes (0x1f)
0000: Cache-Control: max-age=604800
<= Recv header, 40 bytes (0x28)
0000: Content-Type: text/html; charset=UTF-8
<= Recv header, 37 bytes (0x25)
0000: Date: Sun, 12 Nov 2023 22:36:42 GMT
<= Recv header, 26 bytes (0x1a)
0000: Etag: "3147526947+ident"
<= Recv header, 40 bytes (0x28)
0000: Expires: Sun, 19 Nov 2023 22:36:42 GMT
<= Recv header, 46 bytes (0x2e)
0000: Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
<= Recv header, 24 bytes (0x18)
0000: Server: ECS (dab/4B67)
<= Recv header, 23 bytes (0x17)
0000: Vary: Accept-Encoding
<= Recv header, 14 bytes (0xe)
0000: X-Cache: HIT
<= Recv header, 22 bytes (0x16)
0000: Content-Length: 1256
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 1256 bytes (0x4e8)
0000: <!doctype html>.<html>.<head>. <title>Example Domain</title>.
0040: . <meta charset="utf-8" />. <meta http-equiv="Content-type
0080: " content="text/html; charset=utf-8" />. <meta name="viewport
00c0: " content="width=device-width, initial-scale=1" />. <style ty
0100: pe="text/css">. body {. background-color: #f0f0f2;.
0140: margin: 0;. padding: 0;. font-family: -apple-
0180: system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans",
01c0: "Helvetica Neue", Helvetica, Arial, sans-serif;. . }.
0200: div {. width: 600px;. margin: 5em auto;.
0240: padding: 2em;. background-color: #fdfdff;. border
0280: -radius: 0.5em;. box-shadow: 2px 3px 7px 2px rgba(0,0,0,0
02c0: .02);. }. a:link, a:visited {. color: #38488f;.
0300: text-decoration: none;. }. @media (max-width: 700px) {
0340: . div {. margin: 0 auto;. width: au
0380: to;. }. }. </style> .</head>..<body>.<div>. <
03c0: h1>Example Domain</h1>. <p>This domain is for use in illustra
0400: tive examples in documents. You may use this. domain in liter
0440: ature without prior coordination or asking for permission.</p>.
0480: <p><a href="https://www.iana.org/domains/example">More inform
04c0: ation...</a></p>.</div>.</body>.</html>.
== Info: Connection #0 to host www.example.com left intact
23 changes: 23 additions & 0 deletions frontend/src/_generated/api/services/LoginService.ts
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ export class LoginService {
});
}

/**
* GoogleLogin
* Logins a user via google
* @param id_token
* @returns any Successful Response
* @throws ApiError
*/
public static googleLogin(
token: string,
): CancelablePromise<any> {
console.log("Testing");
return __request(OpenAPI, {
method: 'POST',
url: '/login/users/google-login',
query: {
'token': token,
},
errors: {
422: `Validation Error`,
},
});
}

/**
* Read Users Me
* @returns any Successful Response
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/pages/login/login.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</div>

<div class="login-button">
<app-google-sign-in></app-google-sign-in>
<app-google-sign-in [loginRef]="selfRef"></app-google-sign-in>
</div>

<div class="login-button">
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/pages/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { AuthService } from '../../shared/services/auth.service';
styleUrls: ['./login.component.scss']
})
export class LoginComponent {
// Reference to itself
selfRef: LoginComponent = this;
constructor(
private authService: AuthService,
public router: Router
Expand Down
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input, NgZone } from '@angular/core';
import 'zone.js';
import { LoginService } from 'src/_generated/api';
import { Router } from '@angular/router';
import { LoginComponent } from 'src/app/pages/login/login.component';

@Component({
selector: 'app-google-sign-in',
templateUrl: './google-sign-in.component.html',
styleUrls: ['./google-sign-in.component.scss']
})
export class GoogleSignInComponent implements OnInit {
constructor() {}
@Input() loginRef!: LoginComponent;
constructor(private zone: NgZone) {}

ngOnInit(): void {
// @ts-ignore
Expand All @@ -28,7 +33,22 @@ export class GoogleSignInComponent implements OnInit {
// google.accounts.id.prompt((notification: PromptMomentNotification) => {});
}

async handleCredentialResponse(response: any) {
console.log(response);
async handleCredentialResponse(googleUser: any) {
console.log(googleUser);
var token: string = googleUser.credential;

LoginService.googleLogin(token).then(
() => {
// login
console.log('Resetting');
this.loginRef.form.reset();
console.log('Navigating');
this.zone.run(() => this.loginRef.router.navigate(['/jobs']));
},
() => {
// Do nothing since we failed
console.log('Failed');
}
);
}
}
2 changes: 2 additions & 0 deletions frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@900&family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<script src="https://apis.google.com/js/platform.js" async defer></script>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<meta name="google-signin-client_id" content="710734565405-3nkf5plf0m4p460osals94rnksheoh93.apps.googleusercontent.com">
</head>
<body class="mat-typography mat-app-background">
<app-root></app-root>
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
johndpjr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
bcrypt==4.0.1
celery[redis]
fastapi==0.95.2
google-api-python-client
nltk==3.8.1
passlib[bcrypt]==1.7.4
pre-commit==3.5.0
Expand Down