Skip to content

Commit

Permalink
_structures.py moved to structures/, _auth.py, _urls.py moved to util…
Browse files Browse the repository at this point in the history
…s/, _session.py moved to ext/ + docstrings
  • Loading branch information
Lucino772 committed May 7, 2021
1 parent 17f7634 commit 64119c6
Show file tree
Hide file tree
Showing 15 changed files with 336 additions and 109 deletions.
2 changes: 1 addition & 1 deletion mojang/account/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .base import status, get_uuid, get_uuids, names, user
from ._session import connect
from .ext.session import connect
10 changes: 0 additions & 10 deletions mojang/account/_auth.py

This file was deleted.

74 changes: 67 additions & 7 deletions mojang/account/auth/security.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
from typing import List

import requests

from ...exceptions import *
from .._auth import BearerAuth
from .._structures import ChallengeInfo
from ._urls import URLs
from ..structures.auth import ChallengeInfo
from ..utils.auth import BearerAuth, URLs


def check_ip(access_token: str):
def check_ip(access_token: str) -> bool:
"""Check if authenticated user IP is secure
Args:
access_token (str): The session's access token
Returns:
True if IP is secure else False
Raises:
Unauthorized: If access token is invalid
PayloadError: If access token is not formated correctly
"""
response = requests.get(URLs.verify_ip(), auth=BearerAuth(access_token))
try:
handle_response(response, PayloadError, Unauthorized, IPNotSecured)
Expand All @@ -15,7 +28,30 @@ def check_ip(access_token: str):
else:
return True

def get_challenges(access_token: str):
def get_challenges(access_token: str) -> List[ChallengeInfo]:
"""Return a list of challenges to verify IP
Args:
access_token (str): The session's access token
Returns:
A list of ChallengeInfo
Example:
Example of challenges
```python
[
(123, "What is your favorite pet's name?"),
(456, "What is your favorite movie?"),
(789, "What is your favorite author's last name?")
]
```
Raises:
Unauthorized: If access token is invalid
PayloadError: If access token is not formated correctly
"""
response = requests.get(URLs.get_challenges(), auth=BearerAuth(access_token))
data = handle_response(response, PayloadError, Unauthorized)

Expand All @@ -25,8 +61,32 @@ def get_challenges(access_token: str):

return _challenges

def verify_ip(access_token: str, answers: list):
answers = map(lambda a: {'id': a[0], 'answer': a[1]}, answers)
def verify_ip(access_token: str, answers: list) -> bool:
"""Verify IP with the given answers
Args:
access_token (str): The session's access token
answers (list): The answers to the question
Example:
```python
answers = [
(123, "foo"),
(456, "bar"),
(789, "baz")
]
security.verify_user_ip(ACCESS_TOKEN, answers)
```
Returns:
True if IP is secure else False
Raises:
Unauthorized: If access token is invalid
PayloadError: If access token is not formated correctly
"""
answers = list(map(lambda a: {'id': a[0], 'answer': a[1]}, answers))
response = requests.post(URLs.verify_ip(), auth=BearerAuth(access_token), json=answers)
try:
handle_response(response, PayloadError, Unauthorized, IPVerificationError)
Expand Down
67 changes: 63 additions & 4 deletions mojang/account/auth/yggdrasil.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
from typing import Optional

import requests

from ...exceptions import *
from .._structures import AuthenticationInfo
from ._urls import URLs
from ..structures.auth import AuthenticationInfo
from ..utils.auth import URLs


def authenticate(username: str, password: str, client_token: Optional[str] = None) -> AuthenticationInfo:
"""Authenticate a user with name and password
Args:
username (str): The username of email if account is not legacy
password (str): The user password
client_token (str, optional): The client token to use (default to None)
def authenticate(username: str, password: str, client_token: str = None):
Returns:
AuthenticationInfo
Raises:
CredentialsError: If username and password are invalid
PayloadError: If credentials are not formated correctly
"""
payload = {
'username': username,
'password': password,
Expand All @@ -28,7 +44,20 @@ def authenticate(username: str, password: str, client_token: str = None):
}
return AuthenticationInfo(**_dict)

