diff --git a/README.md b/README.md index 36f63f8..bc28798 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ flask --app server.py run If you're going to be working on the application and want hot reloads of the server code, add the `--debug` flag. ```shell -flask --app server.py --debug run +flask --app server.py --debug run -p 5001 ``` -Visit the local webserver at `http://localhost:5000/` and sign in using the credentials: +Visit the local webserver at `http://localhost:5001/` and sign in using the credentials: * username: richard@example.com * password: password diff --git a/complete-application/.env b/complete-application/.env index 8591da4..4feccb7 100644 --- a/complete-application/.env +++ b/complete-application/.env @@ -2,4 +2,4 @@ CLIENT_ID=e9fdb985-9173-4e01-9d73-ac2d60d1dc8e CLIENT_SECRET=super-secret-secret-that-should-be-regenerated-for-production ISSUER=http://localhost:9011 APP_SECRET_KEY=0386ffa9-3bff-4c75-932a-48d6a763ce77 - +API_KEY=this_really_should_be_a_long_random_alphanumeric_value_but_this_still_works diff --git a/complete-application/requirements.txt b/complete-application/requirements.txt index 1cf70ef..0a6beb8 100644 --- a/complete-application/requirements.txt +++ b/complete-application/requirements.txt @@ -2,3 +2,4 @@ Authlib==1.2.0 Flask==2.3.2 python-dotenv==1.0.0 requests==2.31.0 +fusionauth-client==1.48.0 diff --git a/complete-application/server.py b/complete-application/server.py index 75a001f..13e492a 100644 --- a/complete-application/server.py +++ b/complete-application/server.py @@ -6,6 +6,8 @@ from authlib.integrations.flask_client import OAuth from dotenv import find_dotenv, load_dotenv from flask import Flask, redirect, render_template, session, url_for, request, make_response +from fusionauth.fusionauth_client import FusionAuthClient + ACCESS_TOKEN_COOKIE_NAME = "cb_access_token" REFRESH_TOKEN_COOKIE_NAME = "cb_refresh_token" @@ -32,7 +34,7 @@ ) if __name__ == "__main__": - app.run(host="0.0.0.0", port=env.get("PORT", 5000)) + app.run(host="0.0.0.0", port=env.get("PORT", 5001)) def get_logout_url(): return env.get("ISSUER") + "/oauth2/logout?" + urlencode({"client_id": env.get("CLIENT_ID")},quote_via=quote_plus) @@ -146,3 +148,58 @@ def make_change(): change=change, logoutUrl=get_logout_url()) #end::makeChangeRoute[] + +# +# This is the profile page. +# +#tag::profileRoute[] +@app.route("/profile", methods=['GET', 'POST']) +def profile(): + access_token = request.cookies.get(ACCESS_TOKEN_COOKIE_NAME, None) + refresh_token = request.cookies.get(REFRESH_TOKEN_COOKIE_NAME, None) + + if access_token is None: + return redirect(get_logout_url()) + + profile = { + "error": None + } + + fusionauth_api_client = FusionAuthClient(env.get('API_KEY'), env.get('ISSUER')) + if session.get('user') != None: + user = session['user'] + user_id = user['sub'] + application_id = user['aud'] + + client_response = fusionauth_api_client.retrieve_registration(user_id, application_id) + if client_response.was_successful(): + registration_data = client_response.success_response['registration'].get('data') + profile['registration_data'] = registration_data + + if request.method == 'POST': + try: + favorite_color = request.form["favoritecolor"] + current_color = registration_data.get('favoritecolor') + + if favorite_color != current_color and len(favorite_color) > 0: + registration_data['favoritecolor'] = favorite_color + patch_request = { 'registration' : {'applicationId': application_id, 'data' : registration_data }} + client_response = fusionauth_api_client.patch_registration(user_id, patch_request) + if client_response.was_successful(): + profile['registration_data'] = registration_data + else: + profile['error'] = "Unable to save data" + else: + profile["error"] = "Please enter a new color" + + except KeyError: + profile["error"] = "Please enter a color" + + print(profile["error"]) + + return render_template( + "profile.html", + session=json.loads(request.cookies.get(USERINFO_COOKIE_NAME, None)), + profile=profile, + logoutUrl=get_logout_url()) +#end::profileRoute[] \ No newline at end of file diff --git a/complete-application/static/css/changebank.css b/complete-application/static/css/changebank.css index cf72443..799d696 100644 --- a/complete-application/static/css/changebank.css +++ b/complete-application/static/css/changebank.css @@ -132,7 +132,7 @@ body { border-radius: 5px; } -.change-message { +.change-message, .profile-message { font-size: 20px; margin-bottom: 15px; } @@ -152,6 +152,6 @@ body { margin-left: 80px; } -.change-container { +.change-container, .profile-container { flex: 1; } diff --git a/complete-application/templates/account.html b/complete-application/templates/account.html index e259023..4fb03a5 100644 --- a/complete-application/templates/account.html +++ b/complete-application/templates/account.html @@ -16,6 +16,7 @@ diff --git a/complete-application/templates/make-change.html b/complete-application/templates/make-change.html index a23e232..e937744 100644 --- a/complete-application/templates/make-change.html +++ b/complete-application/templates/make-change.html @@ -16,6 +16,7 @@ diff --git a/complete-application/templates/profile.html b/complete-application/templates/profile.html new file mode 100644 index 0000000..ab775dc --- /dev/null +++ b/complete-application/templates/profile.html @@ -0,0 +1,54 @@ + + + + FusionAuth OpenID and PKCE example + + + +
+ + +
+
+
+

