the37lab-authlib 0.1.1758263039__py3-none-any.whl → 0.1.1758266466__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of the37lab-authlib might be problematic. Click here for more details.
- the37lab_authlib/__init__.py +4 -4
- the37lab_authlib/auth.py +1148 -1148
- the37lab_authlib/db.py +90 -90
- the37lab_authlib/decorators.py +32 -32
- the37lab_authlib/exceptions.py +10 -10
- the37lab_authlib/models.py +94 -94
- {the37lab_authlib-0.1.1758263039.dist-info → the37lab_authlib-0.1.1758266466.dist-info}/METADATA +1 -1
- the37lab_authlib-0.1.1758266466.dist-info/RECORD +10 -0
- the37lab_authlib-0.1.1758263039.dist-info/RECORD +0 -10
- {the37lab_authlib-0.1.1758263039.dist-info → the37lab_authlib-0.1.1758266466.dist-info}/WHEEL +0 -0
- {the37lab_authlib-0.1.1758263039.dist-info → the37lab_authlib-0.1.1758266466.dist-info}/top_level.txt +0 -0
the37lab_authlib/db.py
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import psycopg2
|
|
2
|
-
from psycopg2.extras import RealDictCursor
|
|
3
|
-
from psycopg2 import pool
|
|
4
|
-
from contextlib import contextmanager
|
|
5
|
-
from .models import UUIDGenerator, IntegerGenerator
|
|
6
|
-
|
|
7
|
-
class Database:
|
|
8
|
-
def __init__(self, dsn, id_type='uuid', min_conn=1, max_conn=10):
|
|
9
|
-
self.dsn = dsn
|
|
10
|
-
self.id_generator = UUIDGenerator() if id_type == 'uuid' else IntegerGenerator()
|
|
11
|
-
self.id_type = id_type
|
|
12
|
-
self.min_conn = min_conn
|
|
13
|
-
self.max_conn = max_conn
|
|
14
|
-
self._pool = None
|
|
15
|
-
self._init_pool()
|
|
16
|
-
self._init_db()
|
|
17
|
-
|
|
18
|
-
def _init_pool(self):
|
|
19
|
-
self._pool = pool.ThreadedConnectionPool(
|
|
20
|
-
self.min_conn,
|
|
21
|
-
self.max_conn,
|
|
22
|
-
self.dsn,
|
|
23
|
-
cursor_factory=RealDictCursor
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
def _init_db(self):
|
|
27
|
-
with self.get_connection() as conn:
|
|
28
|
-
with conn.cursor() as cur:
|
|
29
|
-
# Create users table with configurable ID type
|
|
30
|
-
cur.execute(f"""
|
|
31
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
32
|
-
id {self._get_id_type()} PRIMARY KEY,
|
|
33
|
-
username VARCHAR(255) UNIQUE NOT NULL,
|
|
34
|
-
email VARCHAR(255) UNIQUE NOT NULL,
|
|
35
|
-
real_name VARCHAR(255) NOT NULL,
|
|
36
|
-
password_hash VARCHAR(255),
|
|
37
|
-
created_at TIMESTAMP NOT NULL,
|
|
38
|
-
updated_at TIMESTAMP NOT NULL
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
CREATE TABLE IF NOT EXISTS roles (
|
|
42
|
-
id {self._get_id_type()} PRIMARY KEY,
|
|
43
|
-
name VARCHAR(255) UNIQUE NOT NULL,
|
|
44
|
-
description TEXT,
|
|
45
|
-
created_at TIMESTAMP NOT NULL
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
CREATE TABLE IF NOT EXISTS user_roles (
|
|
49
|
-
user_id {self._get_id_type()} REFERENCES users(id),
|
|
50
|
-
role_id {self._get_id_type()} REFERENCES roles(id),
|
|
51
|
-
PRIMARY KEY (user_id, role_id)
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
55
|
-
id VARCHAR(8) PRIMARY KEY,
|
|
56
|
-
user_id INTEGER REFERENCES users(id),
|
|
57
|
-
name VARCHAR(255) NOT NULL,
|
|
58
|
-
token VARCHAR(255) NOT NULL,
|
|
59
|
-
created_at TIMESTAMP NOT NULL,
|
|
60
|
-
expires_at TIMESTAMP,
|
|
61
|
-
last_used_at TIMESTAMP
|
|
62
|
-
);
|
|
63
|
-
""")
|
|
64
|
-
|
|
65
|
-
def _get_id_type(self):
|
|
66
|
-
return 'UUID' if self.id_type == 'uuid' else 'SERIAL'
|
|
67
|
-
|
|
68
|
-
@contextmanager
|
|
69
|
-
def get_connection(self):
|
|
70
|
-
conn = self._pool.getconn()
|
|
71
|
-
try:
|
|
72
|
-
yield conn
|
|
73
|
-
conn.commit()
|
|
74
|
-
except Exception:
|
|
75
|
-
conn.rollback()
|
|
76
|
-
raise
|
|
77
|
-
finally:
|
|
78
|
-
self._pool.putconn(conn)
|
|
79
|
-
|
|
80
|
-
@contextmanager
|
|
81
|
-
def get_cursor(self):
|
|
82
|
-
with self.get_connection() as conn:
|
|
83
|
-
with conn.cursor() as cur:
|
|
84
|
-
yield cur
|
|
85
|
-
|
|
86
|
-
def get_id_generator(self):
|
|
87
|
-
return self.id_generator
|
|
88
|
-
|
|
89
|
-
def close(self):
|
|
90
|
-
if self._pool:
|
|
1
|
+
import psycopg2
|
|
2
|
+
from psycopg2.extras import RealDictCursor
|
|
3
|
+
from psycopg2 import pool
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from .models import UUIDGenerator, IntegerGenerator
|
|
6
|
+
|
|
7
|
+
class Database:
|
|
8
|
+
def __init__(self, dsn, id_type='uuid', min_conn=1, max_conn=10):
|
|
9
|
+
self.dsn = dsn
|
|
10
|
+
self.id_generator = UUIDGenerator() if id_type == 'uuid' else IntegerGenerator()
|
|
11
|
+
self.id_type = id_type
|
|
12
|
+
self.min_conn = min_conn
|
|
13
|
+
self.max_conn = max_conn
|
|
14
|
+
self._pool = None
|
|
15
|
+
self._init_pool()
|
|
16
|
+
self._init_db()
|
|
17
|
+
|
|
18
|
+
def _init_pool(self):
|
|
19
|
+
self._pool = pool.ThreadedConnectionPool(
|
|
20
|
+
self.min_conn,
|
|
21
|
+
self.max_conn,
|
|
22
|
+
self.dsn,
|
|
23
|
+
cursor_factory=RealDictCursor
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _init_db(self):
|
|
27
|
+
with self.get_connection() as conn:
|
|
28
|
+
with conn.cursor() as cur:
|
|
29
|
+
# Create users table with configurable ID type
|
|
30
|
+
cur.execute(f"""
|
|
31
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
32
|
+
id {self._get_id_type()} PRIMARY KEY,
|
|
33
|
+
username VARCHAR(255) UNIQUE NOT NULL,
|
|
34
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
35
|
+
real_name VARCHAR(255) NOT NULL,
|
|
36
|
+
password_hash VARCHAR(255),
|
|
37
|
+
created_at TIMESTAMP NOT NULL,
|
|
38
|
+
updated_at TIMESTAMP NOT NULL
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS roles (
|
|
42
|
+
id {self._get_id_type()} PRIMARY KEY,
|
|
43
|
+
name VARCHAR(255) UNIQUE NOT NULL,
|
|
44
|
+
description TEXT,
|
|
45
|
+
created_at TIMESTAMP NOT NULL
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS user_roles (
|
|
49
|
+
user_id {self._get_id_type()} REFERENCES users(id),
|
|
50
|
+
role_id {self._get_id_type()} REFERENCES roles(id),
|
|
51
|
+
PRIMARY KEY (user_id, role_id)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
55
|
+
id VARCHAR(8) PRIMARY KEY,
|
|
56
|
+
user_id INTEGER REFERENCES users(id),
|
|
57
|
+
name VARCHAR(255) NOT NULL,
|
|
58
|
+
token VARCHAR(255) NOT NULL,
|
|
59
|
+
created_at TIMESTAMP NOT NULL,
|
|
60
|
+
expires_at TIMESTAMP,
|
|
61
|
+
last_used_at TIMESTAMP
|
|
62
|
+
);
|
|
63
|
+
""")
|
|
64
|
+
|
|
65
|
+
def _get_id_type(self):
|
|
66
|
+
return 'UUID' if self.id_type == 'uuid' else 'SERIAL'
|
|
67
|
+
|
|
68
|
+
@contextmanager
|
|
69
|
+
def get_connection(self):
|
|
70
|
+
conn = self._pool.getconn()
|
|
71
|
+
try:
|
|
72
|
+
yield conn
|
|
73
|
+
conn.commit()
|
|
74
|
+
except Exception:
|
|
75
|
+
conn.rollback()
|
|
76
|
+
raise
|
|
77
|
+
finally:
|
|
78
|
+
self._pool.putconn(conn)
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def get_cursor(self):
|
|
82
|
+
with self.get_connection() as conn:
|
|
83
|
+
with conn.cursor() as cur:
|
|
84
|
+
yield cur
|
|
85
|
+
|
|
86
|
+
def get_id_generator(self):
|
|
87
|
+
return self.id_generator
|
|
88
|
+
|
|
89
|
+
def close(self):
|
|
90
|
+
if self._pool:
|
|
91
91
|
self._pool.closeall()
|
the37lab_authlib/decorators.py
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
from functools import wraps
|
|
2
|
-
from flask import request, current_app, jsonify
|
|
3
|
-
from .exceptions import AuthError
|
|
4
|
-
|
|
5
|
-
def require_auth(roles=None):
|
|
6
|
-
def decorator(f):
|
|
7
|
-
@wraps(f)
|
|
8
|
-
def decorated(*args, **kwargs):
|
|
9
|
-
if request.method == 'OPTIONS':
|
|
10
|
-
return f(*args, **kwargs)
|
|
11
|
-
try:
|
|
12
|
-
# Get the require_auth decorator from AuthManager
|
|
13
|
-
user = current_app.auth_manager.get_current_user()
|
|
14
|
-
if not user:
|
|
15
|
-
raise AuthError('User not authenticated', 401)
|
|
16
|
-
|
|
17
|
-
auth_decorator = current_app.auth_manager.require_auth
|
|
18
|
-
|
|
19
|
-
# Apply the AuthManager's decorator and get the result
|
|
20
|
-
decorated_func = auth_decorator(f)
|
|
21
|
-
|
|
22
|
-
# Check roles if specified
|
|
23
|
-
if roles and not any(role in user['roles'] for role in roles):
|
|
24
|
-
raise AuthError('Insufficient permissions', 403)
|
|
25
|
-
|
|
26
|
-
# Now execute the function
|
|
27
|
-
return decorated_func(*args, **kwargs)
|
|
28
|
-
except AuthError as e:
|
|
29
|
-
response = jsonify(e.to_dict())
|
|
30
|
-
response.status_code = e.status_code
|
|
31
|
-
return response
|
|
32
|
-
return decorated
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from flask import request, current_app, jsonify
|
|
3
|
+
from .exceptions import AuthError
|
|
4
|
+
|
|
5
|
+
def require_auth(roles=None):
|
|
6
|
+
def decorator(f):
|
|
7
|
+
@wraps(f)
|
|
8
|
+
def decorated(*args, **kwargs):
|
|
9
|
+
if request.method == 'OPTIONS':
|
|
10
|
+
return f(*args, **kwargs)
|
|
11
|
+
try:
|
|
12
|
+
# Get the require_auth decorator from AuthManager
|
|
13
|
+
user = current_app.auth_manager.get_current_user()
|
|
14
|
+
if not user:
|
|
15
|
+
raise AuthError('User not authenticated', 401)
|
|
16
|
+
|
|
17
|
+
auth_decorator = current_app.auth_manager.require_auth
|
|
18
|
+
|
|
19
|
+
# Apply the AuthManager's decorator and get the result
|
|
20
|
+
decorated_func = auth_decorator(f)
|
|
21
|
+
|
|
22
|
+
# Check roles if specified
|
|
23
|
+
if roles and not any(role in user['roles'] for role in roles):
|
|
24
|
+
raise AuthError('Insufficient permissions', 403)
|
|
25
|
+
|
|
26
|
+
# Now execute the function
|
|
27
|
+
return decorated_func(*args, **kwargs)
|
|
28
|
+
except AuthError as e:
|
|
29
|
+
response = jsonify(e.to_dict())
|
|
30
|
+
response.status_code = e.status_code
|
|
31
|
+
return response
|
|
32
|
+
return decorated
|
|
33
33
|
return decorator
|
the37lab_authlib/exceptions.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
class AuthError(Exception):
|
|
2
|
-
def __init__(self, message, status_code=401):
|
|
3
|
-
self.message = message
|
|
4
|
-
self.status_code = status_code
|
|
5
|
-
super().__init__(self.message)
|
|
6
|
-
|
|
7
|
-
def to_dict(self):
|
|
8
|
-
return {
|
|
9
|
-
'error': self.message,
|
|
10
|
-
'status_code': self.status_code
|
|
1
|
+
class AuthError(Exception):
|
|
2
|
+
def __init__(self, message, status_code=401):
|
|
3
|
+
self.message = message
|
|
4
|
+
self.status_code = status_code
|
|
5
|
+
super().__init__(self.message)
|
|
6
|
+
|
|
7
|
+
def to_dict(self):
|
|
8
|
+
return {
|
|
9
|
+
'error': self.message,
|
|
10
|
+
'status_code': self.status_code
|
|
11
11
|
}
|
the37lab_authlib/models.py
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
from datetime import datetime, timedelta
|
|
2
|
-
import uuid
|
|
3
|
-
import bcrypt
|
|
4
|
-
import secrets
|
|
5
|
-
import string
|
|
6
|
-
from abc import ABC, abstractmethod
|
|
7
|
-
|
|
8
|
-
def generate_random_string(length, alphabet=string.ascii_letters + string.digits):
|
|
9
|
-
"""Generate a random string of specified length using the given alphabet."""
|
|
10
|
-
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
11
|
-
|
|
12
|
-
class IDGenerator(ABC):
|
|
13
|
-
@abstractmethod
|
|
14
|
-
def generate(self):
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
class UUIDGenerator(IDGenerator):
|
|
18
|
-
def generate(self):
|
|
19
|
-
return str(uuid.uuid4())
|
|
20
|
-
|
|
21
|
-
class IntegerGenerator(IDGenerator):
|
|
22
|
-
def generate(self):
|
|
23
|
-
return None # Let the database handle ID generation with SERIAL
|
|
24
|
-
|
|
25
|
-
class User:
|
|
26
|
-
def __init__(self, username, email, real_name, roles=None, id_generator=None):
|
|
27
|
-
self.id = id_generator.generate() if id_generator else str(uuid.uuid4())
|
|
28
|
-
if self.id is None: # Let database handle ID generation
|
|
29
|
-
self.id = None
|
|
30
|
-
self.username = username
|
|
31
|
-
self.email = email
|
|
32
|
-
self.real_name = real_name
|
|
33
|
-
self.roles = roles or []
|
|
34
|
-
self.created_at = datetime.utcnow()
|
|
35
|
-
self.updated_at = datetime.utcnow()
|
|
36
|
-
|
|
37
|
-
class Role:
|
|
38
|
-
def __init__(self, name, description=None, id_generator=None):
|
|
39
|
-
self.id = id_generator.generate() if id_generator else str(uuid.uuid4())
|
|
40
|
-
self.name = name
|
|
41
|
-
self.description = description
|
|
42
|
-
self.created_at = datetime.utcnow()
|
|
43
|
-
|
|
44
|
-
class ApiToken:
|
|
45
|
-
def __init__(self, user_id, name, expires_in_days=None):
|
|
46
|
-
self.id = generate_random_string(8) # 8 character ID
|
|
47
|
-
self.user_id = user_id
|
|
48
|
-
self.name = name
|
|
49
|
-
self.nonce = generate_random_string(32) # 32 character nonce
|
|
50
|
-
self.token = self._hash_nonce(self.nonce) # Hash the nonce
|
|
51
|
-
self.created_at = datetime.utcnow()
|
|
52
|
-
self.expires_at = datetime.utcnow() + timedelta(days=expires_in_days) if expires_in_days else None
|
|
53
|
-
self.last_used_at = None
|
|
54
|
-
|
|
55
|
-
def _hash_nonce(self, nonce):
|
|
56
|
-
"""Hash the nonce using bcrypt."""
|
|
57
|
-
salt = bcrypt.gensalt()
|
|
58
|
-
return bcrypt.hashpw(nonce.encode('utf-8'), salt).decode('utf-8')
|
|
59
|
-
|
|
60
|
-
def get_full_token(self):
|
|
61
|
-
"""Get the full token string in the format api_IDNONCE."""
|
|
62
|
-
return f"api_{self.id}{self.nonce}"
|
|
63
|
-
|
|
64
|
-
@staticmethod
|
|
65
|
-
def parse_token(token_string):
|
|
66
|
-
"""Parse a token string into its components."""
|
|
67
|
-
if not token_string.startswith('api_'):
|
|
68
|
-
raise ValueError('Invalid token format')
|
|
69
|
-
|
|
70
|
-
token_string = token_string[4:] # Remove 'api_' prefix
|
|
71
|
-
if len(token_string) != 40: # 8 (id) + 32 (nonce)
|
|
72
|
-
raise ValueError('Invalid token length')
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
'id': token_string[:8],
|
|
76
|
-
'nonce': token_string[8:]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
@staticmethod
|
|
80
|
-
def parse_token_id(token_string):
|
|
81
|
-
if len(token_string) == 8:
|
|
82
|
-
return token_string
|
|
83
|
-
if not token_string.startswith('api_'):
|
|
84
|
-
raise ValueError('Invalid token format')
|
|
85
|
-
return token_string[4:][:8]
|
|
86
|
-
|
|
87
|
-
def verify_token(self, token_string):
|
|
88
|
-
"""Verify if a token string matches this token."""
|
|
89
|
-
try:
|
|
90
|
-
parsed = self.parse_token(token_string)
|
|
91
|
-
if parsed['id'] != self.id:
|
|
92
|
-
return False
|
|
93
|
-
return bcrypt.checkpw(parsed['nonce'].encode('utf-8'), self.token.encode('utf-8'))
|
|
94
|
-
except ValueError:
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
import uuid
|
|
3
|
+
import bcrypt
|
|
4
|
+
import secrets
|
|
5
|
+
import string
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
def generate_random_string(length, alphabet=string.ascii_letters + string.digits):
|
|
9
|
+
"""Generate a random string of specified length using the given alphabet."""
|
|
10
|
+
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
11
|
+
|
|
12
|
+
class IDGenerator(ABC):
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def generate(self):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class UUIDGenerator(IDGenerator):
|
|
18
|
+
def generate(self):
|
|
19
|
+
return str(uuid.uuid4())
|
|
20
|
+
|
|
21
|
+
class IntegerGenerator(IDGenerator):
|
|
22
|
+
def generate(self):
|
|
23
|
+
return None # Let the database handle ID generation with SERIAL
|
|
24
|
+
|
|
25
|
+
class User:
|
|
26
|
+
def __init__(self, username, email, real_name, roles=None, id_generator=None):
|
|
27
|
+
self.id = id_generator.generate() if id_generator else str(uuid.uuid4())
|
|
28
|
+
if self.id is None: # Let database handle ID generation
|
|
29
|
+
self.id = None
|
|
30
|
+
self.username = username
|
|
31
|
+
self.email = email
|
|
32
|
+
self.real_name = real_name
|
|
33
|
+
self.roles = roles or []
|
|
34
|
+
self.created_at = datetime.utcnow()
|
|
35
|
+
self.updated_at = datetime.utcnow()
|
|
36
|
+
|
|
37
|
+
class Role:
|
|
38
|
+
def __init__(self, name, description=None, id_generator=None):
|
|
39
|
+
self.id = id_generator.generate() if id_generator else str(uuid.uuid4())
|
|
40
|
+
self.name = name
|
|
41
|
+
self.description = description
|
|
42
|
+
self.created_at = datetime.utcnow()
|
|
43
|
+
|
|
44
|
+
class ApiToken:
|
|
45
|
+
def __init__(self, user_id, name, expires_in_days=None):
|
|
46
|
+
self.id = generate_random_string(8) # 8 character ID
|
|
47
|
+
self.user_id = user_id
|
|
48
|
+
self.name = name
|
|
49
|
+
self.nonce = generate_random_string(32) # 32 character nonce
|
|
50
|
+
self.token = self._hash_nonce(self.nonce) # Hash the nonce
|
|
51
|
+
self.created_at = datetime.utcnow()
|
|
52
|
+
self.expires_at = datetime.utcnow() + timedelta(days=expires_in_days) if expires_in_days else None
|
|
53
|
+
self.last_used_at = None
|
|
54
|
+
|
|
55
|
+
def _hash_nonce(self, nonce):
|
|
56
|
+
"""Hash the nonce using bcrypt."""
|
|
57
|
+
salt = bcrypt.gensalt()
|
|
58
|
+
return bcrypt.hashpw(nonce.encode('utf-8'), salt).decode('utf-8')
|
|
59
|
+
|
|
60
|
+
def get_full_token(self):
|
|
61
|
+
"""Get the full token string in the format api_IDNONCE."""
|
|
62
|
+
return f"api_{self.id}{self.nonce}"
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def parse_token(token_string):
|
|
66
|
+
"""Parse a token string into its components."""
|
|
67
|
+
if not token_string.startswith('api_'):
|
|
68
|
+
raise ValueError('Invalid token format')
|
|
69
|
+
|
|
70
|
+
token_string = token_string[4:] # Remove 'api_' prefix
|
|
71
|
+
if len(token_string) != 40: # 8 (id) + 32 (nonce)
|
|
72
|
+
raise ValueError('Invalid token length')
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
'id': token_string[:8],
|
|
76
|
+
'nonce': token_string[8:]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def parse_token_id(token_string):
|
|
81
|
+
if len(token_string) == 8:
|
|
82
|
+
return token_string
|
|
83
|
+
if not token_string.startswith('api_'):
|
|
84
|
+
raise ValueError('Invalid token format')
|
|
85
|
+
return token_string[4:][:8]
|
|
86
|
+
|
|
87
|
+
def verify_token(self, token_string):
|
|
88
|
+
"""Verify if a token string matches this token."""
|
|
89
|
+
try:
|
|
90
|
+
parsed = self.parse_token(token_string)
|
|
91
|
+
if parsed['id'] != self.id:
|
|
92
|
+
return False
|
|
93
|
+
return bcrypt.checkpw(parsed['nonce'].encode('utf-8'), self.token.encode('utf-8'))
|
|
94
|
+
except ValueError:
|
|
95
95
|
return False
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
|
|
2
|
+
the37lab_authlib/auth.py,sha256=3igW3vyWXY6Ec4A6WuE0btYZcIcnv0TfweRHQdEDcwQ,49336
|
|
3
|
+
the37lab_authlib/db.py,sha256=cmnmykKvq6V5e-D0HGiRN4DjFBOGB-SL1HpFjR5uyCw,3162
|
|
4
|
+
the37lab_authlib/decorators.py,sha256=L-gJUUwDUT2JXTptQ6XEey1LkI5RprbqzEfArWI7F8Y,1305
|
|
5
|
+
the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
|
|
6
|
+
the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
|
|
7
|
+
the37lab_authlib-0.1.1758266466.dist-info/METADATA,sha256=Za_SVsmAMryV_x8vOOjByTjRpgWUVFZ0zveBORf3Dqw,8339
|
|
8
|
+
the37lab_authlib-0.1.1758266466.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
the37lab_authlib-0.1.1758266466.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
|
|
10
|
+
the37lab_authlib-0.1.1758266466.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
the37lab_authlib/__init__.py,sha256=QxIyIyb-b2C91a9vSE05cFov-MFwprBnPLUTCz1rAGo,136
|
|
2
|
-
the37lab_authlib/auth.py,sha256=3coNtCHg6sMIsTuWXNe8Ni147jmypCB_x-28AYq0ZMw,50484
|
|
3
|
-
the37lab_authlib/db.py,sha256=w8OcWvFWA-MbWals--l64qx_iDQYxv85wKGT52GtNVo,3252
|
|
4
|
-
the37lab_authlib/decorators.py,sha256=hCd6XSEREgeQ8XGJzlmKD-K8sQYbK7WgZCQKdY3m7ng,1337
|
|
5
|
-
the37lab_authlib/exceptions.py,sha256=ONA64ktHAuj4w0ur4xUeWZQQmfZw9hHo4che1Bi-M3s,327
|
|
6
|
-
the37lab_authlib/models.py,sha256=9-9ndGq-o9VGjHF8VvgMHvjhYOEapfOkocfjpqEFHY4,3516
|
|
7
|
-
the37lab_authlib-0.1.1758263039.dist-info/METADATA,sha256=r4jwSFSiHkOrGQ8HJxvhDxcs0UzfILy_NS50Qwy93eU,8339
|
|
8
|
-
the37lab_authlib-0.1.1758263039.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
the37lab_authlib-0.1.1758263039.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
|
|
10
|
-
the37lab_authlib-0.1.1758263039.dist-info/RECORD,,
|
{the37lab_authlib-0.1.1758263039.dist-info → the37lab_authlib-0.1.1758266466.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|