Skip to content

Commit

Permalink
Input data validation and optional field handling (#10)
Browse files Browse the repository at this point in the history
* Add input validation

* Add optional field handling and input validation

* Update README

* Update README
  • Loading branch information
aditeyabaral authored Dec 24, 2024
1 parent 7a060cb commit a01f381
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 75 deletions.
52 changes: 41 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A simple API to authenticate PESU credentials using PESU Academy

The API is secure and protects user privacy by not storing any user credentials. It only validates credentials and
returns the user's profile information.
returns the user's profile information. No personal data is stored or logged.

### PESUAuth LIVE Deployment

Expand Down Expand Up @@ -58,16 +58,19 @@ your system.

# How to use pesu-auth

You can send a request to the `/authenticate` endpoint with the user's credentials and the API will return a JSON object,
You can send a request to the `/authenticate` endpoint with the user's credentials and the API will return a JSON
object,
with the user's profile information if requested.

### Request Parameters

| **Parameter** | **Optional** | **Type** | **Default** | **Description** |
|---------------|--------------|-----------|-------------|--------------------------------------|
| `username` | No | `str` | | The user's SRN or PRN |
| `password` | No | `str` | | The user's password |
| `profile` | Yes | `boolean` | `False` | Whether to fetch profile information |
| **Parameter** | **Optional** | **Type** | **Default** | **Description** |
|-------------------------------|--------------|-------------|-------------|-------------------------------------------------------------------------------------------------|
| `username` | No | `str` | | The user's SRN or PRN |
| `password` | No | `str` | | The user's password |
| `profile` | Yes | `boolean` | `False` | Whether to fetch profile information |
| `know_your_class_and_section` | Yes | `boolean` | `False` | Whether to fetch Know Your Class and Section information |
| `fields` | Yes | `list[str]` | `None` | Which fields to fetch from the profile information. If not provided, all fields will be fetched |

### Response Object

Expand Down Expand Up @@ -100,6 +103,7 @@ profile data was requested, the response's `profile` key will store a dictionary
| `phone` | Phone number of the user registered with PESU |
| `campus_code` | The integer code of the campus (1 for RR and 2 for EC) |
| `campus` | Abbreviation of the user's campus name |
| `error` | The error name and stack trace, if an error occurs |

#### KnowYourClassAndSectionObject

Expand All @@ -114,8 +118,13 @@ profile data was requested, the response's `profile` key will store a dictionary
| `department` | Abbreviation of the branch along with the campus of the user |
| `branch` | Abbreviation of the branch that the user is pursuing |
| `institute_name` | The name of the campus that the user is studying in |
| `error` | The error name and stack trace, if an error occurs |

<details><summary>Here is an example using Python</summary>
## Integrating your application with pesu-auth

Here are some examples of how you can integrate your application with the PESUAuth API using Python and cURL.

### Python

#### Request

Expand All @@ -125,8 +134,9 @@ import requests
data = {
'username': 'your SRN or PRN here',
'password': 'your password here',
'profile': True # Optional, defaults to False
# Set to True if you want to retrieve the user's profile information
'profile': True, # Optional, defaults to False
'know_your_class_and_section': True, # Optional, defaults to False
'fields': None, # Optional, defaults to None to represent all fields
}

response = requests.post("http://localhost:5000/authenticate", json=data)
Expand Down Expand Up @@ -168,5 +178,25 @@ print(response.json())
}
```

</details>
### cURL

#### Request

```bash
curl -X POST http://localhost:5000/authenticate \
-H "Content-Type: application/json" \
-d '{
"username": "your SRN or PRN here",
"password": "your password here"
}'
```

#### Response

```json
{
"status": true,
"message": "Login successful.",
"timestamp": "2024-07-28 22:30:10.103368+05:30"
}
```
78 changes: 66 additions & 12 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ def convert_readme_to_html():
f.write(html)


def validate_input(
username: str,
password: str,
profile: bool,
know_your_class_and_section: bool,
fields: list[str],
):
"""
Validate the input provided by the user.
:param username: str: The username of the user.
:param password: str: The password of the user.
:param profile: bool: Whether to fetch the profile details of the user.
:param know_your_class_and_section: bool: Whether to fetch the class and section details of the user.
:param fields: dict: The fields to fetch from the user's profile.
"""
assert username is not None, "Username not provided."
assert isinstance(username, str), "Username should be a string."
assert password is not None, "Password not provided."
assert isinstance(password, str), "Password should be a string."
assert isinstance(profile, bool), "Profile should be a boolean."
assert isinstance(
know_your_class_and_section, bool
), "know_your_class_and_section should be a boolean."
assert fields is None or (
isinstance(fields, list) and fields
), "Fields should be a non-empty list or None."
if fields is not None:
for field in fields:
assert (
isinstance(field, str) and field in pesu_academy.DEFAULT_FIELDS
), f"Invalid field: '{field}'. Valid fields are: {pesu_academy.DEFAULT_FIELDS}."


@app.route("/")
def index():
"""
Expand All @@ -57,24 +90,45 @@ def authenticate():
"""
Authenticate the user with the provided username and password.
"""
# Extract the input provided by the user
current_time = datetime.datetime.now(IST)
username = request.json.get("username")
password = request.json.get("password")
profile = request.json.get("profile", False)
current_time = datetime.datetime.now(IST)
know_your_class_and_section = request.json.get("know_your_class_and_section", False)
fields = request.json.get("fields")

# Validate the input provided by the user
try:
validate_input(username, password, profile, know_your_class_and_section, fields)
except Exception as e:
stacktrace = traceback.format_exc()
logging.error(f"Could not validate request data: {e}: {stacktrace}")
return (
json.dumps(
{
"status": False,
"message": f"Could not validate request data: {e}",
"timestamp": str(current_time),
}
),
400,
)

# try to log in only if both username and password are provided
if username and password:
username = username.strip()
password = password.strip()
authentication_result = pesu_academy.authenticate(username, password, profile)
# Authenticate the user
try:
authentication_result = pesu_academy.authenticate(
username, password, profile, know_your_class_and_section, fields
)
authentication_result["timestamp"] = str(current_time)
return json.dumps(authentication_result), 200

# if either username or password is not provided, we return an error
return (
json.dumps({"status": False, "message": "Username or password not provided."}),
400,
)
except Exception as e:
stacktrace = traceback.format_exc()
logging.error(f"Error authenticating user: {e}: {stacktrace}")
return (
json.dumps({"status": False, "message": f"Error authenticating user: {e}"}),
500,
)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit a01f381

Please sign in to comment.