Skip to content

Commit

Permalink
updated setup
Browse files Browse the repository at this point in the history
  • Loading branch information
bartes committed Mar 15, 2022
1 parent 7a6068b commit ae71f23
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .env_example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
castle_app_id={{castle_app_id}}
castle_pk={{castle_pk}}
castle_api_secret={{castle_api_secret}}
FLASK_APP=app.py
location=localhost
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.env
__pycache__
venv
node_modules
static/castle.browser.js
93 changes: 48 additions & 45 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
def get_default_params():

default_params = {
"castle_app_id": os.getenv('castle_app_id'),
"castle_pk": os.getenv('castle_pk'),
"location": os.getenv('location'),
"demo_list": demo_list,
"username": os.getenv("valid_username"),
Expand Down Expand Up @@ -90,48 +90,50 @@ def evaluate_login():

print(request.json)

client_id = request.json["client_id"]
email = request.json["email"]
password = request.json["password"]
request_token = request.json["request_token"]

# check validity of username + password combo
if email == os.getenv("valid_username"):

user_id = os.getenv("valid_user_id")

if password == os.getenv("valid_password"):
castle_event = "$login.succeeded"
castle_api_endpoint = "authenticate"
castle_type = "$login"
castle_status = "$succeeded"
castle_api_endpoint = "risk"
else:
castle_api_endpoint = "track"
castle_event = "$login.failed"
castle_type = "$login"
castle_status = "$failed"
castle_api_endpoint = "filter"
else:
castle_api_endpoint = "track"
castle_event = "$login.failed"
castle_api_endpoint = "filter"
castle_type = "$login"
castle_status = "$failed"
user_id = None
registered_at = None

payload_to_castle = {
'event': castle_event,
'user_id': user_id,
'user_traits': {
'email': email
'type': castle_type,
'status': castle_status,
'user': {
'id': user_id,
'email': email
},
'context': {
'client_id': client_id
}
'request_token': request_token
}

if registered_at:
payload_to_castle["user_traits"]["registered_at"] = registered_at
payload_to_castle["user"]["registered_at"] = registered_at

castle = Client.from_request(request)

if castle_api_endpoint == "authenticate":
verdict = castle.authenticate(payload_to_castle)
if castle_api_endpoint == "risk":
verdict = castle.risk(payload_to_castle)

elif castle_api_endpoint == "track":
verdict = castle.track(payload_to_castle)
elif castle_api_endpoint == "filter":
verdict = castle.filter(payload_to_castle)

print("verdict:")
print(verdict)
Expand All @@ -140,49 +142,49 @@ def evaluate_login():
"api_endpoint": castle_api_endpoint,
"payload_to_castle": payload_to_castle,
"result": verdict,
"castle_event": castle_event
"castle_type": castle_type,
"castle_status": castle_status
}

if "device_token" in verdict:
r["device_token"] = verdict["device_token"]

if "action" in verdict:
r["action"] = verdict["action"]

return r, 200, {'ContentType':'application/json'}

@app.route('/evaluate_new_password', methods=['POST'])
def evaluate_new_password():

print(request.json)

client_id = request.json["client_id"]
password = request.json["password"]
request_token = request.json["request_token"]

# check validity of username + password combo
if password == os.getenv("valid_password"):
castle_event = "$password_reset.failed"
castle_type = "$password"
castle_status = "$succeeded"
else:
castle_event = "$password_reset.succeeded"
castle_type = "$password"
castle_status = "$failed"

payload_to_castle = {
'event': castle_event,
'user_id': os.getenv("valid_user_id"),
'user_traits': {
'email': os.getenv("valid_username"),
'registered_at': registered_at
'type': castle_type,
'status': castle_status,
'user': {
'id': os.getenv("valid_user_id"),
'email': os.getenv("valid_username"),
'registered_at': registered_at
},
'context': {
'client_id': client_id
}
'request_token': request_token
}

castle = Client.from_request(request)

r = {
"api_endpoint": "track",
"api_endpoint": "risk",
"payload_to_castle": payload_to_castle,
"castle_event": castle_event
'type': castle_type,
'status': castle_status,
}

return r, 200, {'ContentType':'application/json'}
Expand Down Expand Up @@ -261,29 +263,30 @@ def update_device():
print(request.json)

if request.json["user_verdict"] == "report":
event = '$review.escalated'
castle_type = '$review'
castle_status = '$escalated'
return_msg = "report"
else:
event = '$challenge.succeeded'
castle_type = '$challenge'
castle_status = '$succeeded'

return_msg = "approve"

castle = Client.from_request(request)

payload = {
'event': event,
'type': castle_type,
'status': castle_status,
'device_token': request.json["device_token"],
'context': {}
}

payload["context"]["client_id"] = request.json["client_id"]

result = castle.track(payload)
result = castle.risk(payload)

print(result)

r = {
"api_endpoint": "track",
"castle_event": event,
"api_endpoint": "risk",
"payload": payload
}

Expand Down
2 changes: 1 addition & 1 deletion castle_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

configuration.api_secret = os.getenv('castle_api_secret')

# For authenticate method you can set failover strategies: allow(default), deny, challenge, throw
# For risk method you can set failover strategies: allow(default), deny, challenge, throw
configuration.failover_strategy = 'deny'

# Castle::RequestError is raised when timing out in milliseconds (default: 1000 milliseconds)
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "castle-python-example",
"version": "1.0.0",
"description": "This project demonstrates key components of several essential Castle workflows, including login and reviewing a suspicious device. The application is built in Python on Flask/gunicorn.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/castle/castle-python-example.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/castle/castle-python-example/issues"
},
"homepage": "https://github.com/castle/castle-python-example#readme",
"dependencies": {
"@castleio/castle-js": "^2.1.7"
}
}
10 changes: 7 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ There are three ways to engage with this application:
3. Run a Docker container
> A Dockerfile is included in this repo. Brief instructions for installing locally are below. Or, you can run a container locally immediately from the dockerhub image:
`docker run -d -p 4005:80 -e castle_app_id={{castle_app_id} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} tomgsmith99/castle-demo-python`
`docker run -d -p 4005:80 -e castle_pk={{castle_pk} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} tomgsmith99/castle-demo-python`

