· 5 years ago · Aug 14, 2020, 10:48 PM
1#!/usr/bin/env python3
2
3import os
4import sys
5import argparse
6import logging
7import string
8import random
9import time
10import datetime
11import hashlib
12
13import firebase_admin
14import firebase_admin.firestore
15import firebase_admin.credentials
16
17import typing
18
19import jwt
20import fastapi
21import pydantic
22
23# -------------------------------------------------------
24
25
26class Settings(pydantic.BaseSettings):
27 SERVICE_ACCOUNT: str = os.environ['SERVICE_ACCOUNT']
28 SECRET_KEY: str = os.environ["SECRET_KEY"]
29 RANDOM_KEY: str = "".join(random.choices(string.ascii_letters + string.digits, k=64))
30 TOKEN_EXPIRATION: datetime.timedelta = datetime.timedelta(minutes=60)
31
32# -------------------------------------------------------
33
34# TOKEN=`curl -s --user admin:password http://localhost:5000/token | jq --raw-output .token`
35# curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/protected
36
37
38logger = logging.getLogger(__name__)
39
40settings = Settings()
41security = fastapi.security.HTTPBasic()
42oauth2_scheme = fastapi.security.OAuth2PasswordBearer(tokenUrl="token")
43app = fastapi.FastAPI()
44
45logger.info(f"Configuring credentials with service account file: {settings.SERVICE_ACCOUNT}")
46cert = firebase_admin.credentials.Certificate(settings.SERVICE_ACCOUNT)
47logger.info("Initializing firestore client ...")
48firebase_admin.initialize_app(cert)
49db = firebase_admin.firestore.client()
50
51
52# -------------------------------------------------------
53
54
55async def validate_token(token: str = fastapi.Depends(oauth2_scheme)):
56 try:
57 token_data = jwt.decode(token, settings.SECRET_KEY)
58 except:
59 raise fastapi.HTTPException(status_code=fastapi.status.HTTP_401_UNAUTHORIZED, detail="Invalid token", headers={"WWW-Authenticate": "Bearer"})
60
61 return token_data
62
63
64@app.get('/token')
65def token(credentials: fastapi.security.HTTPBasicCredentials = fastapi.Depends(security)):
66 global db
67
68 if not credentials:
69 raise fastapi.HTTPException(status_code=fastapi.status.HTTP_401_UNAUTHORIZED, detail="Unauthorized.", headers={"WWW-Authenticate": "Basic"})
70
71 try:
72 user_data = db.collection('users').document(credentials.username).get().to_dict()
73 except:
74 raise fastapi.HTTPException(status_code=fastapi.status.HTTP_401_UNAUTHORIZED, detail="Unauthorized.", headers={"WWW-Authenticate": "Basic"})
75
76 if user_data is None: # NOTE: User not found in database
77 raise fastapi.HTTPException(status_code=fastapi.status.HTTP_401_UNAUTHORIZED, detail="Unauthorized.", headers={"WWW-Authenticate": "Basic"})
78
79 digest = hashlib.sha256(credentials.password.encode('UTF-8')).hexdigest()
80
81 if user_data.get("password") != digest:
82 raise fastapi.HTTPException(status_code=fastapi.status.HTTP_401_UNAUTHORIZED, detail="Unauthorized.", headers={"WWW-Authenticate": "Basic"})
83
84 token = jwt.encode(
85 {
86 "user": credentials.username,
87 "exp": datetime.datetime.utcnow() + settings.TOKEN_EXPIRATION
88 },
89 settings.SECRET_KEY
90 )
91 return {"token": token.decode("UTF-8")}
92
93
94@app.get('/ping')
95async def ping():
96 return {"message": "pong"}
97
98
99@app.get('/me')
100async def me(token: None = fastapi.Depends(validate_token)):
101 return token
102
103
104@app.get('/documents')
105async def documents(q: str):
106 global db
107
108 document_id = q
109
110 data = db.collection('documents').document(document_id).get().to_dict()
111 if data is None:
112 raise fastapi.HTTPException(status_code=404, detail="Document not found.")
113
114 return {document_id: data}
115