generateApiKey 0.4.7__tar.gz → 0.4.8__tar.gz
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.
- {generateApiKey-0.4.7/generateApiKey.egg-info → generateapikey-0.4.8}/PKG-INFO +90 -66
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/README.md +75 -61
- generateapikey-0.4.8/generateApiKey/__init__.py +173 -0
- generateapikey-0.4.8/generateApiKey/utils.py +82 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8/generateApiKey.egg-info}/PKG-INFO +90 -66
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/generateApiKey.egg-info/SOURCES.txt +3 -1
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/setup.py +1 -1
- generateapikey-0.4.8/tests/test_generate.py +97 -0
- generateApiKey-0.4.7/generateApiKey/__init__.py +0 -83
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/LICENSE +0 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/MANIFEST.in +0 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/generateApiKey.egg-info/dependency_links.txt +0 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/generateApiKey.egg-info/requires.txt +0 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/generateApiKey.egg-info/top_level.txt +0 -0
- {generateApiKey-0.4.7 → generateapikey-0.4.8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: generateApiKey
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.8
|
|
4
4
|
Summary: This package provides a convenient way to generate API keys using a secret, seed, and an optional include keyword. The generated keys are unique and secure, making them suitable for use in a variety of applications
|
|
5
5
|
Home-page: https://github.com/nuhmanpk/generate-api-key
|
|
6
6
|
Author: Nuhman Pk
|
|
@@ -10,13 +10,25 @@ Project-URL: Documentation, https://github.com/nuhmanpk/generate-api-key/blob/ma
|
|
|
10
10
|
Project-URL: Funding, https://github.com/sponsors/nuhmanpk
|
|
11
11
|
Project-URL: Source, https://github.com/nuhmanpk/generate-api-key/
|
|
12
12
|
Project-URL: Tracker, https://github.com/nuhmanpk/generate-api-key/issues
|
|
13
|
-
Platform: UNKNOWN
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Classifier: Operating System :: OS Independent
|
|
17
16
|
Requires-Python: >=3.9
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: cryptography
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: project-url
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
20
32
|
|
|
21
33
|
[](https://pepy.tech/project/generateApiKey)
|
|
22
34
|
[](https://pypi.org/project/YoutubeTags)
|
|
@@ -24,7 +36,7 @@ License-File: LICENSE
|
|
|
24
36
|

|
|
25
37
|

|
|
26
38
|
[](https://pepy.tech/project/generateApiKey)
|
|
27
|
-
[](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
|
|
28
40
|

|
|
29
41
|
# API Key Generation Package
|
|
30
42
|
|
|
@@ -59,93 +71,107 @@ To install your package, you can use the pip package manager by running the foll
|
|
|
59
71
|
pip install generateApiKey
|
|
60
72
|
```
|
|
61
73
|
|
|
62
|
-
#
|
|
63
|
-
Once the package is installed, you can import the package in your code by using the import statement:
|
|
64
|
-
```python
|
|
65
|
-
from generateApiKey import generateApiKey
|
|
66
|
-
```
|
|
67
|
-
# Generating an API key
|
|
68
|
-
To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
|
|
74
|
+
# Quick start
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
The simplest way to get a secure key — no arguments, no `await`:
|
|
71
77
|
|
|
72
78
|
```python
|
|
73
|
-
from generateApiKey import
|
|
79
|
+
from generateApiKey import generate_api_key
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
secret='Topsecrect'
|
|
77
|
-
api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
|
|
78
|
-
print(api_key)
|
|
81
|
+
api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
That's it. Everything below is optional.
|
|
85
|
+
|
|
86
|
+
## Common recipes
|
|
81
87
|
|
|
82
88
|
```python
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
generate_api_key(length=48) # longer key
|
|
90
|
+
generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
|
|
91
|
+
generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
|
|
92
|
+
generate_api_key(charset="hex", length=32) # hex only
|
|
93
|
+
generate_api_key(charset="base58") # no ambiguous 0/O/I/l
|
|
94
|
+
generate_api_key(charset="ABC123", length=10) # your own alphabet
|
|
95
|
+
generate_api_key(case="upper", charset="alphabetic")
|
|
86
96
|
```
|
|
87
|
-
|
|
97
|
+
|
|
98
|
+
## Charsets
|
|
99
|
+
|
|
100
|
+
Pass a preset name or a literal string of characters to `charset`:
|
|
101
|
+
|
|
102
|
+
| Preset | Characters |
|
|
103
|
+
|--------|-----------|
|
|
104
|
+
| `alphanumeric` (default) | `A-Z a-z 0-9` |
|
|
105
|
+
| `base62` | `A-Z a-z 0-9` |
|
|
106
|
+
| `base58` | base62 minus `0 O I l` |
|
|
107
|
+
| `base64url` | `A-Z a-z 0-9 - _` |
|
|
108
|
+
| `alphabetic` | `A-Z a-z` |
|
|
109
|
+
| `numeric` | `0-9` |
|
|
110
|
+
| `hex` | `0-9 a-f` |
|
|
111
|
+
| `base16` | `0-9 A-F` |
|
|
112
|
+
|
|
113
|
+
## Expiring keys
|
|
114
|
+
|
|
115
|
+
Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
|
|
88
116
|
|
|
89
117
|
```python
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
key = generate_api_key(expiry=60)
|
|
119
|
+
|
|
120
|
+
headers = {"Authorization": key} # works — it's a str
|
|
121
|
+
key.expires_at # unix timestamp
|
|
122
|
+
key.is_expired # False (until 60 min pass)
|
|
94
123
|
```
|
|
95
|
-
### With prefix
|
|
96
124
|
|
|
97
|
-
|
|
98
|
-
seed='12'
|
|
99
|
-
secret='Topsecrect'
|
|
100
|
-
api_key=await generateApiKey(secret,seed,
|
|
101
|
-
prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
|
|
102
|
-
print(api_key)
|
|
125
|
+
## Derived keys (secret + seed)
|
|
103
126
|
|
|
127
|
+
Provide both `secret` and `seed` to derive the key deterministically from them
|
|
128
|
+
(HMAC-SHA256). A random salt still makes each generated key unique:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
generate_api_key("mysecret", "user-42", length=20, include="prod")
|
|
104
132
|
```
|
|
105
133
|
|
|
106
|
-
|
|
134
|
+
## Async is optional
|
|
135
|
+
|
|
136
|
+
The library is synchronous by default. When you need an awaitable:
|
|
107
137
|
|
|
108
138
|
```python
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
113
|
-
print(api_key)
|
|
139
|
+
from generateApiKey import generate_api_key_async
|
|
140
|
+
|
|
141
|
+
api_key = await generate_api_key_async(length=48, prefix="sk")
|
|
114
142
|
```
|
|
115
|
-
|
|
143
|
+
|
|
144
|
+
> The original `await generateApiKey(secret, seed, ...)` still works unchanged
|
|
145
|
+
> for backwards compatibility, but `generate_api_key` is recommended.
|
|
116
146
|
|
|
117
147
|
## Error handling
|
|
118
|
-
It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
|
|
119
148
|
|
|
120
149
|
```python
|
|
121
150
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
126
|
-
print(api_key)
|
|
127
|
-
except Exception as e:
|
|
128
|
-
print(e)
|
|
151
|
+
api_key = generate_api_key(length=32)
|
|
152
|
+
except ValueError as e:
|
|
153
|
+
print(e) # e.g. bad length, or only one of secret/seed given
|
|
129
154
|
```
|
|
130
155
|
|
|
131
|
-
|
|
156
|
+
# Parameters
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
158
|
+
All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
|
|
159
|
+
|
|
160
|
+
| **Parameter** | **Type** | **Default** | **Description** |
|
|
161
|
+
|---------------|----------|-------------|-----------------|
|
|
162
|
+
| `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
|
|
163
|
+
| `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
|
|
164
|
+
| `length` | `int` | `32` | Number of characters in the key body. |
|
|
165
|
+
| `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
|
|
166
|
+
| `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
|
|
167
|
+
| `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
|
|
168
|
+
| `separator` | `str` | `"-"` | String between prefix and key. |
|
|
169
|
+
| `dashes` | `bool` | `False` | Group the key with dashes for readability. |
|
|
170
|
+
| `dash_group` | `int` | `4` | Group size when `dashes=True`. |
|
|
171
|
+
| `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
|
|
172
|
+
| `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
|
|
141
173
|
|
|
142
|
-
|
|
143
|
-
|----------|---------|-----------|--------------------|-------------|-------------------|
|
|
144
|
-
| secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
|
|
145
|
-
| seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
|
|
146
|
-
| include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
|
|
147
|
-
| add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
|
|
148
|
-
| prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
|
|
174
|
+
Returns an `ApiKey` — a `str` subclass, so it works anywhere a string does.
|
|
149
175
|
|
|
150
176
|
|
|
151
177
|
By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
|
|
@@ -158,6 +184,4 @@ We encourage you to use the package responsibly, and to report any security issu
|
|
|
158
184
|
|
|
159
185
|
|
|
160
186
|
|
|
161
|
-
**Happy coding! 🚀**
|
|
162
|
-
|
|
163
|
-
|
|
187
|
+
Made with ❤️ , **Happy coding! 🚀**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
[](https://pepy.tech/project/generateApiKey)
|
|
7
|
-
[](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
|
|
8
8
|

|
|
9
9
|
# API Key Generation Package
|
|
10
10
|
|
|
@@ -39,93 +39,107 @@ To install your package, you can use the pip package manager by running the foll
|
|
|
39
39
|
pip install generateApiKey
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
Once the package is installed, you can import the package in your code by using the import statement:
|
|
44
|
-
```python
|
|
45
|
-
from generateApiKey import generateApiKey
|
|
46
|
-
```
|
|
47
|
-
# Generating an API key
|
|
48
|
-
To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
|
|
42
|
+
# Quick start
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
The simplest way to get a secure key — no arguments, no `await`:
|
|
51
45
|
|
|
52
46
|
```python
|
|
53
|
-
from generateApiKey import
|
|
47
|
+
from generateApiKey import generate_api_key
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
secret='Topsecrect'
|
|
57
|
-
api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
|
|
58
|
-
print(api_key)
|
|
49
|
+
api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
|
|
59
50
|
```
|
|
60
51
|
|
|
52
|
+
That's it. Everything below is optional.
|
|
53
|
+
|
|
54
|
+
## Common recipes
|
|
61
55
|
|
|
62
56
|
```python
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
generate_api_key(length=48) # longer key
|
|
58
|
+
generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
|
|
59
|
+
generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
|
|
60
|
+
generate_api_key(charset="hex", length=32) # hex only
|
|
61
|
+
generate_api_key(charset="base58") # no ambiguous 0/O/I/l
|
|
62
|
+
generate_api_key(charset="ABC123", length=10) # your own alphabet
|
|
63
|
+
generate_api_key(case="upper", charset="alphabetic")
|
|
66
64
|
```
|
|
67
|
-
|
|
65
|
+
|
|
66
|
+
## Charsets
|
|
67
|
+
|
|
68
|
+
Pass a preset name or a literal string of characters to `charset`:
|
|
69
|
+
|
|
70
|
+
| Preset | Characters |
|
|
71
|
+
|--------|-----------|
|
|
72
|
+
| `alphanumeric` (default) | `A-Z a-z 0-9` |
|
|
73
|
+
| `base62` | `A-Z a-z 0-9` |
|
|
74
|
+
| `base58` | base62 minus `0 O I l` |
|
|
75
|
+
| `base64url` | `A-Z a-z 0-9 - _` |
|
|
76
|
+
| `alphabetic` | `A-Z a-z` |
|
|
77
|
+
| `numeric` | `0-9` |
|
|
78
|
+
| `hex` | `0-9 a-f` |
|
|
79
|
+
| `base16` | `0-9 A-F` |
|
|
80
|
+
|
|
81
|
+
## Expiring keys
|
|
82
|
+
|
|
83
|
+
Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
|
|
68
84
|
|
|
69
85
|
```python
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
key = generate_api_key(expiry=60)
|
|
87
|
+
|
|
88
|
+
headers = {"Authorization": key} # works — it's a str
|
|
89
|
+
key.expires_at # unix timestamp
|
|
90
|
+
key.is_expired # False (until 60 min pass)
|
|
74
91
|
```
|
|
75
|
-
### With prefix
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
secret
|
|
80
|
-
|
|
81
|
-
prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
|
|
82
|
-
print(api_key)
|
|
93
|
+
## Derived keys (secret + seed)
|
|
94
|
+
|
|
95
|
+
Provide both `secret` and `seed` to derive the key deterministically from them
|
|
96
|
+
(HMAC-SHA256). A random salt still makes each generated key unique:
|
|
83
97
|
|
|
98
|
+
```python
|
|
99
|
+
generate_api_key("mysecret", "user-42", length=20, include="prod")
|
|
84
100
|
```
|
|
85
101
|
|
|
86
|
-
|
|
102
|
+
## Async is optional
|
|
103
|
+
|
|
104
|
+
The library is synchronous by default. When you need an awaitable:
|
|
87
105
|
|
|
88
106
|
```python
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
93
|
-
print(api_key)
|
|
107
|
+
from generateApiKey import generate_api_key_async
|
|
108
|
+
|
|
109
|
+
api_key = await generate_api_key_async(length=48, prefix="sk")
|
|
94
110
|
```
|
|
95
|
-
|
|
111
|
+
|
|
112
|
+
> The original `await generateApiKey(secret, seed, ...)` still works unchanged
|
|
113
|
+
> for backwards compatibility, but `generate_api_key` is recommended.
|
|
96
114
|
|
|
97
115
|
## Error handling
|
|
98
|
-
It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
|
|
99
116
|
|
|
100
117
|
```python
|
|
101
118
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
106
|
-
print(api_key)
|
|
107
|
-
except Exception as e:
|
|
108
|
-
print(e)
|
|
119
|
+
api_key = generate_api_key(length=32)
|
|
120
|
+
except ValueError as e:
|
|
121
|
+
print(e) # e.g. bad length, or only one of secret/seed given
|
|
109
122
|
```
|
|
110
123
|
|
|
111
|
-
|
|
124
|
+
# Parameters
|
|
112
125
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
|
|
127
|
+
|
|
128
|
+
| **Parameter** | **Type** | **Default** | **Description** |
|
|
129
|
+
|---------------|----------|-------------|-----------------|
|
|
130
|
+
| `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
|
|
131
|
+
| `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
|
|
132
|
+
| `length` | `int` | `32` | Number of characters in the key body. |
|
|
133
|
+
| `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
|
|
134
|
+
| `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
|
|
135
|
+
| `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
|
|
136
|
+
| `separator` | `str` | `"-"` | String between prefix and key. |
|
|
137
|
+
| `dashes` | `bool` | `False` | Group the key with dashes for readability. |
|
|
138
|
+
| `dash_group` | `int` | `4` | Group size when `dashes=True`. |
|
|
139
|
+
| `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
|
|
140
|
+
| `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
|----------|---------|-----------|--------------------|-------------|-------------------|
|
|
124
|
-
| secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
|
|
125
|
-
| seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
|
|
126
|
-
| include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
|
|
127
|
-
| add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
|
|
128
|
-
| prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
|
|
142
|
+
Returns an `ApiKey` — a `str` subclass, so it works anywhere a string does.
|
|
129
143
|
|
|
130
144
|
|
|
131
145
|
By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
|
|
@@ -138,4 +152,4 @@ We encourage you to use the package responsibly, and to report any security issu
|
|
|
138
152
|
|
|
139
153
|
|
|
140
154
|
|
|
141
|
-
**Happy coding! 🚀**
|
|
155
|
+
Made with ❤️ , **Happy coding! 🚀**
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""generateApiKey — simple, secure API key generation.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
from generateApiKey import generate_api_key
|
|
6
|
+
|
|
7
|
+
key = generate_api_key() # secure random, 32 chars
|
|
8
|
+
key = generate_api_key(length=48, prefix="sk") # sk-xxxxxxxx...
|
|
9
|
+
key = generate_api_key(expiry=60) # expires in 60 minutes
|
|
10
|
+
|
|
11
|
+
Everything is synchronous by default. Use :func:`generate_api_key_async`
|
|
12
|
+
when you need an awaitable, or the legacy :func:`generateApiKey`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from .utils import (
|
|
18
|
+
CHARSETS,
|
|
19
|
+
build_charset,
|
|
20
|
+
random_key,
|
|
21
|
+
derived_key,
|
|
22
|
+
add_dashes,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"generate_api_key",
|
|
27
|
+
"generate_api_key_async",
|
|
28
|
+
"generateApiKey",
|
|
29
|
+
"ApiKey",
|
|
30
|
+
"is_expired",
|
|
31
|
+
"is_token_valid",
|
|
32
|
+
"CHARSETS",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ApiKey(str):
|
|
37
|
+
"""An API key.
|
|
38
|
+
|
|
39
|
+
Behaves exactly like a ``str`` (usable in headers, f-strings, comparisons)
|
|
40
|
+
while carrying optional expiry metadata::
|
|
41
|
+
|
|
42
|
+
key = generate_api_key(expiry=60)
|
|
43
|
+
headers = {"Authorization": key} # works, it's a str
|
|
44
|
+
key.expires_at # unix timestamp or None
|
|
45
|
+
key.is_expired # bool
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
expires_at = None
|
|
49
|
+
|
|
50
|
+
def __new__(cls, value: str, expires_at: int = None) -> "ApiKey":
|
|
51
|
+
obj = super().__new__(cls, value)
|
|
52
|
+
obj.expires_at = expires_at
|
|
53
|
+
return obj
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def is_expired(self) -> bool:
|
|
57
|
+
"""True if an expiry was set and it is now in the past."""
|
|
58
|
+
return self.expires_at is not None and time.time() >= self.expires_at
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def generate_api_key(
|
|
62
|
+
secret: str = None,
|
|
63
|
+
seed: str = None,
|
|
64
|
+
*,
|
|
65
|
+
length: int = 32,
|
|
66
|
+
charset: str = "alphanumeric",
|
|
67
|
+
case: str = "mixed",
|
|
68
|
+
prefix: str = None,
|
|
69
|
+
separator: str = "-",
|
|
70
|
+
dashes: bool = False,
|
|
71
|
+
dash_group: int = 4,
|
|
72
|
+
include: str = None,
|
|
73
|
+
expiry: int = None,
|
|
74
|
+
) -> ApiKey:
|
|
75
|
+
"""Generate a secure API key.
|
|
76
|
+
|
|
77
|
+
With no arguments it returns a cryptographically random key. Pass both
|
|
78
|
+
``secret`` and ``seed`` to derive the key from them via HMAC-SHA256 instead.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
secret: Optional secret. If given with ``seed``, the key is HMAC-derived.
|
|
82
|
+
seed: Optional per-key value used together with ``secret``.
|
|
83
|
+
length: Number of characters in the key body (before prefix/dashes). Default 32.
|
|
84
|
+
charset: A preset name (see ``CHARSETS``) or a literal string of allowed
|
|
85
|
+
characters. Presets: alphanumeric, base62, base58, base64url,
|
|
86
|
+
alphabetic, numeric, hex, base16.
|
|
87
|
+
case: ``"mixed"`` (default), ``"lower"`` or ``"upper"``.
|
|
88
|
+
prefix: Optional prefix, joined with ``separator`` (e.g. ``sk-...``).
|
|
89
|
+
separator: String used between prefix and key. Default ``"-"``.
|
|
90
|
+
dashes: If True, group the key body with dashes for readability.
|
|
91
|
+
dash_group: Group size when ``dashes`` is True. Default 4.
|
|
92
|
+
include: Extra value mixed into a derived key (requires secret + seed).
|
|
93
|
+
expiry: Optional expiry in minutes. Sets ``ApiKey.expires_at``.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
An :class:`ApiKey` (a ``str`` subclass).
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: If ``length`` < 1, or a bad ``charset``/``case`` is given, or
|
|
100
|
+
only one of ``secret``/``seed`` is supplied.
|
|
101
|
+
"""
|
|
102
|
+
if length < 1:
|
|
103
|
+
raise ValueError("length must be a positive integer.")
|
|
104
|
+
if (secret is None) != (seed is None):
|
|
105
|
+
raise ValueError("Provide both 'secret' and 'seed', or neither.")
|
|
106
|
+
|
|
107
|
+
chars = build_charset(charset, case)
|
|
108
|
+
|
|
109
|
+
if secret and seed:
|
|
110
|
+
body = derived_key(secret, seed, include, length, chars)
|
|
111
|
+
else:
|
|
112
|
+
body = random_key(length, chars)
|
|
113
|
+
|
|
114
|
+
if dashes:
|
|
115
|
+
body = add_dashes(body, dash_group)
|
|
116
|
+
|
|
117
|
+
key = f"{prefix}{separator}{body}" if prefix else body
|
|
118
|
+
|
|
119
|
+
expires_at = int(time.time()) + expiry * 60 if expiry else None
|
|
120
|
+
return ApiKey(key, expires_at)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def generate_api_key_async(*args, **kwargs) -> ApiKey:
|
|
124
|
+
"""Async wrapper around :func:`generate_api_key` with the same signature."""
|
|
125
|
+
return generate_api_key(*args, **kwargs)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_expired(key: "ApiKey") -> bool:
|
|
129
|
+
"""Return True if ``key`` is an expired :class:`ApiKey`."""
|
|
130
|
+
return getattr(key, "is_expired", False)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# Backwards-compatible legacy API (async). Prefer generate_api_key above.
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
async def generateApiKey(
|
|
138
|
+
secret: str,
|
|
139
|
+
seed: str,
|
|
140
|
+
include: str = None,
|
|
141
|
+
add_dashes: bool = False,
|
|
142
|
+
prefix: str = None,
|
|
143
|
+
length: int = None,
|
|
144
|
+
expiry: int = None,
|
|
145
|
+
) -> ApiKey:
|
|
146
|
+
"""Legacy async entry point. Kept for backwards compatibility.
|
|
147
|
+
|
|
148
|
+
New code should use :func:`generate_api_key` (synchronous, more options).
|
|
149
|
+
"""
|
|
150
|
+
if not secret or not seed:
|
|
151
|
+
raise ValueError("Secret and seed cannot be empty strings.")
|
|
152
|
+
|
|
153
|
+
return generate_api_key(
|
|
154
|
+
secret,
|
|
155
|
+
seed,
|
|
156
|
+
length=length or 32,
|
|
157
|
+
include=include,
|
|
158
|
+
prefix=prefix,
|
|
159
|
+
dashes=add_dashes,
|
|
160
|
+
dash_group=4,
|
|
161
|
+
expiry=expiry,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def is_token_valid(token: str) -> bool:
|
|
166
|
+
"""Legacy check for the ``_<timestamp>`` expiry token format."""
|
|
167
|
+
if "_" not in token:
|
|
168
|
+
return False
|
|
169
|
+
try:
|
|
170
|
+
expiration_time = int(token.split("_")[1])
|
|
171
|
+
return int(time.time()) < expiration_time
|
|
172
|
+
except (IndexError, ValueError):
|
|
173
|
+
return False
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import hmac
|
|
2
|
+
import time
|
|
3
|
+
import hashlib
|
|
4
|
+
import string
|
|
5
|
+
from secrets import choice, token_bytes
|
|
6
|
+
|
|
7
|
+
# Named character sets available via the ``charset`` option.
|
|
8
|
+
CHARSETS = {
|
|
9
|
+
"alphanumeric": string.ascii_letters + string.digits,
|
|
10
|
+
"base62": string.ascii_letters + string.digits,
|
|
11
|
+
"alphabetic": string.ascii_letters,
|
|
12
|
+
"numeric": string.digits,
|
|
13
|
+
"hex": string.digits + "abcdef",
|
|
14
|
+
"base16": string.digits + "ABCDEF",
|
|
15
|
+
"base64url": string.ascii_letters + string.digits + "-_",
|
|
16
|
+
# base58 = base62 minus the ambiguous chars 0, O, I and l
|
|
17
|
+
"base58": "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_charset(charset: str, case: str) -> str:
|
|
22
|
+
"""Resolve a charset name (or a custom literal string) and apply a case rule.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
charset: A named preset in ``CHARSETS`` or a literal string of characters.
|
|
26
|
+
case: One of ``"mixed"``, ``"lower"`` or ``"upper"``. Only affects letters.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The final string of characters to draw from.
|
|
30
|
+
"""
|
|
31
|
+
chars = CHARSETS.get(charset, charset)
|
|
32
|
+
if not chars:
|
|
33
|
+
raise ValueError(f"Unknown or empty charset: {charset!r}")
|
|
34
|
+
|
|
35
|
+
if case == "lower":
|
|
36
|
+
chars = "".join(dict.fromkeys(chars.lower()))
|
|
37
|
+
elif case == "upper":
|
|
38
|
+
chars = "".join(dict.fromkeys(chars.upper()))
|
|
39
|
+
elif case != "mixed":
|
|
40
|
+
raise ValueError("case must be one of 'mixed', 'lower', 'upper'.")
|
|
41
|
+
return chars
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def random_key(length: int, chars: str) -> str:
|
|
45
|
+
"""Return a cryptographically secure random string of ``length`` from ``chars``."""
|
|
46
|
+
return "".join(choice(chars) for _ in range(length))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def derived_key(secret: str, seed: str, include: str, length: int, chars: str) -> str:
|
|
50
|
+
"""Derive a key from ``secret``/``seed`` using HMAC-SHA256 as a byte stream.
|
|
51
|
+
|
|
52
|
+
A fresh random salt keeps every generated key unique even for identical
|
|
53
|
+
inputs. The HMAC output is mapped onto ``chars`` to honour the charset.
|
|
54
|
+
"""
|
|
55
|
+
salt = token_bytes(16)
|
|
56
|
+
message = f"{seed}{include or ''}{int(time.time())}".encode()
|
|
57
|
+
material = bytearray()
|
|
58
|
+
counter = 0
|
|
59
|
+
while len(material) < length:
|
|
60
|
+
block = hmac.new(
|
|
61
|
+
secret.encode(),
|
|
62
|
+
message + salt + counter.to_bytes(4, "big"),
|
|
63
|
+
hashlib.sha256,
|
|
64
|
+
).digest()
|
|
65
|
+
material.extend(block)
|
|
66
|
+
counter += 1
|
|
67
|
+
return "".join(chars[b % len(chars)] for b in material[:length])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def add_dashes(key: str, group: int, sep: str = "-") -> str:
|
|
71
|
+
"""Insert ``sep`` every ``group`` characters, e.g. ``ABCD-EFGH-IJKL``."""
|
|
72
|
+
if group < 1:
|
|
73
|
+
return key
|
|
74
|
+
return sep.join(key[i : i + group] for i in range(0, len(key), group))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def generate_expiration_token(minutes: int) -> str:
|
|
78
|
+
"""Return a stateless expiry token of the form ``_<unix_timestamp>``.
|
|
79
|
+
|
|
80
|
+
Kept for backwards compatibility with :func:`generateApiKey`.
|
|
81
|
+
"""
|
|
82
|
+
return f"_{int(time.time()) + minutes * 60}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: generateApiKey
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.8
|
|
4
4
|
Summary: This package provides a convenient way to generate API keys using a secret, seed, and an optional include keyword. The generated keys are unique and secure, making them suitable for use in a variety of applications
|
|
5
5
|
Home-page: https://github.com/nuhmanpk/generate-api-key
|
|
6
6
|
Author: Nuhman Pk
|
|
@@ -10,13 +10,25 @@ Project-URL: Documentation, https://github.com/nuhmanpk/generate-api-key/blob/ma
|
|
|
10
10
|
Project-URL: Funding, https://github.com/sponsors/nuhmanpk
|
|
11
11
|
Project-URL: Source, https://github.com/nuhmanpk/generate-api-key/
|
|
12
12
|
Project-URL: Tracker, https://github.com/nuhmanpk/generate-api-key/issues
|
|
13
|
-
Platform: UNKNOWN
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
15
|
Classifier: Operating System :: OS Independent
|
|
17
16
|
Requires-Python: >=3.9
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: cryptography
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: project-url
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
20
32
|
|
|
21
33
|
[](https://pepy.tech/project/generateApiKey)
|
|
22
34
|
[](https://pypi.org/project/YoutubeTags)
|
|
@@ -24,7 +36,7 @@ License-File: LICENSE
|
|
|
24
36
|

|
|
25
37
|

|
|
26
38
|
[](https://pepy.tech/project/generateApiKey)
|
|
27
|
-
[](https://github.com/nuhmanpk/generate-api-key/actions/workflows/pypi-upload.yml)
|
|
28
40
|

|
|
29
41
|
# API Key Generation Package
|
|
30
42
|
|
|
@@ -59,93 +71,107 @@ To install your package, you can use the pip package manager by running the foll
|
|
|
59
71
|
pip install generateApiKey
|
|
60
72
|
```
|
|
61
73
|
|
|
62
|
-
#
|
|
63
|
-
Once the package is installed, you can import the package in your code by using the import statement:
|
|
64
|
-
```python
|
|
65
|
-
from generateApiKey import generateApiKey
|
|
66
|
-
```
|
|
67
|
-
# Generating an API key
|
|
68
|
-
To generate an API key, you can use the generateApiKey() function and pass in the secret, seed, and an optional include keyword. For example:
|
|
74
|
+
# Quick start
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
The simplest way to get a secure key — no arguments, no `await`:
|
|
71
77
|
|
|
72
78
|
```python
|
|
73
|
-
from generateApiKey import
|
|
79
|
+
from generateApiKey import generate_api_key
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
secret='Topsecrect'
|
|
77
|
-
api_key= await generateApiKey(secret,seed) # KaEQfEOP0ZMXbkgElkuPRv6iUrMxQDytwrQPKhH0eO8
|
|
78
|
-
print(api_key)
|
|
81
|
+
api_key = generate_api_key() # 'uYTvAuzrTHAeU202ScbN6jV8kxIMogDf'
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
That's it. Everything below is optional.
|
|
85
|
+
|
|
86
|
+
## Common recipes
|
|
81
87
|
|
|
82
88
|
```python
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
generate_api_key(length=48) # longer key
|
|
90
|
+
generate_api_key(prefix="sk") # 'sk-gRsNbb2lQ1cZxoT3...'
|
|
91
|
+
generate_api_key(dashes=True) # 'GdBA-6DhM-M3Fv-g99C...'
|
|
92
|
+
generate_api_key(charset="hex", length=32) # hex only
|
|
93
|
+
generate_api_key(charset="base58") # no ambiguous 0/O/I/l
|
|
94
|
+
generate_api_key(charset="ABC123", length=10) # your own alphabet
|
|
95
|
+
generate_api_key(case="upper", charset="alphabetic")
|
|
86
96
|
```
|
|
87
|
-
|
|
97
|
+
|
|
98
|
+
## Charsets
|
|
99
|
+
|
|
100
|
+
Pass a preset name or a literal string of characters to `charset`:
|
|
101
|
+
|
|
102
|
+
| Preset | Characters |
|
|
103
|
+
|--------|-----------|
|
|
104
|
+
| `alphanumeric` (default) | `A-Z a-z 0-9` |
|
|
105
|
+
| `base62` | `A-Z a-z 0-9` |
|
|
106
|
+
| `base58` | base62 minus `0 O I l` |
|
|
107
|
+
| `base64url` | `A-Z a-z 0-9 - _` |
|
|
108
|
+
| `alphabetic` | `A-Z a-z` |
|
|
109
|
+
| `numeric` | `0-9` |
|
|
110
|
+
| `hex` | `0-9 a-f` |
|
|
111
|
+
| `base16` | `0-9 A-F` |
|
|
112
|
+
|
|
113
|
+
## Expiring keys
|
|
114
|
+
|
|
115
|
+
Set `expiry` (minutes). The returned key **is a string** but also carries expiry metadata:
|
|
88
116
|
|
|
89
117
|
```python
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
key = generate_api_key(expiry=60)
|
|
119
|
+
|
|
120
|
+
headers = {"Authorization": key} # works — it's a str
|
|
121
|
+
key.expires_at # unix timestamp
|
|
122
|
+
key.is_expired # False (until 60 min pass)
|
|
94
123
|
```
|
|
95
|
-
### With prefix
|
|
96
124
|
|
|
97
|
-
|
|
98
|
-
seed='12'
|
|
99
|
-
secret='Topsecrect'
|
|
100
|
-
api_key=await generateApiKey(secret,seed,
|
|
101
|
-
prefix='hf',add_dashes=True) # hf-db27e2d7-d0bb-5014-9f75-4c2fda33f727
|
|
102
|
-
print(api_key)
|
|
125
|
+
## Derived keys (secret + seed)
|
|
103
126
|
|
|
127
|
+
Provide both `secret` and `seed` to derive the key deterministically from them
|
|
128
|
+
(HMAC-SHA256). A random salt still makes each generated key unique:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
generate_api_key("mysecret", "user-42", length=20, include="prod")
|
|
104
132
|
```
|
|
105
133
|
|
|
106
|
-
|
|
134
|
+
## Async is optional
|
|
135
|
+
|
|
136
|
+
The library is synchronous by default. When you need an awaitable:
|
|
107
137
|
|
|
108
138
|
```python
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
113
|
-
print(api_key)
|
|
139
|
+
from generateApiKey import generate_api_key_async
|
|
140
|
+
|
|
141
|
+
api_key = await generate_api_key_async(length=48, prefix="sk")
|
|
114
142
|
```
|
|
115
|
-
|
|
143
|
+
|
|
144
|
+
> The original `await generateApiKey(secret, seed, ...)` still works unchanged
|
|
145
|
+
> for backwards compatibility, but `generate_api_key` is recommended.
|
|
116
146
|
|
|
117
147
|
## Error handling
|
|
118
|
-
It is a good practice to handle errors, you can use the try and except block to handle any exception that may occur during the key generation process.
|
|
119
148
|
|
|
120
149
|
```python
|
|
121
150
|
try:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
api_key = await generateApiKey(secret, seed, include)
|
|
126
|
-
print(api_key)
|
|
127
|
-
except Exception as e:
|
|
128
|
-
print(e)
|
|
151
|
+
api_key = generate_api_key(length=32)
|
|
152
|
+
except ValueError as e:
|
|
153
|
+
print(e) # e.g. bad length, or only one of secret/seed given
|
|
129
154
|
```
|
|
130
155
|
|
|
131
|
-
|
|
156
|
+
# Parameters
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
158
|
+
All parameters are optional. Call `generate_api_key()` with nothing for a secure default.
|
|
159
|
+
|
|
160
|
+
| **Parameter** | **Type** | **Default** | **Description** |
|
|
161
|
+
|---------------|----------|-------------|-----------------|
|
|
162
|
+
| `secret` | `str` | `None` | Optional secret. Given with `seed`, the key is HMAC-derived instead of random. |
|
|
163
|
+
| `seed` | `str` | `None` | Optional per-key value, used together with `secret`. |
|
|
164
|
+
| `length` | `int` | `32` | Number of characters in the key body. |
|
|
165
|
+
| `charset` | `str` | `"alphanumeric"` | Preset name (see table) or a literal string of allowed characters. |
|
|
166
|
+
| `case` | `str` | `"mixed"` | `"mixed"`, `"lower"` or `"upper"`. Affects letters only. |
|
|
167
|
+
| `prefix` | `str` | `None` | Prefix joined with `separator`, e.g. `sk-...`. |
|
|
168
|
+
| `separator` | `str` | `"-"` | String between prefix and key. |
|
|
169
|
+
| `dashes` | `bool` | `False` | Group the key with dashes for readability. |
|
|
170
|
+
| `dash_group` | `int` | `4` | Group size when `dashes=True`. |
|
|
171
|
+
| `include` | `str` | `None` | Extra value mixed into a derived key (needs `secret` + `seed`). |
|
|
172
|
+
| `expiry` | `int` | `None` | Expiry in minutes; sets `.expires_at` / `.is_expired`. |
|
|
141
173
|
|
|
142
|
-
|
|
143
|
-
|----------|---------|-----------|--------------------|-------------|-------------------|
|
|
144
|
-
| secret | `String` | Yes | "Topsecret" | - | The secret key used to generate the API key. It should be kept private and not shared with unauthorized parties. |
|
|
145
|
-
| seed | `String` | Yes | "12" | - | A value used to seed the generation of the API key. It can be any value, but it should be unique for each key. |
|
|
146
|
-
| include | `String` | Optional | "user1" | - | An optional value that can be included in the generated API key. It can be used to identify the user or application associated with the key. |
|
|
147
|
-
| add_dashes | `boolean` | Optional | True | False | A value used to determine wheather to add dashes or not in a key |
|
|
148
|
-
| prefix | `String` | Optional | "hf" | None | A Value that is added infornt of the apikey generted to make your key unique for your platform |
|
|
174
|
+
Returns an `ApiKey` — a `str` subclass, so it works anywhere a string does.
|
|
149
175
|
|
|
150
176
|
|
|
151
177
|
By using this package, you are solely liable for any legal issues that may arise from its unauthorized use or misuse, as well as any security risks that may result from not properly securing the secret key used to generate the API keys.
|
|
@@ -158,6 +184,4 @@ We encourage you to use the package responsibly, and to report any security issu
|
|
|
158
184
|
|
|
159
185
|
|
|
160
186
|
|
|
161
|
-
**Happy coding! 🚀**
|
|
162
|
-
|
|
163
|
-
|
|
187
|
+
Made with ❤️ , **Happy coding! 🚀**
|
|
@@ -3,8 +3,10 @@ MANIFEST.in
|
|
|
3
3
|
README.md
|
|
4
4
|
setup.py
|
|
5
5
|
generateApiKey/__init__.py
|
|
6
|
+
generateApiKey/utils.py
|
|
6
7
|
generateApiKey.egg-info/PKG-INFO
|
|
7
8
|
generateApiKey.egg-info/SOURCES.txt
|
|
8
9
|
generateApiKey.egg-info/dependency_links.txt
|
|
9
10
|
generateApiKey.egg-info/requires.txt
|
|
10
|
-
generateApiKey.egg-info/top_level.txt
|
|
11
|
+
generateApiKey.egg-info/top_level.txt
|
|
12
|
+
tests/test_generate.py
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from generateApiKey import (
|
|
5
|
+
generate_api_key,
|
|
6
|
+
generate_api_key_async,
|
|
7
|
+
generateApiKey,
|
|
8
|
+
ApiKey,
|
|
9
|
+
is_expired,
|
|
10
|
+
CHARSETS,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_default_length_and_charset():
|
|
15
|
+
key = generate_api_key()
|
|
16
|
+
assert isinstance(key, ApiKey) and isinstance(key, str)
|
|
17
|
+
assert len(key) == 32
|
|
18
|
+
assert all(c in CHARSETS["alphanumeric"] for c in key)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_length():
|
|
22
|
+
assert len(generate_api_key(length=48)) == 48
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_charset_hex():
|
|
26
|
+
key = generate_api_key(charset="hex", length=40)
|
|
27
|
+
assert all(c in CHARSETS["hex"] for c in key)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_case_upper():
|
|
31
|
+
key = generate_api_key(charset="alphabetic", case="upper", length=20)
|
|
32
|
+
assert key.isupper()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_custom_charset():
|
|
36
|
+
key = generate_api_key(charset="AB12", length=16)
|
|
37
|
+
assert set(key) <= set("AB12")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_prefix():
|
|
41
|
+
assert generate_api_key(prefix="sk", length=8).startswith("sk-")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_dashes():
|
|
45
|
+
key = generate_api_key(dashes=True, dash_group=4, length=16)
|
|
46
|
+
assert key.count("-") == 3
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_expiry_metadata():
|
|
50
|
+
key = generate_api_key(expiry=60)
|
|
51
|
+
assert key.expires_at is not None
|
|
52
|
+
assert not key.is_expired
|
|
53
|
+
assert not is_expired(key)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_expired():
|
|
57
|
+
past = ApiKey("x", int(time.time()) - 1)
|
|
58
|
+
assert past.is_expired
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_uniqueness():
|
|
62
|
+
assert len({generate_api_key() for _ in range(1000)}) == 1000
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_secret_seed_pair_required():
|
|
66
|
+
for kwargs in ({"secret": "x"}, {"seed": "y"}):
|
|
67
|
+
try:
|
|
68
|
+
generate_api_key(**kwargs)
|
|
69
|
+
assert False, "expected ValueError"
|
|
70
|
+
except ValueError:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_bad_length():
|
|
75
|
+
try:
|
|
76
|
+
generate_api_key(length=0)
|
|
77
|
+
assert False
|
|
78
|
+
except ValueError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_async_wrapper():
|
|
83
|
+
key = asyncio.run(generate_api_key_async(length=8))
|
|
84
|
+
assert len(key) == 8
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_legacy_async():
|
|
88
|
+
key = asyncio.run(generateApiKey("s", "seed", prefix="hf", add_dashes=True))
|
|
89
|
+
assert key.startswith("hf-")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
for name, fn in sorted(globals().items()):
|
|
94
|
+
if name.startswith("test_"):
|
|
95
|
+
fn()
|
|
96
|
+
print("ok", name)
|
|
97
|
+
print("all passed")
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import hmac
|
|
3
|
-
import uuid
|
|
4
|
-
import random
|
|
5
|
-
import base64
|
|
6
|
-
import time
|
|
7
|
-
import datetime
|
|
8
|
-
import re
|
|
9
|
-
from secrets import token_bytes
|
|
10
|
-
from cryptography.hazmat.primitives import hashes
|
|
11
|
-
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
async def generateApiKey(
|
|
15
|
-
secret: str,
|
|
16
|
-
seed: str,
|
|
17
|
-
include: str = None,
|
|
18
|
-
add_dashes: bool = False,
|
|
19
|
-
prefix: str = None
|
|
20
|
-
) -> str:
|
|
21
|
-
"""
|
|
22
|
-
Generate a secure API key based on a secret, seed, and optional parameters.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
secret: The secret key used for hashing.
|
|
26
|
-
seed: A unique identifier used to personalize the key.
|
|
27
|
-
include: An optional string to be included in the key generation process.
|
|
28
|
-
add_dashes: Whether to add dashes to the generated key for improved readability. Default False
|
|
29
|
-
prefix: A string prefix for the API key. Followed by a '-'. Default None
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
A unique and secure API key.
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
ValueError: If the secret or seed is an empty string.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
if not secret or not seed:
|
|
39
|
-
raise ValueError("Secret and seed cannot be empty strings.")
|
|
40
|
-
|
|
41
|
-
current_timestamp = int(time.time())
|
|
42
|
-
now = datetime.datetime.now()
|
|
43
|
-
pbkdf2_iterations = random.randint(1000, 10000)
|
|
44
|
-
numeric_representation = int(
|
|
45
|
-
f"{now.year}{now.month:02d}{now.day:02d}{now.hour:02d}{now.minute:02d}{now.second:02d}"
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
timestamp_hash = hashlib.sha256(str(current_timestamp).encode()).hexdigest()
|
|
49
|
-
numeric_hash = hashlib.sha256(str(numeric_representation).encode()).hexdigest()
|
|
50
|
-
|
|
51
|
-
if add_dashes:
|
|
52
|
-
message = str(seed).encode()
|
|
53
|
-
if include:
|
|
54
|
-
pos = random.randint(0, 32)
|
|
55
|
-
message = message[:pos] + include.encode() + message[pos:]
|
|
56
|
-
|
|
57
|
-
combined_hash = f"{timestamp_hash}{numeric_hash}{message}"
|
|
58
|
-
combined_hash = "".join(random.sample(combined_hash, len(combined_hash)))
|
|
59
|
-
api_key = hmac.new(
|
|
60
|
-
secret.encode(), combined_hash.encode(), hashlib.sha256
|
|
61
|
-
).hexdigest()
|
|
62
|
-
api_key = str(uuid.uuid5(uuid.NAMESPACE_DNS, api_key))
|
|
63
|
-
else:
|
|
64
|
-
salt = token_bytes(16)
|
|
65
|
-
kdf = PBKDF2HMAC(
|
|
66
|
-
algorithm=hashes.SHA256(),
|
|
67
|
-
salt=salt,
|
|
68
|
-
iterations=pbkdf2_iterations,
|
|
69
|
-
length=32,
|
|
70
|
-
)
|
|
71
|
-
seed = f"{seed}{current_timestamp}{numeric_hash}".encode()
|
|
72
|
-
if include:
|
|
73
|
-
pos = random.randint(0, len(seed))
|
|
74
|
-
seed = seed[:pos] + include.encode() + seed[pos:]
|
|
75
|
-
|
|
76
|
-
key_bytes = kdf.derive(seed)
|
|
77
|
-
key_base64 = base64.urlsafe_b64encode(key_bytes).decode()
|
|
78
|
-
api_key = re.sub(r"[^a-zA-Z0-9]", "", key_base64)
|
|
79
|
-
|
|
80
|
-
if prefix:
|
|
81
|
-
api_key = f"{prefix}-{api_key}"
|
|
82
|
-
|
|
83
|
-
return api_key
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|