## Setting up this application locally

Expand Down Expand Up @@ -54,6 +54,10 @@ Copy the `.env_example` file to a file called `.env`

Update the `.env` file with your Castle app id and api secret.

`npm install`

`cp node_modules/@castleio/castle-js/dist/castle.browser.js ./static/`

Run the app:
`flask run`
* Running on http://127.0.0.1:5000/
Expand All @@ -66,7 +70,7 @@ Note - the app also supports gunicorn:

### Run from dockerhub

`docker run -d -p 4005:80 -e castle_app_id={{castle_app_id} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} tomgsmith99/castle-demo-python`
`docker run -d -p 4005:80 -e castle_pk={{castle_pk} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} tomgsmith99/castle-demo-python`

### Build image locally

Expand All @@ -76,7 +80,7 @@ You can build a Docker image and run a Docker container as follows:

`docker build -t castle-demo-python .`

`docker run -d -p 4005:80 -e castle_app_id={{castle_app_id}} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} castle-demo-python`
`docker run -d -p 4005:80 -e castle_pk={{castle_pk}} -e castle_api_secret={{castle_api_secret}} -e valid_password={{valid_password}} castle-demo-python`

## Disclaimer
I’m sharing this sample app with the hope that other developers find it valuable. Although it is not an officially supported sample, we welcome questions and suggestions at `[email protected]`.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Flask
gunicorn
castle
/Users/bartes/Projects/docker-development/repos/castle-python/dist/castle-6.1.0.tar.gz
python-dotenv
3 changes: 2 additions & 1 deletion templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

<title>Castle workflows - {{location}}</title>

<script src="https://d2t77mnxyo7adj.cloudfront.net/v1/c.js?{{castle_app_id}}"></script>
<script src="/static/castle.browser.js"></script>
<script>Castle.configure({pk: "{{castle_pk}}"})</script>

