Skip to content

Commit

Permalink
Reworked Config
Browse files Browse the repository at this point in the history
Switched config to a type validated pydantic setting class
Introduces breaking changes to config file
add support for bitwarden secrets manager
  • Loading branch information
jontyms committed Mar 18, 2024
1 parent a48eb12 commit 574a489
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 163 deletions.
84 changes: 40 additions & 44 deletions index.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
# Import the page rendering library
from util.kennelish import Kennelish
# Import options
from util.options import Options
from util.options import Settings
from util.forms import Forms

### TODO: TEMP
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "0"
###
settings = Settings()

options = Options.fetch()

# Initiate FastAPI.
app = FastAPI()
Expand All @@ -54,9 +55,9 @@
f"""clouds:
hackucf_infra:
auth:
auth_url: {options.get('infra', {}).get('horizon', '')}:5000
application_credential_id: {options.get('infra', {}).get('ad', {}).get('application_credential_id', '')}
application_credential_secret: {options.get('infra', {}).get('ad', {}).get('application_credential_secret', '')}
auth_url: {Settings().infra.horizon}:5000
application_credential_id: {Settings().infra.application_credential_id}
application_credential_secret: {Settings().infra.application_credential_secret.get_secret_value()}
region_name: "hack-ucf-0"
interface: "public"
identity_api_version: 3
Expand All @@ -78,15 +79,15 @@ async def index(request: Request, token: Optional[str] = Cookie(None)):
infra_email = None

try:
payload = jwt.decode(
user_jwt = jwt.decode(
token,
options.get("jwt").get("secret"),
algorithms=options.get("jwt").get("algorithm"),
Settings().jwt.secret.get_secret_value(),
algorithms=Settings().jwt.algorithm,
)
is_full_member: bool = payload.get("is_full_member", False)
is_admin: bool = payload.get("sudo", False)
user_id: bool = payload.get("id", None)
infra_email: bool = payload.get("infra_email", None)
is_full_member: bool = user_jwt.get("is_full_member", False)
is_admin: bool = user_jwt.get("sudo", False)
user_id: bool = user_jwt.get("id", None)
infra_email: bool = user_jwt.get("infra_email", None)
except Exception as e:
print(e)
pass
Expand Down Expand Up @@ -114,15 +115,13 @@ async def oauth_transformer(redir: str = "/join/2"):
# Open redirect check
hostname = urlparse(redir).netloc
print(hostname)
if hostname != "" and hostname != options.get("http", {}).get(
"domain", "my.hackucf.org"
):
if hostname != "" and hostname != Settings().http.domain:
redir = "/join/2"

oauth = OAuth2Session(
options.get("discord").get("client_id"),
redirect_uri=options.get("discord").get("redirect_base") + "_redir",
scope=options.get("discord").get("scope"),
Settings().discord.client_id,
redirect_uri=Settings().discord.redirect_base + "_redir",
scope=Settings().discord.scope,
)
authorization_url, state = oauth.authorization_url(
"https://discord.com/api/oauth2/authorize"
Expand Down Expand Up @@ -151,17 +150,15 @@ async def oauth_transformer_new(
):
# AWS dependencies
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)

# Open redirect check
if redir == "_redir":
redir = redir_endpoint

hostname = urlparse(redir).netloc

if hostname != "" and hostname != options.get("http", {}).get(
"domain", "my.hackucf.org"
):
if hostname != "" and hostname != Settings().http.domain:
redir = "/join/2"

if code is None:
Expand All @@ -174,15 +171,15 @@ async def oauth_transformer_new(

# Get data from Discord
oauth = OAuth2Session(
options.get("discord").get("client_id"),
redirect_uri=options.get("discord").get("redirect_base") + "_redir",
scope=options.get("discord")["scope"],
Settings().discord.client_id,
redirect_uri=Settings().discord.redirect_base + "_redir",
scope=Settings().discord.scope,
)

token = oauth.fetch_token(
"https://discord.com/api/oauth2/token",
client_id=options.get("discord").get("client_id"),
client_secret=options.get("discord").get("secret"),
client_id=Settings().discord.client_id,
client_secret=Settings().discord.secret.get_secret_value(),
# authorization_response=code
code=code,
)
Expand Down Expand Up @@ -231,20 +228,20 @@ async def oauth_transformer_new(
# Make user join the Hack@UCF Discord, if it's their first rodeo.
discord_id = str(discordData["id"])
headers = {
"Authorization": f"Bot {options.get('discord', {}).get('bot_token')}",
"Authorization": f"Bot {Settings().discord.bot_token.get_secret_value()}",
"Content-Type": "application/json",
"X-Audit-Log-Reason": "Hack@UCF OnboardLite Bot",
}
put_join_guild = {"access_token": token["access_token"]}
requests.put(
f"https://discordapp.com/api/guilds/{options.get('discord', {}).get('guild_id')}/members/{discord_id}",
f"https://discordapp.com/api/guilds/{Settings().discord.guild_id}/members/{discord_id}",
headers=headers,
data=json.dumps(put_join_guild),
)

data = {
"id": member_id,
"discord_id": int(discordData["id"]),
"discord_id": discordData["id"],
"discord": {
"email": discordData["email"],
"mfa": discordData["mfa_enabled"],
Expand Down Expand Up @@ -286,8 +283,8 @@ async def oauth_transformer_new(
}
bearer = jwt.encode(
jwtData,
options.get("jwt").get("secret"),
algorithm=options.get("jwt").get("algorithm"),
Settings().jwt.secret.get_secret_value(),
algorithm=Settings().jwt.algorithm,
)
rr = RedirectResponse(redir, status_code=status.HTTP_302_FOUND)
rr.set_cookie(key="token", value=bearer)
Expand Down Expand Up @@ -321,17 +318,16 @@ async def join(request: Request, token: Optional[str] = Cookie(None)):
async def profile(
request: Request,
token: Optional[str] = Cookie(None),
payload: Optional[object] = {},
user_jwt: Optional[object] = {},
):
# Get data from DynamoDB
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
print(token)
table = dynamodb.Table(Settings().aws.table)

user_data = table.get_item(Key={"id": payload.get("id")}).get("Item", None)
user_data = table.get_item(Key={"id": user_jwt.get("id")}).get("Item", None)

# Re-run approval workflow.
Approve.approve_member(payload.get("id"))
Approve.approve_member(user_jwt.get("id"))

return templates.TemplateResponse(
"profile.html", {"request": request, "user_data": user_data}
Expand All @@ -348,20 +344,20 @@ async def profile(
async def forms(
request: Request,
token: Optional[str] = Cookie(None),
payload: Optional[object] = {},
user_jwt: Optional[object] = {},
num: str = 1,
):
# AWS dependencies
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)

if num == "1":
return RedirectResponse("/join/", status_code=status.HTTP_302_FOUND)

data = Options.get_form_body(num)
data = Forms.get_form_body(num)

# Get data from DynamoDB
user_data = table.get_item(Key={"id": payload.get("id")}).get("Item", None)
user_data = table.get_item(Key={"id": user_jwt.get("id")}).get("Item", None)

# Have Kennelish parse the data.
body = Kennelish.parse(data, user_data)
Expand All @@ -371,9 +367,9 @@ async def forms(
"form.html",
{
"request": request,
"icon": payload["pfp"],
"name": payload["name"],
"id": payload["id"],
"icon": user_jwt["pfp"],
"name": user_jwt["name"],
"id": user_jwt["id"],
"body": body,
},
)
Expand Down
5 changes: 2 additions & 3 deletions redacted-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ stripe:
api_key:
webhook_secret:
price_id:
url:
success: "https://join.hackucf.org/final/"
failure: "https://join.hackucf.org/pay/"
url_success: "https://join.hackucf.org/final/"
url_failure: "https://join.hackucf.org/pay/"

aws:
dynamodb:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ black==23.9.1
jinja2
commonmark
redis
pydantic_settings
35 changes: 19 additions & 16 deletions routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import boto3
from boto3.dynamodb.conditions import Attr
from fastapi import APIRouter, Body, Cookie, Request, Response
from fastapi import APIRouter, Body, Cookie, Request, Response, Depends
from fastapi.encoders import jsonable_encoder
from fastapi.templating import Jinja2Templates
from jose import jwt
Expand All @@ -13,15 +13,18 @@
from util.discord import Discord
from util.email import Email
from util.errors import Errors
from util.options import Options
from util.options import Settings

options = Options.fetch()

templates = Jinja2Templates(directory="templates")

router = APIRouter(prefix="/admin", tags=["Admin"], responses=Errors.basic_http())


@router.get("/test")
async def test(request: Request):
return {"msg": Settings().jwt.secret.get_secret_value()}

@router.get("/")
@Authentication.admin
async def admin(request: Request, token: Optional[str] = Cookie(None)):
Expand All @@ -30,8 +33,8 @@ async def admin(request: Request, token: Optional[str] = Cookie(None)):
"""
payload = jwt.decode(
token,
options.get("jwt").get("secret"),
algorithms=options.get("jwt").get("algorithm"),
Settings().jwt.secret.get_secret_value(),
algorithms=Settings().jwt.algorithm,
)
return templates.TemplateResponse(
"admin_searcher.html",
Expand Down Expand Up @@ -66,7 +69,7 @@ async def get_infra(

# Get user data
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)

user_data = table.get_item(Key={"id": member_id}).get("Item", None)

Expand All @@ -75,16 +78,16 @@ async def get_infra(
We are happy to grant you Hack@UCF Private Cloud access!
These credentials can be used to the Hack@UCF Private Cloud. This can be accessed at {options.get('infra', {}).get('horizon')} while on the CyberLab WiFi.
These credentials can be used to the Hack@UCF Private Cloud. This can be accessed at {Settings().infra.horizon} while on the CyberLab WiFi.
```
Username: {creds.get('username', 'Not Set')}
Password: {creds.get('password', f"Please visit https://{options.get('http', {}).get('domain')}/profile and under Danger Zone, reset your Infra creds.")}
Password: {creds.get('password', f"Please visit https://{Settings().http.domain}/profile and under Danger Zone, reset your Infra creds.")}
```
By using the Hack@UCF Infrastructure, you agree to the following EULA located at https://help.hackucf.org/misc/eula
The password for the `Cyberlab` WiFi is currently `{options.get('infra', {}).get('wifi')}`, but this is subject to change (and we'll let you know when that happens).
The password for the `Cyberlab` WiFi is currently `{Settings().infra.wifi}`, but this is subject to change (and we'll let you know when that happens).
Happy Hacking,
- Hack@UCF Bot
Expand Down Expand Up @@ -112,7 +115,7 @@ async def get_refresh(
Approve.approve_member(member_id)

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.get_item(Key={"id": member_id}).get("Item", None)

if not data:
Expand All @@ -135,7 +138,7 @@ async def admin_get_single(
return {"data": {}, "error": "Missing ?member_id"}

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.get_item(Key={"id": member_id}).get("Item", None)

if not data:
Expand All @@ -159,7 +162,7 @@ async def admin_get_snowflake(
return {"data": {}, "error": "Missing ?discord_id"}

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.scan(FilterExpression=Attr("discord_id").eq(str(discord_id))).get(
"Items"
)
Expand Down Expand Up @@ -196,7 +199,7 @@ async def admin_post_discord_message(
return {"data": {}, "error": "Missing ?member_id"}

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.get_item(Key={"id": member_id}).get("Item", None)

if not data:
Expand Down Expand Up @@ -225,7 +228,7 @@ async def admin_edit(
member_id = input_data.id

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
old_data = table.get_item(Key={"id": member_id}).get("Item", None)

if not old_data:
Expand Down Expand Up @@ -255,7 +258,7 @@ async def admin_list(request: Request, token: Optional[str] = Cookie(None)):
API endpoint that dumps all users as JSON.
"""
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.scan().get("Items", None)
return {"data": data}

Expand All @@ -267,7 +270,7 @@ async def admin_list_csv(request: Request, token: Optional[str] = Cookie(None)):
API endpoint that dumps all users as CSV.
"""
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(options.get("aws").get("dynamodb").get("table"))
table = dynamodb.Table(Settings().aws.table)
data = table.scan().get("Items", None)

output = "Membership ID, First Name, Last Name, NID, Is Returning, Gender, Major, Class Standing, Shirt Size, Discord Username, Experience, Cyber Interests, Event Interest, Is C3 Interest, Comments, Ethics Form Timestamp, Minecraft, Infra Email\n"
Expand Down
Loading

0 comments on commit 574a489

Please sign in to comment.