Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
7d0d537
Test
courdegard Aug 23, 2021
7db260e
authentication feature rewrites
courdegard Aug 24, 2021
03ef385
OAuth2/JWT implemented, started user activitation and password reset
courdegard Aug 25, 2021
53235bb
automatic emailworks
courdegard Aug 25, 2021
20f2ff1
Figuring out mail issues
courdegard Aug 26, 2021
7580c08
Sending emails seems to work as needed
courdegard Aug 26, 2021
b1c581a
small adjustment to fix mail
courdegard Aug 26, 2021
186e350
activation backend done; frontend next
courdegard Aug 26, 2021
c9e4695
Everything is completely broken
courdegard Aug 27, 2021
37985f0
password form POST seems to be working
courdegard Aug 27, 2021
c453aeb
login and logout working
courdegard Aug 28, 2021
e8e9874
Activation flow is working
courdegard Aug 29, 2021
efb9da5
login, registration/activation, reset password all working
courdegard Aug 29, 2021
c69e89b
refresh token functionality in place not tested
courdegard Aug 29, 2021
660eb93
working on refresh
courdegard Aug 29, 2021
b4f596c
Security working
courdegard Aug 31, 2021
dc67138
Much progress on the plot routes
courdegard Sep 1, 2021
e2ac93c
Completely redid authentication again
courdegard Sep 1, 2021
9d958e6
exception handling for refresh tokens
courdegard Sep 2, 2021
0d04a22
You can stay logged in for 6 hours
courdegard Sep 2, 2021
1f829f7
Whole lotta red
courdegard Sep 3, 2021
b0587ac
changed plot data model
courdegard Sep 3, 2021
dbda3a1
changes to data store and refactoring
courdegard Sep 3, 2021
9bb7471
data model changes
courdegard Sep 3, 2021
39b5bf9
fixed some data loading issues
courdegard Sep 5, 2021
b78025f
almost works...
courdegard Sep 5, 2021
fa7192e
line chart works. Maybe should consider antoehr career path
courdegard Sep 5, 2021
0c177fb
Fixed plot box classification error
courdegard Sep 5, 2021
be40614
plot box makes a little more sense
courdegard Sep 8, 2021
cbb8011
many changes to frontend
courdegard Sep 9, 2021
7a6716a
Can I have two gitignore files?
courdegard Sep 9, 2021
18a4138
so colorful
courdegard Sep 9, 2021
bc04542
data model for aggregate plot data
courdegard Sep 11, 2021
4f4fe7f
redid the data models again
courdegard Sep 11, 2021
0be3210
backend
courdegard Sep 12, 2021
84f3c2b
everything technically works
courdegard Sep 14, 2021
7430001
Idk
courdegard Sep 14, 2021
4b21ecf
Some frontend stuff and started the rework of weight data model
courdegard Sep 17, 2021
32581e0
Nice
courdegard Sep 18, 2021
c5bfe34
updates to weight page
courdegard Sep 19, 2021
ca9d1fd
To lappy
courdegard Sep 19, 2021
91a7650
Lol
courdegard Sep 20, 2021
d018429
fixed a couple things
courdegard Sep 20, 2021
bad8adb
from the plane
courdegard Sep 21, 2021
4962341
Work
courdegard Sep 21, 2021
ef29636
After vacation
courdegard Oct 13, 2021
91b620a
chartjs is now d3
courdegard Oct 19, 2021
ca106ab
started redoing plotgrid
courdegard Oct 19, 2021
2bf2091
Almost done redoing the grid stuff
courdegard Oct 24, 2021
f62cc0f
Redid the plot grid for the 4th time. Data model changes
courdegard Oct 26, 2021
89044bc
responsive design progress
courdegard Oct 27, 2021
56791f9
Much yet to be done
courdegard Oct 27, 2021
c524d97
Becoming free from persisted state
courdegard Oct 30, 2021
aba87bf
Taking the day off
courdegard Oct 31, 2021
3cf121a
Slow train
courdegard Nov 1, 2021
f7674dd
Linter troubles
courdegard Nov 1, 2021
8646414
Linting
courdegard Nov 1, 2021
ea585d7
The wheel of Ixion turns
courdegard Nov 2, 2021
345e777
Losing most but retaining some faith
courdegard Nov 3, 2021
8f87b54
late nite
courdegard Nov 3, 2021
3db2222
Begin new branch
courdegard Nov 5, 2021
f420f5c
testing demo
courdegard Nov 5, 2021
65edd81
new repo dir
Nov 8, 2021
b563814
no more unbounded arrays 1/2
Nov 9, 2021
eeee6ef
big changes on the way
Nov 12, 2021
65cf771
Back in business with the new cluster
courdegard Nov 28, 2021
3a5e397
graphics on the way
courdegard Dec 14, 2021
20f3b68
big changes
courdegard Dec 26, 2021
5c8d7c2
To lappy
courdegard Dec 26, 2021
a11c90f
Not working
courdegard Dec 27, 2021
062eafb
to lappy
courdegard Dec 29, 2021
b59bf56
DESTROYED THE LINTER
courdegard Dec 29, 2021
d902d6b
i hate this
courdegard Dec 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Binary file added .DS_Store
Binary file not shown.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
__pycache__
moodbase-env
.DS_Store
todo.txt
52 changes: 52 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack-internal:///./src/": "${webRoot}/*",
"webpack-internal:///src/*": "${webRoot}/*"
}
},
{
"type": "firefox",
"request": "launch",
"name": "vuejs: firefox",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/src",
"pathMappings": [{ "url": "webpack:///src/", "path": "${webRoot}/" }]
},
{
"name": "Launch via NPM",
"request": "launch",
"runtimeArgs": [
"run-script",
"debug"
],
"runtimeExecutable": "npm",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Python: FastAPI",
"type": "python",
"request": "launch",
"module": "uvicorn",
"args": [
"main:app"
],
"jinja": true,
"justMyCode":false,

}
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintArgs": [
"--disable=C0103"
]
}
Binary file added __pycache__/db.cpython-39.pyc
Binary file not shown.
Binary file added __pycache__/main.cpython-39.pyc
Binary file not shown.
4 changes: 4 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
moodbase-env
.DS_Store
.env
File renamed without changes.
10 changes: 10 additions & 0 deletions backend/build/lib/models/groupModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pydantic import BaseModel