Current preferences

+ +
+ {% if profile["error"] is not none: %} +
{{profile.error}}
+ {% endif %} + {% if profile["registration_data"] %} + {% set registration_data = profile["registration_data"] %} + {% for key in registration_data.keys() %} +

+ {% if key == 'favoritecolor' and registration_data['favoritecolor'] | length > 0 %} + Favorite Color: + {% else %} + Favorite Color: + {% endif %} +

+ {% endfor %} + {% else %} + Favorite Color: + {% endif %} + +
+
+
+
+ + diff --git a/documentation/load-test/README.md b/documentation/load-test/README.md new file mode 100644 index 0000000..c827ab6 --- /dev/null +++ b/documentation/load-test/README.md @@ -0,0 +1,38 @@ +# Load Testing FusionAuth + +Load testing FusionAuth should be performed using a realistic production configuration. The throughput goal should be to target a number of requests per second. + +## Configure FusionAuth +1. Setup an API key + +2. Setup a tenant + +3. Setup an application + +## Running a load test +1. Register users for the application + +2. Login users using the [Login API ](https://fusionauth.io/docs/apis/login) + +## Cleanup + +Deleting the tenant will remove the application and users. + +## Load Testing Tools +A tool that can make HTTP requests will work. + +1. jmeter +https://jmeter.apache.org + +2. gatling +https://gatling.io + + +## Helpful Articles + +1. FusionAuth [Documentation ](https://fusionauth.io/docs/operate/secure-and-monitor/monitor#load-testing) + +2. Load testing scripts from an [open source project ](https://github.com/FusionAuth/fusionauth-load-tests/) + +3. FusionAuth User Registration Hits 100,000,000 In [Load Test ](https://fusionauth.io/blog/got-users-100-million) + diff --git a/kickstart/kickstart.json b/kickstart/kickstart.json index 8dd7c7e..f602715 100644 --- a/kickstart/kickstart.json +++ b/kickstart/kickstart.json @@ -43,7 +43,7 @@ "OPTIONS" ], "allowedOrigins": [ - "http://localhost:5000" + "http://localhost:5001" ], "debug": false, "enabled": true, @@ -77,14 +77,14 @@ "name": "Example app", "oauthConfiguration": { "authorizedRedirectURLs": [ - "http://localhost:5000/callback", - "http://localhost:5000" + "http://localhost:5001/callback", + "http://localhost:5001" ], "authorizedOriginURLs": [ - "http://localhost:5000" + "http://localhost:5001" ], "clientSecret": "super-secret-secret-that-should-be-regenerated-for-production", - "logoutURL": "http://localhost:5000/logout", + "logoutURL": "http://localhost:5001/logout", "enabledGrants": [ "authorization_code", "refresh_token"