the37lab-authlib 0.1.1749279322__py3-none-any.whl → 0.1.1749556774__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/db.py CHANGED
@@ -1,74 +1,74 @@
1
- import psycopg2
2
- from psycopg2.extras import RealDictCursor
3
- from contextlib import contextmanager
4
- from .models import UUIDGenerator, IntegerGenerator
5
-
6
- class Database:
7
- def __init__(self, dsn, id_type='uuid'):
8
- self.dsn = dsn
9
- self.id_generator = UUIDGenerator() if id_type == 'uuid' else IntegerGenerator()
10
- self.id_type = id_type
11
- self._init_db()
12
-
13
- def _init_db(self):
14
- with self.get_connection() as conn:
15
- with conn.cursor() as cur:
16
- # Create users table with configurable ID type
17
- cur.execute(f"""
18
- CREATE TABLE IF NOT EXISTS users (
19
- id {self._get_id_type()} PRIMARY KEY,
20
- username VARCHAR(255) UNIQUE NOT NULL,
21
- email VARCHAR(255) UNIQUE NOT NULL,
22
- real_name VARCHAR(255) NOT NULL,
23
- password_hash VARCHAR(255),
24
- created_at TIMESTAMP NOT NULL,
25
- updated_at TIMESTAMP NOT NULL
26
- );
27
-
28
- CREATE TABLE IF NOT EXISTS roles (
29
- id {self._get_id_type()} PRIMARY KEY,
30
- name VARCHAR(255) UNIQUE NOT NULL,
31
- description TEXT,
32
- created_at TIMESTAMP NOT NULL
33
- );
34
-
35
- CREATE TABLE IF NOT EXISTS user_roles (
36
- user_id {self._get_id_type()} REFERENCES users(id),
37
- role_id {self._get_id_type()} REFERENCES roles(id),
38
- PRIMARY KEY (user_id, role_id)
39
- );
40
-
41
- CREATE TABLE IF NOT EXISTS api_tokens (
42
- id VARCHAR(8) PRIMARY KEY,
43
- user_id INTEGER REFERENCES users(id),
44
- name VARCHAR(255) NOT NULL,
45
- token VARCHAR(255) NOT NULL,
46
- created_at TIMESTAMP NOT NULL,
47
- expires_at TIMESTAMP,
48
- last_used_at TIMESTAMP
49
- );
50
- """)
51
-
52
- def _get_id_type(self):
53
- return 'UUID' if self.id_type == 'uuid' else 'SERIAL'
54
-
55
- @contextmanager
56
- def get_connection(self):
57
- conn = psycopg2.connect(self.dsn, cursor_factory=RealDictCursor)
58
- try:
59
- yield conn
60
- conn.commit()
61
- except Exception:
62
- conn.rollback()
63
- raise
64
- finally:
65
- conn.close()
66
-
67
- @contextmanager
68
- def get_cursor(self):
69
- with self.get_connection() as conn:
70
- with conn.cursor() as cur:
71
- yield cur
72
-
73
- def get_id_generator(self):
1
+ import psycopg2
2
+ from psycopg2.extras import RealDictCursor
3
+ from contextlib import contextmanager
4
+ from .models import UUIDGenerator, IntegerGenerator
5
+
6
+ class Database:
7
+ def __init__(self, dsn, id_type='uuid'):
8
+ self.dsn = dsn
9
+ self.id_generator = UUIDGenerator() if id_type == 'uuid' else IntegerGenerator()
10
+ self.id_type = id_type
11
+ self._init_db()
12
+
13
+ def _init_db(self):
14
+ with self.get_connection() as conn:
15
+ with conn.cursor() as cur:
16
+ # Create users table with configurable ID type
17
+ cur.execute(f"""
18
+ CREATE TABLE IF NOT EXISTS users (
19
+ id {self._get_id_type()} PRIMARY KEY,
20
+ username VARCHAR(255) UNIQUE NOT NULL,
21
+ email VARCHAR(255) UNIQUE NOT NULL,
22
+ real_name VARCHAR(255) NOT NULL,
23
+ password_hash VARCHAR(255),
24
+ created_at TIMESTAMP NOT NULL,
25
+ updated_at TIMESTAMP NOT NULL
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS roles (
29
+ id {self._get_id_type()} PRIMARY KEY,
30
+ name VARCHAR(255) UNIQUE NOT NULL,
31
+ description TEXT,
32
+ created_at TIMESTAMP NOT NULL
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS user_roles (
36
+ user_id {self._get_id_type()} REFERENCES users(id),
37
+ role_id {self._get_id_type()} REFERENCES roles(id),
38
+ PRIMARY KEY (user_id, role_id)
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS api_tokens (
42
+ id VARCHAR(8) PRIMARY KEY,
43
+ user_id INTEGER REFERENCES users(id),
44
+ name VARCHAR(255) NOT NULL,
45
+ token VARCHAR(255) NOT NULL,
46
+ created_at TIMESTAMP NOT NULL,
47
+ expires_at TIMESTAMP,
48
+ last_used_at TIMESTAMP
49
+ );
50
+ """)
51
+
52
+ def _get_id_type(self):
53
+ return 'UUID' if self.id_type == 'uuid' else 'SERIAL'
54
+
55
+ @contextmanager
56
+ def get_connection(self):
57
+ conn = psycopg2.connect(self.dsn, cursor_factory=RealDictCursor)
58
+ try:
59
+ yield conn
60
+ conn.commit()
61
+ except Exception:
62
+ conn.rollback()
63
+ raise
64
+ finally:
65
+ conn.close()
66
+
67
+ @contextmanager
68
+ def get_cursor(self):
69
+ with self.get_connection() as conn:
70
+ with conn.cursor() as cur:
71
+ yield cur
72
+
73
+ def get_id_generator(self):
74
74
  return self.id_generator
