Skip to content

Commit

Permalink
Merge pull request #637 from hackforla/fix_app_brittle_CORS_config
Browse files Browse the repository at this point in the history
Fix configuration-specific CORS issues & cleanup session endpoints
  • Loading branch information
erikguntner authored Jan 9, 2024
2 parents 6c71316 + 6944201 commit 0e9d6c1
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 68 deletions.
20 changes: 4 additions & 16 deletions api/openapi_server/controllers/auth_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from sqlalchemy.exc import IntegrityError
from sqlalchemy import select

from botocore.exceptions import ClientError

cognito_client_url = 'https://homeuniteus.auth.us-east-1.amazoncognito.com'

Expand Down Expand Up @@ -167,13 +168,7 @@ def signUpCoordinator(): # noqa: E501
msg = f"The parameters you provided are incorrect: {error}"
raise AuthError({"message": msg}, 500)



def signin():
# Validate request data
if connexion.request.is_json:
body = connexion.request.get_json()

def sign_in(body: dict):
secret_hash = current_app.calc_secret_hash(body['email'])

# initiate authentication
Expand All @@ -187,15 +182,8 @@ def signin():
'SECRET_HASH': secret_hash
}
)
except Exception as e:
code = e.response['Error']['Code']
message = e.response['Error']['Message']
status_code = e.response['ResponseMetadata']['HTTPStatusCode']

raise AuthError({
"code": code,
"message": message
}, status_code)
except ClientError as e:
raise AuthError(e.response["Error"], 401)

if(response.get('ChallengeName') and response['ChallengeName'] == 'NEW_PASSWORD_REQUIRED'):
userId = response['ChallengeParameters']['USER_ID_FOR_SRP']
Expand Down
7 changes: 7 additions & 0 deletions api/openapi_server/openapi/parameters/_index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ ProviderId:
type: integer
format: int64
style: simple
SessionCookie:
in: cookie
name: session
schema:
type: string
required: true
description: Session ID provided in a session cookie.
10 changes: 5 additions & 5 deletions api/openapi_server/openapi/paths/auth/authRefresh.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
get:
description: Check for refresh token and current session
operationId: refresh
parameters:
- $ref: '../../parameters/_index.yaml#/SessionCookie'
responses:
"200":
content:
application/json:
schema:
$ref: "../../openapi.yaml#/components/schemas/ApiResponse"
description: successful operation
"401":
description: "Session authentication failed"
tags:
- auth
x-openapi-router-controller: openapi_server.controllers.auth_controller
x-openapi-router-controller: openapi_server.controllers.auth_controller
10 changes: 6 additions & 4 deletions api/openapi_server/openapi/paths/auth/authSession.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
get:
description: Get current session and user information
operationId: current_session
parameters:
- $ref: '../../parameters/_index.yaml#/SessionCookie'
responses:
"200":
content:
application/json:
schema:
$ref: "../../openapi.yaml#/components/schemas/ApiResponse"
description: successful operation
"401":
description: "Session refresh failed"
tags:
- auth
x-openapi-router-controller: openapi_server.controllers.auth_controller
security:
- jwt: []
30 changes: 26 additions & 4 deletions api/openapi_server/openapi/paths/auth/authSignin.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
post:
description: Sign in a user
operationId: signin
operationId: sign_in
requestBody:
content:
application/json:
Expand All @@ -9,18 +9,40 @@ post:
properties:
email:
type: string
format: email
password:
type: string
required:
- email
- password
responses:
'200':
description: Successful login
content:
application/json:
schema:
$ref: '../../openapi.yaml#/components/schemas/ApiResponse'
description: successful operation
type: object
properties:
token:
type: string
description: Access token for the authenticated session
user:
type: object
properties:
email:
type: string
format: email
description: Email of the authenticated user
required:
- token
- user
headers:
Set-Cookie:
description: Session cookie for the authenticated user
schema:
type: string
'401':
description: Authentication failed
tags:
- auth
x-openapi-router-controller: openapi_server.controllers.auth_controller
x-openapi-router-controller: openapi_server.controllers.auth_controller
9 changes: 9 additions & 0 deletions api/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from flask_testing import TestCase

from openapi_server.configs.development import DevelopmentHUUConfig
from openapi_server.models.database import DataAccessLayer
from openapi_server.repositories.service_provider_repository import HousingProviderRepository
from openapi_server.app import create_app
Expand Down Expand Up @@ -53,3 +54,11 @@ def populate_test_database(self, num_entries) -> List[int]:
ids.append(provider.id)
return ids

class TestsWithMockingDisabled(BaseTestCase):
'''
This test suite will be skipped if mocking is enabled.
'''
def setUp(self):
if isinstance(self.app_config, DevelopmentHUUConfig):
pytest.skip("This test suite is only enabled when mocking is disabled")
super().setUpClass()
74 changes: 74 additions & 0 deletions api/tests/test_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import string
from tests import TestsWithMockingDisabled

def strip_punctuation(text):
return text.translate(str.maketrans('', '', string.punctuation))

