feat: implement JWT caching (#2928)

This commit is contained in:
Taimoor Zaeem
2023-09-25 14:46:55 -03:00
committed by GitHub
parent 90e3a5e29f
commit a6e3eda5b2
19 changed files with 206 additions and 19 deletions
+1
View File
@@ -22,6 +22,7 @@ jwt-aud = ""
jwt-role-claim-key = ".\"aliased\""
jwt-secret = ""
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 0
log-level = "error"
openapi-mode = "follow-privileges"
openapi-security-active = false
@@ -22,6 +22,7 @@ jwt-aud = ""
jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 0
log-level = "error"
openapi-mode = "follow-privileges"
openapi-security-active = false
@@ -22,6 +22,7 @@ jwt-aud = ""
jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 0
log-level = "error"
openapi-mode = "follow-privileges"
openapi-security-active = false
+1
View File
@@ -22,6 +22,7 @@ jwt-aud = ""
jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = false
jwt-cache-max-lifetime = 0
log-level = "error"
openapi-mode = "follow-privileges"
openapi-security-active = false
@@ -22,6 +22,7 @@ jwt-aud = "https://otherexample.org"
jwt-role-claim-key = ".\"other\".\"pre_config_role\""
jwt-secret = "ODERREALLYREALLYREALLYREALLYVERYSAFE"
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 86400
log-level = "info"
openapi-mode = "disabled"
openapi-security-active = false
@@ -22,6 +22,7 @@ jwt-aud = "https://example.org"
jwt-role-claim-key = ".\"a\".\"role\""
jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE"
jwt-secret-is-base64 = false
jwt-cache-max-lifetime = 86400
log-level = "info"
openapi-mode = "ignore-privileges"
openapi-security-active = true
@@ -22,6 +22,7 @@ jwt-aud = "https://postgrest.org"
jwt-role-claim-key = ".\"user\"[0].\"real-role\""
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 86400
log-level = "info"
openapi-mode = "ignore-privileges"
openapi-security-active = true
+1
View File
@@ -22,6 +22,7 @@ jwt-aud = ""
jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = false
jwt-cache-max-lifetime = 0
log-level = "error"
openapi-mode = "follow-privileges"
openapi-security-active = false
+1
View File
@@ -24,6 +24,7 @@ PGRST_JWT_AUD: 'https://postgrest.org'
PGRST_JWT_ROLE_CLAIM_KEY: '.user[0]."real-role"'
PGRST_JWT_SECRET: c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5
PGRST_JWT_SECRET_IS_BASE64: true
PGRST_JWT_CACHE_MAX_LIFETIME: 86400
PGRST_LOG_LEVEL: info
PGRST_OPENAPI_MODE: 'ignore-privileges'
PGRST_OPENAPI_SECURITY_ACTIVE: true
+1
View File
@@ -22,6 +22,7 @@ jwt-aud = "https://postgrest.org"
jwt-role-claim-key = ".user[0].\"real-role\""
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
jwt-secret-is-base64 = true
jwt-cache-max-lifetime = 86400
log-level = "info"
openapi-mode = "ignore-privileges"
openapi-security-active = true
+120 -1
View File
@@ -1,6 +1,6 @@
"Unit tests for Input/Ouput of PostgREST seen as a black box."
from datetime import datetime
from datetime import datetime, timedelta, timezone
from operator import attrgetter
import os
import re
@@ -1095,3 +1095,122 @@ def test_fail_with_automatic_recovery_disabled_and_terminated_using_query(defaul
exitCode = wait_until_exit(postgrest)
assert exitCode == 1
def test_server_timing_jwt_should_decrease_on_subsequent_requests(defaultenv):
"assert that server-timing duration for JWT should decrease on subsequent requests"
env = {
**defaultenv,
"PGRST_DB_PLAN_ENABLED": "true",
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
"PGRST_JWT_SECRET": "@/dev/stdin",
"PGRST_DB_CONFIG": "false",
}
headers = jwtauthheader(
{
"role": "postgrest_test_author",
"exp": int(
(datetime.now(timezone.utc) + timedelta(minutes=30)).timestamp()
),
},
SECRET,
)
with run(stdin=SECRET.encode(), env=env) as postgrest:
first_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
second_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
second_dur = float(second_dur_text[8:])
# their difference should be atleast 300, implying
# that JWT Caching is working as expected
assert (first_dur - second_dur) > 300.0
# just added to complete code coverage
def test_jwt_caching_works_with_db_plan_disabled(defaultenv):
"assert that JWT caching words even when Server-Timing header is not returned"
env = {
**defaultenv,
"PGRST_DB_PLAN_ENABLED": "false",
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
"PGRST_JWT_SECRET": "@/dev/stdin",
"PGRST_DB_CONFIG": "false",
}
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
with run(stdin=SECRET.encode(), env=env) as postgrest:
first_request = postgrest.session.get("/authors_only", headers=headers)
second_request = postgrest.session.get("/authors_only", headers=headers)
# in this case we don't get server-timing in response headers
# so we can't compare durations, we just check if request succeeds
assert first_request.status_code == 200 and second_request.status_code == 200
def test_server_timing_jwt_should_not_decrease_when_caching_disabled(defaultenv):
"assert than jwt duration should not decrease when disabled"
env = {
**defaultenv,
"PGRST_DB_PLAN_ENABLED": "true",
"PGRST_JWT_CACHE_MAX_LIFETIME": "0", # cache disabled
"PGRST_JWT_SECRET": "@/dev/stdin",
"PGRST_DB_CONFIG": "false",
}
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
with run(stdin=SECRET.encode(), env=env) as postgrest:
warmup_req = postgrest.session.get("/authors_only", headers=headers)
first_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
second_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
second_dur = float(second_dur_text[8:])
# their difference should be less than 100
# implying that token is not cached
assert (first_dur - second_dur) < 100.0
def test_jwt_cache_with_no_exp_claim(defaultenv):
"assert than jwt duration should decrease"
env = {
**defaultenv,
"PGRST_DB_PLAN_ENABLED": "true",
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
"PGRST_JWT_SECRET": "@/dev/stdin",
"PGRST_DB_CONFIG": "false",
}
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET) # no exp
with run(stdin=SECRET.encode(), env=env) as postgrest:
first_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
second_dur_text = postgrest.session.get(
"/authors_only", headers=headers
).headers["Server-Timing"]
first_dur = float(first_dur_text[8:]) # skip "jwt;dur="
second_dur = float(second_dur_text[8:])
# their difference should be less than 100
# implying that token is not cached
assert (first_dur - second_dur) > 300.0
+1 -1
View File
@@ -102,7 +102,7 @@ postJsonArrayTest(){
echo "Running memory usage tests.."
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "16M"
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "23M"
jsonKeyTest "1M" "POST" "/leak?columns=blob" "16M"
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "16M"
+1
View File
@@ -118,6 +118,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configJwtRoleClaimKey = [JSPKey "role"]
, configJwtSecret = secret
, configJwtSecretIsBase64 = False
, configJwtCacheMaxLifetime = 0
, configLogLevel = LogCrit
, configOpenApiMode = OAFollowPriv
, configOpenApiSecurityActive = False