Add a description field to api tokens and make api tokens start with a 'ctfd_' prefix (#2337)

* Add a description field for API tokens 
* API tokens now start with a `ctfd_` prefix to make them easier to identify
* Closes #2184
This commit is contained in:
Kevin Chung
2023-06-22 00:20:32 -04:00
committed by GitHub
parent e5518b54bd
commit eac44adf69
7 changed files with 51 additions and 8 deletions

View File

@@ -85,11 +85,14 @@ class TokenList(Resource):
def post(self): def post(self):
req = request.get_json() req = request.get_json()
expiration = req.get("expiration") expiration = req.get("expiration")
description = req.get("description")
if expiration: if expiration:
expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d") expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d")
user = get_current_user() user = get_current_user()
token = generate_user_token(user, expiration=expiration) token = generate_user_token(
user, expiration=expiration, description=description
)
# Explicitly use admin view so that user's can see the value of their token # Explicitly use admin view so that user's can see the value of their token
schema = TokenSchema(view="admin") schema = TokenSchema(view="admin")

View File

@@ -1,6 +1,6 @@
from flask import session from flask import session
from flask_babel import lazy_gettext as _l from flask_babel import lazy_gettext as _l
from wtforms import PasswordField, SelectField, StringField from wtforms import PasswordField, SelectField, StringField, TextAreaField
from wtforms.fields.html5 import DateField, URLField from wtforms.fields.html5 import DateField, URLField
from CTFd.constants.languages import SELECT_LANGUAGE_LIST from CTFd.constants.languages import SELECT_LANGUAGE_LIST
@@ -50,4 +50,5 @@ def SettingsForm(*args, **kwargs):
class TokensForm(BaseForm): class TokensForm(BaseForm):
expiration = DateField(_l("Expiration")) expiration = DateField(_l("Expiration"))
description = TextAreaField("Usage Description")
submit = SubmitField(_l("Generate")) submit = SubmitField(_l("Generate"))

View File

@@ -916,6 +916,7 @@ class Tokens(db.Model):
db.DateTime, db.DateTime,
default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30), default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30),
) )
description = db.Column(db.Text)
value = db.Column(db.String(128), unique=True) value = db.Column(db.String(128), unique=True)
user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select") user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select")

View File

@@ -9,8 +9,16 @@ class TokenSchema(ma.ModelSchema):
dump_only = ("id", "expiration", "type") dump_only = ("id", "expiration", "type")
views = { views = {
"admin": ["id", "type", "user_id", "created", "expiration", "value"], "admin": [
"user": ["id", "type", "created", "expiration"], "id",
"type",
"user_id",
"created",
"expiration",
"description",
"value",
],
"user": ["id", "type", "created", "expiration", "description"],
} }
def __init__(self, view=None, *args, **kwargs): def __init__(self, view=None, *args, **kwargs):

View File

@@ -81,7 +81,10 @@
<b>{{ form.expiration.label }}</b> <b>{{ form.expiration.label }}</b>
{{ form.expiration(class="form-control") }} {{ form.expiration(class="form-control") }}
</div> </div>
<div class="form-group">
<b>{{ form.description.label }}</b>
{{ form.description(class="form-control", rows="3") }}
</div>
<div class="form-group text-right"> <div class="form-group text-right">
{{ form.submit(class="btn btn-md btn-primary btn-outlined") }} {{ form.submit(class="btn btn-md btn-primary btn-outlined") }}
</div> </div>
@@ -96,6 +99,7 @@
<tr> <tr>
<td class="text-center"><b>Created</b></td> <td class="text-center"><b>Created</b></td>
<td class="text-center"><b>Expiration</b></td> <td class="text-center"><b>Expiration</b></td>
<td class="text-center"><b>Description</b></td>
<td class="text-center"><b>Delete</b></td> <td class="text-center"><b>Delete</b></td>
</tr> </tr>
</thead> </thead>
@@ -104,6 +108,7 @@
<tr> <tr>
<td><span data-time="{{ token.created | isoformat }}"></span></td> <td><span data-time="{{ token.created | isoformat }}"></span></td>
<td><span data-time="{{ token.expiration | isoformat }}"></span></td> <td><span data-time="{{ token.expiration | isoformat }}"></span></td>
<td><span>{{ token.description | default('', true) }}</span></td>
<td class="text-center"> <td class="text-center">
<span class="delete-token" role="button" data-token-id="{{ token.id }}"> <span class="delete-token" role="button" data-token-id="{{ token.id }}">
<i class="btn-fa fas fa-times"></i> <i class="btn-fa fas fa-times"></i>

View File

@@ -34,13 +34,15 @@ def logout_user():
session.clear() session.clear()
def generate_user_token(user, expiration=None): def generate_user_token(user, expiration=None, description=None):
temp_token = True temp_token = True
while temp_token is not None: while temp_token is not None:
value = hexencode(os.urandom(32)) value = "ctfd_" + hexencode(os.urandom(32))
temp_token = UserTokens.query.filter_by(value=value).first() temp_token = UserTokens.query.filter_by(value=value).first()
token = UserTokens(user_id=user.id, expiration=expiration, value=value) token = UserTokens(
user_id=user.id, expiration=expiration, description=description, value=value
)
db.session.add(token) db.session.add(token)
db.session.commit() db.session.commit()
return token return token

View File

@@ -0,0 +1,23 @@
"""Add description column to tokens table
Revision ID: 9e6f6578ca84
Revises: 0def790057c1
Create Date: 2023-06-21 23:22:34.179636
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "9e6f6578ca84"
down_revision = "0def790057c1"
branch_labels = None
depends_on = None
def upgrade():
op.add_column("tokens", sa.Column("description", sa.Text(), nullable=True))
def downgrade():
op.drop_column("tokens", "description")