-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: DIA-1839: Add JWT auth for API tokens #6996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 58 commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
eb1f662
wip: add jwt auth
pakelley 15a7e10
Merge branch 'develop' into 'fb-dia-1839'
pakelley 9d3bf6d
only use jwt for orgs with it enabled
pakelley e577e97
add simplejwt dep
pakelley 823d4fe
log when basic token auth is used
pakelley b82fa07
enable jwt token auth for newly created orgs
pakelley 1397c0d
move basic jwt token to LSO
pakelley 5924e15
fix app naming
pakelley 4dfa205
Merge branch 'develop' into fb-dia-1839
pakelley d6b340e
update lockfile hash
pakelley 7df9ed9
add missing serializer
pakelley 56b8dfc
fix migration
pakelley 81beb77
separate settings
pakelley e39d427
move middleware
pakelley 6ebebe7
block old tokens
pakelley 3108c36
add tests
pakelley 4dc2e90
remove ttl_days
pakelley 04f7d51
fix swagger docs
pakelley 5effe4c
add tests
pakelley a96ad85
remove unnecessary fields
pakelley 955d823
jwt access token FF
pakelley 63a5a98
use standard .authenticate call
pakelley 5f6a6f1
test: improve tests
pakelley 37ff6fc
combine settings modules
pakelley 408d70d
fallback to other auth if jwt fails
pakelley e81e814
add ability to revoke token
pakelley fdaacc7
allow admin to make jwt settings changes
pakelley 0a3138f
typo
pakelley aa8c635
Merge branch 'develop' into fb-dia-1839
pakelley 0ec9928
fix hash
pakelley 8fca88d
improve serializer behavior
pakelley 1b2407f
fix api docs
pakelley 3e52496
lint
pakelley e2278fd
lint
pakelley 41d0d9d
change log level to info
pakelley 626b1e4
don't need hasattr
pakelley 302ca78
separate settings for legacy vs jwt api tokens
pakelley a094e77
make tokens last effectively forever
pakelley c62e80b
better testing for auth enablement matrix
pakelley 8182edd
fix token settings for new orgs
pakelley b1bf031
improve testing
pakelley f615c42
lint
pakelley af9e0be
improve exception handling
pakelley 7fbeecc
add jwt urls
pakelley 060883f
add utils file
pakelley 8616f3a
fix imports
pakelley ee9a257
lint
pakelley 322c50e
improve exception when user does not exist
pakelley 2b15928
update urls
pakelley ed7742c
lint
pakelley b91195d
lint
pakelley 97a466e
improve token list behavior
pakelley bb7040a
only return refresh tokens
pakelley 3ab5841
lint
pakelley f91f92c
exclude blacklisted tokens from token list
pakelley f45a7de
return 404 when blacklisting already-blacklisted token
pakelley e5c6104
include legacy token settings in settings serializer
pakelley a9ad9cf
add tests for blacklist statuses
pakelley 1bf2ef7
lint
pakelley 4c782d2
only enable new auth if FF turned on
pakelley dd83f71
doc: get_queryset behavior
pakelley a720570
test: fix test
pakelley 94555e4
don't 401 directly when JWT auth fails
pakelley 7f70163
improve woding on legacy token auth phaseout
pakelley 9e1ef8b
one more wording change
pakelley d998425
tidying
pakelley 611e406
tidying
pakelley 1f54189
updates from CR
pakelley 0187cbb
Merge branch 'develop' into 'fb-dia-1839'
pakelley 14f74c4
don't allow token management from admin console
pakelley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pakelley marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class JWTAuthConfig(AppConfig): | ||
name = 'jwt_auth' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import logging | ||
|
||
from rest_framework.authentication import TokenAuthentication | ||
from rest_framework.exceptions import AuthenticationFailed | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class TokenAuthenticationPhaseout(TokenAuthentication): | ||
"""TokenAuthentication that logs usage to help track basic token authentication usage.""" | ||
|
||
def authenticate(self, request): | ||
"""Authenticate the request and log if successful.""" | ||
from core.feature_flags import flag_set | ||
|
||
auth_result = super().authenticate(request) | ||
JWT_ACCESS_TOKEN_ENABLED = flag_set('fflag__feature_develop__prompts__dia_1829_jwt_token_auth') | ||
if JWT_ACCESS_TOKEN_ENABLED and (auth_result is not None): | ||
user, _ = auth_result | ||
org = user.active_organization | ||
org_id = org.id if org else None | ||
|
||
# raise 401 if legacy API token auth disabled (i.e. this token is no longer valid) | ||
if org and (not org.jwt.legacy_api_tokens_enabled): | ||
raise AuthenticationFailed( | ||
'Authentication token no longer valid: JWT authentication is required for this organization' | ||
) | ||
|
||
logger.info( | ||
'Basic token authentication used', | ||
extra={'user_id': user.id, 'organization_id': org_id, 'endpoint': request.path}, | ||
) | ||
return auth_result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import logging | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.http import JsonResponse | ||
from rest_framework import status | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
User = get_user_model() | ||
|
||
|
||
class JWTAuthenticationMiddleware: | ||
def __init__(self, get_response): | ||
self.get_response = get_response | ||
|
||
def __call__(self, request): | ||
from core.feature_flags import flag_set | ||
from rest_framework_simplejwt.authentication import JWTAuthentication | ||
from rest_framework_simplejwt.exceptions import AuthenticationFailed, InvalidToken, TokenError | ||
|
||
JWT_ACCESS_TOKEN_ENABLED = flag_set('fflag__feature_develop__prompts__dia_1829_jwt_token_auth') | ||
if JWT_ACCESS_TOKEN_ENABLED: | ||
try: | ||
user_and_token = JWTAuthentication().authenticate(request) | ||
if not user_and_token: | ||
return self.get_response(request) | ||
|
||
user = User.objects.get(pk=user_and_token[0].pk) | ||
if user.active_organization.jwt.api_tokens_enabled: | ||
request.user = user | ||
request.is_jwt = True | ||
except User.DoesNotExist: | ||
logger.info('JWT authentication failed: User no longer exists') | ||
return JsonResponse({'detail': 'User not found'}, status=status.HTTP_401_UNAUTHORIZED) | ||
except (InvalidToken, TokenError, AuthenticationFailed) as e: | ||
logger.info('JWT authentication failed: %s', e) | ||
return JsonResponse({'detail': str(e)}, status=status.HTTP_401_UNAUTHORIZED) | ||
return self.get_response(request) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Generated by Django 5.1.4 on 2025-02-03 15:51 | ||
|
||
import annoying.fields | ||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
("organizations", "0006_alter_organizationmember_deleted_at"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="jwtsettings", | ||
fields=[ | ||
( | ||
"organization", | ||
annoying.fields.AutoOneToOneField( | ||
on_delete=django.db.models.deletion.DO_NOTHING, | ||
primary_key=True, | ||
related_name='jwt', | ||
serialize=False, | ||
to='organizations.organization' | ||
) | ||
), | ||
( | ||
"api_tokens_enabled", | ||
models.BooleanField( | ||
default=False, | ||
help_text="Enable JWT API token authentication for this organization", | ||
verbose_name="JWT API tokens enabled", | ||
), | ||
), | ||
( | ||
'api_token_ttl_days', | ||
models.IntegerField( | ||
default=30, | ||
help_text='Number of days before JWT API tokens expire', | ||
verbose_name='JWT API token time to live (days)') | ||
), | ||
( | ||
"legacy_api_tokens_enabled", | ||
models.BooleanField( | ||
default=True, | ||
help_text="Enable legacy API token authentication for this organization", | ||
verbose_name="legacy API tokens enabled", | ||
), | ||
), | ||
( | ||
"created_at", | ||
models.DateTimeField(auto_now_add=True, verbose_name="created at"), | ||
), | ||
( | ||
"updated_at", | ||
models.DateTimeField(auto_now=True, verbose_name="updated at"), | ||
), | ||
], | ||
), | ||
] |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.