Skip to content

Commit 90a92cf

Browse files
mydeamdtro
andauthored
feat(org-tokens): Adjust token format from JWT (#51341)
This PR adjusts the token format of the new org auth tokens from using JWT to a custom format based on base64 encoding. This should result in nicer endings of tokens, and also makes the tokens considerably shorter. Related to getsentry/rfcs#105 --------- Co-authored-by: Matthew T <matthew.trostel@sentry.io>
1 parent 88755b9 commit 90a92cf

File tree

5 files changed

+120
-125
lines changed

5 files changed

+120
-125
lines changed

src/sentry/api/endpoints/org_auth_tokens.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from sentry.models.organization import Organization
1717
from sentry.models.orgauthtoken import OrgAuthToken
1818
from sentry.utils import hashlib
19-
from sentry.utils.security.orgauthtoken_jwt import generate_token
19+
from sentry.utils.security.orgauthtoken_token import generate_token
2020

2121

2222
@control_silo_endpoint

src/sentry/utils/security/orgauthtoken_jwt.py

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import secrets
2+
from base64 import b64decode, b64encode
3+
from datetime import datetime
4+
5+
from django.conf import settings
6+
7+
from sentry.utils import json
8+
9+
SENTRY_ORG_AUTH_TOKEN_PREFIX = "sntrys_"
10+
11+
12+
def generate_token(org_slug: str, region_url: str):
13+
sentry_url = settings.SENTRY_OPTIONS.get("system.url-prefix")
14+
payload = {
15+
"iat": datetime.utcnow().timestamp(),
16+
"url": sentry_url,
17+
"region_url": region_url,
18+
"org": org_slug,
19+
}
20+
secret = b64encode(secrets.token_bytes(nbytes=32)).decode("ascii").rstrip("=")
21+
22+
json_str = json.dumps(payload)
23+
payload_encoded = base64_encode_str(json_str)
24+
25+
return f"{SENTRY_ORG_AUTH_TOKEN_PREFIX}{payload_encoded}_{secret}"
26+
27+
28+
def parse_token(token: str):
29+
if not token.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX) or token.count("_") != 2:
30+
return None
31+
32+
payload_hashed = token[len(SENTRY_ORG_AUTH_TOKEN_PREFIX) : token.rindex("_")]
33+
34+
try:
35+
payload_str = b64decode((payload_hashed).encode("ascii")).decode("ascii")
36+
payload = json.loads(payload_str)
37+
if not payload.get("iat"):
38+
return None
39+
return payload
40+
except Exception:
41+
return None
42+
43+
44+
def base64_encode_str(str):
45+
return b64encode(str.encode("ascii")).decode("ascii")

tests/sentry/utils/security/test_orgauthtoken_jwt.py

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from sentry.testutils import TestCase
2+
from sentry.utils import json
3+
from sentry.utils.security.orgauthtoken_token import (
4+
SENTRY_ORG_AUTH_TOKEN_PREFIX,
5+
base64_encode_str,
6+
generate_token,
7+
parse_token,
8+
)
9+
10+
11+
class OrgAuthTokenTokenTest(TestCase):
12+
def test_generate_token(self):
13+
token = generate_token("test-org", "https://test-region.sentry.io")
14+
15+
assert token
16+
assert token.startswith(SENTRY_ORG_AUTH_TOKEN_PREFIX)
17+
18+
def test_parse_token(self):
19+
token = generate_token("test-org", "https://test-region.sentry.io")
20+
token_payload = parse_token(token)
21+
22+
assert token_payload["org"] == "test-org"
23+
assert token_payload["url"] == "http://testserver"
24+
assert token_payload["region_url"] == "https://test-region.sentry.io"
25+
26+
def test_parse_invalid_token(self):
27+
assert parse_token("invalid-token") is None
28+
29+
def test_parse_invalid_token_json(self):
30+
payload_str = (
31+
'{"iat": 12345678,"url": "test-site","region_url": "test-site","org": "test-org}'
32+
)
33+
payload_hashed = base64_encode_str(payload_str)
34+
token = SENTRY_ORG_AUTH_TOKEN_PREFIX + payload_hashed + "_secret"
35+
36+
assert parse_token(token) is None
37+
38+
def test_parse_invalid_token_iat(self):
39+
payload = {
40+
"url": "test-site",
41+
"region_url": "test-site",
42+
"org": "test-org",
43+
}
44+
45+
payload_str = json.dumps(payload)
46+
payload_hashed = base64_encode_str(payload_str)
47+
token = SENTRY_ORG_AUTH_TOKEN_PREFIX + payload_hashed + "_secret"
48+
49+
assert parse_token(token) is None
50+
51+
def test_parse_invalid_token_missing_secret(self):
52+
payload = {
53+
"iat": 12345678,
54+
"url": "test-site",
55+
"region_url": "test-site",
56+
"org": "test-org",
57+
}
58+
59+
payload_str = json.dumps(payload)
60+
payload_hashed = base64_encode_str(payload_str)
61+
token = SENTRY_ORG_AUTH_TOKEN_PREFIX + payload_hashed
62+
63+
assert parse_token(token) is None
64+
65+
def test_generate_token_unique(self):
66+
jwt1 = generate_token("test-org", "https://test-region.sentry.io")
67+
jwt2 = generate_token("test-org", "https://test-region.sentry.io")
68+
jwt3 = generate_token("test-org", "https://test-region.sentry.io")
69+
70+
assert jwt1
71+
assert jwt2
72+
assert jwt3
73+
assert jwt1 != jwt2
74+
assert jwt2 != jwt3

0 commit comments

Comments
 (0)