django-keygen 0.1__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.
- django_keygen-0.1.0/LICENSE +21 -0
- django_keygen-0.1.0/MANIFEST.in +0 -0
- django_keygen-0.1.0/PKG-INFO +270 -0
- django_keygen-0.1.0/README.rst +245 -0
- django_keygen-0.1.0/django_keygen/__init__.py +0 -0
- django_keygen-0.1.0/django_keygen/apps.py +6 -0
- django_keygen-0.1.0/django_keygen/core/__init__.py +0 -0
- django_keygen-0.1.0/django_keygen/core/passwords/__init__.py +38 -0
- django_keygen-0.1.0/django_keygen/management/__init__.py +0 -0
- django_keygen-0.1.0/django_keygen/management/commands/__init__.py +0 -0
- django_keygen-0.1.0/django_keygen/management/commands/generate_django_secret.py +176 -0
- django_keygen-0.1.0/django_keygen/management/commands/generate_password.py +94 -0
- django_keygen-0.1.0/django_keygen/management/commands/keygen.py +140 -0
- django_keygen-0.1.0/django_keygen.egg-info/PKG-INFO +270 -0
- django_keygen-0.1.0/django_keygen.egg-info/SOURCES.txt +18 -0
- django_keygen-0.1.0/django_keygen.egg-info/dependency_links.txt +1 -0
- django_keygen-0.1.0/django_keygen.egg-info/requires.txt +2 -0
- django_keygen-0.1.0/django_keygen.egg-info/top_level.txt +1 -0
- django_keygen-0.1.0/pyproject.toml +38 -0
- django_keygen-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mouhib Sellami
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
File without changes
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-keygen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: django-keygen is a Django management command utility designed to securely generate cryptographic secret keys.
|
|
5
|
+
Author-email: Mouhib sellami <mouhib.sellami@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mouhib-Sellami/django-keygen
|
|
8
|
+
Classifier: Environment :: Web Environment
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 4.2
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: django>=4.2
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
django-keygen
|
|
27
|
+
=============
|
|
28
|
+
|
|
29
|
+
django-keygen is a Django management command utility for securely generating
|
|
30
|
+
cryptographic secret keys and passwords, and optionally writing them into your
|
|
31
|
+
project's environment (``.env``) files automatically.
|
|
32
|
+
|
|
33
|
+
Detailed documentation will be available in the "docs" directory.
|
|
34
|
+
|
|
35
|
+
Installation
|
|
36
|
+
------------
|
|
37
|
+
|
|
38
|
+
Install via pip::
|
|
39
|
+
|
|
40
|
+
pip install django-keygen
|
|
41
|
+
|
|
42
|
+
Or install the latest version directly from GitHub::
|
|
43
|
+
|
|
44
|
+
pip install git+https://github.com/mouhib-Sellami/django-keygen.git
|
|
45
|
+
|
|
46
|
+
Quick start
|
|
47
|
+
-----------
|
|
48
|
+
|
|
49
|
+
1. Add ``django_keygen`` to your ``INSTALLED_APPS``::
|
|
50
|
+
|
|
51
|
+
INSTALLED_APPS = [
|
|
52
|
+
...,
|
|
53
|
+
'django_keygen',
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
Commands Overview
|
|
57
|
+
-----------------
|
|
58
|
+
|
|
59
|
+
django-keygen provides three management commands:
|
|
60
|
+
|
|
61
|
+
+---------------------------+-------------------------------------------------------+
|
|
62
|
+
| Command | Purpose |
|
|
63
|
+
+===========================+=======================================================+
|
|
64
|
+
| ``keygen`` | Generate secret keys and/or passwords into a named |
|
|
65
|
+
| | ``.env`` file. The recommended all-in-one command. |
|
|
66
|
+
+---------------------------+-------------------------------------------------------+
|
|
67
|
+
| ``generate_django_secret``| Dedicated command for generating a single |
|
|
68
|
+
| | ``DJANGO_SECRET_KEY`` and optionally writing it to |
|
|
69
|
+
| | an env file via an interactive or flag-driven flow. |
|
|
70
|
+
+---------------------------+-------------------------------------------------------+
|
|
71
|
+
| ``generate_password`` | Interactive terminal interface for generating and |
|
|
72
|
+
| | tweaking secure passwords. |
|
|
73
|
+
+---------------------------+-------------------------------------------------------+
|
|
74
|
+
|
|
75
|
+
----
|
|
76
|
+
|
|
77
|
+
1. ``keygen`` — Unified Key & Password Generator
|
|
78
|
+
-------------------------------------------------
|
|
79
|
+
|
|
80
|
+
The ``keygen`` command is the recommended way to generate one or more secret
|
|
81
|
+
keys and/or passwords and write them into an environment file in a single step.
|
|
82
|
+
Variable names are passed directly as arguments, so each generated value is
|
|
83
|
+
stored under the name you choose.
|
|
84
|
+
|
|
85
|
+
**Positional arguments:**
|
|
86
|
+
|
|
87
|
+
* ``SECRET [SECRET ...]``
|
|
88
|
+
One or more variable names to generate a Django secret key for (e.g.
|
|
89
|
+
``SECRET_KEY API_KEY``). Each name receives its own independently generated
|
|
90
|
+
cryptographic key.
|
|
91
|
+
|
|
92
|
+
**Flags:**
|
|
93
|
+
|
|
94
|
+
* ``--passwords PASSWORDS [PASSWORDS ...]``, ``-p``
|
|
95
|
+
One or more variable names to generate a secure password for instead of a
|
|
96
|
+
Django secret key (e.g. ``-p DB_PASSWORD REDIS_PASSWORD``).
|
|
97
|
+
|
|
98
|
+
* ``--file FILE``, ``-f``
|
|
99
|
+
Path to the env file, relative to ``BASE_DIR`` (default: ``.env``). If the
|
|
100
|
+
file does not exist, the command offers to create it.
|
|
101
|
+
|
|
102
|
+
* ``--append``
|
|
103
|
+
Update the existing file in place rather than recreating it from scratch.
|
|
104
|
+
Without this flag the file is fully rewritten (all previous content is
|
|
105
|
+
preserved but reformatted alongside the new values). Prompts a confirmation
|
|
106
|
+
warning because replacing an active ``SECRET_KEY`` invalidates all existing
|
|
107
|
+
sessions, tokens, and signed cookies.
|
|
108
|
+
|
|
109
|
+
* ``--no-input``
|
|
110
|
+
Skip all confirmation prompts. Useful for automated deployments, Docker
|
|
111
|
+
entrypoints, and CI/CD pipelines.
|
|
112
|
+
|
|
113
|
+
**Examples**::
|
|
114
|
+
|
|
115
|
+
# Generate SECRET_KEY and write it to .env (recreates the file)
|
|
116
|
+
python manage.py keygen SECRET_KEY
|
|
117
|
+
|
|
118
|
+
# Generate two secret keys into a specific file, no prompts
|
|
119
|
+
python manage.py keygen SECRET_KEY API_KEY --file .env.production --no-input
|
|
120
|
+
|
|
121
|
+
# Generate a secret key and two passwords into .env.local, appending
|
|
122
|
+
python manage.py keygen SECRET_KEY --passwords DB_PASSWORD REDIS_PASSWORD \
|
|
123
|
+
--file .env.local --append
|
|
124
|
+
|
|
125
|
+
# Generate only passwords (no secret keys)
|
|
126
|
+
python manage.py keygen --passwords DB_PASSWORD --file .env
|
|
127
|
+
|
|
128
|
+
----
|
|
129
|
+
|
|
130
|
+
2. ``generate_django_secret`` — Dedicated Secret Key Command
|
|
131
|
+
-------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
The ``generate_django_secret`` command is dedicated to generating a single
|
|
134
|
+
Django secret key stored under the ``DJANGO_SECRET_KEY`` variable. Run it
|
|
135
|
+
without any flags to simply print a key to the terminal, or add ``--append``
|
|
136
|
+
to write it to an env file.
|
|
137
|
+
|
|
138
|
+
When writing to a file, the command either accepts a path via ``--file`` or
|
|
139
|
+
launches an interactive file-selection menu that scans your project for
|
|
140
|
+
existing ``.env*`` files.
|
|
141
|
+
|
|
142
|
+
**Flags:**
|
|
143
|
+
|
|
144
|
+
* ``--append``
|
|
145
|
+
Write the generated key to an environment file. Without this flag the key
|
|
146
|
+
is only printed to stdout — no files are touched. Prompts a confirmation
|
|
147
|
+
warning because replacing a live ``SECRET_KEY`` invalidates all existing
|
|
148
|
+
sessions, tokens, and signed cookies.
|
|
149
|
+
|
|
150
|
+
* ``--file FILE``, ``-f``
|
|
151
|
+
Directly specify the env file to update (e.g. ``-f .env.production``),
|
|
152
|
+
bypassing the interactive selection menu. If the file does not exist it is
|
|
153
|
+
created automatically. Only used together with ``--append``.
|
|
154
|
+
|
|
155
|
+
* ``--no-input``
|
|
156
|
+
Suppress all interactive prompts, menus, and risk warnings. Useful for
|
|
157
|
+
automated deployments and CI/CD pipelines.
|
|
158
|
+
|
|
159
|
+
**Examples**::
|
|
160
|
+
|
|
161
|
+
# Print a new secret key to the terminal only (no file changes)
|
|
162
|
+
python manage.py generate_django_secret
|
|
163
|
+
|
|
164
|
+
# Interactively choose which env file to update
|
|
165
|
+
python manage.py generate_django_secret --append
|
|
166
|
+
|
|
167
|
+
# Write directly to a specific file, no prompts
|
|
168
|
+
python manage.py generate_django_secret --append --no-input -f .env.local
|
|
169
|
+
|
|
170
|
+
**After running**, update your ``settings.py`` to read the key from the
|
|
171
|
+
environment::
|
|
172
|
+
|
|
173
|
+
import os
|
|
174
|
+
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
|
175
|
+
|
|
176
|
+
----
|
|
177
|
+
|
|
178
|
+
3. ``generate_password`` — Interactive Password Generator
|
|
179
|
+
---------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
The ``generate_password`` command launches an interactive terminal session for
|
|
182
|
+
generating and refining secure passwords. After each password is shown, you can
|
|
183
|
+
regenerate it or adjust the composition settings without restarting the command.
|
|
184
|
+
|
|
185
|
+
**Flags** (set initial defaults; all can be changed interactively):
|
|
186
|
+
|
|
187
|
+
* ``--length LENGTH``, ``-len``
|
|
188
|
+
Desired password length (default: 12).
|
|
189
|
+
|
|
190
|
+
* ``--letters``
|
|
191
|
+
Include lowercase letters (a–z).
|
|
192
|
+
|
|
193
|
+
* ``--uppercase``
|
|
194
|
+
Include uppercase letters (A–Z).
|
|
195
|
+
|
|
196
|
+
* ``--numbers``
|
|
197
|
+
Include numerical digits (0–9).
|
|
198
|
+
|
|
199
|
+
* ``--symbol``
|
|
200
|
+
Include symbols and punctuation characters.
|
|
201
|
+
|
|
202
|
+
* ``--secure``
|
|
203
|
+
Shortcut that forces uppercase letters, numbers, symbols, and a minimum
|
|
204
|
+
length of 20. Overrides individual composition flags.
|
|
205
|
+
|
|
206
|
+
**Examples**::
|
|
207
|
+
|
|
208
|
+
# Launch with defaults (12 characters, no composition constraints)
|
|
209
|
+
python manage.py generate_password
|
|
210
|
+
|
|
211
|
+
# Start with a longer length and reconfigure interactively
|
|
212
|
+
python manage.py generate_password --length 16
|
|
213
|
+
|
|
214
|
+
# Instantly generate a highly secure 20-character password
|
|
215
|
+
python manage.py generate_password --secure
|
|
216
|
+
|
|
217
|
+
----
|
|
218
|
+
|
|
219
|
+
Programmatic Usage
|
|
220
|
+
------------------
|
|
221
|
+
|
|
222
|
+
The underlying password generator can be imported directly into your own
|
|
223
|
+
Django apps — useful for custom registration flows, invitation tokens, or
|
|
224
|
+
background tasks::
|
|
225
|
+
|
|
226
|
+
from django_keygen.core.passwords import generate_password
|
|
227
|
+
|
|
228
|
+
# Generate a secure password with specific character sets
|
|
229
|
+
new_password = generate_password(
|
|
230
|
+
length=14,
|
|
231
|
+
use_uppercase=True,
|
|
232
|
+
use_numbers=True,
|
|
233
|
+
use_symbols=True,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Use it in your application logic
|
|
237
|
+
from django.contrib.auth.models import User
|
|
238
|
+
user = User.objects.create_user(username='johndoe', email='john@example.com')
|
|
239
|
+
user.set_password(new_password)
|
|
240
|
+
user.save()
|
|
241
|
+
|
|
242
|
+
----
|
|
243
|
+
|
|
244
|
+
License
|
|
245
|
+
-------
|
|
246
|
+
|
|
247
|
+
django-keygen is released under the **MIT License**.
|
|
248
|
+
|
|
249
|
+
Copyright (c) 2026 Mouhib Sellami
|
|
250
|
+
|
|
251
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
252
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
253
|
+
in the Software without restriction, including without limitation the rights
|
|
254
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
255
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
256
|
+
furnished to do so, subject to the following conditions:
|
|
257
|
+
|
|
258
|
+
The above copyright notice and this permission notice shall be included in all
|
|
259
|
+
copies or substantial portions of the Software.
|
|
260
|
+
|
|
261
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
262
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
263
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
264
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
265
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
266
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
267
|
+
SOFTWARE.
|
|
268
|
+
|
|
269
|
+
See the ``LICENSE`` file in the repository root for the full license text,
|
|
270
|
+
or visit: https://github.com/mouhib-Sellami/django-keygen/blob/main/LICENSE
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
django-keygen
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
django-keygen is a Django management command utility for securely generating
|
|
5
|
+
cryptographic secret keys and passwords, and optionally writing them into your
|
|
6
|
+
project's environment (``.env``) files automatically.
|
|
7
|
+
|
|
8
|
+
Detailed documentation will be available in the "docs" directory.
|
|
9
|
+
|
|
10
|
+
Installation
|
|
11
|
+
------------
|
|
12
|
+
|
|
13
|
+
Install via pip::
|
|
14
|
+
|
|
15
|
+
pip install django-keygen
|
|
16
|
+
|
|
17
|
+
Or install the latest version directly from GitHub::
|
|
18
|
+
|
|
19
|
+
pip install git+https://github.com/mouhib-Sellami/django-keygen.git
|
|
20
|
+
|
|
21
|
+
Quick start
|
|
22
|
+
-----------
|
|
23
|
+
|
|
24
|
+
1. Add ``django_keygen`` to your ``INSTALLED_APPS``::
|
|
25
|
+
|
|
26
|
+
INSTALLED_APPS = [
|
|
27
|
+
...,
|
|
28
|
+
'django_keygen',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
Commands Overview
|
|
32
|
+
-----------------
|
|
33
|
+
|
|
34
|
+
django-keygen provides three management commands:
|
|
35
|
+
|
|
36
|
+
+---------------------------+-------------------------------------------------------+
|
|
37
|
+
| Command | Purpose |
|
|
38
|
+
+===========================+=======================================================+
|
|
39
|
+
| ``keygen`` | Generate secret keys and/or passwords into a named |
|
|
40
|
+
| | ``.env`` file. The recommended all-in-one command. |
|
|
41
|
+
+---------------------------+-------------------------------------------------------+
|
|
42
|
+
| ``generate_django_secret``| Dedicated command for generating a single |
|
|
43
|
+
| | ``DJANGO_SECRET_KEY`` and optionally writing it to |
|
|
44
|
+
| | an env file via an interactive or flag-driven flow. |
|
|
45
|
+
+---------------------------+-------------------------------------------------------+
|
|
46
|
+
| ``generate_password`` | Interactive terminal interface for generating and |
|
|
47
|
+
| | tweaking secure passwords. |
|
|
48
|
+
+---------------------------+-------------------------------------------------------+
|
|
49
|
+
|
|
50
|
+
----
|
|
51
|
+
|
|
52
|
+
1. ``keygen`` — Unified Key & Password Generator
|
|
53
|
+
-------------------------------------------------
|
|
54
|
+
|
|
55
|
+
The ``keygen`` command is the recommended way to generate one or more secret
|
|
56
|
+
keys and/or passwords and write them into an environment file in a single step.
|
|
57
|
+
Variable names are passed directly as arguments, so each generated value is
|
|
58
|
+
stored under the name you choose.
|
|
59
|
+
|
|
60
|
+
**Positional arguments:**
|
|
61
|
+
|
|
62
|
+
* ``SECRET [SECRET ...]``
|
|
63
|
+
One or more variable names to generate a Django secret key for (e.g.
|
|
64
|
+
``SECRET_KEY API_KEY``). Each name receives its own independently generated
|
|
65
|
+
cryptographic key.
|
|
66
|
+
|
|
67
|
+
**Flags:**
|
|
68
|
+
|
|
69
|
+
* ``--passwords PASSWORDS [PASSWORDS ...]``, ``-p``
|
|
70
|
+
One or more variable names to generate a secure password for instead of a
|
|
71
|
+
Django secret key (e.g. ``-p DB_PASSWORD REDIS_PASSWORD``).
|
|
72
|
+
|
|
73
|
+
* ``--file FILE``, ``-f``
|
|
74
|
+
Path to the env file, relative to ``BASE_DIR`` (default: ``.env``). If the
|
|
75
|
+
file does not exist, the command offers to create it.
|
|
76
|
+
|
|
77
|
+
* ``--append``
|
|
78
|
+
Update the existing file in place rather than recreating it from scratch.
|
|
79
|
+
Without this flag the file is fully rewritten (all previous content is
|
|
80
|
+
preserved but reformatted alongside the new values). Prompts a confirmation
|
|
81
|
+
warning because replacing an active ``SECRET_KEY`` invalidates all existing
|
|
82
|
+
sessions, tokens, and signed cookies.
|
|
83
|
+
|
|
84
|
+
* ``--no-input``
|
|
85
|
+
Skip all confirmation prompts. Useful for automated deployments, Docker
|
|
86
|
+
entrypoints, and CI/CD pipelines.
|
|
87
|
+
|
|
88
|
+
**Examples**::
|
|
89
|
+
|
|
90
|
+
# Generate SECRET_KEY and write it to .env (recreates the file)
|
|
91
|
+
python manage.py keygen SECRET_KEY
|
|
92
|
+
|
|
93
|
+
# Generate two secret keys into a specific file, no prompts
|
|
94
|
+
python manage.py keygen SECRET_KEY API_KEY --file .env.production --no-input
|
|
95
|
+
|
|
96
|
+
# Generate a secret key and two passwords into .env.local, appending
|
|
97
|
+
python manage.py keygen SECRET_KEY --passwords DB_PASSWORD REDIS_PASSWORD \
|
|
98
|
+
--file .env.local --append
|
|
99
|
+
|
|
100
|
+
# Generate only passwords (no secret keys)
|
|
101
|
+
python manage.py keygen --passwords DB_PASSWORD --file .env
|
|
102
|
+
|
|
103
|
+
----
|
|
104
|
+
|
|
105
|
+
2. ``generate_django_secret`` — Dedicated Secret Key Command
|
|
106
|
+
-------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
The ``generate_django_secret`` command is dedicated to generating a single
|
|
109
|
+
Django secret key stored under the ``DJANGO_SECRET_KEY`` variable. Run it
|
|
110
|
+
without any flags to simply print a key to the terminal, or add ``--append``
|
|
111
|
+
to write it to an env file.
|
|
112
|
+
|
|
113
|
+
When writing to a file, the command either accepts a path via ``--file`` or
|
|
114
|
+
launches an interactive file-selection menu that scans your project for
|
|
115
|
+
existing ``.env*`` files.
|
|
116
|
+
|
|
117
|
+
**Flags:**
|
|
118
|
+
|
|
119
|
+
* ``--append``
|
|
120
|
+
Write the generated key to an environment file. Without this flag the key
|
|
121
|
+
is only printed to stdout — no files are touched. Prompts a confirmation
|
|
122
|
+
warning because replacing a live ``SECRET_KEY`` invalidates all existing
|
|
123
|
+
sessions, tokens, and signed cookies.
|
|
124
|
+
|
|
125
|
+
* ``--file FILE``, ``-f``
|
|
126
|
+
Directly specify the env file to update (e.g. ``-f .env.production``),
|
|
127
|
+
bypassing the interactive selection menu. If the file does not exist it is
|
|
128
|
+
created automatically. Only used together with ``--append``.
|
|
129
|
+
|
|
130
|
+
* ``--no-input``
|
|
131
|
+
Suppress all interactive prompts, menus, and risk warnings. Useful for
|
|
132
|
+
automated deployments and CI/CD pipelines.
|
|
133
|
+
|
|
134
|
+
**Examples**::
|
|
135
|
+
|
|
136
|
+
# Print a new secret key to the terminal only (no file changes)
|
|
137
|
+
python manage.py generate_django_secret
|
|
138
|
+
|
|
139
|
+
# Interactively choose which env file to update
|
|
140
|
+
python manage.py generate_django_secret --append
|
|
141
|
+
|
|
142
|
+
# Write directly to a specific file, no prompts
|
|
143
|
+
python manage.py generate_django_secret --append --no-input -f .env.local
|
|
144
|
+
|
|
145
|
+
**After running**, update your ``settings.py`` to read the key from the
|
|
146
|
+
environment::
|
|
147
|
+
|
|
148
|
+
import os
|
|
149
|
+
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
|
150
|
+
|
|
151
|
+
----
|
|
152
|
+
|
|
153
|
+
3. ``generate_password`` — Interactive Password Generator
|
|
154
|
+
---------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
The ``generate_password`` command launches an interactive terminal session for
|
|
157
|
+
generating and refining secure passwords. After each password is shown, you can
|
|
158
|
+
regenerate it or adjust the composition settings without restarting the command.
|
|
159
|
+
|
|
160
|
+
**Flags** (set initial defaults; all can be changed interactively):
|
|
161
|
+
|
|
162
|
+
* ``--length LENGTH``, ``-len``
|
|
163
|
+
Desired password length (default: 12).
|
|
164
|
+
|
|
165
|
+
* ``--letters``
|
|
166
|
+
Include lowercase letters (a–z).
|
|
167
|
+
|
|
168
|
+
* ``--uppercase``
|
|
169
|
+
Include uppercase letters (A–Z).
|
|
170
|
+
|
|
171
|
+
* ``--numbers``
|
|
172
|
+
Include numerical digits (0–9).
|
|
173
|
+
|
|
174
|
+
* ``--symbol``
|
|
175
|
+
Include symbols and punctuation characters.
|
|
176
|
+
|
|
177
|
+
* ``--secure``
|
|
178
|
+
Shortcut that forces uppercase letters, numbers, symbols, and a minimum
|
|
179
|
+
length of 20. Overrides individual composition flags.
|
|
180
|
+
|
|
181
|
+
**Examples**::
|
|
182
|
+
|
|
183
|
+
# Launch with defaults (12 characters, no composition constraints)
|
|
184
|
+
python manage.py generate_password
|
|
185
|
+
|
|
186
|
+
# Start with a longer length and reconfigure interactively
|
|
187
|
+
python manage.py generate_password --length 16
|
|
188
|
+
|
|
189
|
+
# Instantly generate a highly secure 20-character password
|
|
190
|
+
python manage.py generate_password --secure
|
|
191
|
+
|
|
192
|
+
----
|
|
193
|
+
|
|
194
|
+
Programmatic Usage
|
|
195
|
+
------------------
|
|
196
|
+
|
|
197
|
+
The underlying password generator can be imported directly into your own
|
|
198
|
+
Django apps — useful for custom registration flows, invitation tokens, or
|
|
199
|
+
background tasks::
|
|
200
|
+
|
|
201
|
+
from django_keygen.core.passwords import generate_password
|
|
202
|
+
|
|
203
|
+
# Generate a secure password with specific character sets
|
|
204
|
+
new_password = generate_password(
|
|
205
|
+
length=14,
|
|
206
|
+
use_uppercase=True,
|
|
207
|
+
use_numbers=True,
|
|
208
|
+
use_symbols=True,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Use it in your application logic
|
|
212
|
+
from django.contrib.auth.models import User
|
|
213
|
+
user = User.objects.create_user(username='johndoe', email='john@example.com')
|
|
214
|
+
user.set_password(new_password)
|
|
215
|
+
user.save()
|
|
216
|
+
|
|
217
|
+
----
|
|
218
|
+
|
|
219
|
+
License
|
|
220
|
+
-------
|
|
221
|
+
|
|
222
|
+
django-keygen is released under the **MIT License**.
|
|
223
|
+
|
|
224
|
+
Copyright (c) 2026 Mouhib Sellami
|
|
225
|
+
|
|
226
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
227
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
228
|
+
in the Software without restriction, including without limitation the rights
|
|
229
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
230
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
231
|
+
furnished to do so, subject to the following conditions:
|
|
232
|
+
|
|
233
|
+
The above copyright notice and this permission notice shall be included in all
|
|
234
|
+
copies or substantial portions of the Software.
|
|
235
|
+
|
|
236
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
237
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
238
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
239
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
240
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
241
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
242
|
+
SOFTWARE.
|
|
243
|
+
|
|
244
|
+
See the ``LICENSE`` file in the repository root for the full license text,
|
|
245
|
+
or visit: https://github.com/mouhib-Sellami/django-keygen/blob/main/LICENSE
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import string
|
|
2
|
+
import secrets
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def generate_password(
|
|
6
|
+
length: int = 12,
|
|
7
|
+
use_uppercase: bool = False,
|
|
8
|
+
use_numbers: bool = False,
|
|
9
|
+
use_symbols: bool = False,
|
|
10
|
+
) -> str:
|
|
11
|
+
"""Generates a cryptographically secure random password based on specified constraints.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
length (int, optional): The length of the generated password. Defaults to
|
|
15
|
+
8.
|
|
16
|
+
use_uppercase (bool, optional): If True, includes uppercase letters (A-Z).
|
|
17
|
+
Defaults to False.
|
|
18
|
+
use_numbers (bool, optional): If True, includes numerical digits (0-9).
|
|
19
|
+
Defaults to False.
|
|
20
|
+
use_symbols (bool, optional): If True, includes punctuation/special
|
|
21
|
+
characters. Defaults to False.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: A randomly generated password string.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> generate_password(length=12, use_uppercase=True, use_numbers=True)
|
|
28
|
+
'aB3k9P1zLxQ9'
|
|
29
|
+
"""
|
|
30
|
+
content = string.ascii_lowercase
|
|
31
|
+
if use_uppercase:
|
|
32
|
+
content += string.ascii_uppercase
|
|
33
|
+
if use_symbols:
|
|
34
|
+
content += string.punctuation
|
|
35
|
+
if use_numbers:
|
|
36
|
+
content += string.digits
|
|
37
|
+
|
|
38
|
+
return "".join(secrets.choice(content) for i in range(length))
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from django.core.management.utils import get_random_secret_key
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from dotenv import dotenv_values
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = "Generate a secure secret key for Django and manage environment files."
|
|
10
|
+
|
|
11
|
+
env_content = """# Security Warning: keep the secret key used in production secret!
|
|
12
|
+
# This file was generated by django-env
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
env_generated_key_warning = "\n# This is managed by django-keygen"
|
|
16
|
+
|
|
17
|
+
def add_arguments(self, parser):
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--append",
|
|
20
|
+
action="store_true",
|
|
21
|
+
help="Append or update the generated secret key in chosen environment files."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--no-input",
|
|
26
|
+
action="store_true",
|
|
27
|
+
help="Skip all interactive prompts and warnings automatically."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--file",
|
|
32
|
+
"-f",
|
|
33
|
+
type=str,
|
|
34
|
+
default=None,
|
|
35
|
+
help="Directly specify the name or path of an env file (skips interactive selection)."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _append_to_env(self, generated_secret_key: str, env_files: list[str]):
|
|
39
|
+
for env in env_files:
|
|
40
|
+
env_path = Path(settings.BASE_DIR, env)
|
|
41
|
+
config = dotenv_values(env_path)
|
|
42
|
+
|
|
43
|
+
config['DJANGO_SECRET_KEY'] = generated_secret_key
|
|
44
|
+
self._write_to_env(config, env_path)
|
|
45
|
+
|
|
46
|
+
def _write_to_env(self, config: dict, env_file_path: Path):
|
|
47
|
+
content = self.env_content
|
|
48
|
+
for key, value in config.items():
|
|
49
|
+
if key == 'DJANGO_SECRET_KEY':
|
|
50
|
+
content += self.env_generated_key_warning + '\n'
|
|
51
|
+
content += f"{key}='{value}'\n"
|
|
52
|
+
|
|
53
|
+
with open(env_file_path, 'w') as f:
|
|
54
|
+
f.write(content)
|
|
55
|
+
|
|
56
|
+
def _select_env_file(self, no_input: bool = False) -> list[str]:
|
|
57
|
+
self.stdout.write(
|
|
58
|
+
"Searching for environment files (.env, .env.local, .env.production, etc.)...")
|
|
59
|
+
|
|
60
|
+
env_files = []
|
|
61
|
+
for file in Path(settings.BASE_DIR).rglob('*.env*'):
|
|
62
|
+
relative_path = file.relative_to(settings.BASE_DIR)
|
|
63
|
+
env_files.append(str(relative_path))
|
|
64
|
+
|
|
65
|
+
if not env_files:
|
|
66
|
+
self.stdout.write(self.style.WARNING(
|
|
67
|
+
"No environment files found to write to."))
|
|
68
|
+
|
|
69
|
+
self.stdout.write(
|
|
70
|
+
"Would you like to create a new '.env.local' file? [Y/n]: ", ending="")
|
|
71
|
+
if not no_input:
|
|
72
|
+
user_choice = input().strip().lower()
|
|
73
|
+
else:
|
|
74
|
+
user_choice = 'y'
|
|
75
|
+
|
|
76
|
+
if user_choice in ['y', 'yes', '']:
|
|
77
|
+
new_file = '.env.local'
|
|
78
|
+
|
|
79
|
+
new_file_path = Path(settings.BASE_DIR) / new_file
|
|
80
|
+
new_file_path.touch(exist_ok=True)
|
|
81
|
+
|
|
82
|
+
self.stdout.write(self.style.SUCCESS(
|
|
83
|
+
f"Created new file: {new_file}"))
|
|
84
|
+
return [new_file]
|
|
85
|
+
else:
|
|
86
|
+
self.stdout.write(self.style.WARNING(
|
|
87
|
+
"Operation cancelled. No files were updated."))
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
self.stdout.write(self.style.WARNING(
|
|
91
|
+
"\n⚠️ WARNING: Modifying configuration files can alter or overwrite existing variables.\n"
|
|
92
|
+
"Ensure you have a backup of your secrets before continuing."
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
self.stdout.write("\nSelect an environment file to update:")
|
|
96
|
+
self.stdout.write(f" 0 - Select all files ({len(env_files)} found)")
|
|
97
|
+
for idx, file_name in enumerate(env_files):
|
|
98
|
+
self.stdout.write(f" {idx + 1} - {file_name}")
|
|
99
|
+
if not no_input:
|
|
100
|
+
while True:
|
|
101
|
+
self.stdout.write("\nEnter selection number: ", ending="")
|
|
102
|
+
action = input().strip()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
action_number = int(action)
|
|
106
|
+
if action_number < 0 or action_number > len(env_files):
|
|
107
|
+
self.stdout.write(self.style.ERROR(
|
|
108
|
+
"Invalid selection. Number out of range."))
|
|
109
|
+
else:
|
|
110
|
+
if action_number == 0:
|
|
111
|
+
return env_files
|
|
112
|
+
return [env_files[action_number - 1]]
|
|
113
|
+
except ValueError:
|
|
114
|
+
self.stdout.write(self.style.ERROR(
|
|
115
|
+
"Invalid input. Please enter a valid integer number."))
|
|
116
|
+
else:
|
|
117
|
+
return env_files
|
|
118
|
+
|
|
119
|
+
def handle(self, *args, **options):
|
|
120
|
+
if settings.DEBUG:
|
|
121
|
+
self.stdout.write(self.style.WARNING(
|
|
122
|
+
"\n⚠️ SECURITY NOTICE: 'settings.DEBUG' is currently set to True.\n"
|
|
123
|
+
"Never run your Django application in a production environment with DEBUG enabled.\n"
|
|
124
|
+
))
|
|
125
|
+
|
|
126
|
+
if options['append']:
|
|
127
|
+
self.stdout.write(self.style.WARNING(
|
|
128
|
+
"⚠️ IMPORTANT NOTICE:\n"
|
|
129
|
+
"Replacing an active server's SECRET_KEY will immediately invalidate all existing user sessions,\n"
|
|
130
|
+
"auth tokens, and signed cookies. If you only want to generate a text key without updating files,\n"
|
|
131
|
+
"abort this command and run it again without the '--append' flag.\n"
|
|
132
|
+
))
|
|
133
|
+
|
|
134
|
+
self.stdout.write(
|
|
135
|
+
"Press [ENTER] to acknowledge this risk and continue, or Ctrl+C to abort...")
|
|
136
|
+
if not options['no_input']:
|
|
137
|
+
input()
|
|
138
|
+
|
|
139
|
+
self.stdout.write("Generating new secret key...")
|
|
140
|
+
secret_key = get_random_secret_key()
|
|
141
|
+
|
|
142
|
+
self.stdout.write(self.style.SUCCESS(
|
|
143
|
+
f"\nYour Secret Key: {secret_key}\n"))
|
|
144
|
+
|
|
145
|
+
if options['append']:
|
|
146
|
+
if options['file']:
|
|
147
|
+
target_file = options['file']
|
|
148
|
+
target_path = Path(settings.BASE_DIR) / target_file
|
|
149
|
+
|
|
150
|
+
if not target_path.exists():
|
|
151
|
+
self.stdout.write(self.style.WARNING(
|
|
152
|
+
f"File '{target_file}' does not exist. Creating it now."))
|
|
153
|
+
target_path.touch(exist_ok=True)
|
|
154
|
+
|
|
155
|
+
selected_files = [target_file]
|
|
156
|
+
else:
|
|
157
|
+
selected_files = self._select_env_file(no_input=options['no_input'])
|
|
158
|
+
|
|
159
|
+
if selected_files:
|
|
160
|
+
self._append_to_env(secret_key, selected_files)
|
|
161
|
+
self.stdout.write(self.style.SUCCESS(
|
|
162
|
+
f"Successfully updated files: {', '.join(selected_files)}"))
|
|
163
|
+
else:
|
|
164
|
+
self.stdout.write(self.style.WARNING(
|
|
165
|
+
"No updates were made to files."))
|
|
166
|
+
else:
|
|
167
|
+
self.stdout.write(self.style.SUCCESS("Successfully generated!"))
|
|
168
|
+
|
|
169
|
+
self.stdout.write(self.style.SUCCESS(
|
|
170
|
+
"\n"
|
|
171
|
+
"Action Required: Update your settings.py \n"
|
|
172
|
+
"--------------------------------------------------\n"
|
|
173
|
+
"To make Django read your newly generated key, use:\n\n"
|
|
174
|
+
"SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')\n"
|
|
175
|
+
"--------------------------------------------------"
|
|
176
|
+
))
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from django_keygen.core.passwords import generate_password
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = "Interactively generate highly secure, random passwords."
|
|
8
|
+
|
|
9
|
+
def add_arguments(self, parser):
|
|
10
|
+
parser.add_argument(
|
|
11
|
+
"--symbol",
|
|
12
|
+
action="store_true",
|
|
13
|
+
help="Include symbols/punctuation in the generated password.",
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--numbers",
|
|
17
|
+
action="store_true",
|
|
18
|
+
help="Include numerical digits (0-9) in the generated password.",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--letters",
|
|
22
|
+
action="store_true",
|
|
23
|
+
help="Include lowercase letters (a-z) in the generated password.",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--uppercase",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Include uppercase letters (A-Z) in the generated password.",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--secure",
|
|
32
|
+
action="store_true",
|
|
33
|
+
help="Shortcut to force uppercase, numbers, symbols, and a length of 20.",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--length",
|
|
37
|
+
"-len",
|
|
38
|
+
type=int,
|
|
39
|
+
default=12,
|
|
40
|
+
help="The length of the generated password (default: 12).",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def handle(self, *args, **options):
|
|
44
|
+
use_uppercase = True if options["secure"] else options["uppercase"]
|
|
45
|
+
use_symbols = True if options["secure"] else options["symbol"]
|
|
46
|
+
use_numbers = True if options["secure"] else options["numbers"]
|
|
47
|
+
length = options["length"]
|
|
48
|
+
|
|
49
|
+
while True:
|
|
50
|
+
password = generate_password(
|
|
51
|
+
length=length,
|
|
52
|
+
use_numbers=use_numbers,
|
|
53
|
+
use_symbols=use_symbols,
|
|
54
|
+
use_uppercase=use_uppercase,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.stdout.write("\n" + "=" * 40)
|
|
58
|
+
self.stdout.write(
|
|
59
|
+
self.style.SUCCESS(f"Generated Password: {password}")
|
|
60
|
+
)
|
|
61
|
+
self.stdout.write("=" * 40 + "\n")
|
|
62
|
+
|
|
63
|
+
regen = (
|
|
64
|
+
input("Regenerate a new password? (y/N): ").strip().lower()
|
|
65
|
+
)
|
|
66
|
+
if regen != "y":
|
|
67
|
+
self.stdout.write(self.style.SUCCESS("Exiting."))
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
modify_config = (
|
|
71
|
+
input("Modify password configurations? (y/N): ")
|
|
72
|
+
.strip()
|
|
73
|
+
.lower()
|
|
74
|
+
)
|
|
75
|
+
if modify_config == "y":
|
|
76
|
+
action = input("Use symbols? (y/N): ").strip().lower()
|
|
77
|
+
use_symbols = action == "y"
|
|
78
|
+
|
|
79
|
+
action = input("Use numbers? (y/N): ").strip().lower()
|
|
80
|
+
use_numbers = action == "y"
|
|
81
|
+
|
|
82
|
+
action = input("Use uppercase? (y/N): ").strip().lower()
|
|
83
|
+
use_uppercase = action == "y"
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
len_input = input(f"Password length ({length}): ").strip()
|
|
87
|
+
if len_input:
|
|
88
|
+
length = int(len_input)
|
|
89
|
+
except ValueError:
|
|
90
|
+
self.stdout.write(
|
|
91
|
+
self.style.WARNING(
|
|
92
|
+
f"Invalid number. Keeping current length of {length}."
|
|
93
|
+
)
|
|
94
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from django.core.management.utils import get_random_secret_key
|
|
3
|
+
from django.core.management.base import BaseCommand
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from dotenv import set_key, dotenv_values
|
|
6
|
+
from django_keygen.core.passwords import generate_password
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Command(BaseCommand):
|
|
10
|
+
help = "Generate secret keys or passwords into an environment file."
|
|
11
|
+
|
|
12
|
+
def add_arguments(self, parser):
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"secret",
|
|
15
|
+
nargs="*",
|
|
16
|
+
metavar="SECRET",
|
|
17
|
+
help="One or more variable names to generate a Django secret key for.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--passwords",
|
|
21
|
+
"-p",
|
|
22
|
+
nargs="+",
|
|
23
|
+
metavar="PASSWORDS",
|
|
24
|
+
default=[],
|
|
25
|
+
help="One or more variable names to generate a secure password for.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--file", "-f",
|
|
29
|
+
type=str,
|
|
30
|
+
default=".env",
|
|
31
|
+
help="Path to the env file relative to BASE_DIR (default: .env).",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--append",
|
|
35
|
+
action="store_true",
|
|
36
|
+
help="Append/update keys into the existing file instead of recreating it.",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--no-input",
|
|
40
|
+
action="store_true",
|
|
41
|
+
help="Skip all confirmation prompts.",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def _affix(self, value: str, options: dict) -> str:
|
|
45
|
+
prefix = options.get("prefix") or ""
|
|
46
|
+
suffix = options.get("suffix") or ""
|
|
47
|
+
return f"{prefix}{value}{suffix}"
|
|
48
|
+
|
|
49
|
+
def _resolve_file(self, options: dict) -> Path | None:
|
|
50
|
+
env_path = Path(settings.BASE_DIR) / options["file"]
|
|
51
|
+
|
|
52
|
+
if not env_path.exists():
|
|
53
|
+
if not options["no_input"]:
|
|
54
|
+
self.stdout.write(
|
|
55
|
+
self.style.WARNING(
|
|
56
|
+
f"'{options['file']}' does not exist. Create it? [Y/n]: "),
|
|
57
|
+
ending="",
|
|
58
|
+
)
|
|
59
|
+
if input().strip().lower() not in ("y", "yes", ""):
|
|
60
|
+
self.stdout.write(self.style.ERROR("Aborted."))
|
|
61
|
+
return None
|
|
62
|
+
env_path.touch()
|
|
63
|
+
self.stdout.write(self.style.SUCCESS(f"Created {options['file']}"))
|
|
64
|
+
|
|
65
|
+
if not options["append"] and not options["no_input"]:
|
|
66
|
+
self.stdout.write(self.style.WARNING(
|
|
67
|
+
f"\n⚠️ '{options['file']}' will be recreated and all existing values overwritten.\n"
|
|
68
|
+
"Press [ENTER] to continue or Ctrl+C to abort..."
|
|
69
|
+
))
|
|
70
|
+
input()
|
|
71
|
+
|
|
72
|
+
return env_path
|
|
73
|
+
|
|
74
|
+
def _write_all(self, env_path: Path, secrets: dict[str, str]):
|
|
75
|
+
existing = dict(dotenv_values(env_path)) if env_path.exists() else {}
|
|
76
|
+
existing.update(secrets)
|
|
77
|
+
lines = "# Security Warning: keep the secret key used in production secret!\n"
|
|
78
|
+
lines += "# This file was managed/generated by django-keygen\n\n"
|
|
79
|
+
for key, value in existing.items():
|
|
80
|
+
lines += "\n# This secret was managed/generated by django-keygen\n"
|
|
81
|
+
lines += f"{key}='{value}'\n"
|
|
82
|
+
env_path.write_text(lines)
|
|
83
|
+
|
|
84
|
+
def handle(self, *args, **options):
|
|
85
|
+
if settings.DEBUG:
|
|
86
|
+
self.stdout.write(self.style.WARNING(
|
|
87
|
+
"\n⚠️ SECURITY NOTICE: 'settings.DEBUG' is currently set to True.\n"
|
|
88
|
+
"Never run your Django application in a production environment with DEBUG enabled.\n"
|
|
89
|
+
))
|
|
90
|
+
|
|
91
|
+
if options['append']:
|
|
92
|
+
self.stdout.write(self.style.WARNING(
|
|
93
|
+
"⚠️ IMPORTANT NOTICE:\n"
|
|
94
|
+
"Replacing an active server's SECRET_KEY will immediately invalidate all existing user sessions,\n"
|
|
95
|
+
"auth tokens, and signed cookies. If you only want to generate a text key without updating files,\n"
|
|
96
|
+
"abort this command and run it again without the '--append' flag.\n"
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
self.stdout.write(
|
|
100
|
+
"Press [ENTER] to acknowledge this risk and continue, or Ctrl+C to abort...")
|
|
101
|
+
if not options['no_input']:
|
|
102
|
+
input()
|
|
103
|
+
secret_names: list[str] = options["secret"]
|
|
104
|
+
passwords_names: list[str] = options["passwords"]
|
|
105
|
+
print(passwords_names)
|
|
106
|
+
if not secret_names and not passwords_names:
|
|
107
|
+
self.stdout.write(self.style.ERROR(
|
|
108
|
+
"Provide at least one variable name.\n"
|
|
109
|
+
" Secrets: python manage.py keygen KEY1 KEY2\n"
|
|
110
|
+
" Passwords: python manage.py keygen --passwords PASS1 PASS2\n"
|
|
111
|
+
" Mixed: python manage.py keygen KEY1 --passwords PASS1 --file .env.local"
|
|
112
|
+
))
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
env_path = self._resolve_file(options)
|
|
116
|
+
if env_path is None:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
generated: dict[str, str] = {}
|
|
120
|
+
|
|
121
|
+
for name in secret_names:
|
|
122
|
+
generated[name] = self._affix(get_random_secret_key(), options)
|
|
123
|
+
|
|
124
|
+
for name in passwords_names:
|
|
125
|
+
generated[name] = self._affix(generate_password(), options)
|
|
126
|
+
|
|
127
|
+
if options["append"]:
|
|
128
|
+
for key, value in generated.items():
|
|
129
|
+
set_key(env_path, key, value)
|
|
130
|
+
else:
|
|
131
|
+
self._write_all(env_path, generated)
|
|
132
|
+
|
|
133
|
+
self.stdout.write(self.style.MIGRATE_HEADING(
|
|
134
|
+
f"\n--- {options['file']} ---"))
|
|
135
|
+
for key, value in generated.items():
|
|
136
|
+
self.stdout.write(f" {self.style.MIGRATE_LABEL(key)}='{value}'")
|
|
137
|
+
|
|
138
|
+
self.stdout.write(self.style.SUCCESS(
|
|
139
|
+
f"\n {len(generated)} value(s) written to {options['file']}"
|
|
140
|
+
))
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-keygen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: django-keygen is a Django management command utility designed to securely generate cryptographic secret keys.
|
|
5
|
+
Author-email: Mouhib sellami <mouhib.sellami@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mouhib-Sellami/django-keygen
|
|
8
|
+
Classifier: Environment :: Web Environment
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 4.2
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: django>=4.2
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
django-keygen
|
|
27
|
+
=============
|
|
28
|
+
|
|
29
|
+
django-keygen is a Django management command utility for securely generating
|
|
30
|
+
cryptographic secret keys and passwords, and optionally writing them into your
|
|
31
|
+
project's environment (``.env``) files automatically.
|
|
32
|
+
|
|
33
|
+
Detailed documentation will be available in the "docs" directory.
|
|
34
|
+
|
|
35
|
+
Installation
|
|
36
|
+
------------
|
|
37
|
+
|
|
38
|
+
Install via pip::
|
|
39
|
+
|
|
40
|
+
pip install django-keygen
|
|
41
|
+
|
|
42
|
+
Or install the latest version directly from GitHub::
|
|
43
|
+
|
|
44
|
+
pip install git+https://github.com/mouhib-Sellami/django-keygen.git
|
|
45
|
+
|
|
46
|
+
Quick start
|
|
47
|
+
-----------
|
|
48
|
+
|
|
49
|
+
1. Add ``django_keygen`` to your ``INSTALLED_APPS``::
|
|
50
|
+
|
|
51
|
+
INSTALLED_APPS = [
|
|
52
|
+
...,
|
|
53
|
+
'django_keygen',
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
Commands Overview
|
|
57
|
+
-----------------
|
|
58
|
+
|
|
59
|
+
django-keygen provides three management commands:
|
|
60
|
+
|
|
61
|
+
+---------------------------+-------------------------------------------------------+
|
|
62
|
+
| Command | Purpose |
|
|
63
|
+
+===========================+=======================================================+
|
|
64
|
+
| ``keygen`` | Generate secret keys and/or passwords into a named |
|
|
65
|
+
| | ``.env`` file. The recommended all-in-one command. |
|
|
66
|
+
+---------------------------+-------------------------------------------------------+
|
|
67
|
+
| ``generate_django_secret``| Dedicated command for generating a single |
|
|
68
|
+
| | ``DJANGO_SECRET_KEY`` and optionally writing it to |
|
|
69
|
+
| | an env file via an interactive or flag-driven flow. |
|
|
70
|
+
+---------------------------+-------------------------------------------------------+
|
|
71
|
+
| ``generate_password`` | Interactive terminal interface for generating and |
|
|
72
|
+
| | tweaking secure passwords. |
|
|
73
|
+
+---------------------------+-------------------------------------------------------+
|
|
74
|
+
|
|
75
|
+
----
|
|
76
|
+
|
|
77
|
+
1. ``keygen`` — Unified Key & Password Generator
|
|
78
|
+
-------------------------------------------------
|
|
79
|
+
|
|
80
|
+
The ``keygen`` command is the recommended way to generate one or more secret
|
|
81
|
+
keys and/or passwords and write them into an environment file in a single step.
|
|
82
|
+
Variable names are passed directly as arguments, so each generated value is
|
|
83
|
+
stored under the name you choose.
|
|
84
|
+
|
|
85
|
+
**Positional arguments:**
|
|
86
|
+
|
|
87
|
+
* ``SECRET [SECRET ...]``
|
|
88
|
+
One or more variable names to generate a Django secret key for (e.g.
|
|
89
|
+
``SECRET_KEY API_KEY``). Each name receives its own independently generated
|
|
90
|
+
cryptographic key.
|
|
91
|
+
|
|
92
|
+
**Flags:**
|
|
93
|
+
|
|
94
|
+
* ``--passwords PASSWORDS [PASSWORDS ...]``, ``-p``
|
|
95
|
+
One or more variable names to generate a secure password for instead of a
|
|
96
|
+
Django secret key (e.g. ``-p DB_PASSWORD REDIS_PASSWORD``).
|
|
97
|
+
|
|
98
|
+
* ``--file FILE``, ``-f``
|
|
99
|
+
Path to the env file, relative to ``BASE_DIR`` (default: ``.env``). If the
|
|
100
|
+
file does not exist, the command offers to create it.
|
|
101
|
+
|
|
102
|
+
* ``--append``
|
|
103
|
+
Update the existing file in place rather than recreating it from scratch.
|
|
104
|
+
Without this flag the file is fully rewritten (all previous content is
|
|
105
|
+
preserved but reformatted alongside the new values). Prompts a confirmation
|
|
106
|
+
warning because replacing an active ``SECRET_KEY`` invalidates all existing
|
|
107
|
+
sessions, tokens, and signed cookies.
|
|
108
|
+
|
|
109
|
+
* ``--no-input``
|
|
110
|
+
Skip all confirmation prompts. Useful for automated deployments, Docker
|
|
111
|
+
entrypoints, and CI/CD pipelines.
|
|
112
|
+
|
|
113
|
+
**Examples**::
|
|
114
|
+
|
|
115
|
+
# Generate SECRET_KEY and write it to .env (recreates the file)
|
|
116
|
+
python manage.py keygen SECRET_KEY
|
|
117
|
+
|
|
118
|
+
# Generate two secret keys into a specific file, no prompts
|
|
119
|
+
python manage.py keygen SECRET_KEY API_KEY --file .env.production --no-input
|
|
120
|
+
|
|
121
|
+
# Generate a secret key and two passwords into .env.local, appending
|
|
122
|
+
python manage.py keygen SECRET_KEY --passwords DB_PASSWORD REDIS_PASSWORD \
|
|
123
|
+
--file .env.local --append
|
|
124
|
+
|
|
125
|
+
# Generate only passwords (no secret keys)
|
|
126
|
+
python manage.py keygen --passwords DB_PASSWORD --file .env
|
|
127
|
+
|
|
128
|
+
----
|
|
129
|
+
|
|
130
|
+
2. ``generate_django_secret`` — Dedicated Secret Key Command
|
|
131
|
+
-------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
The ``generate_django_secret`` command is dedicated to generating a single
|
|
134
|
+
Django secret key stored under the ``DJANGO_SECRET_KEY`` variable. Run it
|
|
135
|
+
without any flags to simply print a key to the terminal, or add ``--append``
|
|
136
|
+
to write it to an env file.
|
|
137
|
+
|
|
138
|
+
When writing to a file, the command either accepts a path via ``--file`` or
|
|
139
|
+
launches an interactive file-selection menu that scans your project for
|
|
140
|
+
existing ``.env*`` files.
|
|
141
|
+
|
|
142
|
+
**Flags:**
|
|
143
|
+
|
|
144
|
+
* ``--append``
|
|
145
|
+
Write the generated key to an environment file. Without this flag the key
|
|
146
|
+
is only printed to stdout — no files are touched. Prompts a confirmation
|
|
147
|
+
warning because replacing a live ``SECRET_KEY`` invalidates all existing
|
|
148
|
+
sessions, tokens, and signed cookies.
|
|
149
|
+
|
|
150
|
+
* ``--file FILE``, ``-f``
|
|
151
|
+
Directly specify the env file to update (e.g. ``-f .env.production``),
|
|
152
|
+
bypassing the interactive selection menu. If the file does not exist it is
|
|
153
|
+
created automatically. Only used together with ``--append``.
|
|
154
|
+
|
|
155
|
+
* ``--no-input``
|
|
156
|
+
Suppress all interactive prompts, menus, and risk warnings. Useful for
|
|
157
|
+
automated deployments and CI/CD pipelines.
|
|
158
|
+
|
|
159
|
+
**Examples**::
|
|
160
|
+
|
|
161
|
+
# Print a new secret key to the terminal only (no file changes)
|
|
162
|
+
python manage.py generate_django_secret
|
|
163
|
+
|
|
164
|
+
# Interactively choose which env file to update
|
|
165
|
+
python manage.py generate_django_secret --append
|
|
166
|
+
|
|
167
|
+
# Write directly to a specific file, no prompts
|
|
168
|
+
python manage.py generate_django_secret --append --no-input -f .env.local
|
|
169
|
+
|
|
170
|
+
**After running**, update your ``settings.py`` to read the key from the
|
|
171
|
+
environment::
|
|
172
|
+
|
|
173
|
+
import os
|
|
174
|
+
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
|
175
|
+
|
|
176
|
+
----
|
|
177
|
+
|
|
178
|
+
3. ``generate_password`` — Interactive Password Generator
|
|
179
|
+
---------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
The ``generate_password`` command launches an interactive terminal session for
|
|
182
|
+
generating and refining secure passwords. After each password is shown, you can
|
|
183
|
+
regenerate it or adjust the composition settings without restarting the command.
|
|
184
|
+
|
|
185
|
+
**Flags** (set initial defaults; all can be changed interactively):
|
|
186
|
+
|
|
187
|
+
* ``--length LENGTH``, ``-len``
|
|
188
|
+
Desired password length (default: 12).
|
|
189
|
+
|
|
190
|
+
* ``--letters``
|
|
191
|
+
Include lowercase letters (a–z).
|
|
192
|
+
|
|
193
|
+
* ``--uppercase``
|
|
194
|
+
Include uppercase letters (A–Z).
|
|
195
|
+
|
|
196
|
+
* ``--numbers``
|
|
197
|
+
Include numerical digits (0–9).
|
|
198
|
+
|
|
199
|
+
* ``--symbol``
|
|
200
|
+
Include symbols and punctuation characters.
|
|
201
|
+
|
|
202
|
+
* ``--secure``
|
|
203
|
+
Shortcut that forces uppercase letters, numbers, symbols, and a minimum
|
|
204
|
+
length of 20. Overrides individual composition flags.
|
|
205
|
+
|
|
206
|
+
**Examples**::
|
|
207
|
+
|
|
208
|
+
# Launch with defaults (12 characters, no composition constraints)
|
|
209
|
+
python manage.py generate_password
|
|
210
|
+
|
|
211
|
+
# Start with a longer length and reconfigure interactively
|
|
212
|
+
python manage.py generate_password --length 16
|
|
213
|
+
|
|
214
|
+
# Instantly generate a highly secure 20-character password
|
|
215
|
+
python manage.py generate_password --secure
|
|
216
|
+
|
|
217
|
+
----
|
|
218
|
+
|
|
219
|
+
Programmatic Usage
|
|
220
|
+
------------------
|
|
221
|
+
|
|
222
|
+
The underlying password generator can be imported directly into your own
|
|
223
|
+
Django apps — useful for custom registration flows, invitation tokens, or
|
|
224
|
+
background tasks::
|
|
225
|
+
|
|
226
|
+
from django_keygen.core.passwords import generate_password
|
|
227
|
+
|
|
228
|
+
# Generate a secure password with specific character sets
|
|
229
|
+
new_password = generate_password(
|
|
230
|
+
length=14,
|
|
231
|
+
use_uppercase=True,
|
|
232
|
+
use_numbers=True,
|
|
233
|
+
use_symbols=True,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Use it in your application logic
|
|
237
|
+
from django.contrib.auth.models import User
|
|
238
|
+
user = User.objects.create_user(username='johndoe', email='john@example.com')
|
|
239
|
+
user.set_password(new_password)
|
|
240
|
+
user.save()
|
|
241
|
+
|
|
242
|
+
----
|
|
243
|
+
|
|
244
|
+
License
|
|
245
|
+
-------
|
|
246
|
+
|
|
247
|
+
django-keygen is released under the **MIT License**.
|
|
248
|
+
|
|
249
|
+
Copyright (c) 2026 Mouhib Sellami
|
|
250
|
+
|
|
251
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
252
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
253
|
+
in the Software without restriction, including without limitation the rights
|
|
254
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
255
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
256
|
+
furnished to do so, subject to the following conditions:
|
|
257
|
+
|
|
258
|
+
The above copyright notice and this permission notice shall be included in all
|
|
259
|
+
copies or substantial portions of the Software.
|
|
260
|
+
|
|
261
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
262
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
263
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
264
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
265
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
266
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
267
|
+
SOFTWARE.
|
|
268
|
+
|
|
269
|
+
See the ``LICENSE`` file in the repository root for the full license text,
|
|
270
|
+
or visit: https://github.com/mouhib-Sellami/django-keygen/blob/main/LICENSE
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.rst
|
|
4
|
+
pyproject.toml
|
|
5
|
+
django_keygen/__init__.py
|
|
6
|
+
django_keygen/apps.py
|
|
7
|
+
django_keygen.egg-info/PKG-INFO
|
|
8
|
+
django_keygen.egg-info/SOURCES.txt
|
|
9
|
+
django_keygen.egg-info/dependency_links.txt
|
|
10
|
+
django_keygen.egg-info/requires.txt
|
|
11
|
+
django_keygen.egg-info/top_level.txt
|
|
12
|
+
django_keygen/core/__init__.py
|
|
13
|
+
django_keygen/core/passwords/__init__.py
|
|
14
|
+
django_keygen/management/__init__.py
|
|
15
|
+
django_keygen/management/commands/__init__.py
|
|
16
|
+
django_keygen/management/commands/generate_django_secret.py
|
|
17
|
+
django_keygen/management/commands/generate_password.py
|
|
18
|
+
django_keygen/management/commands/keygen.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_keygen
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools.packages.find]
|
|
6
|
+
where = ["."]
|
|
7
|
+
include = ["django_keygen*"]
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "django-keygen"
|
|
11
|
+
version = "0.1.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"django>=4.2",
|
|
14
|
+
"python-dotenv>=1.0.0",
|
|
15
|
+
]
|
|
16
|
+
description = "django-keygen is a Django management command utility designed to securely generate cryptographic secret keys."
|
|
17
|
+
readme = "README.rst"
|
|
18
|
+
license = "MIT"
|
|
19
|
+
requires-python = ">= 3.9"
|
|
20
|
+
authors = [
|
|
21
|
+
{name = "Mouhib sellami", email = "mouhib.sellami@gmail.com"},
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Environment :: Web Environment",
|
|
25
|
+
"Framework :: Django",
|
|
26
|
+
"Framework :: Django :: 4.2",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Programming Language :: Python :: 3.14"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/mouhib-Sellami/django-keygen"
|