-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 17bf5a3
Showing
25 changed files
with
1,677 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: Deploy Dev | ||
|
||
on: | ||
workflow_run: | ||
workflows: ["Python tests"] | ||
branches: [main] | ||
types: | ||
- completed | ||
workflow_dispatch: | ||
branches: | ||
- kip/* | ||
|
||
env: | ||
AWS_REGION: eu-west-2 # set this to your preferred AWS region, e.g. us-west-1 | ||
|
||
jobs: | ||
deploy-dev: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Configure AWS credentials | ||
uses: aws-actions/configure-aws-credentials@v1 | ||
with: | ||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
aws-region: ${{ env.AWS_REGION }} | ||
- name: Install Copilot CLI | ||
uses: ksivamuthu/aws-copilot-github-action@v0.0.1 | ||
with: | ||
command: install | ||
- run: | | ||
copilot --version | ||
copilot deploy | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Python tests | ||
|
||
on: [push] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.12' | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install pipenv | ||
pipenv install --dev | ||
- name: Lint with ruff | ||
run: | | ||
# stop the build if there are Python syntax errors or undefined names | ||
pipenv run ruff . | ||
# - name: Run tests | ||
# run: | | ||
# # stop the build if there are Python syntax errors or undefined names | ||
# pipenv run pytest . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
*.sqlite | ||
*.pyc | ||
venv/* | ||
instance/ | ||
.venv/ | ||
.mypy_cache/ | ||
.env | ||
|
||
.env* | ||
.pytest_cache | ||
__pycache__ | ||
.DS_Store | ||
.ruff_cache | ||
.mypy_cache | ||
|
||
certs | ||
requirements.txt | ||
.python-version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
|
||
[dev-packages] | ||
|
||
[requires] | ||
python_version = "3.12" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Perseus demo authentication api | ||
|
||
Emulates authentication endpoints for the Perseus demo. These endpoints in production will be provided by an energy provider's authentication platform. Api documentation is available at https://perseus-demo-authentication.ib1.org/api-docs. | ||
|
||
The authentication api is responsible for authenticating and identifying its own users, and for handling and passing on requests from the client API to the FAPI API. | ||
|
||
## Run the dev server | ||
|
||
```bash | ||
pipenv install --dev | ||
pipenv run uvicorn api.main:app --reload | ||
``` | ||
|
||
## Running the local docker environment | ||
|
||
```bash | ||
docker-compose up | ||
``` | ||
|
||
The docker environment uses nginx to proxy requests to uvicorn, with nginx configuration to pass through client certificates to the backend, using the same header as used by AWS ALB (`x-amzn-mtls-clientcert`). It requires a set of certificates as generated by [scripts/certmaker.sh], which should be available in the `certs` directory. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
**/* | ||
!Pipfile | ||
!Pipfile.lock | ||
!api/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM python:3.12-slim | ||
RUN pip install pipenv | ||
COPY Pipfile* /code/ | ||
WORKDIR /code | ||
RUN pipenv install --system --deploy --ignore-pipfile | ||
COPY ./api /code/api | ||
EXPOSE 8000 | ||
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8080"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[[source]] | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
name = "pypi" | ||
|
||
[packages] | ||
fastapi = "*" | ||
pyjwt = "*" | ||
uvicorn = "*" | ||
requests = "*" | ||
python-jose = {extras = ["cryptography"] } | ||
passlib = {extras = ["bcrypt"] } | ||
python-multipart = "*" | ||
|
||
[dev-packages] | ||
black = "*" | ||
mypy = "*" | ||
ruff = "*" | ||
types-python-jose = "*" | ||
types-passlib = "*" | ||
types-requests = "*" | ||
|
||
[requires] | ||
python_version = "3.12" |
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
from datetime import datetime, timedelta, timezone | ||
from typing import Annotated | ||
import bcrypt | ||
|
||
from fastapi import Depends, HTTPException, status | ||
from fastapi.security import OAuth2PasswordBearer | ||
from jose import JWTError, jwt | ||
|
||
from . import models | ||
|
||
# to get a string like this run: | ||
# openssl rand -hex 32 | ||
SECRET_KEY = "059300339d8e2d0bc4405ceaf1c28f95c0f81ec6b73d7bce2cb36bd1a597512e" | ||
ALGORITHM = "HS256" | ||
ACCESS_TOKEN_EXPIRE_MINUTES = 30 | ||
|
||
|
||
fake_users_db = { | ||
"platform_user": { | ||
"username": "platform_user", | ||
"full_name": "Platform User", | ||
"email": "user@platform.org", | ||
"hashed_password": "$2b$12$P1c1ltZ37o8db.cXooGn7.w9GjLhbx/y7uAvirrkm1NtnFwL7Lgpm", # 'perseus' | ||
"disabled": False, | ||
} | ||
} | ||
|
||
|
||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") | ||
|
||
|
||
# Hash a password using bcrypt | ||
def get_password_hash(password): | ||
pwd_bytes = password.encode("utf-8") | ||
salt = bcrypt.gensalt() | ||
hashed_password = bcrypt.hashpw(password=pwd_bytes, salt=salt) | ||
return hashed_password | ||
|
||
|
||
# Check if the provided password matches the stored password (hashed) | ||
def verify_password(plain_password, hashed_password): | ||
password_byte_enc = plain_password.encode("utf-8") | ||
return bcrypt.checkpw(password=password_byte_enc, hashed_password=hashed_password) | ||
|
||
|
||
def get_user(db, username: str): | ||
if username in db: | ||
user_dict = db[username] | ||
return models.UserInDB(**user_dict) | ||
|
||
|
||
def authenticate_user(username: str, password: str): | ||
user = get_user(fake_users_db, username) | ||
if not user: | ||
return False | ||
if not verify_password(password, user.hashed_password.encode("utf-8")): | ||
return False | ||
return user | ||
|
||
|
||
def create_access_token(data: dict, expires_delta: timedelta | None = None): | ||
to_encode = data.copy() | ||
if expires_delta: | ||
expire = datetime.now(timezone.utc) + expires_delta | ||
else: | ||
expire = datetime.now(timezone.utc) + timedelta(minutes=15) | ||
to_encode.update({"exp": expire}) | ||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) | ||
return encoded_jwt | ||
|
||
|
||
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): | ||
credentials_exception = HTTPException( | ||
status_code=status.HTTP_401_UNAUTHORIZED, | ||
detail="Could not validate credentials", | ||
headers={"WWW-Authenticate": "Bearer"}, | ||
) | ||
try: | ||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) | ||
username: str | None = payload.get("sub") | ||
if username is None: | ||
raise credentials_exception | ||
token_data = models.UserTokenData(username=username) | ||
except JWTError: | ||
raise credentials_exception | ||
if token_data.username is None: | ||
raise credentials_exception | ||
user = get_user(fake_users_db, username=token_data.username) | ||
if user is None: | ||
raise credentials_exception | ||
return user | ||
|
||
|
||
async def get_current_active_user( | ||
current_user: Annotated[models.User, Depends(get_current_user)] | ||
): | ||
if current_user.disabled: | ||
raise HTTPException(status_code=400, detail="Inactive user") | ||
return current_user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import os | ||
|
||
FAPI_API = os.environ.get("FAPI_API", "https://perseus-demo-fapi.ib1.org") | ||
CLIENT_ID = "21653835348762" | ||
CLIENT_SECRET = "uE4NgqeIpuSV_XejQ7Ds3jsgA1yXhjR1MXJ1LbPuyls" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from pydantic.config import JsonDict | ||
|
||
CLIENT_ID = "21653835348762" | ||
CLIENT_SECRET = "uE4NgqeIpuSV_XejQ7Ds3jsgA1yXhjR1MXJ1LbPuyls" | ||
|
||
CLIENT_CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIDPDCCAiQCCQDWNMOIuzwDfzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xEzARBgNVBAcMCkNoaXlvZGEta3UxDzANBgNVBAoM\nBkNsaWVudDEbMBkGA1UEAwwSY2xpZW50LmV4YW1wbGUub3JnMB4XDTE5MTAyODA3\nMjczMFoXDTIwMTAyNzA3MjczMFowYDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRv\na3lvMRMwEQYDVQQHDApDaGl5b2RhLWt1MQ8wDQYDVQQKDAZDbGllbnQxGzAZBgNV\nBAMMEmNsaWVudC5leGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAK2Oyc+BV4N5pYcp47opUwsb2NaJq4X+d5Itq8whpFlZ9uCCHzF5TWSF\nXrpYscOp95veGPF42eT1grfxYyvjFotE76caHhBLCkIbBh6Vf222IGMwwBbSZfO9\nJ3eURtEADBvsZ117HkPVdjYqvt3Pr4RxdR12zG1TcBAoTLGchyr8nBqRADFhUTCL\nmsYaz1ADiQ/xbJN7VUNQpKhzRWHCdYS03HpbGjYCtAbl9dJnH2EepNF0emGiSPFq\ndf6taToyCr7oZjM7ufmKPjiiEDbeSYTf6kbPNmmjtoPNNLeejHjP9p0IYx7l0Gkj\nmx4kSMLp4vSDftrFgGfcxzaMmKBsosMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA\nqzdDYbntFLPBlbwAQlpwIjvmvwzvkQt6qgZ9Y0oMAf7pxq3i9q7W1bDol0UF4pIM\nz3urEJCHO8w18JRlfOnOENkcLLLntrjOUXuNkaCDLrnv8pnp0yeTQHkSpsyMtJi9\nR6r6JT9V57EJ/pWQBgKlN6qMiBkIvX7U2hEMmhZ00h/E5xMmiKbySBiJV9fBzDRf\nmAy1p9YEgLsEMLnGjKHTok+hd0BLvcmXVejdUsKCg84F0zqtXEDXLCiKcpXCeeWv\nlmmXxC5PH/GEMkSPiGSR7+b1i0sSotsq+M3hbdwabpJ6nQLLbKkFSGcsQ87yL+gr\nSo6zun26vAUJTu1o9CIjxw==\n-----END CERTIFICATE-----\n" | ||
CLIENT_PUSHED_AUTHORIZATION_REQUEST: JsonDict = { | ||
"response_type": "code", | ||
"client_id": "3280859750204", | ||
"redirect_uri": "https://mobile.example.com/cb", | ||
"code_challenge": "W78hCS0q72DfIHa...kgZkEJuAFaT4", | ||
"code_challenge_method": "S256", | ||
} | ||
|
||
PUSHED_AUTHORIZATION_REQUEST: JsonDict = { | ||
"parameters": "response_type=code&client_id=3280859750204&redirect_uri=https%3A%2F%2Fmobile.example.com%2Fcb&code_challenge=W78hCS0q72DfIHa...kgZkEJuAFaT4&code_challenge_method=S256", | ||
"clientId": CLIENT_ID, | ||
"clientCertificate": CLIENT_CERTIFICATE, | ||
} | ||
|
||
|
||
PUSHED_AUTHORIZATION_RESPONSE: JsonDict = { | ||
"expires_in": 600, | ||
"request_uri": "urn:ietf:params:oauth:request_uri:UymBrux4ZEMrBRKx9UyKyIm98zpX1cHmAPGAGNofmm4", | ||
} | ||
|
||
AUTHORIZATION_REQUEST: JsonDict = { | ||
"client_id": 3280859750204, | ||
"request_uri": "urn:ietf:params:oauth:request_uri:UymBrux4ZEMrBRKx9UyKyIm98zpX1cHmAPGAGNofmm4", | ||
} | ||
|
||
AUTHORIZATION_RESPONSE: JsonDict = { | ||
"message": "Authorisation code request issued", | ||
"ticket": "b0JGD-ZkT8ElBGw2ck-T-t87Z033jXvhqC2omPT1bQ4", | ||
} | ||
|
||
ISSUE_RESPONSE: JsonDict = { | ||
"type": "authorizationIssueResponse", | ||
"resultCode": "A040001", | ||
"resultMessage": "[A040001] The authorization request was processed successfully.", | ||
"accessTokenDuration": 0, | ||
"accessTokenExpiresAt": 0, | ||
"action": "LOCATION", | ||
"authorizationCode": "DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70", | ||
"idToken": "eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwNTk4NzgxNjQ5MCJdLCJjX2hhc2giOiJqR2kyOElvYm5HcjNNQ3Y0UUVQRTNnIiwiaXNzIjoiaHR0cHM6Ly9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMjY4MiwiaWF0IjoxNTcyMzI2MjgyLCJub25jZSI6Im4tMFM2X1d6QTJNaiJ9.1PFmc0gAsBWtLBriq3z9a4Tsi_ioEYlOqOYbicGEXWIS1WGX5ffGOyZNSzVBMamZbltZmSys0jlYmmYYLqgGsg", | ||
"responseContent": ( | ||
"https://client.example.org/cb/example.com#" | ||
"code=DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70&" | ||
"id_token=eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN" | ||
"Tk4NzgxNjQ5MCJdLCJjX2hhc2giOiJqR2kyOElvYm5HcjNNQ3Y0UUVQRTNnIiwiaXNzIjoiaHR0cHM6L" | ||
"y9hcy5leGFtcGxlLmNvbSIsImV4cCI6MTU3MjQxMjY4MiwiaWF0IjoxNTcyMzI2MjgyLCJub25jZSI6I" | ||
"m4tMFM2X1d6QTJNaiJ9.1PFmc0gAsBWtLBriq3z9a4Tsi_ioEYlOqOYbicGEXWIS1WGX5ffGOyZNSzVB" | ||
"MamZbltZmSys0jlYmmYYLqgGsg" | ||
), | ||
} | ||
|
||
|
||
TOKEN_REQUEST: JsonDict = { | ||
"client_id": f"{CLIENT_ID}", | ||
"parameters": "grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70", | ||
"client_certificate": CLIENT_CERTIFICATE, | ||
} | ||
|
||
TOKEN_RESPONSE: JsonDict = { | ||
"access_token": "SUtEVc3Tj3D3xOdysQtssQxe9egAhI4fimexNVMjRyU", | ||
"id_token": ( | ||
"eyJraWQiOiIxIiwiYWxnIjoiRVMyNTYifQ.eyJzdWIiOiJ0ZXN0dXNlcjAxIiwiYXVkIjpbIjU5MTIwN" | ||
"Tk4NzgxNjQ5MCJdLCJpc3MiOiJodHRwczovL2FzLmV4YW1wbGUuY29tIiwiZXhwIjoxNTcyNDEyNzY5L" | ||
"CJpYXQiOjE1NzIzMjYzNjksIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.9EQojck-Cf2hnKAZWR164kr21" | ||
"o5lPKehvIHyViZgRg4CY_ZGmnyFooG4FCwlZxu-QOTtaDCffCsuCdz4GqknTA" | ||
), | ||
"refresh_token": "tXZjYfoK35I-djg9V3n6s58zsrVqRIzTNMXKIS_wkj8", | ||
} |
Oops, something went wrong.