<link rel="icon" type="image/x-icon" href="https://castle.io/favicon-32x32.png"/>

Expand Down
2 changes: 1 addition & 1 deletion templates/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
{% block right_col %}

<div id = "metadata">
<p>castle app id: {{castle_app_id}}</p>
<p>castle pk: {{castle_pk}}</p>
<p>valid username: {{valid_username}}</p>
<p>valid password: {{valid_password}}</p>

Expand Down
30 changes: 16 additions & 14 deletions templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</tr>
</table>
<br>
<p><button onclick='evaluate_login()'>log in</button></p>
<p><button onclick='get_token_and_evaluate_login()'>log in</button></p>
</div>

{% endblock %}
Expand All @@ -41,9 +41,9 @@
<p>When a user (or bot) tries to authenticate against your /login endpoint, there are generally three possible outcomes.</p>

<ol>
<li>valid username + valid password: successful authentication. In this case your application should send a <b>$login.succeeded</b> event to the Castle /authenticate endpoint, and act upon the response: allow, challenge, deny.</li>
<li>valid username + invalid password: failed authentication (invalid password). In this case your application should send a <b>$login.failed</b> event (with the username) to the Castle /track endpoint to help Castle understand if this login attempt is part of a broader attack.</li>
<li>invalid username: failed authentication (invalid username). In this case your application should send a <b>$login.failed</b> event (with username = null) to the Castle /track endpoint to help Castle understand if this login attempt is part of a broader attack.</li>
<li>valid username + valid password: successful authentication. In this case your application should send a <b>$login.succeeded</b> event to the Castle /risk endpoint, and act upon the response: allow, challenge, deny.</li>
<li>valid username + invalid password: failed authentication (invalid password). In this case your application should send a <b>$login.failed</b> event (with the username) to the Castle /filter endpoint to help Castle understand if this login attempt is part of a broader attack.</li>
<li>invalid username: failed authentication (invalid username). In this case your application should send a <b>$login.failed</b> event (with username = null) to the Castle /filter endpoint to help Castle understand if this login attempt is part of a broader attack.</li>
</ol>

<p>You can use the valid username and password indicated at right to try these three scenarios.</p>
Expand All @@ -59,6 +59,8 @@
<br>
event: <span id="castle_event" style="font-weight: normal;"></span>
</td>
</tr>
<tr colspan="2">
<td id = "verdict_header" style="font-weight: bold; display: none">response from castle</td>
</tr>

Expand All @@ -78,16 +80,13 @@
{% block javascript %}

<script type="text/javascript">
function evaluate_login(requestToken) {

function evaluate_login() {

var client_id = _castle('getClientId')

console.log("the castle client id is: " + client_id)
console.log("the castle requestToken is: " + requestToken)

var user_data = {
email: $("#email").val(),
client_id: client_id,
request_token: requestToken,
password: $("#password").val()
}

Expand All @@ -111,10 +110,10 @@
$("#castle_api_endpoint").html("/" + data.api_endpoint)
$("#castle_event").html(data.castle_event)

if (data.api_endpoint == "track") {
desc = "Successfully tracked an event: " + data.castle_event
if (data.api_endpoint == "log") {
desc = "Successfully used log on event: " + data.castle_type + '.' + data.castle_status
}
else if (data.api_endpoint == "authenticate") {
else if (data.api_endpoint == "risk" || data.api_endpoint == "filter") {
desc = "Retrieved a recommendation from Castle on the next step in the authentication process."
var y = new JsonEditor('#response_from_castle', getJson(JSON.stringify(data.result)));
$("#verdict_header").show()
Expand Down Expand Up @@ -150,6 +149,9 @@
$("#password").val("{{invalid_password}}")
}
}
function get_token_and_evaluate_login() {
Castle.createRequestToken().then(evaluate_login)
}
</script>

{% endblock %}
{% endblock %}
Loading

0 comments on commit ae71f23

Please sign in to comment.