@@ -1,31 +1,31 @@
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
- try:
10
- # Get the require_auth decorator from AuthManager
11
- user = current_app.auth_manager.get_current_user()
12
- if not user:
13
- raise AuthError('User not authenticated', 401)
14
-
15
- auth_decorator = current_app.auth_manager.require_auth
16
-
17
- # Apply the AuthManager's decorator and get the result
18
- decorated_func = auth_decorator(f)
19
-
20
- # Check roles if specified
21
- if roles and not any(role in user['roles'] for role in roles):
22
- raise AuthError('Insufficient permissions', 403)
23
-
24
- # Now execute the function
25
- return decorated_func(*args, **kwargs)
26
- except AuthError as e:
27
- response = jsonify(e.to_dict())
28
- response.status_code = e.status_code
29
- return response
30
- 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
+ try:
10
+ # Get the require_auth decorator from AuthManager
11
+ user = current_app.auth_manager.get_current_user()
12
+ if not user:
13
+ raise AuthError('User not authenticated', 401)
14
+
15
+ auth_decorator = current_app.auth_manager.require_auth
16
+
17
+ # Apply the AuthManager's decorator and get the result
18
+ decorated_func = auth_decorator(f)
19
+
20
+ # Check roles if specified
21
+ if roles and not any(role in user['roles'] for role in roles):
22
+ raise AuthError('Insufficient permissions', 403)
23
+
24
+ # Now execute the function
25
+ return decorated_func(*args, **kwargs)
26
+ except AuthError as e:
27
+ response = jsonify(e.to_dict())
28
+ response.status_code = e.status_code
29
+ return response
30
+ return decorated
31
31
  return decorator
@@ -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
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: the37lab_authlib
3
- Version: 0.1.1749279322
3
+ Version: 0.1.1749556774
4
4
  Summary: Python SDK for the Authlib
5
5
  Author-email: the37lab <info@the37lab.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,10 @@
1
+ the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
2
+ the37lab_authlib/auth.py,sha256=VP1_E7_iYkt5fzm6wYCx5Jx8kQtUVQjUcLIwYN9FVyw,20656
3
+ the37lab_authlib/db.py,sha256=fTXxnfju0lmbFGPVbXpTMeDmJMeBgURVZTndyxyRyCc,2734
4
+ the37lab_authlib/decorators.py,sha256=AEQfix31fHUZvhEZd4Ud8Zh2KBGjV6O_braiPL-BU7w,1219
5
+ the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
6
+ the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
7
+ the37lab_authlib-0.1.1749556774.dist-info/METADATA,sha256=z-lj2n8i0V4dArH4VC4PgxS1v7EFJPAtkiNqxCcpXgk,2711
8
+ the37lab_authlib-0.1.1749556774.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ the37lab_authlib-0.1.1749556774.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
+ the37lab_authlib-0.1.1749556774.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- the37lab_authlib/__init__.py,sha256=QxIyIyb-b2C91a9vSE05cFov-MFwprBnPLUTCz1rAGo,136
2
- the37lab_authlib/auth.py,sha256=olon7oBUeB_V6KIZ4H42rbN34c7h1_owJ5yUhOdYDoI,21429
3
- the37lab_authlib/db.py,sha256=iXA8kPAZ2SCZgXtrfNIoCnkDwm5W-Cl2MvT1X6ulwqY,2807
4
- the37lab_authlib/decorators.py,sha256=UaBPvMnOcNnYI8VdkXtKWHRBAVJ8yCb6ZhQCBUCgmE4,1249
5
- the37lab_authlib/exceptions.py,sha256=ONA64ktHAuj4w0ur4xUeWZQQmfZw9hHo4che1Bi-M3s,327
6
- the37lab_authlib/models.py,sha256=9-9ndGq-o9VGjHF8VvgMHvjhYOEapfOkocfjpqEFHY4,3516
7
- the37lab_authlib-0.1.1749279322.dist-info/METADATA,sha256=IExajURnxE--J9SdXmlPOfN8yMFgeXQw_WsFfM9WuYg,2711
8
- the37lab_authlib-0.1.1749279322.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- the37lab_authlib-0.1.1749279322.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
- the37lab_authlib-0.1.1749279322.dist-info/RECORD,,