selfprivacy-rest-api/selfprivacy_api/utils/auth.py

111 lines
3.0 KiB
Python
Raw Normal View History

2022-01-14 07:38:53 +02:00
#!/usr/bin/env python3
"""Token management utils"""
import secrets
from datetime import datetime, timedelta
import re
2022-06-29 20:39:46 +03:00
import typing
2022-01-14 07:38:53 +02:00
from pydantic import BaseModel
2022-01-14 07:38:53 +02:00
from mnemonic import Mnemonic
2022-07-08 18:28:08 +03:00
from . import ReadUserData, UserDataFiles, WriteUserData, parse_date
2022-01-14 07:38:53 +02:00
"""
Token are stored in the tokens.json file.
File contains device tokens, recovery token and new device auth token.
File structure:
{
"tokens": [
{
"token": "device token",
"name": "device name",
"date": "date of creation",
}
],
"recovery_token": {
"token": "recovery token",
"date": "date of creation",
"expiration": "date of expiration",
"uses_left": "number of uses left"
},
"new_device": {
"token": "new device auth token",
"date": "date of creation",
"expiration": "date of expiration",
}
}
Recovery token may or may not have expiration date and uses_left.
There may be no recovery token at all.
Device tokens must be unique.
"""
def _get_tokens():
"""Get all tokens as list of tokens of every device"""
with ReadUserData(UserDataFiles.TOKENS) as tokens:
return [token["token"] for token in tokens["tokens"]]
def _get_token_names():
"""Get all token names"""
with ReadUserData(UserDataFiles.TOKENS) as tokens:
return [t["name"] for t in tokens["tokens"]]
def _validate_token_name(name):
"""Token name must be an alphanumeric string and not empty.
Replace invalid characters with '_'
If token name exists, add a random number to the end of the name until it is unique.
"""
if not re.match("^[a-zA-Z0-9]*$", name):
name = re.sub("[^a-zA-Z0-9]", "_", name)
if name == "":
name = "Unknown device"
while name in _get_token_names():
name += str(secrets.randbelow(10))
return name
def is_token_valid(token):
"""Check if token is valid"""
if token in _get_tokens():
return True
return False
2022-01-17 13:29:54 +02:00
class BasicTokenInfo(BaseModel):
"""Token info"""
name: str
date: datetime
2022-01-14 07:38:53 +02:00
def _generate_token():
"""Generates new token and makes sure it is unique"""
token = secrets.token_urlsafe(32)
while token in _get_tokens():
token = secrets.token_urlsafe(32)
return token
def _get_recovery_token():
"""Get recovery token"""
with ReadUserData(UserDataFiles.TOKENS) as tokens:
if "recovery_token" not in tokens:
return None
return tokens["recovery_token"]["token"]
def _get_new_device_auth_token():
"""Get new device auth token. If it is expired, return None"""
with ReadUserData(UserDataFiles.TOKENS) as tokens:
if "new_device" not in tokens:
return None
new_device = tokens["new_device"]
if "expiration" not in new_device:
return None
2022-07-08 18:28:08 +03:00
expiration = parse_date(new_device["expiration"])
2022-05-31 11:46:58 +03:00
if datetime.now() > expiration:
2022-01-14 07:38:53 +02:00
return None
return new_device["token"]