FlowAuth: drop the silent absolute-expiry cap (#7274)#7277
FlowAuth: drop the silent absolute-expiry cap (#7274)#7277jakejellinek merged 1 commit intomasterfrom
Conversation
WalkthroughThis pull request implements Phase 1 of a two-release deprecation plan converting absolute token expiry caps to relative ones. It introduces optional Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as TokenBuilder UI
participant API as Token API
participant DB as Database Models
User->>UI: Enter name, select roles, enter lifetime (optional)
UI->>API: POST /tokens with name, roles, lifetime_minutes
API->>DB: Fetch Server.next_expiry() and Role.next_expiry() for each role
DB-->>API: Return expiry values from latest_token_expiry (or compute from longest_token_life_minutes if NULL)
alt lifetime_minutes provided
API->>API: Calculate requested_expiry = now + lifetime_minutes
API->>API: Validate requested_expiry ≤ max_token_expiry
alt Invalid
API-->>UI: 400 error, lifetime_minutes exceeds cap
else Valid
API->>API: Use requested_expiry for token
end
else lifetime_minutes omitted
API->>API: Use max_token_expiry for token
end
API->>DB: Create token with computed expiry
DB-->>API: Token created
API-->>UI: Return token to user
UI-->>User: Display token
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
FlowAuth
|
||||||||||||||||||||||||||||
| Project |
FlowAuth
|
| Branch Review |
flowauth/drop-absolute-expiry-cap
|
| Run status |
|
| Run duration | 00m 42s |
| Commit |
|
| Committer | Joachim Jellinek |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
4
|
| View all changes introduced in this branch ↗︎ | |
Server.latest_token_expiry and Role.latest_token_expiry are now nullable. A NULL value means the row imposes no absolute expiry cap on tokens; only longest_token_life_minutes then bounds the lifetime. Existing rows keep their non-null values, so behaviour is unchanged until an operator nulls the column out — Ghana and any other deployment can adopt at their own pace by tag bump. The mint endpoint now accepts an optional lifetime_minutes so users can request a token shorter than the maximum permitted by the selected server and roles. Without it, tokens are issued at the maximum permitted lifetime as before. Also fixes a bug in User.token_limits where the role query did not filter by user, so the function returned the most-permissive role on the server across all users instead of just the calling user's roles. Migration is two-step by intent: this change makes the columns nullable. Dropping them entirely is a follow-up for a later release once both production deployments have been on the nullable schema for a while.
cd6fda6 to
5b6989c
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
flowauth/backend/flowauth/token_management.py (1)
138-167:⚠️ Potential issue | 🟠 MajorCapture
nowonce and reject booleans explicitly forlifetime_minutes.
isinstance(True, int)isTrue, so JSONtruecurrently mints a 1-minute token. Multipledatetime.now()calls can also make an exact-cap request fail on the boundary. Capture the timestamp once and usetype()for strict type checking.Note: The same pattern exists in
renew_token()and should be fixed identically.Suggested fix
- max_token_expiry = min(server.next_expiry(), min(rr.next_expiry() for rr in roles)) + now = datetime.datetime.now() + max_token_expiry = min(server.next_expiry(), min(rr.next_expiry() for rr in roles)) - if max_token_expiry < datetime.datetime.now(): + if max_token_expiry <= now: raise Unauthorized(f"Token for {current_user.username} expired") - if not isinstance(requested_lifetime, int) or requested_lifetime <= 0: + if type(requested_lifetime) is not int or requested_lifetime <= 0: raise InvalidUsage( "lifetime_minutes must be a positive integer", payload={"bad_field": "lifetime_minutes"}, ) - requested_expiry = datetime.datetime.now() + datetime.timedelta( + requested_expiry = now + datetime.timedelta( minutes=requested_lifetime ) - lifetime=token_expiry - datetime.datetime.now(), + lifetime=token_expiry - now,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flowauth/backend/flowauth/token_management.py` around lines 138 - 167, Capture a single "now" datetime at the start of the token issuance flow and use that single timestamp for all expiry comparisons and for computing lifetimes (replace multiple datetime.datetime.now() calls used around max_token_expiry checks and when creating requested_expiry and lifetime passed to generate_token); also reject boolean JSON values by using type(requested_lifetime) is not int (instead of isinstance) when validating lifetime_minutes. Apply the identical change in renew_token() so both issuance and renewal use the same captured now and strict int type-checking for lifetime_minutes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@flowauth/backend/flowauth/models.py`:
- Around line 156-196: User.latest_token_expiry currently mixes independent
maxima (role_expiries and longest) and can produce an impossible combo; change
the logic to iterate actual Role rows (join User.roles) and for each role
compute that role's effective end as min(server.latest_token_expiry,
role.latest_token_expiry, now +
timedelta(minutes=min(server.longest_token_life_minutes,
role.longest_token_life_minutes))) treating None as “no cap” (i.e., ignored in
the min), then take the maximum across those per-role effective ends and also
set longest_life as min(server.longest_token_life_minutes,
role.longest_token_life_minutes) computed per role and then max across roles;
update the queries around role_expiries and longest so you fetch
Role.latest_token_expiry and Role.longest_token_life_minutes together (via the
join used in the current code) and replace the independent longest/role_expiries
computations with this per-role computation inside User.latest_token_expiry().
In `@flowauth/backend/flowauth/roles.py`:
- Around line 56-61: Wrap the datetime parsing into a small helper (e.g.,
parse_iso_datetime or parse_token_expiry) that takes the raw string, returns a
datetime or None, and catches ValueError to raise InvalidUsage with a bad_field
payload (include the field name like "latest_token_expiry"). Replace the inline
datetime.datetime.strptime calls that set json["latest_token_expiry"] and the
other strptime at lines ~119–123 with calls to this helper so malformed
timestamps produce a 400 InvalidUsage containing bad_field instead of bubbling a
ValueError (ensure you reference and raise the existing InvalidUsage type used
in this module).
In `@flowauth/frontend/src/TokenBuilder.jsx`:
- Around line 49-50: Token lifetime UI never computes or enforces the effective
maximum from server.longest_token_life_minutes and the selected roles, so update
TokenBuilder.jsx to compute the allowed max (e.g. derive maxLifetime =
Math.min(server.longest_token_life_minutes,
selectedRoles.map(r=>r.max_life_minutes).filter(Boolean)...) or similar) and use
that value to (1) clamp lifetimeMinutes on input/change via setLifetimeMinutes,
(2) add a max attribute/validation to the input control and (3) update the
helper text (and any validation error) to show the actual computed cap.
Reference the lifetimeMinutes/setLifetimeMinutes state and the
server.longest_token_life_minutes and selected roles data when making these
changes so the picker reflects role/server caps immediately.
In `@flowauth/frontend/src/util/api.js`:
- Around line 398-405: The createToken function currently uses parseInt on
lifetime_minutes which silently accepts decimals and exponential notation or
yields NaN; replace that with strict integer validation (e.g. test
lifetime_minutes against a digits-only pattern like /^\d+$/) before setting
body.lifetime_minutes and only then parseInt to an integer; if the validation
fails, reject/throw or return a rejected Promise indicating invalid
lifetime_minutes so the payload never sends an ambiguous/null lifetime.
---
Outside diff comments:
In `@flowauth/backend/flowauth/token_management.py`:
- Around line 138-167: Capture a single "now" datetime at the start of the token
issuance flow and use that single timestamp for all expiry comparisons and for
computing lifetimes (replace multiple datetime.datetime.now() calls used around
max_token_expiry checks and when creating requested_expiry and lifetime passed
to generate_token); also reject boolean JSON values by using
type(requested_lifetime) is not int (instead of isinstance) when validating
lifetime_minutes. Apply the identical change in renew_token() so both issuance
and renewal use the same captured now and strict int type-checking for
lifetime_minutes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 51e392a2-ded0-46aa-96e8-e3fc4da5d249
📒 Files selected for processing (9)
CHANGELOG.mdflowauth/backend/flowauth/migrations/versions/c1d4e7b9a2f3_nullable_latest_token_expiry.pyflowauth/backend/flowauth/models.pyflowauth/backend/flowauth/roles.pyflowauth/backend/flowauth/servers.pyflowauth/backend/flowauth/token_management.pyflowauth/backend/tests/test_token_generation.pyflowauth/frontend/src/TokenBuilder.jsxflowauth/frontend/src/util/api.js
| role_expiries = ( | ||
| db.session.execute( | ||
| db.select(Role.latest_token_expiry) | ||
| .where(Role.server_id == server.id) | ||
| .join(User.roles) | ||
| .where(User.id == self.id) | ||
| ) | ||
| .scalars() | ||
| .all() | ||
| ) | ||
|
|
||
| longest = db.session.execute( | ||
| db.select(Role.longest_token_life_minutes) | ||
| .where(Role.server_id == server.id) | ||
| .join(User.roles) | ||
| .where(User.id == self.id) | ||
| .order_by(Role.longest_token_life_minutes.desc()) | ||
| ).scalar() | ||
|
|
||
| if not latest or not longest: | ||
| raise Unauthorized(f"No roles for {self.username} on {Server.name}") | ||
| if not role_expiries or longest is None: | ||
| raise Unauthorized(f"No roles for {self.username} on {server.name}") | ||
|
|
||
| if any(expiry is None for expiry in role_expiries): | ||
| roles_latest = None | ||
| else: | ||
| roles_latest = max(role_expiries) | ||
|
|
||
| server_latest = server.latest_token_expiry | ||
| if roles_latest is None and server_latest is None: | ||
| latest_end = None | ||
| elif roles_latest is None: | ||
| latest_end = server_latest | ||
| elif server_latest is None: | ||
| latest_end = roles_latest | ||
| else: | ||
| latest_end = min(server_latest, roles_latest) | ||
|
|
||
| return { | ||
| "latest_end": min(server.latest_token_expiry, latest), | ||
| "latest_end": latest_end, | ||
| "longest_life": min(server.longest_token_life_minutes, longest), | ||
| } |
There was a problem hiding this comment.
Don't combine cap and lifetime maxima from different roles.
roles_latest and longest are computed independently, so they can come from different roles. That makes User.latest_token_expiry() overstate the real limit. For example, a user with roles (latest=None, life=10) and (latest=now+20m, life=1000) gets now+1000m here, but the true maximum is now+20m. Please compute each role's effective end first — min(server cap, role cap, now + min(server life, role life)) — and then take the maximum across those per-role values instead of mixing independent maxima.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flowauth/backend/flowauth/models.py` around lines 156 - 196,
User.latest_token_expiry currently mixes independent maxima (role_expiries and
longest) and can produce an impossible combo; change the logic to iterate actual
Role rows (join User.roles) and for each role compute that role's effective end
as min(server.latest_token_expiry, role.latest_token_expiry, now +
timedelta(minutes=min(server.longest_token_life_minutes,
role.longest_token_life_minutes))) treating None as “no cap” (i.e., ignored in
the min), then take the maximum across those per-role effective ends and also
set longest_life as min(server.longest_token_life_minutes,
role.longest_token_life_minutes) computed per role and then max across roles;
update the queries around role_expiries and longest so you fetch
Role.latest_token_expiry and Role.longest_token_life_minutes together (via the
join used in the current code) and replace the independent longest/role_expiries
computations with this per-role computation inside User.latest_token_expiry().
| raw_latest = json.get("latest_token_expiry") | ||
| json["latest_token_expiry"] = ( | ||
| datetime.datetime.strptime(raw_latest, "%Y-%m-%dT%H:%M:%S.%fZ") | ||
| if raw_latest | ||
| else None | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import datetime
samples = ["2026-04-28T00:00:00.000000Z", "not-a-date", "2026-04-28", ""]
for raw in samples:
try:
parsed = datetime.datetime.strptime(raw, "%Y-%m-%dT%H:%M:%S.%fZ") if raw else None
print(f"{raw!r} -> {parsed!r}")
except Exception as exc:
print(f"{raw!r} -> {type(exc).__name__}: {exc}")
PYRepository: Flowminder/FlowKit
Length of output: 337
🏁 Script executed:
cat -n flowauth/backend/flowauth/roles.py | head -130 | tail -80Repository: Flowminder/FlowKit
Length of output: 3702
🏁 Script executed:
rg "InvalidUsage" flowauth/backend/flowauth/ -A 2 -B 2 | head -50Repository: Flowminder/FlowKit
Length of output: 3294
🏁 Script executed:
rg "strptime|datetime.datetime" flowauth/backend/flowauth/roles.py -B 2 -A 2Repository: Flowminder/FlowKit
Length of output: 453
Malformed expiry timestamps will return 500 instead of 400.
The strptime calls at lines 56–61 and 119–123 raise ValueError on invalid input without catching it, bypassing field-level InvalidUsage errors. Extract these to a helper function that catches ValueError and raises InvalidUsage with a bad_field payload instead.
Suggested refactor
+def _parse_optional_latest_token_expiry(value):
+ if not value:
+ return None
+ try:
+ return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")
+ except ValueError as err:
+ raise InvalidUsage(
+ "Invalid latest_token_expiry",
+ payload={"bad_field": "latest_token_expiry"},
+ ) from err
+
def add_role():
json = request.get_json()
- raw_latest = json.get("latest_token_expiry")
- json["latest_token_expiry"] = (
- datetime.datetime.strptime(raw_latest, "%Y-%m-%dT%H:%M:%S.%fZ")
- if raw_latest
- else None
- )
+ json["latest_token_expiry"] = _parse_optional_latest_token_expiry(
+ json.get("latest_token_expiry")
+ )
@@
elif key == "latest_token_expiry":
- value = (
- datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")
- if value
- else None
- )
+ value = _parse_optional_latest_token_expiry(value)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| raw_latest = json.get("latest_token_expiry") | |
| json["latest_token_expiry"] = ( | |
| datetime.datetime.strptime(raw_latest, "%Y-%m-%dT%H:%M:%S.%fZ") | |
| if raw_latest | |
| else None | |
| ) | |
| json["latest_token_expiry"] = _parse_optional_latest_token_expiry( | |
| json.get("latest_token_expiry") | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flowauth/backend/flowauth/roles.py` around lines 56 - 61, Wrap the datetime
parsing into a small helper (e.g., parse_iso_datetime or parse_token_expiry)
that takes the raw string, returns a datetime or None, and catches ValueError to
raise InvalidUsage with a bad_field payload (include the field name like
"latest_token_expiry"). Replace the inline datetime.datetime.strptime calls that
set json["latest_token_expiry"] and the other strptime at lines ~119–123 with
calls to this helper so malformed timestamps produce a 400 InvalidUsage
containing bad_field instead of bubbling a ValueError (ensure you reference and
raise the existing InvalidUsage type used in this module).
| const [lifetimeMinutes, setLifetimeMinutes] = useState(""); | ||
| const [tokenErrorOpen, setTokenErrorOpen] = useState(false); |
There was a problem hiding this comment.
The new lifetime field still does not reflect the actual cap.
This component never computes a max from server.longest_token_life_minutes or the selected roles, so the user can only discover the valid range after a backend 400. That leaves the issue’s “picker reflects role/server caps” requirement incomplete and makes the helper text misleading.
Also applies to: 190-200
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flowauth/frontend/src/TokenBuilder.jsx` around lines 49 - 50, Token lifetime
UI never computes or enforces the effective maximum from
server.longest_token_life_minutes and the selected roles, so update
TokenBuilder.jsx to compute the allowed max (e.g. derive maxLifetime =
Math.min(server.longest_token_life_minutes,
selectedRoles.map(r=>r.max_life_minutes).filter(Boolean)...) or similar) and use
that value to (1) clamp lifetimeMinutes on input/change via setLifetimeMinutes,
(2) add a max attribute/validation to the input control and (3) update the
helper text (and any validation error) to show the actual computed cap.
Reference the lifetimeMinutes/setLifetimeMinutes state and the
server.longest_token_life_minutes and selected roles data when making these
changes so the picker reflects role/server caps immediately.
| export async function createToken(name, server_id, roles, lifetime_minutes) { | ||
| const body = { name: name, roles: roles }; | ||
| if (lifetime_minutes != null && lifetime_minutes !== "") { | ||
| body.lifetime_minutes = parseInt(lifetime_minutes, 10); | ||
| } | ||
| var dat = { | ||
| method: "POST", | ||
| body: JSON.stringify({ name: name, roles: roles }), | ||
| body: JSON.stringify(body), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
node - <<'NODE'
for (const raw of ["1.5", "1e2", " ", "08"]) {
const parsed = parseInt(raw, 10);
console.log({
raw,
parsed,
json: JSON.stringify({ lifetime_minutes: parsed }),
});
}
NODERepository: Flowminder/FlowKit
Length of output: 296
🏁 Script executed:
cd flowauth && find . -name "api.js" -path "*/frontend/src/util/*" 2>/dev/nullRepository: Flowminder/FlowKit
Length of output: 89
🏁 Script executed:
cd flowauth && git ls-files | grep -E "frontend/src/util/api\.js"Repository: Flowminder/FlowKit
Length of output: 87
🏁 Script executed:
cd flowauth && head -420 frontend/src/util/api.js | tail -30Repository: Flowminder/FlowKit
Length of output: 948
🏁 Script executed:
cd flowauth && find . -path "*/backend*" -name "*.py" -o -path "*/server*" -name "*.py" | head -20Repository: Flowminder/FlowKit
Length of output: 830
🏁 Script executed:
cd flowauth && rg -i "tokens/tokens" --type py -A 5 -B 2Repository: Flowminder/FlowKit
Length of output: 11923
🏁 Script executed:
cd flowauth && rg -i "lifetime_minutes" backend/ --type py -B 3 -A 3Repository: Flowminder/FlowKit
Length of output: 6549
🏁 Script executed:
cd flowauth && rg -i "bad_field.*lifetime_minutes" backend/ --type py -B 5 -A 5Repository: Flowminder/FlowKit
Length of output: 4007
Frontend validation for lifetime_minutes is insufficient.
The parseInt approach silently truncates decimals ("1.5" → 1, "1e2" → 1) and converts whitespace to NaN, which serialises as null and causes the backend to issue a default-lifetime token. Add strict integer validation before building the payload.
Suggested fix
export async function createToken(name, server_id, roles, lifetime_minutes) {
- const body = { name: name, roles: roles };
+ const body = { name, roles };
if (lifetime_minutes != null && lifetime_minutes !== "") {
- body.lifetime_minutes = parseInt(lifetime_minutes, 10);
+ const parsedLifetime = Number(lifetime_minutes);
+ if (!Number.isInteger(parsedLifetime) || parsedLifetime <= 0) {
+ throw new APIError("lifetime_minutes must be a positive integer");
+ }
+ body.lifetime_minutes = parsedLifetime;
}
var dat = {
method: "POST",
body: JSON.stringify(body),
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flowauth/frontend/src/util/api.js` around lines 398 - 405, The createToken
function currently uses parseInt on lifetime_minutes which silently accepts
decimals and exponential notation or yields NaN; replace that with strict
integer validation (e.g. test lifetime_minutes against a digits-only pattern
like /^\d+$/) before setting body.lifetime_minutes and only then parseInt to an
integer; if the validation fails, reject/throw or return a rejected Promise
indicating invalid lifetime_minutes so the payload never sends an ambiguous/null
lifetime.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #7277 +/- ##
==========================================
- Coverage 92.08% 91.95% -0.13%
==========================================
Files 278 256 -22
Lines 10826 10652 -174
Branches 697 681 -16
==========================================
- Hits 9969 9795 -174
- Misses 704 705 +1
+ Partials 153 152 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Closes #7274.
Summary
ServerandRolecarry an absolutelatest_token_expirydatetime that ratchets the token-expiry cap downward over time. Renewing a long-lived token therefore requires an admin to log in first and bump those datetimes on the server and every role used by the token — and if they forget, the new token is silently capped at the old (near-expired) date with no UI warning. Wiki-documented runbook, the# feature todo: flag this to the userattoken_management.py:138, and #6454 are all symptoms of the same bug.This PR makes the column nullable as the first half of a two-release deprecation. A
NULLvalue means "no absolute expiry cap, onlylongest_token_life_minutesbounds the lifetime". Operators opt in per server/role by nulling the column. The actual column drop is a follow-up for a later release once both production deployments have been on the nullable schema for a while.The mint endpoint also gains an optional
lifetime_minutesso users can request a shorter token (addressing the lifetime-half of #5719). Without it, tokens are still issued at the maximum permitted lifetime.Changes
Backend
models.pyServer.latest_token_expiryandRole.latest_token_expiryare nownullable=True.Server.next_expiry()andRole.next_expiry()returnnow + longest_token_life_minuteswhenlatest_token_expiryisNULL.User.token_limits()rewritten to handleNULLrole/server caps. If any of the user's roles on the server hasNULL, that side imposes no cap; otherwise the most-permissive role wins. Server's value (orNULL) caps that. Returnslatest_end: Nonewhen there is no cap from either side.User.latest_token_expiry()falls back tonow + longest_lifewhenlatest_endisNone.Role.to_dict()serialisesNULLas JSONnull.token_limitspreviously did not filter by user id, so it returned the most-permissive role on the server across all users. Now correctly filters byUser.id == self.id.token_management.py / add_tokenaccepts optionallifetime_minutes(positive integer). Validated againstmin(server.next_expiry(), role.next_expiry()). Returns 400 withbad_field: lifetime_minutesif invalid or above the cap.servers.py—add_server,edit_server,get_server,get_rolesall tolerate / serialiseNULL.roles.py—add_role,edit_roletolerateNULL. The "role cap can't exceed server cap" check is skipped when either side isNULL.c1d4e7b9a2f3_nullable_latest_token_expiry.pyrunsALTER COLUMN ... DROP NOT NULLon both tables. Down-revision is the current head (976c731ff30f).Frontend
TokenBuilder.jsxgains an optional "Lifetime (minutes)" field. Empty = "issue at the maximum permitted by the selected roles" (existing behaviour).util/api.js—createTokenaccepts optionallifetime_minutesand includes it in the request body when set.Tests
test_token_honours_requested_lifetime— short lifetime is honoured.test_token_rejects_lifetime_above_cap— over-cap lifetime → 400.test_token_mint_with_no_absolute_caps— server and roles withNULLlatest_token_expiry mint atnow + longest_token_life_minutes.Changelog
Entries under
[Unreleased]Added,Changed, andFixedreferencing #7274.Backwards compatibility
latest_token_expiryvalues, so on first deploy of this version nothing changes operationally.ghana-flowauthrepo, pinned to its own image tag) keeps working until its operator chooses to bump tags.null(or omitted) forlatest_token_expiry.Test plan
pytest flowauth/backend/tests/test_token_generation.py— three new tests pass and existing ones don't regress.pytest flowauth/backend/tests/test_access_reflects_server_limits.py— still green.flask db upgradethenflask db downgraderound-trips on a populated database.lifetime_minutes=60→ token expires in 60 minutes; mint with a lifetime above the cap → error message surfaces.Related work
This is the second of three planned PRs against FlowAuth. The visibility-of-roles change is in #7276 (issue #7273). The renewal endpoint is #7275 and depends on #7276's
tokens_with_rolestable.Summary by CodeRabbit
Release Notes
New Features
lifetime_minutesparameter to token minting, allowing tokens to be issued with shorter lifetimes below the configured maximum.Changed
Bug Fixes