class TestAuthenticationEndpoints(TestsWithMockingDisabled):

def test_signin_with_fake_credentials(self):
'''
Attempts to login using incorrect credentials
should return a 401 error.
'''
response = self.client.post(
'/api/auth/signin',
json = {
'email': '[email protected]',
'password': '_pp#FXo;h$i~'
}
)
self.assert401(response)

def test_signin_without_email_format(self):
'''
Attempts to login using an email field that
does not follow the email format will return a
400 error instead of 401.
'''
response = self.client.post(
'/api/auth/signin',
json = {
'email': 'notta_email',
'password': '_pp#FXo;h$i~'
}
)
self.assert400(response)
assert "is not a email" in strip_punctuation(response.json["detail"].lower())

def test_refresh_without_cookie(self):
'''
Attempts to use the refresh endpoint without a session
cookie attached should return a 'cookie missing'
error instead of an authentication failure.
'''
response = self.client.get(
'api/auth/refresh'
)
self.assert400(response)
assert "missing cookie" in response.json['detail'].lower()

def test_session_without_jwt(self):
'''
Attempts to use the refresh endpoint without a session
cookie attached should return a 'JWT missing'
error instead of an authentication failure.
'''
response = self.client.get(
'api/auth/session'
)
self.assert401(response)
assert "no authorization token provided" in response.json['detail'].lower()

def test_session_without_cookie(self):
'''
Attempts to use the refresh endpoint without a session
cookie attached should return a 'cookie missing'
error instead of an authentication failure.
'''
response = self.client.get(
'api/auth/session',
headers={"Authorization": "Bearer fake_jwt_token_here"}
)
self.assert401(response)
assert "invalid access token" in response.json['message'].lower()
3 changes: 1 addition & 2 deletions app/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
SERVER_URL=http://localhost:6060
VITE_COGNITO_CLIENT_ID=
VITE_COGNITO_REDIRECT_URI=http://localhost:4040/signin
VITE_HUU_API_BASE_URL=http://localhost:4040/api/
VITE_HUU_API_BASE_URL=http://localhost:8080/api/
30 changes: 28 additions & 2 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,33 @@ The setup for the front end application is now complete and you should see the w

## Testing

Within this (`app`) directory, run the command: `npm test`. This command runs the `vitest`, a Vite-native unit test framework.
The frontend app leverages two different testing frameworks.

### Component testing

Isolated component-level tests are written using [vitest](https://vitest.dev/guide/why.html), a Vite-native test framework that is optimized for testing the individual units of code in isolation, typically functions and components, rather than the entire application with full user interactions.

Existing tests can be executed by running `npm test` within the (`app`) directory. New tests can be added within files with the `.test.tsx` or `.test.ts` extension. We store our test cases alongside the associated source code files, within `__test__` subdirectories.

### End-to-end (e2e) testing

Integrated browser based tests are written using [Cypress](https://docs.cypress.io/guides/overview/why-cypress). `Cypress` allows you to write tests that interact with our web application in the same way that a user would, by running tests in a real browser against an actual backend.

By default we mock out backend integration by intercepting and simulating responses from the backend, however the mocking can be removed if real user credentials are provided as environment variables.

```pwsh
cd app
# Launch app before testing
npm run dev
# Run tests with mocking. The backend does not need to be running.
npm run cypress:open
# Run tests without mocking, using a real user account
$env:CYPRESS_REAL_EMAIL = "[email protected]"
$env:CYPRESS_REAL_PASSWORD = "Quantum-encrypted-p@ssw0rd-here"
npm run cypress:open:nomock
```

If the e2e tests are not working as expected then verify the `cypress.config.ts` `defineConfig.e2e.baseUrl` matches the deployed app's base url.

## Configuration

Expand All @@ -36,7 +62,7 @@ The table below describes the environment variables that are used by this app:

| Variable | Required? | Example | Description |
|----------|-----------|---------|-------------|
| `VITE_HUU_API_BASE_URL` | YES | http://localhost:4040/api/ | The HUU API's base URL. In a development environment (mode is 'development' or 'test'): if this variable is not defined, then `http://localhost:4040/api/` will be used by default. In non-development environment: if this variable is not defined, then the build will throw an error. |
| `VITE_HUU_API_BASE_URL` | YES | http://localhost:8080/api/ | The HUU API's base URL. In a development environment (mode is 'development' or 'test'): if this variable is not defined, then `http://localhost:4040/api/` will be used by default. In non-development environment: if this variable is not defined, then the build will throw an error. |
| | | | |

## Production
Expand Down
7 changes: 7 additions & 0 deletions app/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ export default defineConfig({
bundler: 'vite',
},
},
env: {
USE_MOCK: true,
// If mocking is disabled, then you must pass in
// the email and password as a system environment
// variable using $env:CYPRESS_REAL_EMAIL and
// $env:CYPRESS_REAL_PASSWORD
}
});
Loading

0 comments on commit 0e9d6c1

Please sign in to comment.