the37lab-authlib 0.1.1751371611__py3-none-any.whl → 0.1.1768813136__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.
- the37lab_authlib/_git_version.txt +7 -0
- the37lab_authlib/auth.py +1689 -163
- the37lab_authlib/db.py +46 -4
- the37lab_authlib/decorators.py +6 -2
- the37lab_authlib-0.1.1768813136.dist-info/METADATA +19 -0
- the37lab_authlib-0.1.1768813136.dist-info/RECORD +11 -0
- {the37lab_authlib-0.1.1751371611.dist-info → the37lab_authlib-0.1.1768813136.dist-info}/top_level.txt +0 -0
- the37lab_authlib-0.1.1751371611.dist-info/METADATA +0 -250
- the37lab_authlib-0.1.1751371611.dist-info/RECORD +0 -10
- {the37lab_authlib-0.1.1751371611.dist-info → the37lab_authlib-0.1.1768813136.dist-info}/WHEEL +0 -0
the37lab_authlib/db.py
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import psycopg2
|
|
2
2
|
from psycopg2.extras import RealDictCursor
|
|
3
|
+
from psycopg2 import pool
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from .models import UUIDGenerator, IntegerGenerator
|
|
5
6
|
|
|
6
7
|
class Database:
|
|
7
|
-
def __init__(self, dsn, id_type='uuid'):
|
|
8
|
+
def __init__(self, dsn, id_type='uuid', min_conn=1, max_conn=10):
|
|
8
9
|
self.dsn = dsn
|
|
9
10
|
self.id_generator = UUIDGenerator() if id_type == 'uuid' else IntegerGenerator()
|
|
10
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()
|
|
11
16
|
self._init_db()
|
|
12
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
|
+
|
|
13
26
|
def _init_db(self):
|
|
14
27
|
with self.get_connection() as conn:
|
|
15
28
|
with conn.cursor() as cur:
|
|
@@ -47,6 +60,31 @@ class Database:
|
|
|
47
60
|
expires_at TIMESTAMP,
|
|
48
61
|
last_used_at TIMESTAMP
|
|
49
62
|
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS groups (
|
|
65
|
+
id {self._get_id_type()} PRIMARY KEY,
|
|
66
|
+
name VARCHAR(255) UNIQUE NOT NULL,
|
|
67
|
+
description TEXT,
|
|
68
|
+
created_at TIMESTAMP NOT NULL
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS group_users (
|
|
72
|
+
group_id {self._get_id_type()} REFERENCES groups(id),
|
|
73
|
+
user_id {self._get_id_type()} REFERENCES users(id),
|
|
74
|
+
PRIMARY KEY (group_id, user_id)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS group_groups (
|
|
78
|
+
group_id {self._get_id_type()} REFERENCES groups(id),
|
|
79
|
+
child_group_id {self._get_id_type()} REFERENCES groups(id),
|
|
80
|
+
PRIMARY KEY (group_id, child_group_id)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS group_roles (
|
|
84
|
+
group_id {self._get_id_type()} REFERENCES groups(id),
|
|
85
|
+
role_id {self._get_id_type()} REFERENCES roles(id),
|
|
86
|
+
PRIMARY KEY (group_id, role_id)
|
|
87
|
+
);
|
|
50
88
|
""")
|
|
51
89
|
|
|
52
90
|
def _get_id_type(self):
|
|
@@ -54,7 +92,7 @@ class Database:
|
|
|
54
92
|
|
|
55
93
|
@contextmanager
|
|
56
94
|
def get_connection(self):
|
|
57
|
-
conn =
|
|
95
|
+
conn = self._pool.getconn()
|
|
58
96
|
try:
|
|
59
97
|
yield conn
|
|
60
98
|
conn.commit()
|
|
@@ -62,7 +100,7 @@ class Database:
|
|
|
62
100
|
conn.rollback()
|
|
63
101
|
raise
|
|
64
102
|
finally:
|
|
65
|
-
|
|
103
|
+
self._pool.putconn(conn)
|
|
66
104
|
|
|
67
105
|
@contextmanager
|
|
68
106
|
def get_cursor(self):
|
|
@@ -71,4 +109,8 @@ class Database:
|
|
|
71
109
|
yield cur
|
|
72
110
|
|
|
73
111
|
def get_id_generator(self):
|
|
74
|
-
return self.id_generator
|
|
112
|
+
return self.id_generator
|
|
113
|
+
|
|
114
|
+
def close(self):
|
|
115
|
+
if self._pool:
|
|
116
|
+
self._pool.closeall()
|
the37lab_authlib/decorators.py
CHANGED
|
@@ -20,8 +20,12 @@ def require_auth(roles=None):
|
|
|
20
20
|
decorated_func = auth_decorator(f)
|
|
21
21
|
|
|
22
22
|
# Check roles if specified
|
|
23
|
-
if roles
|
|
24
|
-
|
|
23
|
+
if roles:
|
|
24
|
+
user_roles = set(user.get('roles', []))
|
|
25
|
+
expanded_user_roles = current_app.auth_manager._expand_roles(user_roles)
|
|
26
|
+
required_roles = set(roles)
|
|
27
|
+
if not expanded_user_roles.intersection(required_roles):
|
|
28
|
+
raise AuthError('Insufficient permissions', 403)
|
|
25
29
|
|
|
26
30
|
# Now execute the function
|
|
27
31
|
return decorated_func(*args, **kwargs)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: the37lab_authlib
|
|
3
|
+
Version: 0.1.1768813136
|
|
4
|
+
Summary: Python SDK for the Authlib
|
|
5
|
+
Author-email: the37lab <info@the37lab.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: flask
|
|
11
|
+
Requires-Dist: psycopg2-binary
|
|
12
|
+
Requires-Dist: pyjwt
|
|
13
|
+
Requires-Dist: python-dotenv
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: authlib
|
|
16
|
+
Requires-Dist: bcrypt
|
|
17
|
+
Requires-Dist: msal
|
|
18
|
+
Requires-Dist: slack-sdk
|
|
19
|
+
Requires-Dist: cachetools
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
|
|
2
|
+
the37lab_authlib/_git_version.txt,sha256=CrWzycLKyBIHzUIn0w-HoMZUmfbnfTWW7M19s8MxG_8,103
|
|
3
|
+
the37lab_authlib/auth.py,sha256=q0bL86C8dL5eFTqyvhjPjHoANZgE_-L5mp5UWuHkB50,90655
|
|
4
|
+
the37lab_authlib/db.py,sha256=-supyzQGL7ej-Vhl3ViFMIlBUVkvZe_-Wg4RuIh9ijU,4359
|
|
5
|
+
the37lab_authlib/decorators.py,sha256=9tfAPP7eMCPXUEDpiLOs1Fya_bstX6cYM2cn35tCNio,1534
|
|
6
|
+
the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
|
|
7
|
+
the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
|
|
8
|
+
the37lab_authlib-0.1.1768813136.dist-info/METADATA,sha256=d9eWR-MisU1GICD_5bJ9SjcXwljI5SzeZqWC27wfhpI,548
|
|
9
|
+
the37lab_authlib-0.1.1768813136.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
the37lab_authlib-0.1.1768813136.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
|
|
11
|
+
the37lab_authlib-0.1.1768813136.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: the37lab_authlib
|
|
3
|
-
Version: 0.1.1751371611
|
|
4
|
-
Summary: Python SDK for the Authlib
|
|
5
|
-
Author-email: the37lab <info@the37lab.com>
|
|
6
|
-
Classifier: Programming Language :: Python :: 3
|
|
7
|
-
Classifier: Operating System :: OS Independent
|
|
8
|
-
Requires-Python: >=3.9
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: flask
|
|
11
|
-
Requires-Dist: psycopg2-binary
|
|
12
|
-
Requires-Dist: pyjwt
|
|
13
|
-
Requires-Dist: python-dotenv
|
|
14
|
-
Requires-Dist: requests
|
|
15
|
-
Requires-Dist: authlib
|
|
16
|
-
Requires-Dist: bcrypt
|
|
17
|
-
|
|
18
|
-
# AuthLib
|
|
19
|
-
|
|
20
|
-
A Python authentication library that provides JWT, OAuth2, and API token authentication with PostgreSQL backend. This library is designed for seamless integration with Flask applications and provides a robust set of endpoints and utilities for user management, authentication, and API token handling.
|
|
21
|
-
|
|
22
|
-
## Table of Contents
|
|
23
|
-
- [AuthLib](#authlib)
|
|
24
|
-
- [Table of Contents](#table-of-contents)
|
|
25
|
-
- [Installation](#installation)
|
|
26
|
-
- [Quick Start](#quick-start)
|
|
27
|
-
- [Configuration](#configuration)
|
|
28
|
-
- [Required Parameters](#required-parameters)
|
|
29
|
-
- [Optional Parameters](#optional-parameters)
|
|
30
|
-
- [Example `oauth_config`:](#example-oauth_config)
|
|
31
|
-
- [API Endpoints](#api-endpoints)
|
|
32
|
-
- [Authentication](#authentication)
|
|
33
|
-
- [User Management](#user-management)
|
|
34
|
-
- [API Tokens](#api-tokens)
|
|
35
|
-
- [Authentication Flow](#authentication-flow)
|
|
36
|
-
- [User Object](#user-object)
|
|
37
|
-
- [Token Management](#token-management)
|
|
38
|
-
- [Development](#development)
|
|
39
|
-
- [Setup](#setup)
|
|
40
|
-
- [Database Setup](#database-setup)
|
|
41
|
-
- [Running Tests](#running-tests)
|
|
42
|
-
- [API Token Override for Testing](#api-token-override-for-testing)
|
|
43
|
-
- [Usage](#usage)
|
|
44
|
-
- [Warning](#warning)
|
|
45
|
-
- [User Override for Testing](#user-override-for-testing)
|
|
46
|
-
- [Usage](#usage-1)
|
|
47
|
-
- [Warning](#warning-1)
|
|
48
|
-
|
|
49
|
-
## Installation
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
pip install -e .
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Quick Start
|
|
56
|
-
|
|
57
|
-
```python
|
|
58
|
-
from flask import Flask
|
|
59
|
-
from authlib import AuthManager
|
|
60
|
-
|
|
61
|
-
app = Flask(__name__)
|
|
62
|
-
|
|
63
|
-
# Option 1: Explicit configuration
|
|
64
|
-
auth = AuthManager(
|
|
65
|
-
app=app,
|
|
66
|
-
db_dsn="postgresql://user:pass@localhost/dbname",
|
|
67
|
-
jwt_secret="your-secret-key",
|
|
68
|
-
oauth_config={
|
|
69
|
-
"google": {
|
|
70
|
-
"client_id": "your-client-id",
|
|
71
|
-
"client_secret": "your-client-secret"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Option 2: Use environment variables with a prefix (e.g., AMPA_)
|
|
77
|
-
# This will load:
|
|
78
|
-
# AMPA_DATABASE_URL, AMPA_JWT_SECRET, AMPA_GOOGLE_CLIENT_ID, AMPA_GOOGLE_CLIENT_SECRET
|
|
79
|
-
# auth = AuthManager(app=app, environment_prefix="AMPA")
|
|
80
|
-
|
|
81
|
-
@app.route("/protected")
|
|
82
|
-
@auth.require_auth(roles=["admin"])
|
|
83
|
-
def protected_route():
|
|
84
|
-
return "Protected content"
|
|
85
|
-
|
|
86
|
-
@app.route("/public")
|
|
87
|
-
@auth.public_endpoint
|
|
88
|
-
def custom_public_route():
|
|
89
|
-
return "Public content"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
`AuthManager`'s blueprint now registers a global error handler for
|
|
93
|
-
`AuthError` and authenticates requests for all of its routes by default.
|
|
94
|
-
Authenticated users are made available as `flask.g.requesting_user`.
|
|
95
|
-
Only the login, OAuth, token refresh, registration and role listing
|
|
96
|
-
endpoints are exempt from this check. Additional routes can be marked as
|
|
97
|
-
public using the `@auth.public_endpoint` decorator or
|
|
98
|
-
`auth.add_public_endpoint("auth.some_endpoint")`.
|
|
99
|
-
|
|
100
|
-
## Configuration
|
|
101
|
-
|
|
102
|
-
### Required Parameters
|
|
103
|
-
- `app`: Flask application instance
|
|
104
|
-
- `db_dsn`: PostgreSQL connection string
|
|
105
|
-
- `jwt_secret`: Secret key for JWT signing
|
|
106
|
-
|
|
107
|
-
### Optional Parameters
|
|
108
|
-
- `oauth_config`: Dictionary of OAuth provider configurations (see below)
|
|
109
|
-
- `token_expiry`: JWT token expiry time in seconds (default: 3600)
|
|
110
|
-
- `refresh_token_expiry`: Refresh token expiry time in seconds (default: 2592000)
|
|
111
|
-
- `environment_prefix`: If set, loads all configuration from environment variables with this prefix (e.g., `AMPA_DATABASE_URL`, `AMPA_JWT_SECRET`, `AMPA_GOOGLE_CLIENT_ID`, `AMPA_GOOGLE_CLIENT_SECRET`). Overrides other config if set.
|
|
112
|
-
|
|
113
|
-
#### Example `oauth_config`:
|
|
114
|
-
```python
|
|
115
|
-
{
|
|
116
|
-
"google": {
|
|
117
|
-
"client_id": "...",
|
|
118
|
-
"client_secret": "..."
|
|
119
|
-
},
|
|
120
|
-
"github": {
|
|
121
|
-
"client_id": "...",
|
|
122
|
-
"client_secret": "..."
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## API Endpoints
|
|
128
|
-
|
|
129
|
-
### Authentication
|
|
130
|
-
- `POST /api/v1/users/login` - Login with username/password
|
|
131
|
-
- **Request:** `{ "username": "string", "password": "string" }`
|
|
132
|
-
- **Response:** `{ "token": "jwt", "refresh_token": "jwt", "user": { ... } }`
|
|
133
|
-
- `POST /api/v1/users/login/oauth` - Get OAuth redirect URL
|
|
134
|
-
- **Request:** `{ "provider": "google|github|..." }`
|
|
135
|
-
- **Response:** `{ "redirect_url": "string" }`
|
|
136
|
-
- `GET /api/v1/users/login/oauth2callback` - OAuth callback
|
|
137
|
-
- **Query Params:** `code`, `state`, `provider`
|
|
138
|
-
- **Response:** `{ "token": "jwt", "refresh_token": "jwt", "user": { ... } }`
|
|
139
|
-
- `POST /api/v1/users/token-refresh` - Refresh JWT token
|
|
140
|
-
- **Request:** `{ "refresh_token": "jwt" }`
|
|
141
|
-
- **Response:** `{ "token": "jwt", "refresh_token": "jwt" }`
|
|
142
|
-
|
|
143
|
-
### User Management
|
|
144
|
-
- `POST /api/v1/users/register` - Register new user
|
|
145
|
-
- **Request:** `{ "username": "string", "password": "string", "email": "string", ... }`
|
|
146
|
-
- **Response:** `{ "user": { ... }, "token": "jwt", "refresh_token": "jwt" }`
|
|
147
|
-
- `GET /api/v1/users/login/profile` - Get user profile
|
|
148
|
-
- **Auth:** Bearer JWT
|
|
149
|
-
- **Response:** `{ "user": { ... } }`
|
|
150
|
-
- `GET /api/v1/users/roles` - Get available roles
|
|
151
|
-
- **Response:** `[ "admin", "user", ... ]`
|
|
152
|
-
|
|
153
|
-
### API Tokens
|
|
154
|
-
- `POST /api/v1/users/{user}/api-tokens` - Create API token
|
|
155
|
-
- **Request:** `{ "name": "string", "scopes": [ ... ] }`
|
|
156
|
-
- **Response:** `{ "token": "string", "id": "uuid", ... }`
|
|
157
|
-
- `GET /api/v1/users/{user}/api-tokens` - List API tokens
|
|
158
|
-
- **Response:** `[ { "id": "uuid", "name": "string", ... } ]`
|
|
159
|
-
- `DELETE /api/v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
160
|
-
- **Response:** `{ "success": true }`
|
|
161
|
-
|
|
162
|
-
## Authentication Flow
|
|
163
|
-
|
|
164
|
-
1. **Login:**
|
|
165
|
-
- User submits credentials to `/api/v1/users/login`.
|
|
166
|
-
- Receives JWT and refresh token.
|
|
167
|
-
2. **Token Refresh:**
|
|
168
|
-
- Use `/api/v1/users/token-refresh` with refresh token to get new JWT.
|
|
169
|
-
3. **OAuth:**
|
|
170
|
-
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
171
|
-
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
172
|
-
4. **Protected Routes:**
|
|
173
|
-
- All routes inside the provided blueprint are authenticated by default.
|
|
174
|
-
The authenticated user can be accessed via `g.requesting_user`.
|
|
175
|
-
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
176
|
-
|
|
177
|
-
## User Object
|
|
178
|
-
|
|
179
|
-
The user object returned by the API typically includes:
|
|
180
|
-
```json
|
|
181
|
-
{
|
|
182
|
-
"id": "uuid",
|
|
183
|
-
"username": "string",
|
|
184
|
-
"email": "string",
|
|
185
|
-
"roles": ["user", "admin"],
|
|
186
|
-
"created_at": "timestamp",
|
|
187
|
-
"last_login": "timestamp"
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Token Management
|
|
192
|
-
- **JWT:** Used for authenticating API requests. Include in `Authorization: Bearer <token>` header.
|
|
193
|
-
- **Refresh Token:** Used to obtain new JWTs without re-authenticating.
|
|
194
|
-
- **API Tokens:** Long-lived tokens for programmatic access, managed per user.
|
|
195
|
-
|
|
196
|
-
## Development
|
|
197
|
-
|
|
198
|
-
### Setup
|
|
199
|
-
1. Clone the repository
|
|
200
|
-
2. Create virtual environment:
|
|
201
|
-
```bash
|
|
202
|
-
python -m venv venv
|
|
203
|
-
venv\Scripts\activate
|
|
204
|
-
```
|
|
205
|
-
3. Install dependencies:
|
|
206
|
-
```bash
|
|
207
|
-
pip install -e ".[dev]"
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Database Setup
|
|
211
|
-
```bash
|
|
212
|
-
createdb authlib
|
|
213
|
-
python -m authlib.cli db init
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Running Tests
|
|
217
|
-
```bash
|
|
218
|
-
pytest
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## API Token Override for Testing
|
|
222
|
-
|
|
223
|
-
For testing purposes, you can bypass the database and provide a static mapping of API tokens to usernames using the `api_tokens` argument to `AuthManager` or the `{PREFIX}API_TOKENS` environment variable.
|
|
224
|
-
|
|
225
|
-
### Usage
|
|
226
|
-
|
|
227
|
-
- **Constructor argument:**
|
|
228
|
-
```python
|
|
229
|
-
AuthManager(api_tokens={"token1": "user1", "token2": "user2"})
|
|
230
|
-
```
|
|
231
|
-
- **Environment variable:**
|
|
232
|
-
Set `{PREFIX}API_TOKENS` to a comma-separated list of `token:username` pairs, e.g.:
|
|
233
|
-
```
|
|
234
|
-
export MYAPP_API_TOKENS="token1:user1,token2:user2"
|
|
235
|
-
```
|
|
236
|
-
Replace `MYAPP` with your environment prefix.
|
|
237
|
-
|
|
238
|
-
**Warning:** This method is intended only for testing and development. Do not use this approach in production environments.
|
|
239
|
-
|
|
240
|
-
## User Override for Testing
|
|
241
|
-
|
|
242
|
-
For testing purposes, you can force all authentication to return a specific user by setting the `{PREFIX}USER_OVERRIDE` environment variable:
|
|
243
|
-
|
|
244
|
-
```bash
|
|
245
|
-
export MYAPP_USER_OVERRIDE="testuser"
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
If set, all requests will be authenticated as the specified user, regardless of any tokens or credentials provided. This cannot be combined with `api_tokens` or `db_dsn`.
|
|
249
|
-
|
|
250
|
-
**Warning:** This method is intended only for testing and development. Do not use this approach in production environments.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
|
|
2
|
-
the37lab_authlib/auth.py,sha256=NKQ-k2QDB4ddw6QKO76RL5WeRpX2gNcpVr6VSHX2yrc,23358
|
|
3
|
-
the37lab_authlib/db.py,sha256=fTXxnfju0lmbFGPVbXpTMeDmJMeBgURVZTndyxyRyCc,2734
|
|
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.1751371611.dist-info/METADATA,sha256=J3mleZGwizcERDRBfrFnBW-21tTRh4nzfmWR575z3Bk,8319
|
|
8
|
-
the37lab_authlib-0.1.1751371611.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
the37lab_authlib-0.1.1751371611.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
|
|
10
|
-
the37lab_authlib-0.1.1751371611.dist-info/RECORD,,
|
{the37lab_authlib-0.1.1751371611.dist-info → the37lab_authlib-0.1.1768813136.dist-info}/WHEEL
RENAMED
|
File without changes
|