mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-08 15:33:53 -05:00
Encrypt Strava secrets with Fernet, update frontend logic
[backend] added new ENV variable "FERNET_KEY" [backend] added new cryptography file to core logic to handle Fernet crypt [backend] Strava secrets are now stored with encryption at rest using a fernet key [frontend] Frontend no longer deletes Strava client id and secret after linking
This commit is contained in:
@@ -22,6 +22,7 @@ def check_required_env_vars():
|
||||
"DB_PASSWORD",
|
||||
"DB_DATABASE",
|
||||
"SECRET_KEY",
|
||||
"FERNET_KEY",
|
||||
"ALGORITHM",
|
||||
"ACCESS_TOKEN_EXPIRE_MINUTES",
|
||||
"REFRESH_TOKEN_EXPIRE_DAYS",
|
||||
|
||||
63
backend/app/core/cryptography.py
Normal file
63
backend/app/core/cryptography.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
from cryptography.fernet import Fernet
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
def create_fernet_cipher():
|
||||
try:
|
||||
# Get the key from environment variable and encode it to bytes
|
||||
key = os.environ["FERNET_KEY"].encode()
|
||||
return Fernet(key)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in encrypt_token_fernet: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
|
||||
|
||||
def encrypt_token_fernet(token: str) -> str:
|
||||
try:
|
||||
# Create a Fernet cipher
|
||||
cipher = create_fernet_cipher()
|
||||
|
||||
# Encrypt the token
|
||||
return cipher.encrypt(token.encode())
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in encrypt_token_fernet: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
|
||||
|
||||
def decrypt_token_fernet(encrypted_token: str) -> str:
|
||||
try:
|
||||
# Create a Fernet cipher
|
||||
cipher = create_fernet_cipher()
|
||||
|
||||
# Encrypt the token
|
||||
return cipher.decrypt(encrypted_token).decode()
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
f"Error in decrypt_token_fernet: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
@@ -18,6 +18,7 @@ import strava.activity_utils as strava_activity_utils
|
||||
import strava.utils as strava_utils
|
||||
import strava.schema as strava_schema
|
||||
|
||||
import core.cryptography as core_cryptography
|
||||
import core.logger as core_logger
|
||||
import core.database as core_database
|
||||
|
||||
@@ -64,8 +65,8 @@ async def strava_link(
|
||||
|
||||
# Exchange code for token
|
||||
tokens = strava_client.exchange_code_for_token(
|
||||
client_id=user_integrations.strava_client_id,
|
||||
client_secret=user_integrations.strava_client_secret,
|
||||
client_id=core_cryptography.decrypt_token_fernet(user_integrations.strava_client_id),
|
||||
client_secret=core_cryptography.decrypt_token_fernet(user_integrations.strava_client_secret),
|
||||
code=code,
|
||||
)
|
||||
|
||||
@@ -81,9 +82,9 @@ async def strava_link(
|
||||
f"Unable to link Strava account: {err}", "error", exc=err
|
||||
)
|
||||
|
||||
# Clean up by setting Strava client to None
|
||||
user_integrations_crud.set_user_strava_client(
|
||||
user_integrations.user_id, None, None, db
|
||||
# Clean up by setting Strava
|
||||
user_integrations_crud.unlink_strava_account(
|
||||
user_integrations.user_id, db
|
||||
)
|
||||
|
||||
# Raise an HTTPException with appropriate status code
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from stravalib.client import Client
|
||||
|
||||
import core.cryptography as core_cryptography
|
||||
import core.logger as core_logger
|
||||
|
||||
import activities.activity.schema as activities_schema
|
||||
@@ -62,6 +61,16 @@ def create_strava_client(
|
||||
) -> Client:
|
||||
# Create a Strava client with the user's access token and return it
|
||||
return Client(
|
||||
access_token=user_integrations.strava_token,
|
||||
refresh_token=user_integrations.strava_refresh_token,
|
||||
access_token=(
|
||||
core_cryptography.decrypt_token_fernet(user_integrations.strava_token)
|
||||
if user_integrations.strava_token
|
||||
else None
|
||||
),
|
||||
refresh_token=(
|
||||
core_cryptography.decrypt_token_fernet(
|
||||
user_integrations.strava_refresh_token
|
||||
)
|
||||
if user_integrations.strava_refresh_token
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
import users.user_integrations.schema as user_integrations_schema
|
||||
import users.user_integrations.models as user_integrations_models
|
||||
|
||||
import core.cryptography as core_cryptography
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
@@ -44,7 +45,7 @@ def get_user_integrations_by_strava_state(strava_state: str, db: Session):
|
||||
user_integrations = (
|
||||
db.query(user_integrations_models.UsersIntegrations)
|
||||
.filter(
|
||||
user_integrations_models.UsersIntegrations.strava_state == strava_state
|
||||
user_integrations_models.UsersIntegrations.strava_state == core_cryptography.encrypt_token_fernet(strava_state)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
@@ -106,15 +107,13 @@ def link_strava_account(
|
||||
):
|
||||
try:
|
||||
# Update the user integrations with the tokens
|
||||
user_integrations.strava_token = tokens["access token"]
|
||||
user_integrations.strava_refresh_token = tokens["refresh token"]
|
||||
user_integrations.strava_token = core_cryptography.encrypt_token_fernet(tokens["access_token"])
|
||||
user_integrations.strava_refresh_token = core_cryptography.encrypt_token_fernet(tokens["refresh_token"])
|
||||
user_integrations.strava_token_expires_at = datetime.fromtimestamp(
|
||||
tokens["expires_at"]
|
||||
)
|
||||
|
||||
# Set the strava state, client ID and client Secret to None
|
||||
user_integrations.strava_state = None
|
||||
user_integrations.strava_client_id = None
|
||||
# Set the strava state to None
|
||||
user_integrations.strava_client_secret = None
|
||||
|
||||
# Commit the changes to the database
|
||||
@@ -149,6 +148,8 @@ def unlink_strava_account(user_id: int, db: Session):
|
||||
)
|
||||
|
||||
# Set the user integrations Strava tokens to None
|
||||
user_integrations.strava_state = None
|
||||
user_integrations.strava_client_id = None
|
||||
user_integrations.strava_token = None
|
||||
user_integrations.strava_refresh_token = None
|
||||
user_integrations.strava_token_expires_at = None
|
||||
@@ -186,8 +187,8 @@ def set_user_strava_client(user_id: int, id: int, secret: str, db: Session):
|
||||
)
|
||||
|
||||
# Set the user Strava client id and secret
|
||||
user_integrations.strava_client_id = id
|
||||
user_integrations.strava_client_secret = secret
|
||||
user_integrations.strava_client_id = core_cryptography.encrypt_token_fernet(id)
|
||||
user_integrations.strava_client_secret = core_cryptography.encrypt_token_fernet(secret)
|
||||
|
||||
# Commit the changes to the database
|
||||
db.commit()
|
||||
@@ -224,7 +225,7 @@ def set_user_strava_state(user_id: int, state: str, db: Session):
|
||||
)
|
||||
|
||||
# Set the user Strava state
|
||||
user_integrations.strava_state = state
|
||||
user_integrations.strava_state = core_cryptography.encrypt_token_fernet(state)
|
||||
|
||||
# Commit the changes to the database
|
||||
db.commit()
|
||||
|
||||
@@ -70,6 +70,7 @@ ENV UID=1000 \
|
||||
DB_PASSWORD="changeme" \
|
||||
DB_DATABASE="endurain" \
|
||||
SECRET_KEY="changeme" \
|
||||
FERNET_KEY="changeme" \
|
||||
ALGORITHM="HS256" \
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=15 \
|
||||
REFRESH_TOKEN_EXPIRE_DAYS=7 \
|
||||
|
||||
@@ -31,7 +31,8 @@ Environment variable | Default value | Optional | Notes |
|
||||
| DB_USER | endurain | Yes | N/A |
|
||||
| DB_PASSWORD | changeme | `No` | N/A |
|
||||
| DB_DATABASE | endurain | Yes | N/A |
|
||||
| SECRET_KEY | changeme | `No` | Run "openssl rand -hex 32" on a terminal to get a secret |
|
||||
| SECRET_KEY | changeme | `No` | Run `openssl rand -hex 32` on a terminal to get a secret |
|
||||
| FERNET_KEY | changeme | `No` | Run `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` on a terminal to get a secret or go to [https://fernetkeygen.com](https://fernetkeygen.com). Example output is `7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y=` |
|
||||
| ALGORITHM | HS256 | Yes | Currently only HS256 is supported |
|
||||
| ACCESS_TOKEN_EXPIRE_MINUTES | 15 | Yes | Time in minutes |
|
||||
| REFRESH_TOKEN_EXPIRE_DAYS | 7 | Yes | Time in days |
|
||||
|
||||
@@ -181,7 +181,6 @@ export default {
|
||||
|
||||
try {
|
||||
await strava.setUniqueUserStateStravaLink(null);
|
||||
await strava.setUserStravaClientSettings(null, null);
|
||||
} catch (error) {
|
||||
// If there is an error, show the error alert.
|
||||
push.error(
|
||||
|
||||
Reference in New Issue
Block a user