summaryrefslogtreecommitdiff
path: root/server/szurubooru/func/auth.py
blob: d01377569c146e773409d19f0553aa1adda12aea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import hashlib
import random
import uuid
from collections import OrderedDict
from datetime import datetime
from typing import Optional, Tuple

from nacl import pwhash
from nacl.exceptions import InvalidkeyError

from szurubooru import config, db, errors, model
from szurubooru.func import util

RANK_MAP = OrderedDict(
    [
        (model.User.RANK_ANONYMOUS, "anonymous"),
        (model.User.RANK_RESTRICTED, "restricted"),
        (model.User.RANK_REGULAR, "regular"),
        (model.User.RANK_POWER, "power"),
        (model.User.RANK_MODERATOR, "moderator"),
        (model.User.RANK_ADMINISTRATOR, "administrator"),
        (model.User.RANK_NOBODY, "nobody"),
    ]
)


def get_password_hash(salt: str, password: str) -> Tuple[str, int]:
    """ Retrieve argon2id password hash. """
    return (
        pwhash.argon2id.str(
            (config.config["secret"] + salt + password).encode("utf8")
        ).decode("utf8"),
        3,
    )


def get_sha256_legacy_password_hash(
    salt: str, password: str
) -> Tuple[str, int]:
    """ Retrieve old-style sha256 password hash. """
    digest = hashlib.sha256()
    digest.update(config.config["secret"].encode("utf8"))
    digest.update(salt.encode("utf8"))
    digest.update(password.encode("utf8"))
    return digest.hexdigest(), 2


def get_sha1_legacy_password_hash(salt: str, password: str) -> Tuple[str, int]:
    """ Retrieve old-style sha1 password hash. """
    digest = hashlib.sha1()
    digest.update(b"1A2/$_4xVa")
    digest.update(salt.encode("utf8"))
    digest.update(password.encode("utf8"))
    return digest.hexdigest(), 1


def create_password() -> str:
    alphabet = {
        "c": list("bcdfghijklmnpqrstvwxyz"),
        "v": list("aeiou"),
        "n": list("0123456789"),
    }
    pattern = "cvcvnncvcv"
    return "".join(random.choice(alphabet[type]) for type in list(pattern))


def is_valid_password(user: model.User, password: str) -> bool:
    assert user
    salt, valid_hash = user.password_salt, user.password_hash

    try:
        return pwhash.verify(
            user.password_hash.encode("utf8"),
            (config.config["secret"] + salt + password).encode("utf8"),
        )
    except InvalidkeyError:
        possible_hashes = [
            get_sha256_legacy_password_hash(salt, password)[0],
            get_sha1_legacy_password_hash(salt, password)[0],
        ]
        if valid_hash in possible_hashes:
            # Convert the user password hash to the new hash
            new_hash, revision = get_password_hash(salt, password)
            user.password_hash = new_hash
            user.password_revision = revision
            db.session.commit()
            return True

    return False


def is_valid_token(user_token: Optional[model.UserToken]) -> bool:
    """
    Token must be enabled and if it has an expiration, it must be
    greater than now.
    """
    if user_token is None:
        return False
    if not user_token.enabled:
        return False
    if (
        user_token.expiration_time is not None
        and user_token.expiration_time < datetime.utcnow()
    ):
        return False
    return True


def has_privilege(user: model.User, privilege_name: str) -> bool:
    assert user
    all_ranks = list(RANK_MAP.keys())
    assert privilege_name in config.config["privileges"]
    assert user.rank in all_ranks
    minimal_rank = util.flip(RANK_MAP)[
        config.config["privileges"][privilege_name]
    ]
    good_ranks = all_ranks[all_ranks.index(minimal_rank) :]
    return user.rank in good_ranks


def verify_privilege(user: model.User, privilege_name: str) -> None:
    assert user
    if not has_privilege(user, privilege_name):
        raise errors.AuthError("Insufficient privileges to do this.")


def generate_authentication_token(user: model.User) -> str:
    """ Generate nonguessable challenge (e.g. links in password reminder). """
    assert user
    digest = hashlib.md5()
    digest.update(config.config["secret"].encode("utf8"))
    digest.update(user.password_salt.encode("utf8"))
    return digest.hexdigest()


def generate_authorization_token() -> str:
    return uuid.uuid4().__str__()