class GroupData(BaseModel):
name:str
memberDict:dict
colors:dict
weightButtons:dict
adminUser:str
timeInterval:int
password:str
44 changes: 44 additions & 0 deletions backend/build/lib/models/plotModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import ValuesView
from pydantic import BaseModel, Field
from datetime import datetime, timezone


# remember x value is happiness y value is calm
class PlotDataSubmission(BaseModel):
lineChart: dict
clickMap: dict
timestamp = datetime.now(timezone.utc)

def truncateCoordinates(self):
for key, value in self.lineChart.items():
self.lineChart[key] = round(value,1)
for key,value in self.clickMap.items():
self.clickMap[key]=int(value)
return True

def returnList(self):
listOfValues = []
for value in self.clickMap.values():
listOfValues.append(value)
for value in self.lineChart.values():
listOfValues.append(value)
return listOfValues

def prepareForCommunityData(self,key):
communityEntry=getattr(self,key)
communityEntry['day']=datetime.today()
return communityEntry

class UserPlotData(BaseModel):
user:str
dictWithLists:dict

class UserColorChoices(BaseModel):
happyColor:dict
calmColor:dict
sadColor:dict
anxiousColor:dict

class UserColorChange(BaseModel):
colorChange:dict
variable:str
20 changes: 20 additions & 0 deletions backend/build/lib/models/userModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Optional
from db import userData
from pydantic import BaseModel, EmailStr, Field

class TestModel(BaseModel):
testing:str

class User(BaseModel):
username: str
password: str
email: Optional[EmailStr]
active= False

