Merge branch 'main' into madlittlemods/soft-limit-account-session-management

This commit is contained in:
Eric Eastwood
2026-05-20 15:06:24 -05:00
10 changed files with 513 additions and 187 deletions
+3 -7
View File
@@ -11,11 +11,7 @@ updates:
- "A-Dependencies"
- "Z-Deps-Backend"
schedule:
interval: "daily"
ignore:
# We plan to remove apalis soon, let's ignore it for now
- dependency-name: "apalis"
- dependency-name: "apalis-*"
interval: "monthly"
groups:
axum:
patterns:
@@ -53,7 +49,7 @@ updates:
- "A-Dependencies"
- "Z-Deps-CI"
schedule:
interval: "daily"
interval: "monthly"
cooldown:
default-days: 14
@@ -63,7 +59,7 @@ updates:
- "A-Dependencies"
- "Z-Deps-Frontend"
schedule:
interval: "daily"
interval: "monthly"
groups:
storybook:
patterns:
+333 -49
View File
@@ -27,9 +27,12 @@ env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
IMAGE: ghcr.io/element-hq/matrix-authentication-service
BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
# metadata-action defaults to `manifest`, which `docker buildx imagetools
# create --annotation` refuses with "manifest annotations are not supported
# yet". We only want annotations on the manifest list anyway, so narrow it
# to `index`.
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
jobs:
compute-version:
@@ -208,32 +211,30 @@ jobs:
name: mas-cli-x86_64-linux
path: mas-cli-x86_64-linux.tar.gz
build-image:
name: Build and push Docker image
compute-image-meta:
name: Compute Docker image metadata
if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
runs-on: ubuntu-24.04
outputs:
metadata: ${{ steps.output.outputs.metadata }}
permissions:
contents: read
packages: write
id-token: write
needs:
- compute-version
env:
VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }}
SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }}
outputs:
regular-tags: ${{ steps.meta.outputs.tags }}
regular-annotations: ${{ steps.meta.outputs.annotations }}
debug-tags: ${{ steps.meta-debug.outputs.tags }}
debug-annotations: ${{ steps.meta-debug.outputs.annotations }}
steps:
- name: Docker meta
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: "${{ env.IMAGE }}"
# The oci-push registry login requires Tailscale + Vault, which only
# works for `push` events (PR-labelled runs lack the right OIDC token).
images: |
ghcr.io/element-hq/matrix-authentication-service
${{ github.event_name == 'push' && 'oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }}
bake-target: docker-metadata-action
flavor: |
latest=auto
@@ -244,12 +245,23 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
# GitHub's license detection (via Licensee) can only report a single
# SPDX identifier and still emits the legacy `AGPL-3.0` form, which
# `metadata-action` would otherwise propagate as-is. Override it with
# the project's actual dual-license SPDX expression so the image
# advertises both halves of the dual licensing.
labels: |
org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial
annotations: |
org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial
- name: Docker meta (debug variant)
id: meta-debug
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: "${{ env.IMAGE }}"
images: |
ghcr.io/element-hq/matrix-authentication-service
${{ github.event_name == 'push' && 'oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }}
bake-target: docker-metadata-action-debug
flavor: |
latest=auto
@@ -261,10 +273,63 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
labels: |
org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial
annotations: |
org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial
- name: Setup Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
# Stage the labels bake files under predictable names (the metadata-action
# writes them under a random temp dir with the same base name) and ship
# them to `build-image` as an artifact. We deliberately only pass through
# `bake-file-labels` (not `bake-file-annotations`): the per-arch images
# then carry the same config labels as today, while annotations are only
# applied at the index level in `finalize-image` — matching `:latest`'s
# shape and sidestepping `metadata-action`'s empty-value annotations
# which would otherwise trip `docker buildx imagetools create`.
- name: Stage bake files
env:
REGULAR_FILE: ${{ steps.meta.outputs.bake-file-labels }}
DEBUG_FILE: ${{ steps.meta-debug.outputs.bake-file-labels }}
run: |
mkdir -p /tmp/bake
cp "$REGULAR_FILE" /tmp/bake/regular.json
cp "$DEBUG_FILE" /tmp/bake/debug.json
- name: Upload bake files
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: bake-files
path: /tmp/bake/
retention-days: 1
build-image:
name: Build Docker image (${{ matrix.arch }})
if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
id-token: write
needs:
- compute-version
- compute-image-meta
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
env:
VERGEN_GIT_DESCRIBE: ${{ needs.compute-version.outputs.describe }}
SOURCE_DATE_EPOCH: ${{ needs.compute-version.outputs.timestamp }}
# Comma-separated list of registries to push each per-arch image to.
# The oci-push registry is only included on `push` events because the
# Tailscale + Vault login below requires the right OIDC token.
IMAGES: ghcr.io/element-hq/matrix-authentication-service${{ github.event_name == 'push' && ',oci-push.vpn.infra.element.io/matrix-authentication-service' || '' }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
with:
@@ -279,29 +344,218 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
# The Element OCI Registry is only reachable via Tailscale, and the Vault
# JWT exchange relies on a GitHub OIDC token issued from a `push` event.
# PR-labelled builds (`Z-Build-Workflow`) skip this and push only to ghcr.
- name: Tailscale
if: github.event_name == 'push'
uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4.1.1
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
audience: ${{ secrets.TS_AUDIENCE }}
tags: tag:github-actions
- name: Compute vault jwt role name
id: vault-jwt-role
if: github.event_name == 'push'
run: |
echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT"
- name: Get team registry token
id: import-secrets
if: github.event_name == 'push'
uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0
with:
url: https://vault.infra.ci.i.element.dev
role: ${{ steps.vault-jwt-role.outputs.role_name }}
path: service-management/github-actions
jwtGithubAudience: https://vault.infra.ci.i.element.dev
method: jwt
secrets: |
services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ;
services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
- name: Login to Element OCI Registry
if: github.event_name == 'push'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: oci-push.vpn.infra.element.io
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
- name: Download bake files
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: bake-files
path: /tmp/bake
- name: Build and push by digest
id: bake
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
env:
# By default, docker bake will add provenance information to the
# metadata output. This makes the output larger and may exceed the
# shell ARG_MAX limit. Disabling through this environment variable
# disables provenance in the metadata while still attaching provenance
# attestations to the image we push.
# https://github.com/docker/bake-action/issues/239#issuecomment-3828170326
BUILDX_METADATA_PROVENANCE: disabled
with:
files: |
./docker-bake.hcl
cwd://${{ steps.meta.outputs.bake-file }}
cwd://${{ steps.meta-debug.outputs.bake-file }}
cwd:///tmp/bake/regular.json
cwd:///tmp/bake/debug.json
set: |
base.output=type=image,push=true
base.cache-from=type=registry,ref=${{ env.BUILDCACHE }}:buildcache
base.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache,mode=max
*.platform=linux/${{ matrix.arch }}
*.output=type=image,"name=${{ env.IMAGES }}",push-by-digest=true,name-canonical=true,push=true
*.cache-from=type=registry,ref=${{ env.BUILDCACHE }}:buildcache-${{ matrix.arch }}
*.cache-to=type=registry,ref=${{ env.BUILDCACHE }}:buildcache-${{ matrix.arch }},mode=max
- name: Transform bake output
# This transforms the ouput to an object which looks like this:
# { reguar: { digest: "…", tags: ["…", "…"] }, debug: { digest: "…", tags: ["…"] }, … }
id: output
run: |
echo 'metadata<<EOF' >> $GITHUB_OUTPUT
echo "$STEPS_BAKE_OUTPUTS_METADATA" | jq -c 'with_entries(select(.value | (type == "object" and has("containerimage.digest")))) | map_values({ digest: .["containerimage.digest"], tags: (.["image.name"] | split(",")) })' >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
# We use github-script rather than shelling out to jq because the bake
# metadata can exceed the shell ARG_MAX limit when inherited as an env
# var by an exec'd jq.
- name: Export digests
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
STEPS_BAKE_OUTPUTS_METADATA: ${{ steps.bake.outputs.metadata }}
ARCH: ${{ matrix.arch }}
with:
script: |
const fs = require('node:fs');
const path = require('node:path');
const bakeOutput = JSON.parse(process.env.STEPS_BAKE_OUTPUTS_METADATA);
const arch = process.env.ARCH;
fs.mkdirSync('/tmp/digests', { recursive: true });
for (const target of ['regular', 'debug']) {
const digest = bakeOutput[target]?.['containerimage.digest'];
if (!digest) {
throw new Error(`Missing containerimage.digest for target ${target}`);
}
fs.writeFileSync(path.join('/tmp/digests', `${target}-${arch}`), digest);
}
- name: Upload digests
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
retention-days: 1
finalize-image:
name: Create multi-arch manifests
if: github.event_name == 'push' || github.event.label.name == 'Z-Build-Workflow'
runs-on: ubuntu-24.04
needs:
- build-image
- compute-image-meta
outputs:
metadata: ${{ steps.output.outputs.metadata }}
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: digests-*
path: /tmp/digests
# Collect digests from both amd64 and arm64 builds
merge-multiple: true
- name: Setup Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# See `build-image` for why the Element OCI Registry login is gated on
# `push` events.
- name: Tailscale
if: github.event_name == 'push'
uses: tailscale/github-action@53acf823325fe9ca47f4cdaa951f90b4b0de5bb9 # v4.1.1
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
audience: ${{ secrets.TS_AUDIENCE }}
tags: tag:github-actions
- name: Compute vault jwt role name
id: vault-jwt-role
if: github.event_name == 'push'
run: |
echo "role_name=github_service_management_$( echo "${{ github.repository }}" | sed -r 's|[/-]|_|g')" | tee -a "$GITHUB_OUTPUT"
- name: Get team registry token
id: import-secrets
if: github.event_name == 'push'
uses: hashicorp/vault-action@4c06c5ccf5c0761b6029f56cfb1dcf5565918a3b # v3.4.0
with:
url: https://vault.infra.ci.i.element.dev
role: ${{ steps.vault-jwt-role.outputs.role_name }}
path: service-management/github-actions
jwtGithubAudience: https://vault.infra.ci.i.element.dev
method: jwt
secrets: |
services/backend-repositories/secret/data/oci.element.io username | OCI_USERNAME ;
services/backend-repositories/secret/data/oci.element.io password | OCI_PASSWORD ;
- name: Login to Element OCI Registry
if: github.event_name == 'push'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: oci-push.vpn.infra.element.io
username: ${{ steps.import-secrets.outputs.OCI_USERNAME }}
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
- name: Create regular manifest
id: regular
env:
TAGS: ${{ needs.compute-image-meta.outputs.regular-tags }}
ANNOTATIONS: ${{ needs.compute-image-meta.outputs.regular-annotations }}
run: |
# Construct the `imagetools create` command line from the tag and annotation inputs.
args=()
# Add a `-t <tag>` argument for each non-empty tag.
while IFS= read -r t; do [[ -n $t ]] && args+=(-t "$t"); done <<< "$TAGS"
# Add a `--annotation <key>=<value>` argument for each non-empty annotation
while IFS= read -r a; do [[ -n $a && $a != *= ]] && args+=(--annotation "$a"); done <<< "$ANNOTATIONS"
docker buildx imagetools create "${args[@]}" \
"ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/regular-amd64)" \
"ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/regular-arm64)" \
--metadata-file regular-metadata.json
# `imagetools create` wrote the digest to regular-metadata.json
echo "digest=$(jq -r '.["containerimage.descriptor"].digest' regular-metadata.json)" >> "$GITHUB_OUTPUT"
- name: Create debug manifest
id: debug
env:
TAGS: ${{ needs.compute-image-meta.outputs.debug-tags }}
ANNOTATIONS: ${{ needs.compute-image-meta.outputs.debug-annotations }}
run: |
# See comments in regular manifest creation for argument construction.
args=()
while IFS= read -r t; do [[ -n $t ]] && args+=(-t "$t"); done <<< "$TAGS"
while IFS= read -r a; do [[ -n $a && $a != *= ]] && args+=(--annotation "$a"); done <<< "$ANNOTATIONS"
docker buildx imagetools create "${args[@]}" \
"ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/debug-amd64)" \
"ghcr.io/element-hq/matrix-authentication-service@$(cat /tmp/digests/debug-arm64)" \
--metadata-file debug-metadata.json
echo "digest=$(jq -r '.["containerimage.descriptor"].digest' debug-metadata.json)" >> "$GITHUB_OUTPUT"
- name: Sign the images with GitHub Actions provided token
# Only sign on tags and on commits on main branch
@@ -310,13 +564,38 @@ jobs:
&& (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
env:
REGULAR_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).regular.digest }}
DEBUG_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).debug.digest }}
REGULAR_DIGEST: ${{ steps.regular.outputs.digest }}
DEBUG_DIGEST: ${{ steps.debug.outputs.digest }}
run: |-
cosign sign --yes \
"$IMAGE@$REGULAR_DIGEST" \
"$IMAGE@$DEBUG_DIGEST" \
"ghcr.io/element-hq/matrix-authentication-service@$REGULAR_DIGEST" \
"ghcr.io/element-hq/matrix-authentication-service@$DEBUG_DIGEST"
cosign sign --yes \
"oci-push.vpn.infra.element.io/matrix-authentication-service@$REGULAR_DIGEST" \
"oci-push.vpn.infra.element.io/matrix-authentication-service@$DEBUG_DIGEST"
- name: Output metadata
id: output
env:
REGULAR_DIGEST: ${{ steps.regular.outputs.digest }}
DEBUG_DIGEST: ${{ steps.debug.outputs.digest }}
REGULAR_TAGS: ${{ needs.compute-image-meta.outputs.regular-tags }}
DEBUG_TAGS: ${{ needs.compute-image-meta.outputs.debug-tags }}
run: |
# Convert the newline-separated tag lists into JSON arrays.
regular_tags=$(jq -Rnc '[inputs | select(length > 0)]' <<< "$REGULAR_TAGS")
debug_tags=$(jq -Rnc '[inputs | select(length > 0)]' <<< "$DEBUG_TAGS")
{
echo 'metadata<<EOF'
jq -nc \
--arg regular_digest "$REGULAR_DIGEST" \
--arg debug_digest "$DEBUG_DIGEST" \
--argjson regular_tags "$regular_tags" \
--argjson debug_tags "$debug_tags" \
'{regular: {digest: $regular_digest, tags: $regular_tags}, debug: {digest: $debug_digest, tags: $debug_tags}}'
echo 'EOF'
} >> "$GITHUB_OUTPUT"
release:
name: Release
@@ -324,7 +603,8 @@ jobs:
runs-on: ubuntu-24.04
needs:
- assemble-archives
- build-image
- finalize-image
steps:
- name: Download the artifacts from the previous job
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@@ -344,11 +624,12 @@ jobs:
- Digest:
```
${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }}
ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }}
oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }}
```
- Tags:
```
${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, '
${{ join(fromJSON(needs.finalize-image.outputs.metadata).regular.tags, '
') }}
```
@@ -356,11 +637,12 @@ jobs:
- Digest:
```
${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }}
ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }}
oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }}
```
- Tags:
```
${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, '
${{ join(fromJSON(needs.finalize-image.outputs.metadata).debug.tags, '
') }}
```
@@ -376,7 +658,7 @@ jobs:
needs:
- assemble-archives
- build-image
- finalize-image
permissions:
contents: write
@@ -422,11 +704,12 @@ jobs:
- Digest:
```
${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).regular.digest }}
ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }}
oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).regular.digest }}
```
- Tags:
```
${{ join(fromJSON(needs.build-image.outputs.metadata).regular.tags, '
${{ join(fromJSON(needs.finalize-image.outputs.metadata).regular.tags, '
') }}
```
@@ -434,11 +717,12 @@ jobs:
- Digest:
```
${{ env.IMAGE }}@${{ fromJSON(needs.build-image.outputs.metadata).debug.digest }}
ghcr.io/element-hq/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }}
oci.element.io/matrix-authentication-service@${{ fromJSON(needs.finalize-image.outputs.metadata).debug.digest }}
```
- Tags:
```
${{ join(fromJSON(needs.build-image.outputs.metadata).debug.tags, '
${{ join(fromJSON(needs.finalize-image.outputs.metadata).debug.tags, '
') }}
```
@@ -454,7 +738,7 @@ jobs:
if: github.event_name == 'pull_request' && github.event.label.name == 'Z-Build-Workflow'
needs:
- build-image
- finalize-image
permissions:
contents: read
@@ -471,7 +755,7 @@ jobs:
- name: Remove label and comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
BUILD_IMAGE_MANIFEST: ${{ needs.build-image.outputs.metadata }}
BUILD_IMAGE_MANIFEST: ${{ needs.finalize-image.outputs.metadata }}
with:
script: |
const script = require('./.github/scripts/cleanup-pr.cjs');
Generated
+28 -28
View File
@@ -3092,7 +3092,7 @@ dependencies = [
[[package]]
name = "mas-axum-utils"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"axum",
@@ -3126,7 +3126,7 @@ dependencies = [
[[package]]
name = "mas-cli"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"axum",
@@ -3201,7 +3201,7 @@ dependencies = [
[[package]]
name = "mas-config"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"camino",
@@ -3232,7 +3232,7 @@ dependencies = [
[[package]]
name = "mas-context"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"console",
"opentelemetry",
@@ -3248,7 +3248,7 @@ dependencies = [
[[package]]
name = "mas-data-model"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"base64ct",
"chrono",
@@ -3270,7 +3270,7 @@ dependencies = [
[[package]]
name = "mas-email"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"async-trait",
"lettre",
@@ -3281,7 +3281,7 @@ dependencies = [
[[package]]
name = "mas-handlers"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"aide",
"anyhow",
@@ -3362,7 +3362,7 @@ dependencies = [
[[package]]
name = "mas-http"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"futures-util",
"headers",
@@ -3383,7 +3383,7 @@ dependencies = [
[[package]]
name = "mas-i18n"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"camino",
"icu_calendar",
@@ -3405,7 +3405,7 @@ dependencies = [
[[package]]
name = "mas-i18n-scan"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"camino",
"clap",
@@ -3419,7 +3419,7 @@ dependencies = [
[[package]]
name = "mas-iana"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"schemars 0.9.0",
"serde",
@@ -3427,7 +3427,7 @@ dependencies = [
[[package]]
name = "mas-iana-codegen"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3444,7 +3444,7 @@ dependencies = [
[[package]]
name = "mas-jose"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"base64ct",
"chrono",
@@ -3474,7 +3474,7 @@ dependencies = [
[[package]]
name = "mas-keystore"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"aead",
"base64ct",
@@ -3502,7 +3502,7 @@ dependencies = [
[[package]]
name = "mas-listener"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"bytes",
@@ -3526,7 +3526,7 @@ dependencies = [
[[package]]
name = "mas-matrix"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3536,7 +3536,7 @@ dependencies = [
[[package]]
name = "mas-matrix-synapse"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3553,7 +3553,7 @@ dependencies = [
[[package]]
name = "mas-oidc-client"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"assert_matches",
"async-trait",
@@ -3589,7 +3589,7 @@ dependencies = [
[[package]]
name = "mas-policy"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -3606,7 +3606,7 @@ dependencies = [
[[package]]
name = "mas-router"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"axum",
"serde",
@@ -3617,7 +3617,7 @@ dependencies = [
[[package]]
name = "mas-spa"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"camino",
"serde",
@@ -3626,7 +3626,7 @@ dependencies = [
[[package]]
name = "mas-storage"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"async-trait",
"chrono",
@@ -3648,7 +3648,7 @@ dependencies = [
[[package]]
name = "mas-storage-pg"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"async-trait",
"chrono",
@@ -3678,7 +3678,7 @@ dependencies = [
[[package]]
name = "mas-tasks"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3710,7 +3710,7 @@ dependencies = [
[[package]]
name = "mas-templates"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -3742,7 +3742,7 @@ dependencies = [
[[package]]
name = "mas-tower"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"http",
"opentelemetry",
@@ -3995,7 +3995,7 @@ dependencies = [
[[package]]
name = "oauth2-types"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"assert_matches",
"base64ct",
@@ -6094,7 +6094,7 @@ dependencies = [
[[package]]
name = "syn2mas"
version = "1.17.0-rc.0"
version = "1.17.0"
dependencies = [
"anyhow",
"arc-swap",
+30 -30
View File
@@ -9,7 +9,7 @@ members = ["crates/*"]
resolver = "2"
# Updated in the CI with a `sed` command
package.version = "1.17.0-rc.0"
package.version = "1.17.0"
package.license = "AGPL-3.0-only OR LicenseRef-Element-Commercial"
package.authors = ["Element Backend Team"]
package.edition = "2024"
@@ -39,35 +39,35 @@ broken_intra_doc_links = "deny"
[workspace.dependencies]
# Workspace crates
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.17.0-rc.0" }
mas-cli = { path = "./crates/cli/", version = "=1.17.0-rc.0" }
mas-config = { path = "./crates/config/", version = "=1.17.0-rc.0" }
mas-context = { path = "./crates/context/", version = "=1.17.0-rc.0" }
mas-data-model = { path = "./crates/data-model/", version = "=1.17.0-rc.0" }
mas-email = { path = "./crates/email/", version = "=1.17.0-rc.0" }
mas-graphql = { path = "./crates/graphql/", version = "=1.17.0-rc.0" }
mas-handlers = { path = "./crates/handlers/", version = "=1.17.0-rc.0" }
mas-http = { path = "./crates/http/", version = "=1.17.0-rc.0" }
mas-i18n = { path = "./crates/i18n/", version = "=1.17.0-rc.0" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.17.0-rc.0" }
mas-iana = { path = "./crates/iana/", version = "=1.17.0-rc.0" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.17.0-rc.0" }
mas-jose = { path = "./crates/jose/", version = "=1.17.0-rc.0" }
mas-keystore = { path = "./crates/keystore/", version = "=1.17.0-rc.0" }
mas-listener = { path = "./crates/listener/", version = "=1.17.0-rc.0" }
mas-matrix = { path = "./crates/matrix/", version = "=1.17.0-rc.0" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.17.0-rc.0" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.17.0-rc.0" }
mas-policy = { path = "./crates/policy/", version = "=1.17.0-rc.0" }
mas-router = { path = "./crates/router/", version = "=1.17.0-rc.0" }
mas-spa = { path = "./crates/spa/", version = "=1.17.0-rc.0" }
mas-storage = { path = "./crates/storage/", version = "=1.17.0-rc.0" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.17.0-rc.0" }
mas-tasks = { path = "./crates/tasks/", version = "=1.17.0-rc.0" }
mas-templates = { path = "./crates/templates/", version = "=1.17.0-rc.0" }
mas-tower = { path = "./crates/tower/", version = "=1.17.0-rc.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.17.0-rc.0" }
syn2mas = { path = "./crates/syn2mas", version = "=1.17.0-rc.0" }
mas-axum-utils = { path = "./crates/axum-utils/", version = "=1.17.0" }
mas-cli = { path = "./crates/cli/", version = "=1.17.0" }
mas-config = { path = "./crates/config/", version = "=1.17.0" }
mas-context = { path = "./crates/context/", version = "=1.17.0" }
mas-data-model = { path = "./crates/data-model/", version = "=1.17.0" }
mas-email = { path = "./crates/email/", version = "=1.17.0" }
mas-graphql = { path = "./crates/graphql/", version = "=1.17.0" }
mas-handlers = { path = "./crates/handlers/", version = "=1.17.0" }
mas-http = { path = "./crates/http/", version = "=1.17.0" }
mas-i18n = { path = "./crates/i18n/", version = "=1.17.0" }
mas-i18n-scan = { path = "./crates/i18n-scan/", version = "=1.17.0" }
mas-iana = { path = "./crates/iana/", version = "=1.17.0" }
mas-iana-codegen = { path = "./crates/iana-codegen/", version = "=1.17.0" }
mas-jose = { path = "./crates/jose/", version = "=1.17.0" }
mas-keystore = { path = "./crates/keystore/", version = "=1.17.0" }
mas-listener = { path = "./crates/listener/", version = "=1.17.0" }
mas-matrix = { path = "./crates/matrix/", version = "=1.17.0" }
mas-matrix-synapse = { path = "./crates/matrix-synapse/", version = "=1.17.0" }
mas-oidc-client = { path = "./crates/oidc-client/", version = "=1.17.0" }
mas-policy = { path = "./crates/policy/", version = "=1.17.0" }
mas-router = { path = "./crates/router/", version = "=1.17.0" }
mas-spa = { path = "./crates/spa/", version = "=1.17.0" }
mas-storage = { path = "./crates/storage/", version = "=1.17.0" }
mas-storage-pg = { path = "./crates/storage-pg/", version = "=1.17.0" }
mas-tasks = { path = "./crates/tasks/", version = "=1.17.0" }
mas-templates = { path = "./crates/templates/", version = "=1.17.0" }
mas-tower = { path = "./crates/tower/", version = "=1.17.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=1.17.0" }
syn2mas = { path = "./crates/syn2mas", version = "=1.17.0" }
# OpenAPI schema generation and validation
[workspace.dependencies.aide]
+13 -10
View File
@@ -6,9 +6,9 @@
# Please see LICENSE files in the repository root for full details.
# Builds a minimal image with the binary only. It is multi-arch capable,
# cross-building to aarch64 and x86_64. When cross-compiling, Docker sets two
# cross-building to aarch64 or x86_64. When cross-compiling, Docker sets two
# implicit BUILDARG: BUILDPLATFORM being the host platform and TARGETPLATFORM
# being the platform being built.
# being the platform being built. Each architecture is built separately.
# The Debian version and version name must be in sync
ARG DEBIAN_VERSION=13
@@ -119,20 +119,25 @@ ENV SQLX_OFFLINE=true
ARG VERGEN_GIT_DESCRIBE
ENV VERGEN_GIT_DESCRIBE=${VERGEN_GIT_DESCRIBE}
ARG TARGETARCH
# Network access: cargo auditable needs it
RUN --network=default \
--mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/app/target \
RUST_TARGET=$(case "${TARGETARCH}" in \
amd64) echo "x86_64-unknown-linux-gnu" ;; \
arm64) echo "aarch64-unknown-linux-gnu" ;; \
*) echo "unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac) && \
cargo auditable build \
--locked \
--release \
--bin mas-cli \
--no-default-features \
--features docker \
--target x86_64-unknown-linux-gnu \
--target aarch64-unknown-linux-gnu \
&& mv "target/x86_64-unknown-linux-gnu/release/mas-cli" /usr/local/bin/mas-cli-amd64 \
&& mv "target/aarch64-unknown-linux-gnu/release/mas-cli" /usr/local/bin/mas-cli-arm64
--target "${RUST_TARGET}" \
&& mv "target/${RUST_TARGET}/release/mas-cli" /usr/local/bin/mas-cli
#######################################
## Prepare /usr/local/share/mas-cli/ ##
@@ -149,8 +154,7 @@ COPY ./translations/ /share/translations
##################################
FROM gcr.io/distroless/cc-debian${DEBIAN_VERSION}:debug-nonroot AS debug
ARG TARGETARCH
COPY --from=builder /usr/local/bin/mas-cli-${TARGETARCH} /usr/local/bin/mas-cli
COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli
COPY --from=share /share /usr/local/share/mas-cli
WORKDIR /
@@ -161,8 +165,7 @@ ENTRYPOINT ["/usr/local/bin/mas-cli"]
###################
FROM gcr.io/distroless/cc-debian${DEBIAN_VERSION}:nonroot
ARG TARGETARCH
COPY --from=builder /usr/local/bin/mas-cli-${TARGETARCH} /usr/local/bin/mas-cli
COPY --from=builder /usr/local/bin/mas-cli /usr/local/bin/mas-cli
COPY --from=share /share /usr/local/share/mas-cli
WORKDIR /
+36 -15
View File
@@ -457,6 +457,17 @@ pub(crate) async fn post(
// Now we can create the device on the homeserver, without holding the
// transaction
//
// Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
// want the device to be created synchronously on the homeserver, so that
// when we respond, the access token works completely. If the device doesn't
// exist on the homeserver side, token introspection from Synapse to MAS
// will work but Synapse will return a 401 because it doesn't see the
// device.
//
// We're using an upsert so if the device already exists for some reason (like
// when we're replacing it, or a concurrent device sync happening) it won't
// have any effect.
if let Err(err) = homeserver
.upsert_device(
&user.username,
@@ -499,28 +510,33 @@ pub(crate) async fn post(
}))
}
/// Given the violations from [`Policy::evaluate_compat_login`], return the
/// appropriate `RouteError` response.
/// Given the evaluation result/violations from
/// [`Policy::evaluate_compat_login`], return the appropriate `RouteError`
/// response.
async fn process_violations_for_compat_login(
rng: &mut (dyn RngCore + Send),
clock: &dyn Clock,
repo: &mut BoxRepository,
session_limit_config: Option<&SessionLimitConfig>,
user: &User,
violations: Vec<Violation>,
res: mas_policy::EvaluationResult,
) -> Result<(), RouteError> {
// We're using slice syntax here so we can match easily
match &violations[..] {
match (res.valid(), &res.violations[..]) {
// If the only violation is having reached the session limit, we might be
// able to resolve the situation.
//
// We don't trigger this if there was some other violation anyway, since
// that means that removing a session wouldn't actually unblock the login.
[
Violation {
variant: Some(ViolationVariant::TooManySessions { need_to_remove }),
..
},
] => {
(
false,
[
Violation {
variant: Some(ViolationVariant::TooManySessions { need_to_remove }),
..
},
],
) => {
// Normally, if we are seeing a `TooManySessions` violation, we would
// expect `session_limit_config` to be filled in but if someone created
// their own policies which emit a `TooManySessions` violation that isn't
@@ -596,6 +612,11 @@ async fn process_violations_for_compat_login(
.finish(clock, compat_session.to_owned())
.await?;
}
// Schedule a device sync with the homeserver
repo.queue_job()
.schedule_job(rng, clock, SyncDevicesJob::new_for_id(user.id))
.await?;
} else {
// Tell the user about the limit
return Err(RouteError::PolicyHardSessionLimitReached);
@@ -613,9 +634,9 @@ async fn process_violations_for_compat_login(
}
}
// Nothing is wrong
[] => return Ok(()),
(true, _) => return Ok(()),
// Just throw an error for any other violation
_violations => {
(false, _violations) => {
// FIXME: We should be exposing the violations to the user
return Err(RouteError::PolicyRejected);
}
@@ -840,11 +861,12 @@ async fn token_login(
})
.await?;
process_violations_for_compat_login(
rng,
clock,
repo,
session_limit_config,
&browser_session.user,
res.violations,
res,
)
.await?;
@@ -966,8 +988,7 @@ async fn user_password_login(
requester: policy_requester,
})
.await?;
process_violations_for_compat_login(clock, repo, session_limit_config, &user, res.violations)
.await?;
process_violations_for_compat_login(rng, clock, repo, session_limit_config, &user, res).await?;
let session = repo
.compat_session()
+10 -2
View File
@@ -117,13 +117,21 @@ pub(crate) async fn post(
// XXX: this is probably not the right error
.ok_or(RouteError::InvalidAuthorization)?;
// This will make the access token invalid
repo.compat_session().finish(&clock, session).await?;
// Schedule a job to sync the devices of the user with the homeserver
//
// Doing this in a background job is ok as the access token will be invalid
// right away (from the session being finished above) and we do actually
// want to do a full device list sync (as opposed to
// `homeserver.delete_device(...)`), because we're not sure whether we want
// to delete the device (if there is for example a concurrent logout and
// login with the same device ID).
repo.queue_job()
.schedule_job(&mut rng, &clock, SyncDevicesJob::new(&user))
.await?;
repo.compat_session().finish(&clock, session).await?;
repo.save().await?;
LOGOUT_COUNTER.add(1, &[KeyValue::new(RESULT, "success")]);
+18
View File
@@ -573,6 +573,15 @@ async fn authorization_code_grant(
// Look for device to provision
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
// Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
// want the device to be created synchronously on the homeserver, so
// that when we respond, the access token works completely. If the
// device doesn't exist on the homeserver side, token introspection
// from Synapse to MAS will work but Synapse will return a 401
// because it doesn't see the device.
//
// We're using an upsert so if the device already exists for some reason
// (like when a concurrent device sync happening) it won't have any effect.
homeserver
.upsert_device(
&browser_session.user.username,
@@ -977,6 +986,15 @@ async fn device_code_grant(
// Look for device to provision
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
// Normally, devices get synced to the homeserver in a `SyncDevicesJob` but we
// want the device to be created synchronously on the homeserver, so
// that when we respond, the access token works completely. If the
// device doesn't exist on the homeserver side, token introspection
// from Synapse to MAS will work but Synapse will return a 401
// because it doesn't see the device.
//
// We're using an upsert so if the device already exists for some reason
// (like when a concurrent device sync happening) it won't have any effect.
homeserver
.upsert_device(&browser_session.user.username, device.as_str(), None)
.await
+3 -7
View File
@@ -1,3 +1,4 @@
# Copyright 2025, 2026 Element Creations Ltd.
# Copyright 2025 New Vector Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
@@ -15,8 +16,8 @@ group "default" { targets = ["regular", "debug"] }
target "docker-metadata-action" {}
target "docker-metadata-action-debug" {}
// This sets the platforms and is further extended by GitHub Actions to set the
// output and the cache locations
// This is extended by GitHub Actions to set the output, cache locations,
// and platforms (one architecture per job for parallel builds)
target "base" {
args = {
// This is set so that when we use a git context, the .git directory is
@@ -26,11 +27,6 @@ target "base" {
// Pass down the version from an external git describe source
VERGEN_GIT_DESCRIBE = "${VERGEN_GIT_DESCRIBE}"
}
platforms = [
"linux/amd64",
"linux/arm64",
]
}
target "regular" {
+39 -39
View File
@@ -27,7 +27,7 @@ export type LocalazyMetadata = {
};
const localazyMetadata: LocalazyMetadata = {
projectUrl: "https://localazy.com/p/matrix-authentication-service",
projectUrl: "https://localazy.com/p/matrix-authentication-service!v1.17",
baseLocale: "en",
languages: [
{
@@ -208,25 +208,25 @@ const localazyMetadata: LocalazyMetadata = {
file: "frontend.json",
path: "",
cdnFiles: {
"cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json",
"da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json",
"de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json",
"en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json",
"et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json",
"fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json",
"fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json",
"hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json",
"nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json",
"nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json",
"pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json",
"pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json",
"pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt-BR/frontend.json",
"ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json",
"sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sk/frontend.json",
"sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json",
"uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json",
"uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uz/frontend.json",
"zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json"
"cs": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/cs/frontend.json",
"da": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/da/frontend.json",
"de": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/de/frontend.json",
"en": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/en/frontend.json",
"et": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/et/frontend.json",
"fi": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fi/frontend.json",
"fr": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/fr/frontend.json",
"hu": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/hu/frontend.json",
"nb_NO": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nb-NO/frontend.json",
"nl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/nl/frontend.json",
"pl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pl/frontend.json",
"pt": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt/frontend.json",
"pt_BR": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/pt-BR/frontend.json",
"ru": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/ru/frontend.json",
"sk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sk/frontend.json",
"sv": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/sv/frontend.json",
"uk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uk/frontend.json",
"uz": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/uz/frontend.json",
"zh#Hans": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/7c203a8ac8bd48c3c4609a8effcd0fbac430f9b2/zh-Hans/frontend.json"
}
},
{
@@ -234,25 +234,25 @@ const localazyMetadata: LocalazyMetadata = {
file: "file.json",
path: "",
cdnFiles: {
"cs": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json",
"da": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json",
"de": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json",
"en": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json",
"et": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json",
"fi": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json",
"fr": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json",
"hu": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json",
"nb_NO": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json",
"nl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json",
"pl": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json",
"pt": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json",
"pt_BR": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt-BR/file.json",
"ru": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json",
"sk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sk/file.json",
"sv": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json",
"uk": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json",
"uz": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uz/file.json",
"zh#Hans": "https://delivery.localazy.com/_a7686032324574572744739e0707/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json"
"cs": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/cs/file.json",
"da": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/da/file.json",
"de": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/de/file.json",
"en": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/en/file.json",
"et": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/et/file.json",
"fi": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fi/file.json",
"fr": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/fr/file.json",
"hu": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/hu/file.json",
"nb_NO": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nb-NO/file.json",
"nl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/nl/file.json",
"pl": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pl/file.json",
"pt": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt/file.json",
"pt_BR": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/pt-BR/file.json",
"ru": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/ru/file.json",
"sk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sk/file.json",
"sv": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/sv/file.json",
"uk": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uk/file.json",
"uz": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/uz/file.json",
"zh#Hans": "https://delivery.localazy.com/_a65347067752521597762935bd1b/_e0/5b69b0350dccfd47c245a5d41c1b9fdf6912cc6e/zh-Hans/file.json"
}
}
]