def refresh(access_token: str, client_token: str):
def refresh(access_token: str, client_token: str) -> AuthenticationInfo:
"""Refresh an invalid access token
Args:
access_token (str): The access token to refresh
client_token (str): The client token used to generate the access token
Returns:
AuthenticationInfo
Raises:
TokenError: If client token is not the one used to generate the access token
PayloadError: If the tokens are not formated correctly
"""
payload = {
'accessToken': access_token,
'clientToken': client_token
Expand All @@ -47,6 +76,16 @@ def refresh(access_token: str, client_token: str):
return AuthenticationInfo(**_dict)

def validate(access_token: str, client_token: str):
"""Validate an access token
Args:
access_token (str): The access token to validate
client_token (str): The client token used to generate the access token
Raises:
TokenError: If client token is not the one used to generate the access token
PayloadError: If the tokens are not formated correctly
"""
payload = {
'accessToken': access_token,
'clientToken': client_token
Expand All @@ -55,6 +94,16 @@ def validate(access_token: str, client_token: str):
handle_response(response, PayloadError, TokenError)

def signout(username: str, password: str):
"""Signout user with name and password
Args:
username (str): The username or email if account is not legacy
password (str): The user password
Raises:
CredentialsError: If username and password are invalid
PayloadError: If credentials are not formated correctly
"""
payload = {
'username': username,
'password': password
Expand All @@ -63,6 +112,16 @@ def signout(username: str, password: str):
handle_response(response, PayloadError, CredentialsError)

def invalidate(access_token: str, client_token: str):
"""Invalidate an access token
Args:
access_token (str): The access token to invalidate
client_token (str): The client token used to generate the access token
Raises:
TokenError: If client token is not the one used to generate the access token
PayloadError: If the tokens are not formated correctly
"""
payload = {
'accessToken': access_token,
'clientToken': client_token
Expand Down
56 changes: 49 additions & 7 deletions mojang/account/base.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import base64
import json
from dataclasses import fields
from typing import List

import requests

from ..exceptions import handle_response
from ._structures import *
from ._urls import URLs
from .structures.base import *
from .utils.urls import URLs


def status():
def status() -> StatusCheck:
"""Get the status of Mojang's services
Returns:
StatusCheck
"""
response = requests.get(URLs.status_check())
data = handle_response(response)

Expand All @@ -20,15 +26,35 @@ def status():

return StatusCheck(_status)

def get_uuid(username: str):
def get_uuid(username: str) -> UUIDInfo:
"""Get uuid of username
Args:
username (str): The username which you want the uuid of
Returns:
UUIDInfo
"""
response = requests.get(URLs.uuid(username))
data = handle_response(response)

data['uuid'] = data.pop('id')

return UUIDInfo(**data)

def get_uuids(usernames: list):
def get_uuids(usernames: list) -> List[UUIDInfo]:
"""Get uuid of multiple username
Note: Limited Endpoint
The Mojang API only allow 10 usernames maximum, if more than 10 usernames are
given to the function, multiple request will be made.
Args:
usernames (list): The list of username which you want the uuid of
Returns:
A list of UUIDInfo
"""
usernames = list(map(lambda u: u.lower(), usernames))
_uuids = [None]*len(usernames)

Expand All @@ -43,7 +69,15 @@ def get_uuids(usernames: list):

return _uuids

def names(uuid: str):
def names(uuid: str) -> NameInfoList:
"""Get the user's name history
Args:
uuid (str): The user's uuid
Returns:
NameInfoList
"""
response = requests.get(URLs.name_history(uuid))
data = handle_response(response)

Expand All @@ -56,7 +90,15 @@ def names(uuid: str):

return NameInfoList(_names)

def user(uuid: str):
def user(uuid: str) -> UserProfile:
"""Get profile information by uuid
Args:
uuid (str): The uuid of the profile
Returns:
UserProfile
"""
response = requests.get(URLs.profile(uuid))
data = handle_response(response)
_dict = dict.fromkeys([f.name for f in fields(UserProfile) if f.init], None)
Expand Down
Empty file added mojang/account/ext/__init__.py
Empty file.
19 changes: 15 additions & 4 deletions mojang/account/_session.py → mojang/account/ext/session.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import datetime as dt
from dataclasses import dataclass, field
from typing import Optional

from . import session, user
from ._structures import UserProfile
from .auth import security, yggdrasil
from .. import session, user
from ..auth import security, yggdrasil
from ..structures.base import UserProfile


def connect(username: str, password: str, client_token: str = None):
def connect(username: str, password: str, client_token: Optional[str] = None) -> 'UserSession':
"""Connect a user with name and password
Args:
username (str): The username or email if account is not legacy
password (str): The user password
client_token (str, optional): The client token to use in the authentication (default to None)
Returns:
UserSession
"""
auth = yggdrasil.authenticate(username, password, client_token)
return UserSession(auth.access_token, auth.client_token)

Expand Down
Loading

0 comments on commit 64119c6

Please sign in to comment.