class ActivationModel(BaseModel):
token:str

class ResetModel(BaseModel):
email:Optional[EmailStr]
token:Optional[str]
newPassword:Optional[str]
7 changes: 7 additions & 0 deletions backend/build/lib/models/weightModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pydantic import BaseModel
from datetime import datetime,timezone

class WeightData(BaseModel):
name:str
value:int
delta:int
160 changes: 160 additions & 0 deletions backend/build/lib/modules/AuthenticationModule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# database stuff
from db import userData,bannedKeys,aggregateData, groupData
from models.userModel import User
from modules.UserAggregationUtilities import updateUserCountOnActivation

# email stuff
import modules.EmailModule as email

# dependency injection stuff
from fastapi import HTTPException, Depends,BackgroundTasks, Security, Header

# jwt and password stuff
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime,timedelta,timezone

# environment variable and bearer stuff
from config import Secret
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from dotenv import load_dotenv
load_dotenv('.env')
from decouple import config

security = HTTPBearer()

secret=config('secret')
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def gate(refresh: str = Header(None), userAuth: HTTPAuthorizationCredentials = Security(security)):
try:
return getUserFromToken(userAuth.credentials)
except:
raise HTTPException(status_code=401,detail='invalid token')

def verifyUserAtLogin(loginAttempt: User) -> User:
checkUsername={'username':loginAttempt['username']}
if userData.find(checkUsername).limit(1).count()<1:
return False
else:
userToCheck=userData.find_one(checkUsername)
if pwd_context.verify(loginAttempt['password'].encode('utf-8'), userToCheck['password']):
return userToCheck
else:
return False

def createAccessToken(subName: str,*args) -> dict():
payload = {
'exp' : datetime.now(timezone.utc) + timedelta(days=args[0], hours=args[1], minutes=args[2]),
'iat' : datetime.now(tz=timezone.utc),
'sub' : subName }
return jwt.encode(
payload,
secret,
algorithm='HS256'
)

def getUserFromToken(tokenToUser:str)-> str:
decoded = jwt.decode(tokenToUser, secret, 'HS256')
userStr=decoded['sub']
return userStr

def checkToken(tokenToCheck:str)->str:
try:
jwt.decode(tokenToCheck,secret,'HS256')
return tokenToCheck
except:
raise HTTPException(status_code=401,detail='invalid token')

def addUserToDatabase(user:User) -> dict:
checkUsername={'username':user.username}
# checkEmail={'email':user.email}
if userData.find(checkUsername).limit(1).count()>0:
raise HTTPException(status_code=404,detail="User already exists with this ID or email address")
else:
userToDB=user.dict()
userToDB['groupMemberships']={}
userToDB['password']=pwd_context.hash(user.password)
userData.insert_one(userToDB)
activationToken=createAccessToken(user.username,2,0,0)
url=createUrlForEmail("activate",activationToken)
activationSpecs={'urlDict':{'url':url},'subject':'Activation email',
'recipient':user.email,'template':'ActivationEmail.html'}
return activationSpecs

def createUrlForEmail(route: str,token:str) -> str:
url=f"http://localhost:8080/{route}/{token}"
return url

def activateUser(tokenDict: dict) -> dict:
user=getUserFromToken(tokenDict.token)
userData.update_one({'username':user},{'$set':{'active':True}})
updateUserCountOnActivation()
activationToken=createAccessToken(user,0,0,30)
return activationToken

def sendResetEmail(emailObj):
lookupEmail={'email':emailObj.email}
user=userData.find_one(lookupEmail)
forgotToken=createAccessToken(user,0,0,60)
url=createUrlForEmail("reset",forgotToken)
emailSpecifications={
'recipient':emailObj.email,
'urlDict': {'url':url},
'subject':"Reset password email",
'template':'ResetEmail.html'}
return emailSpecifications

