Skip to content

Commit 2fb99b5

Browse files
authored
Merge pull request #649 from supertokens/release/v0.31.4
chore: prepare release v0.31.4
2 parents b498da1 + 700f4e3 commit 2fb99b5

149 files changed

Lines changed: 10218 additions & 2854 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changes/v0.31.4.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## [0.31.4] - 2026-10-21
2+
3+
### Changed
4+
- multifactorauth: a tenant whose `first_factors` is an empty list now allows no first factors, matching the documented meaning and the other SDKs. Sign in and sign up are rejected for such tenants, and with multi-factor auth enabled, their existing sessions that have no MFA claim yet are signed out. Current cores store tenants created through the API without `first_factors` with an empty list, so set `first_factors` explicitly (or to `None`) for those tenants, e.g. with `create_or_update_tenant` or the dashboard.
5+
- thirdparty: the GitHub provider now validates the access token with GitHub (using `validate_access_token`) on every sign in, which needs the client's `client_secret`. Add it to the GitHub provider config, or set a custom `validate_access_token`.
6+
- webauthn: account recovery now replaces the account's existing passkeys with the newly registered one and signs out its existing sessions if the account's email was not verified before the recovery. Accounts with a verified email keep their passkeys and sessions. Apps that don't use the EmailVerification recipe never have verified emails, so for them every account recovery resets the account's passkeys and sessions.
7+
- dashboard: an empty or whitespace-only `api_key` is now treated as not set, so the dashboard uses its own sign in. In API key mode, the `Authorization` header must now be exactly `Bearer <api_key>`.
8+
- auth: when there is a session, the sign in and sign up APIs now use its tenant (which already took precedence) before looking up third party providers and running the email, phone number and form field validators, so these now run with the tenant the user actually signs in to.
9+
- auth: the sign in and sign up APIs (when they use an existing session to link a new login method) and the webauthn register, remove and list credential APIs now check the session with the core, so revoked sessions are rejected. This adds one core call to those requests.
10+
- totp: removing a device without completing multi-factor auth is now only allowed when the only device with that name is unverified and no verified device has the same name (compared case-insensitively).
11+
- passwordless: `revoke_all_codes` with `phone_number` now revokes the codes of that phone number. Passing both `email` and `phone_number` now raises an error.
12+
- dashboard: when `admins` is set, unlinking accounts through the dashboard now requires an admin, like the other dashboard APIs that change data.
13+
- session: access tokens whose payload has an `stt` claim other than `0` are now rejected, and `stt` is now a protected access token payload claim: `create_new_session` drops an `stt` key from the payload you pass, claim validation drops it from its payload updates, and `merge_into_access_token_payload` removes an existing `stt` claim (it still stores an `stt` key in the update you pass to it, so don't pass one). If your code sets a custom access token claim named `stt`, rename it before upgrading. Existing sessions that carry such a claim are rejected with a 401 and can't be refreshed or signed out until they are repaired: repair them with `merge_into_access_token_payload(session_handle, {})`, which removes the now-protected claim, or revoke them.
14+
- thirdparty: when the Google Workspaces provider is configured with a specific `hd` (anything other than `"*"`), a sign in without an id token (for example, when only an access token is sent to the sign in API) is now only accepted if Google's user info response has the same `hd`. Sign ins with an id token are checked against its `hd` claim, as before. If your app sends only an access token, you can send the id token as well to have that checked instead.
15+
- session: when `validate_claims_for_session_handle` refetches claim values, it now fetches them for the session's own tenant, and it drops protected access token payload claims (such as `sub` or `tId`) from the resulting payload update before merging it into the session.
16+
- thirdparty: the Google (including Google Workspaces), Facebook, GitLab and Discord providers now check access tokens sent to the sign in up API in `oAuthTokens` with the provider before using them, and fail the sign in if the token wasn't issued to one of the provider's configured clients. Sign ins with an authorisation code (`redirectURIInfo`) are not affected. Google: the token info API must return one of the configured client ids as `aud` or `azp`, so add every client that requests tokens for your app (e.g. web, Android and iOS) to the provider's `clients`. Facebook: the check uses Facebook's debug token API, which needs the client's `client_secret`, so add it to the provider config. Setting `validate_access_token` in the provider config replaces this check.
17+
### Fixed
18+
- oauth2provider: use the website base path when building frontend redirect URLs instead of the API base path.
19+
- webauthn: fix sign in failing for counter-incrementing authenticators (e.g. Windows Hello) — the assertion was verified against the core twice
20+
### Infrastructure
21+
- Replace the local lint-pr workflow with a thin caller of the shared supertokens/actions reusable lint-pr workflow (behavior unchanged)
22+
- Replace the local dev-sync workflow with a thin caller of the shared supertokens/actions reusable dev-sync workflow (behavior unchanged)
23+
- Mint short-lived GitHub App installation tokens in-job (actions/create-github-app-token) for the release-tag and check-docs workflows, replacing the ALL_REPO_PAT org secret
24+
- Adopt the shared reusable release-tag workflow from supertokens/actions; keep only the docs and PyPI publish jobs as thin callers consuming its outputs. The git tag is now pushed before the API release mark.
25+
### Security
26+
- thirdparty: the Apple form_post callback now only redirects to the configured `origin` / `website_domain` and answers invalid callback state with a 400. Apps whose `origin` depends on the request, or that redirect to other targets (e.g. another origin, https universal/app links or custom-scheme mobile deep links such as `myapp://callback`), can allow them with the new `is_valid_frontend_redirect_uri` thirdparty config option. `javascript:`, `data:` and similar schemes are always rejected. An `origin` with an internationalised domain name is converted to its punycode (`xn--`) form for this check, and http(s) redirect targets must use that form too. Python converts some domain names differently from browsers (e.g. ones with `ß` or `ς`), and the callback rejects redirects to such an `origin`, so set it in the punycode form browsers use (e.g. `https://xn--strae-oqa.example` for `https://straße.example`). The callback redirects to the target in a normalised form (e.g. with `.` and `..` path segments resolved) and passes that same form to `is_valid_frontend_redirect_uri`, so parse the URL there to check it instead of comparing strings.
27+
- session: access token structure validation now requires a string `tId` claim for v4 and later access tokens.
28+
- dashboard: the sign-out and analytics exemptions from the `admins` write restriction now match on the request path only, ignoring the query string.
29+
- accountlinking: when `should_require_verification` is `True`, automatic account linking now also requires the matching email to be verified in the primary user (as it already did for phone numbers). Apps using `should_require_verification=True` now get no link in this case, which was always the documented intent.

‎.github/workflows/check-docs.yml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,21 @@ jobs:
2020
runs-on: ubuntu-latest
2121

2222
steps:
23+
- uses: actions/create-github-app-token@v2
24+
id: app-token
25+
with:
26+
app-id: ${{ vars.RELEASE_APP_ID }}
27+
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
28+
owner: supertokens
29+
repositories: ${{ github.event.repository.name }}
30+
2331
- uses: actions/checkout@v4
2432
with:
2533
ref: ${{ github.ref }}
2634
# Need a complete fetch to make the master merge check work
2735
fetch-depth: 0
2836
fetch-tags: true
29-
token: ${{ secrets.ALL_REPO_PAT }}
37+
token: ${{ steps.app-token.outputs.token }}
3038

3139

3240
- name: Setup git

‎.github/workflows/dev-sync.yml‎

Lines changed: 18 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
name: "Sync Dev to Version Branch"
22

3-
# On every push to dev, computes the next version from unreleased changelog
3+
# Thin caller of the shared reusable dev-sync workflow in supertokens/actions.
4+
# On every push to dev, it computes the next version from unreleased changie
45
# fragments and creates or force-updates a release PR:
56
#
6-
# release/vX.Y.Z → X.Y (version branch)
7+
# release/vX.Y.Z -> X.Y (version branch)
78
#
8-
# The PR title and branch name make the target version explicit — that's the
9-
# sanity check. Review it, merge it, then trigger the Release Pipeline.
10-
#
11-
# Can also be triggered manually to override the bump type (e.g. force a minor
12-
# release when auto would compute a patch).
9+
# Can also be triggered manually to override the bump type. The python-specific
10+
# release prep (install doc deps + rebuild the html/ API docs), the Python
11+
# version, and the set of files to stage are passed as inputs; the orchestration
12+
# logic lives once in the reusable workflow.
1313

1414
on:
1515
push:
@@ -32,146 +32,14 @@ permissions:
3232

3333
jobs:
3434
sync:
35-
runs-on: ubuntu-latest
36-
37-
steps:
38-
- uses: actions/checkout@v4
39-
with:
40-
ref: dev
41-
fetch-depth: 0
42-
token: ${{ secrets.ALL_REPO_PAT }}
43-
44-
- name: Setup git
45-
run: |
46-
git config user.name "github-actions[bot]"
47-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
48-
git fetch --all --tags
49-
50-
- uses: miniscruff/changie-action@v2
51-
with:
52-
version: latest
53-
54-
- name: Compute next version
55-
id: next
56-
run: |
57-
bump="${{ inputs.bump || 'auto' }}"
58-
59-
if ! raw_version=$(changie next $bump 2>&1); then
60-
echo "changie next returned: $raw_version"
61-
echo "No bumpable fragments found (all changes may be Infrastructure/none). Skipping sync."
62-
echo "skip=true" >> $GITHUB_OUTPUT
63-
exit 0
64-
fi
65-
66-
version="${raw_version#v}" # strip leading 'v' if present (changie next returns e.g. v0.31.2)
67-
version_branch=$(echo "$version" | grep -oE '^[0-9]+\.[0-9]+')
68-
69-
echo "version=$version" >> $GITHUB_OUTPUT
70-
echo "version_branch=$version_branch" >> $GITHUB_OUTPUT
71-
echo "pr_branch=release/v$version" >> $GITHUB_OUTPUT
72-
73-
- name: Create version branch if it doesn't exist
74-
if: steps.next.outputs.skip != 'true'
75-
run: |
76-
version_branch="${{ steps.next.outputs.version_branch }}"
77-
78-
if ! git show-ref --verify --quiet "refs/remotes/origin/$version_branch"; then
79-
git checkout -b "$version_branch"
80-
git push origin "$version_branch"
81-
echo "Created new version branch: $version_branch"
82-
else
83-
echo "Version branch $version_branch already exists."
84-
fi
85-
86-
- uses: actions/setup-python@v5
87-
if: steps.next.outputs.skip != 'true'
88-
with:
89-
python-version: '3.13'
90-
91-
- name: Install doc dependencies
92-
if: steps.next.outputs.skip != 'true'
93-
run: pip install pdoc3==0.11.0 -e ".[fastapi,flask,django,drf]"
94-
95-
- name: Build release branch
96-
if: steps.next.outputs.skip != 'true'
97-
run: |
98-
pr_branch="${{ steps.next.outputs.pr_branch }}"
99-
version_branch="${{ steps.next.outputs.version_branch }}"
100-
version="${{ steps.next.outputs.version }}"
101-
102-
# Start from the version branch (clean base), merge dev on top.
103-
# -X theirs ensures dev wins on any conflict — dev is the source of truth.
104-
if git show-ref --verify --quiet "refs/remotes/origin/$pr_branch"; then
105-
git checkout "$pr_branch"
106-
git reset --hard "origin/$version_branch"
107-
else
108-
git checkout -b "$pr_branch" "origin/$version_branch"
109-
fi
110-
111-
git merge origin/dev --no-edit -X theirs
112-
113-
# changie batch:
114-
# - collects unreleased fragments into .changes/v{version}.md
115-
# - updates setup.py and constants.py via replacements
116-
changie batch "v$version"
117-
changie merge
118-
119-
# Regenerate API docs (publish-docs job in release pipeline reads from html/)
120-
make build-docs
121-
122-
git add .changes/ CHANGELOG.md setup.py supertokens_python/constants.py html/
123-
git diff --staged --quiet \
124-
|| git commit -m "chore: prepare release v$version"
125-
126-
git push origin "$pr_branch" --force
127-
128-
- name: Create or update pull request
129-
if: steps.next.outputs.skip != 'true'
130-
env:
131-
GH_TOKEN: ${{ secrets.ALL_REPO_PAT }}
132-
run: |
133-
version="${{ steps.next.outputs.version }}"
134-
pr_branch="${{ steps.next.outputs.pr_branch }}"
135-
target_branch="${{ steps.next.outputs.version_branch }}"
136-
137-
pr_number=$(gh pr list \
138-
--base "$target_branch" \
139-
--head "$pr_branch" \
140-
--json number \
141-
--jq '.[0].number')
142-
143-
# Strip the version header line for the PR body
144-
notes=$(sed '1d' ".changes/v${version}.md" | sed '/./,$!d')
145-
146-
body=$(cat <<EOF
147-
## Release v${version}
148-
149-
This PR is automatically kept in sync with \`dev\`. It will be force-updated on every push to \`dev\`.
150-
151-
**Review checklist:**
152-
- [ ] Version \`${version}\` is correct (check \`setup.py\` and \`constants.py\`)
153-
- [ ] Changelog entries below are accurate and complete
154-
155-
**After merging:** trigger the [Release Pipeline](../actions/workflows/pipeline-release-tag.yml) with branch \`${target_branch}\`.
156-
157-
---
158-
159-
${notes}
160-
EOF
161-
)
162-
163-
if [[ -n "$pr_number" ]]; then
164-
gh pr edit "$pr_number" \
165-
--title "chore: prepare release v${version}" \
166-
--body "$body" \
167-
--add-label "run-tests"
168-
echo "Updated PR #$pr_number"
169-
else
170-
gh pr create \
171-
--base "$target_branch" \
172-
--head "$pr_branch" \
173-
--title "chore: prepare release v${version}" \
174-
--body "$body" \
175-
--label "run-tests,Skip-Changelog"
176-
echo "Created new release PR"
177-
fi
35+
uses: supertokens/actions/.github/workflows/dev-sync.yml@main
36+
secrets: inherit
37+
with:
38+
bump: ${{ inputs.bump || 'auto' }}
39+
python_version: "3.13"
40+
prepare: |
41+
pip install pdoc3==0.11.0 -e ".[fastapi,flask,django,drf]"
42+
make build-docs
43+
add_paths: ".changes/ CHANGELOG.md setup.py supertokens_python/constants.py html/"
44+
version_check_hint: '\`setup.py\` and \`constants.py\`'
45+
post_merge_note: 'trigger the [Release Pipeline](../actions/workflows/pipeline-release-tag.yml) with branch \`${target_branch}\`.'

‎.github/workflows/lint-pr.yml‎

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,12 @@ on:
1010
- labeled
1111
- unlabeled
1212

13-
permissions:
14-
contents: read
15-
1613
# Only one instance of this workflow will run on the same ref (PR/Branch/Tag)
1714
# Previous runs will be cancelled.
1815
concurrency:
1916
group: ${{ github.workflow }}-${{ github.ref }}
2017
cancel-in-progress: true
2118

2219
jobs:
23-
lint-pr-title:
24-
name: Lint PR Title
25-
runs-on: ubuntu-latest
26-
steps:
27-
- uses: amannn/action-semantic-pull-request@v6
28-
env:
29-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30-
with:
31-
validateSingleCommit: true
32-
33-
lint-changelog:
34-
name: Require Changelog Fragment
35-
runs-on: ubuntu-latest
36-
steps:
37-
- uses: actions/checkout@v4
38-
with:
39-
fetch-depth: 0
40-
41-
- name: Check for changelog fragment
42-
if: "!contains(github.event.pull_request.labels.*.name, 'Skip-Changelog')"
43-
run: |
44-
git fetch origin ${{ github.base_ref }}
45-
46-
new_fragments=$(git diff --name-only --diff-filter=A \
47-
"origin/${{ github.base_ref }}...HEAD" -- .changes/unreleased/)
48-
49-
if [[ -z "$new_fragments" ]]; then
50-
echo "::error::No changelog fragment found in .changes/unreleased/"
51-
echo ""
52-
echo "Please add a fragment before merging:"
53-
echo " changie new"
54-
echo ""
55-
echo "Or add the 'Skip-Changelog' label to skip this check."
56-
exit 1
57-
fi
58-
59-
echo "Found changelog fragment(s):"
60-
echo "$new_fragments"
20+
lint:
21+
uses: supertokens/actions/.github/workflows/lint-pr.yml@main

0 commit comments

Comments
 (0)