def resetPassword(newPassword):
resetToken=newPassword.token
user=getUserFromToken(resetToken)
if not user: raise HTTPException(status_code=404,detail='Did not work')
userData.update_one({'username':user},
{'$set':{'password':pwd_context.hash(newPassword.newPassword)}})
return {'message':'password reset successfully'}

def attemptRefresh(refreshToken):
user=getUserFromToken(refreshToken)
if user:
return {'access_token':createAccessToken(user,0,0,1),'token_type':'bearer'}
else:
raise HTTPException(status_code=401,detail='invalid token')

def banKeyTTL(tokenToBan):
try:
expiry = createTTL(tokenToBan)
bannedKeys.insert_one({'expires':expiry,'tokenValue':tokenToBan})
return True
except:
raise HTTPException(status_code=418, detail="Something happened")

def createTTL(token:str):
decoded = jwt.decode(token,secret,'HS256')
expDT = datetime.fromtimestamp(decoded['exp'],tz=timezone.utc)
return expDT
# timeToExpire = (expDT - datetime.now(timezone.utc)).total_seconds()
# return timeToExpire

def sendGroupInvitations(groupName:str,membersToInvite:dict):
for member in membersToInvite.keys():
email.sendEmailBackground({
'recipient':userData.find_one({'user':member})['email'],
'urlDict':{
'url':createUrlForEmail("groups/"+groupName,createAccessToken(member)),
'groupName':groupName
},
'subject':'Your invitation to a moodgroup',
'template':'GroupInvitation.html'
})
return True

def handleGroupLogin(userToken:str,groupName:str):
userToCheck= userData.find_one (
{'username': getUserFromToken(userToken)}
)
groupToCheck = groupData.find_one(
{'name':groupName})
if 'groupName' in userToCheck['groupMemberships'] and + userToCheck['username'] in groupToCheck['memberDict' ]:
return True
else:
raise HTTPException(status_code=404,detail='You are not currently a member of this group. Speak to the group administrator to regain access.')
59 changes: 59 additions & 0 deletions backend/build/lib/modules/EmailModule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from fastapi import BackgroundTasks
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig
from pathlib import Path
from dotenv import load_dotenv
load_dotenv('.env')
from decouple import config

# environment variables
class Envs:
MAIL_USERNAME = config('MAIL_USERNAME')
MAIL_PASSWORD = config('MAIL_PASSWORD')
MAIL_FROM = config('MAIL_FROM')
MAIL_PORT = int(config('MAIL_PORT'))
MAIL_SERVER = config('MAIL_SERVER')
MAIL_FROM_NAME = config('MAIL_FROM_NAME')

# mail config

mailer = ConnectionConfig(
MAIL_USERNAME=Envs.MAIL_USERNAME,
MAIL_PASSWORD=Envs.MAIL_PASSWORD,
MAIL_FROM=Envs.MAIL_FROM,
MAIL_PORT=Envs.MAIL_PORT,
MAIL_SERVER=Envs.MAIL_SERVER,
MAIL_FROM_NAME=Envs.MAIL_FROM_NAME,
MAIL_TLS=True,
MAIL_SSL=False,
USE_CREDENTIALS=True,
# TEMPLATE_FOLDER=Path(__file__).parent.parent / "templates"
)

# mail functions

async def send_email_async(emailSpecs):
recipient="danbidikov@gmail.com"
# template_body={'url':url}
message = MessageSchema(
subject=emailSpecs['subject'],
recipients=[recipient],
template_body=emailSpecs['urlDict'],
subtype='html',
)

fm = FastMail(mailer)
await fm.send_message(message,template_name=emailSpecs['template'])

async def sendEmailBackground(emailSpecs:dict):
recipient="danbidikov@gmail.com"
message = MessageSchema(
subject=emailSpecs['subject'],
recipients=[recipient],
template_body=emailSpecs['urlDict'],
subtype='html',
)
# background_task=BackgroundTasks
fm = FastMail(mailer)
await fm.send_message( message, template_name=emailSpecs['template'])

# return {'message':'email has been sent'}
Loading